Как усреднить подмножество данных в SAS по дате? - PullRequest
0 голосов
/ 01 мая 2020

Я пытаюсь написать код SAS, который может l oop по набору данных, который содержит даты событий, которые выглядят следующим образом:

Data event;
     input Date;
     cards;
     20200428
     20200429
     ;
run;

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

Data vol;
     input Date Volume;
     cards;
     20200430  100
     20200429  110
     20200428  86
     20200427  95
     20200426  80
     20200425  90
     ;
run;

Например, для даты 20200428 среднее значение должно составлять 88,33 [(95 + 80 + 90) / 3], а для даты 20200429 среднее значение должно составлять 87,00 [( 86 + 95 + 80) / 3]. Я хочу, чтобы эти значения и объем даты были сохранены в новом наборе данных, который выглядит следующим образом, если это возможно.

Data clean;
     input Date Vol Avg;
     cards;
     20200428 86 88.33
     20200429 110 87.00
     ;
run;

Фактические данные, с которыми я работаю, относятся к 1970-2010 гг. Я также могу увеличить свой средний период с 3 дней до 10 дней назад, поэтому я хочу иметь гибкий код. Из того, что я прочитал, я думаю, что макрос и / или call symput могут очень хорошо работать для этого, но я не уверен, как их кодировать, чтобы делать то, что я хочу. Честно говоря, я не знаю с чего начать. Может кто-то указать мне верное направление? Я открыт для любых советов / идей. Благодаря.

Ответы [ 3 ]

1 голос
/ 01 мая 2020
Оператор

A SQL на сегодняшний день является наиболее кратким кодом для получения набора результатов. Запрос объединится с 2 независимыми ссылками на объемные данные. Первый - для получения объема даты события, а второй - для вычисления среднего объема за три предыдущих дня.

Данные date должны считываться как дата SAS, так что условие BETWEEN будет правильный.

Data event;
     input Date: yymmdd8.;
     cards;
     20200428
     20200429
     ;
run;

Data vol;
     input Date: yymmdd8. Volume;
     cards;
     20200430  100
     20200429  110
     20200428  86
     20200427  95
     20200426  80
     20200425  90
     ;
run;

* SQL запрос с GROUP BY;

proc sql;
  create table want as
  select 
    event.date
  , volume_one.volume
  , mean(volume_two.volume) as avg
  from event
  left join vol as volume_one
  on event.date = volume_one.date
  left join vol as volume_two
  on volume_two.date between event.date-1 and event.date-3
  group by 
  event.date, volume_one.volume
  ;

* альтернативный запрос с использованием коррелированного подзапроса;

  create table want_2 as
  select 
    event.date
  , volume
  , ( select mean(volume) as avg from vol where vol.date between event.date-1 and event.date-3 )
    as avg
  from event
  left join vol
  on event.date = vol.date
  ;

1 голос
/ 02 мая 2020

Для случая с Volumes данными, имеющими разрыв по дате, лучшим решением было бы отдельно рассчитать скользящее среднее N предыдущих объемов. Разница между датами может быть из выходных, праздничных дней или из-за отсутствия даты из-за проблем с вводом данных или ошибки оператора. Концептуально для усреднения единственная роль date заключается только в упорядочении данных.

После вычисления скользящих средних можно выполнить простые join или merge.

Пример:

* Simulate some volume data that excludes weekends, holidays, and a 2% rate of missing dates;

data volumes(keep=date volume);
  call streaminit(20200502);
  do date = '01jan1970'd to today();
    length holiday $25;
    year = year(date);
    holiday = 'NEWYEAR';         hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'USINDEPENDENCE';  hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'THANKSGIVING';    hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'CHRISTMAS';       hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'MEMORIAL';        hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'LABOR';           hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'EASTER';          hdate = holiday(holiday, year); if date=hdate then continue;
    holiday = 'USPRESIDENTS';    hdate = holiday(holiday, year); if date=hdate then continue;

    if weekday(date) in (1,7) then continue; *1=Sun, 7=Sat;

    volume = 100 + ceil(75 * sin (date / 8));

    if rand('uniform') < 0.02 then continue;

    output;
  end;
  format date yymmdd10.;
