根据SAS中的其他数据集对值进行排名

时间:2015-03-22 12:35:08

标签: sas rank

假设我有两个数据集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中的观测数量很少时,这是可以的。但是当谈到非常大的数字时,它需要很长时间,因此不是一个好的解决方案。

提前感谢您的建议。

2 个答案:

答案 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