SAS:跨多列输出第一个非缺失的最有效方法
SAS: most efficient method to output first non-missing across multiple columns
我拥有的数据有数百万行,而且相当稀疏,只有 3 到 10 个变量需要处理。我的最终结果需要是包含每一列的 第一个非缺失 值的单行。取以下测试数据:
** test data **;
data test;
length ID AID 8 TYPE ;
input ID $ AID TYPE $;
datalines;
A . .
. 123 .
C . XYZ
;
run;
最终结果应该是这样的:
ID AID TYPE
A 123 XYZ
使用宏列表和循环我可以用多个合并语句强制这个结果,其中变量是非缺失的 obs=1
但是当数据非常大时这效率不高(下面我会循环在这些变量上而不是写多个 merge
语句):
** works but takes too long on big data **;
data one_row;
merge
test(keep=ID where=(ID ne "") obs=1) /* character */
test(keep=AID where=(AID ne .) obs=1) /* numeric */
test(keep=TYPE where=(TYPE ne "") obs=1); /* character */
run;
coalesce
函数看起来很有前途,但我相信我需要将它与 array
和 output
结合使用才能构建此单行结果。该函数也不同(coalesce
和 coalescec
取决于变量类型)而使用 proc sql
无关紧要。我使用 array
时出错,因为数组列表中的所有变量都不是同一类型。
究竟什么是最有效的将在很大程度上取决于数据的特征。特别是,最后一个变量的第一个非缺失值是否通常在数据集中相对 "early",或者您是否通常必须遍历整个数据集才能找到它。
我假设您的数据集未编入索引(因为这会大大简化事情)。
一个选项是标准数据步骤。这不一定很快,但它可能不会比大多数其他选项慢太多,因为无论您做什么,您都必须阅读 most/all 行。这有一个很好的优势,即它可以在每一行完成时停止。
data want;
if 0 then set test; *defines characteristics;
set test(rename=(id=_id aid=_aid type=_type)) end=eof;
id=coalescec(id,_id);
aid=coalesce(aid,_aid);
type=coalescec(type,_type);
if cmiss(of id aid type)=0 then do;
output;
stop;
end;
else if eof then output;
drop _:;
run;
您可以从 dictionary.columns
的宏变量中填充所有这些,甚至可以使用临时数组,但我认为这太混乱了。
另一个选项是自我更新,但它需要进行两次更改。第一,您需要 something 才能加入(与没有 by 变量的合并相反)。第二,它将为您提供 last 非缺失值,而不是第一个,因此您必须对数据集进行反向排序。
但假设您将 x
添加到第一个数据集,任何值(无关紧要,但每一行都是常数),就是这么简单:
data want;
update test(obs=0) test;
by x;
run;
这样就有了代码简洁的巨大优势,换来了一些时间成本(反向排序和添加新变量)。
如果您的数据集非常稀疏,转置可能是一个很好的折衷方案。不需要知道变量名称,因为您可以使用数组处理它们。
data test_t;
set test;
array numvars _numeric_;
array charvars _character_;
do _i = 1 to dim(numvars);
if not missing(numvars[_i]) then do;
varname = vname(numvars[_i]);
numvalue= numvars[_i];
output;
end;
end;
do _i = 1 to dim(charvars);
if not missing(charvars[_i]) then do;
varname = vname(charvars[_i]);
charvalue= charvars[_i];
output;
end;
end;
keep numvalue charvalue varname;
run;
proc sort data=test_t;
by varname;
run;
data want;
set test_t;
by varname;
if first.varname;
run;
然后你转置它以获得所需的需求(或者这可能对你有用)。它确实丢失了 formats/etc。关于值,因此请考虑到这一点,并且您的字符值长度可能需要设置为适当的长度 - 然后再设置(您可以使用 if 0 then set
来修复它)。
类似的散列方法的工作方式大致相同;它的优点是它会更快停止,并且不需要求助。
data test_h;
set test end=eof;
array numvars _numeric_;
array charvars _character_;
length varname numvalue 8 charvalue 24; *or longest charvalue length;
if _n_=1 then do;
declare hash h(ordered:'a');
h.defineKey('varname');
h.defineData('varname','numvalue','charvalue');
h.defineDone();
end;
do _i = 1 to dim(numvars);
if not missing(numvars[_i]) then do;
varname = vname(numvars[_i]);
rc = h.find();
if rc ne 0 then do;
numvalue= numvars[_i];
rc=h.add();
end;
end;
end;
do _i = 1 to dim(charvars);
if not missing(charvars[_i]) then do;
varname = vname(charvars[_i]);
rc = h.find();
if rc ne 0 then do;
charvalue= charvars[_i];
rc=h.add();
end;
end;
end;
if eof or h.num_items = dim(numvars) + dim(charvars) then do;
rc = h.output(dataset:'want');
end;
run;
还有很多其他解决方案,具体取决于您的数据,哪种解决方案最有效。
我拥有的数据有数百万行,而且相当稀疏,只有 3 到 10 个变量需要处理。我的最终结果需要是包含每一列的 第一个非缺失 值的单行。取以下测试数据:
** test data **;
data test;
length ID AID 8 TYPE ;
input ID $ AID TYPE $;
datalines;
A . .
. 123 .
C . XYZ
;
run;
最终结果应该是这样的:
ID AID TYPE
A 123 XYZ
使用宏列表和循环我可以用多个合并语句强制这个结果,其中变量是非缺失的 obs=1
但是当数据非常大时这效率不高(下面我会循环在这些变量上而不是写多个 merge
语句):
** works but takes too long on big data **;
data one_row;
merge
test(keep=ID where=(ID ne "") obs=1) /* character */
test(keep=AID where=(AID ne .) obs=1) /* numeric */
test(keep=TYPE where=(TYPE ne "") obs=1); /* character */
run;
coalesce
函数看起来很有前途,但我相信我需要将它与 array
和 output
结合使用才能构建此单行结果。该函数也不同(coalesce
和 coalescec
取决于变量类型)而使用 proc sql
无关紧要。我使用 array
时出错,因为数组列表中的所有变量都不是同一类型。
究竟什么是最有效的将在很大程度上取决于数据的特征。特别是,最后一个变量的第一个非缺失值是否通常在数据集中相对 "early",或者您是否通常必须遍历整个数据集才能找到它。
我假设您的数据集未编入索引(因为这会大大简化事情)。
一个选项是标准数据步骤。这不一定很快,但它可能不会比大多数其他选项慢太多,因为无论您做什么,您都必须阅读 most/all 行。这有一个很好的优势,即它可以在每一行完成时停止。
data want;
if 0 then set test; *defines characteristics;
set test(rename=(id=_id aid=_aid type=_type)) end=eof;
id=coalescec(id,_id);
aid=coalesce(aid,_aid);
type=coalescec(type,_type);
if cmiss(of id aid type)=0 then do;
output;
stop;
end;
else if eof then output;
drop _:;
run;
您可以从 dictionary.columns
的宏变量中填充所有这些,甚至可以使用临时数组,但我认为这太混乱了。
另一个选项是自我更新,但它需要进行两次更改。第一,您需要 something 才能加入(与没有 by 变量的合并相反)。第二,它将为您提供 last 非缺失值,而不是第一个,因此您必须对数据集进行反向排序。
但假设您将 x
添加到第一个数据集,任何值(无关紧要,但每一行都是常数),就是这么简单:
data want;
update test(obs=0) test;
by x;
run;
这样就有了代码简洁的巨大优势,换来了一些时间成本(反向排序和添加新变量)。
如果您的数据集非常稀疏,转置可能是一个很好的折衷方案。不需要知道变量名称,因为您可以使用数组处理它们。
data test_t;
set test;
array numvars _numeric_;
array charvars _character_;
do _i = 1 to dim(numvars);
if not missing(numvars[_i]) then do;
varname = vname(numvars[_i]);
numvalue= numvars[_i];
output;
end;
end;
do _i = 1 to dim(charvars);
if not missing(charvars[_i]) then do;
varname = vname(charvars[_i]);
charvalue= charvars[_i];
output;
end;
end;
keep numvalue charvalue varname;
run;
proc sort data=test_t;
by varname;
run;
data want;
set test_t;
by varname;
if first.varname;
run;
然后你转置它以获得所需的需求(或者这可能对你有用)。它确实丢失了 formats/etc。关于值,因此请考虑到这一点,并且您的字符值长度可能需要设置为适当的长度 - 然后再设置(您可以使用 if 0 then set
来修复它)。
类似的散列方法的工作方式大致相同;它的优点是它会更快停止,并且不需要求助。
data test_h;
set test end=eof;
array numvars _numeric_;
array charvars _character_;
length varname numvalue 8 charvalue 24; *or longest charvalue length;
if _n_=1 then do;
declare hash h(ordered:'a');
h.defineKey('varname');
h.defineData('varname','numvalue','charvalue');
h.defineDone();
end;
do _i = 1 to dim(numvars);
if not missing(numvars[_i]) then do;
varname = vname(numvars[_i]);
rc = h.find();
if rc ne 0 then do;
numvalue= numvars[_i];
rc=h.add();
end;
end;
end;
do _i = 1 to dim(charvars);
if not missing(charvars[_i]) then do;
varname = vname(charvars[_i]);
rc = h.find();
if rc ne 0 then do;
charvalue= charvars[_i];
rc=h.add();
end;
end;
end;
if eof or h.num_items = dim(numvars) + dim(charvars) then do;
rc = h.output(dataset:'want');
end;
run;
还有很多其他解决方案,具体取决于您的数据,哪种解决方案最有效。