假设我有两个数据集A和B,它们具有相同的变量,并希望根据A中的值对B中的值进行排名,而不是B本身(如“PROC RANK data = B”那样)。
以下是数据集A,B和want(所需输出)的简化示例:
A:
obs_A VAR1 VAR2 VAR3
1 10 100 2000
2 20 300 1000
3 30 200 4000
4 40 500 3000
5 50 400 5000
B:
obs_B VAR1 VAR2 VAR3
1 15 150 2234
2 14 352 1555
3 36 251 1000
4 41 350 2011
5 60 553 5012
want:
obs VAR1 VAR2 VAR3
1 2 2 3
2 2 4 2
3 4 3 1
4 5 4 3
5 6 6 6
我想出了一个涉及PROC RANK和PROC APPEND的宏循环,如下所示:
%macro MyRank(A,B);
data AB; set &A &B; run;
%do i=1 %to 5;
proc rank data=AB(where=(obs_A ne . OR obs_B=&i) out=tmp;
var VAR1-3;
run;
proc append base=want data=tmp(where=(obs_B=&i) rename=(obs_B=obs)); run;
%end;
%mend;
当B中的观测数量很少时,这是可以的。但是当谈到非常大的数字时,它需要很长时间,因此不是一个好的解决方案。
提前感谢您的建议。
答案 0 :(得分:2)
我会创建格式来执行此操作。您真正要做的是通过A来定义要应用于B的范围。格式非常快 - 这里假设" A"相对较小," B"可以像你喜欢的一样大,只需要读取和写出一次B数据集,再加上A的几次读/写就可以了。
首先,阅读A数据集:
data ranking_vals;
input obs_A VAR1 VAR2 VAR3;
datalines;
1 10 100 2000
2 20 300 1000
3 30 200 4000
4 40 500 3000
5 50 400 5000
;;;;
run;
然后将其转换为垂直,因为这将是对它们进行排名的最简单方法(只是简单的旧排序,不需要proc排名)。
data for_ranking;
set ranking_vals;
array var[3];
do _i = 1 to dim(var);
var_name = vname(var[_i]);
var_value = var[_i];
output;
end;
run;
proc sort data=for_ranking;
by var_name var_value;
run;
然后我们创建一个格式输入数据集,并使用rank作为标签。范围是(先前值 - >当前值),标签是等级。我告诉你如何处理关系。
data for_fmt;
set for_ranking;
by var_name var_value;
retain prev_value;
if first.var_name then do; *initialize things for a new varname;
rank=0;
prev_value=.;
hlo='l'; *first record has 'minimum' as starting point;
end;
rank+1;
fmtname=cats(var_name,'F');
start=prev_value;
end=var_value;
label=rank;
output;
if last.var_name then do; *For last record, some special stuff;
start=var_value;
end=.;
hlo='h';
label=rank+1;
output; * Output that 'high' record;
start=.;
end=.;
label=.;
hlo='o';
output; * And a "invalid" record, though this should never happen;
end;
prev_value=var_value; * Store the value for next row.;
run;
proc format cntlin=for_fmt;
quit;
然后我们测试它。
data test_b;
input obs_B VAR1 VAR2 VAR3;
var1r=put(var1,var1f.);
var2r=put(var2,var2f.);
var3r=put(var3,var3f.);
datalines;
1 15 150 2234
2 14 352 1555
3 36 251 1000
4 41 350 2011
5 60 553 5012
;;;;
run;
答案 1 :(得分:0)
您可以使用proc sql
' s correlated subqueries通过单独数据集中的变量排名的一种方法。实质上,您可以计算要对要排名的数据中的每个值的查找数据集中较低值的数量。
proc sql;
create table want as
select
B.obs_B,
(
select count(distinct A.Var1) + 1
from A
where A.var1 <= B.var1.
) as var1
from B;
quit;
哪个可以包装在宏中。下面,宏循环用于写入每个子查询。它根据需要查看变量列表和子查询参数。
%macro rankBy(
inScore /*Dataset containing data to be ranked*/,
inLookup /*Dataset containing data against which to rank*/,
varID /*Variable by which to identify an observation*/,
varsRank /*Space separated list of variable names to be ranked*/,
outData /*Output dataset name*/);
/* Rank variables in one dataset by identically named variables in another */
proc sql;
create table &outData. as
select
scr.&varID.
/* Loop through each variable to be ranked */
%do i = 1 %to %sysfunc(countw(&varsRank., %str( )));
/* Store the variable name in a macro variable */
%let var = %scan(&varsRank., &i., %str( ));
/* Rank: count all the rows with lower value in lookup */
, (
select count(distinct lkp&i..&var.) + 1
from &inLookup. as lkp&i.
where lkp&i..&var. <= scr.&var.
) as &var.
%end;
from &inScore. as scr;
quit;
%mend rankBy;
%rankBy(
inScore = B,
inLookup = A,
varID = obs_B,
varsRank = VAR1 VAR2 VAR3,
outData = want);
关于速度,如果您的A
很大,那么这将会很慢,但 对于大B
和小A
来说是可以的。
在慢速PC上的粗略测试中,我看到了:
A: 1e1 B: 1e6 time: ~1s
A: 1e2 B: 1e6 time: ~2s
A: 1e3 B: 1e6 time: ~5s
A: 1e1 B: 1e7 time: ~10s
A: 1e2 B: 1e7 time: ~12s
A: 1e4 B: 1e6 time: ~30s
编辑:
正如乔指出的那样,查询所花费的时间长度不仅取决于数据集中的观察数量,还取决于数据中存在多少个唯一值。显然,SAS执行优化以将比较仅减少到B
中的不同值,从而减少A
中元素需要计数的次数。这意味着如果数据集B
包含大量唯一值(在排名变量中),则该过程将比显示的时间长得多。如果您的数据不是Joe演示的整数,则更有可能发生这种情况。
编辑: 运行时测试台:
data A;
input obs_A VAR1 VAR2 VAR3;
datalines;
1 10 100 2000
2 20 300 1000
3 30 200 4000
4 40 500 3000
5 50 400 5000
;
run;
data B;
do obs_B = 1 to 1e7;
VAR1 = ceil(rand("uniform")* 60);
VAR2 = ceil(rand("uniform")* 500);
VAR3 = ceil(rand("uniform")* 6000);
output;
end;
run;
%let start = %sysfunc(time());
%rankBy(
inScore = B,
inLookup = A,
varID = obs_B,
varsRank = VAR1 VAR2 VAR3,
outData = want);
%let time = %sysfunc(putn(%sysevalf(%sysfunc(time()) - &start.), time12.2));
%put &time.;
输出:
0:00:12.41