SAS - репликация нескольких наблюдений по строкам - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть структура данных, которая выглядит следующим образом:

DATA have ; 
INPUT famid indid implicate imp_inc; 
CARDS ; 
1 1 1 40000
1 1 2 25000
1 1 3 34000
1 1 4 23555
1 1 5 49850
1 2 1 1000
1 2 2 2000
1 2 3 3000
1 2 4 4000
1 2 5 5000
1 3 1 .
1 3 2 .
1 3 3 .
1 3 4 .
1 3 5 .
2 1 1 40000
2 1 2 45000
2 1 3 50000
2 1 4 34000
2 1 5 23500
2 2 1 .
2 2 2 .
2 2 3 .
2 2 4 .
2 2 5 .
2 3 1 41000
2 3 2 39000
2 3 3 24000
2 3 4 32000
2 3 5 53000
RUN ;

Итак, у нас есть семейный идентификатор, индивидуальный идентификатор, номер участника и вмененный доход для каждого участника.

Что мне нужно, так это скопировать результаты первого индивида в каждой семье (все пять вариантов) для оставшихся индивидов в каждой семье, заменив любые значения, которые мы ранее имели в этих ячейках, например:

DATA want ; 
INPUT famid indid implicate imp_inc; 
CARDS ; 
1 1 1 40000
1 1 2 25000
1 1 3 34000
1 1 4 23555
1 1 5 49850
1 2 1 40000
1 2 2 25000
1 2 3 34000
1 2 4 23555
1 2 5 49850
1 3 1 40000
1 3 2 25000
1 3 3 34000
1 3 4 23555
1 3 5 49850
2 1 1 40000
2 1 2 45000
2 1 3 50000
2 1 4 34000
2 1 5 23500
2 2 1 40000
2 2 2 45000
2 2 3 50000
2 2 4 34000
2 2 5 23500
2 3 1 40000
2 3 2 45000
2 3 3 50000
2 3 4 34000
2 3 5 23500
RUN ;

В этом примере я пытаюсь реплицировать только одну переменную, но в моем проекте мне придется сделать это для десятков переменных.

Пока я придумал это решение:

%let implist_1=imp_inc;

%macro copyv1(list);
    %let nwords=%sysfunc(countw(&list));
    %do i=1 %to &nwords;
    %let varl=%scan(&list, &i);
        proc means data=have max noprint;
            var &varl;
            by famid implicate;
            where indid=1;
            OUTPUT OUT=copy max=max_&varl;  
        run;
        data want;
            set have;
            drop &varl;
        run;
        data want (drop=_TYPE_ _FREQ_);
            merge want copy;
            by famid implicate;
            rename max_&varl=&varl;
        run;
    %end;
%mend;
%copyv1(&imp_list1);

Это хорошо работает для одной или двух переменных. Однако, если вы сделаете это для 400 переменных в наборе данных размером 1,5 ГБ, это будет чрезвычайно медленным.

Я почти уверен, что есть более быстрый способ сделать это с какой-либо формой proc sql или first.var и т. Д., Но я относительно новичок в SAS и до сих пор не смог придумать лучшего решения .

Большое спасибо за вашу поддержку.

С наилучшими пожеланиями

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

Да, это можно сделать на шаге DATA, используя ссылку first., доступную через оператор by.

data want;
  set have (keep=famid indid implicate imp_inc /* other vars */);

  by famid indid implicate; /* by implicate is so step logs an error (at run-time) if data not sorted */

  if first.famid then if indid ne 1 then abort;

  array across imp_inc           /* other vars */;
  array hold [1,5] _temporary_;  /* or [<n>,5] where <n> means the number of variables in the across array */

  if indid = 1 then do;          /* hold data for 1st individuals implicate across data */
    do _n_ = 1 to dim(across);
      hold[_n_,implicate] = across[_n_];  /* store info of each implicate of first individual */
    end;
  end;
  else do;
    do _n_ = 1 to dim(across);
      across[_n_] = hold[_n_,implicate];  /* apply 1st persons info to subsequent persons */
    end;
  end;
run;

Шаг DATA может быть значительно быстрее из-за однократного прохождения данных, однако при вычислении всех этих надоедливых [] адресов массива за run; время возникают внутренние затраты на обработку, и эти затраты могут стать значительными в некоторых случаях <n>

SQL является более простым синтаксисом, более ясным для понимания и работает, если have набор данных не отсортирован или имеет некоторую специфическую последовательность в группе по.

0 голосов
/ 07 ноября 2018

Это довольно просто с небольшим количеством SQL:

proc sql;
create table want as 
  select a.famid, a.indid, a.implicate, b.* from 
  have a 
  left join (
    select * from have 
    group by famid 
    having indid = min(indid)
  ) b 
  on
        a.famid = b.famid 
    and a.implicate = b.implicate
  order by a.famid, a.indid, a.implicate
  ;
quit;

Идея состоит в том, чтобы объединить таблицу с ее собственным подмножеством, содержащим только строки, соответствующие первому человеку в каждой семье.

Он настроен на выбор человека с наименьшим номером в каждой семье, поэтому он будет работать, даже если нет строки с indid = 1. Если вы уверены, что такая строка всегда будет, вы можете использовать слегка более простой запрос:

proc sql;
create table want as 
  select a.famid, a.indid, a.implicate, b.* from 
  have(sortedby = famid) a 
  left join have(where = (indid = 1)) b 
  on
        a.famid = b.famid 
    and a.implicate = b.implicate
  order by a.famid, a.indid, a.implicate
  ;
quit;

Указание sortedby = famid подсказывает оптимизатору запросов, что он может пропустить один из начальных видов сортировки, необходимых для объединения, что может немного повысить производительность.

...