Ранжирование значений на основе другого набора данных в SAS



Скажем, у меня есть два набора данных A и B, которые имеют одинаковые переменные и хотят ранжировать значения в B на основе значений в A, а не самого 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 невелико. Но когда дело доходит до очень большого числа, это занимает так много времени и таким образом это не было бы хорошим решением.



Заранее спасибо за предложения.

567   2  

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;

Затем мы создаем входной набор данных формата и используем ранг в качестве метки. Диапазон-это (Предыдущее значение - > текущее значение), а метка-это ранг. Я оставляю тебе решать, как ты будешь обращаться с галстуками.

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;

Один из способов ранжирования по переменной из отдельного набора данных заключается в использовании proc sql'Sкоррелированных подзапросов . По сути, вы подсчитываете количество нижних значений в наборе данных подстановки для каждого значения в данных, подлежащих ранжированию.

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.

В грубом тестировании на медленном ПК я увидел:

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 содержит большое количество уникальных значений (в переменных ранжирования), то процесс займет значительно больше времени, чем показано на рисунке. Это более вероятно, если ваши данные не являются целыми числами, как показывает Джо.


Править: Runtime test rig:

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

Comments

    Ничего не найдено.