run;
* Compute an N item rolling average from N prior values;

%let ROLLING_N = 5;

data volume_averages;

  set volumes;
  by date;      * enforce sort order requirement;

  array v[0:&ROLLING_N] _temporary_;          %* <---- &ROLLING_N ;
  retain index -1;

  avg_prior_&ROLLING_N. = mean (of v(*));     %* <---- &ROLLING_N ;

  OUTPUT;

  index = mod(index + 1,&ROLLING_N);     %* <---- Modular arithmetic, the foundation of rolling ;
  v[index] = volume;

  format v: 6.;
  drop index;
run;
* merge;

data want_merge;
  merge events(in=event_date) volume_averages;
  by date;

  if event_date;
run;

* join;

proc sql;
  create table want_join as
  select events.*, volume_averages.avg_prior_5
  from events join volume_averages
  on events.date = volume_averages.date;
quit;
0 голосов
/ 01 мая 2020

Вы хотите l oop в течение ряда дат во входном наборе данных. Поэтому я использую оператор PROC SQL, в котором я выбираю различные даты в этом наборе входных данных в макропеременную. Эта макропеременная затем используется для l oop over. Таким образом, в вашем примере макропеременная будет: 20200428 20200429. Затем вы можете использовать макрофункцию %SCAN, чтобы начать цикл по этим датам.

Для каждой даты в l oop мы затем вычислим среднее значение: в вашем примере среднее значение за 3 дня до даты цикла. Поскольку число дней, за которое вы хотите рассчитать среднее значение, является переменным, оно также передается в качестве параметра в макрос. Затем я использую INTNX function, чтобы вычислить нижнюю границу дат, которые вы хотите выбрать для расчета среднего значения. Затем для вычисления среднего объема по дням используется процедура PROC MEANS: нижняя граница - дата цикла.

Затем я помещаю небольшой шаг данных между ними, чтобы снова прикрепить дату цикла к вычисленному среднему значению. Наконец, все добавляется в окончательный набор данных.

%macro dayAverage(input = , range = , selectiondata = );

  /* Input = input dataset
     range = number of days prior to the selected date for which you want to calculate 
             the average
     selectiondata = data where the volumes are in */

  /* Create a macro variable with the dates for which you want to calculate the 
     average, to loop over */

    proc sql noprint;
      select distinct date into: datesrange separated by " "
      from &input.;
    quit;

  /*Start looping over the dates for which you want to calculate the average */

    %let I = 1;
    %do %while (%scan(&datesrange.,&I.) ne %str());

        /* Assign the current date in the loop to the variable currentdate */

        %let currentdate =  %scan(&datesrange.,&I.);

        /* Create the minimum date in the range based on input parameter range */

      %let mindate = 
      %sysfunc(putn(%sysfunc(intnx(day,%sysfunc(inputn(&currentdate.,yymmdd8.)),- 
      &range.)),yymmddn8.));

      /* Calculate the mean volume for the selected date and selected range */

      proc means data = &selectiondata.(where = (date >= &mindate. and date < 
      &currentdate.)) noprint ;
      output out  = averagecurrent(drop = _type_ _freq_) mean(volume)=avgerage_volume;
      run;

      /* Add the current date to the calculated average */

      data averagecurrent;
        retain date average_volume;
        set averagecurrent;

        date = &currentdate.;
      run;

     /* Append the result to a final list */
        proc datasets nolist;
        append base = final data = averagecurrent force;
        run;

        %let I = %eval(&I. + 1);

  %end;
 %mend;

Этот макрос в вашем примере можно назвать так:

%dayAverage(input = event, range = 3, selectiondata = vol);

Он даст вам набор данных в вашей рабочей библиотеке, который называется final

...