SAS выполняет функцию цикла + задержки?

Это мой первый пост, поэтому, пожалуйста, дайте мне знать, если я недостаточно понимаю. Вот что я пытаюсь сделать - это мой набор данных. Мой подход к этому - цикл do с задержкой, но результат - мусор.

data a;
input @1 obs @4 mindate mmddyy10. @15 maxdate mmddyy10.;
format mindate maxdate date9.;
datalines;
1   01/02/2013 01/05/2013
2   01/02/2013 01/05/2013
3   01/02/2013 01/05/2013
4   01/03/2013 01/06/2013
5   02/02/2013 02/08/2013
6   02/02/2013 02/08/2013
7   02/02/2013 02/08/2013
8   03/10/2013 03/11/2013
9   04/02/2013 04/22/2013
10  04/10/2013 04/22/2013
11  05/04/2013 05/07/2013
12  06/10/2013 06/20/2013
;
run;

Теперь я пытаюсь создать новый столбец - «Замена» на основе следующей логики:

  1. Если Mindate записи происходит до maxdate ее лага, она не может быть ее заменой. Если это невозможно заменить, переходите вперед (так, 2,3,4 не могут заменить 1, а 5 можно).
  2. В противном случае ... если значение меньше 30 дней, Replacement = Y. Если нет, replace = N. Как только запись заменяет другую (так, в этом случае 5 действительно заменяет 1, потому что 02.02.2013 равно ‹30 чем 01/05/2013, он не может дублироваться в качестве замены для другой записи. Но если это N для одной записи выше, это все равно может быть Y для какой-то другой записи. Итак, 6 теперь оценивается против 2, 7 против 3 и т. д. Поскольку обе эти комбинации равны "Y", 8 теперь оценивается по сравнению с 4, но поскольку его значение> 30 относительно maxdate 4, это N. Но затем оно сравнивается с
  3. И так далее...

Я бы сказал, что в наборе данных из 100 записей это будет означать, что 100-я запись может технически заменить 1-ю, поэтому я пробовал лаги внутри циклов. Любые советы / помощь приветствуются! Ожидаемый результат:

                      obs      mindate      maxdate    Replacement

                        1    02JAN2013    05JAN2013
                        2    02JAN2013    05JAN2013
                        3    02JAN2013    05JAN2013
                        4    03JAN2013    06JAN2013
                        5    02FEB2013    08FEB2013         Y
                        6    02FEB2013    08FEB2013         Y
                        7    02FEB2013    08FEB2013         Y
                        8    10MAR2013    11MAR2013         Y
                        9    02APR2013    22APR2013         Y
                       10    10APR2013    22APR2013         N
                       11    04MAY2013    07MAY2013         Y
                       12    10JUN2013    20JUN2013         Y

person AidKulesh    schedule 10.02.2015    source источник
comment
Можете ли вы опубликовать ожидаемый результат? Я думаю, что вам нужно будет сделать самостоятельное соединение SQL вместо циклов / задержек. Любой код, который вы пробовали, тоже полезен :)   -  person Reeza    schedule 10.02.2015
comment
@Reeza - это, вероятно, также можно было бы сделать с помощью двойного цикла DOW.   -  person user667489    schedule 10.02.2015
comment
@ Риза. Спасибо. Ожидаемый результат:   -  person AidKulesh    schedule 10.02.2015
comment
Вы уверены, что replace = Y вместо obs = 12? Дата 10 июня - более 30 дней после любого из предыдущих maxdates.   -  person user667489    schedule 11.02.2015
comment
@ user667489 - Да, ты прав.   -  person AidKulesh    schedule 11.02.2015
comment
@ user667489. Еще одна вещь, о которой я хотел бы вас спросить - я всегда игнорировал массивы из-за того, что мне не приходилось их использовать или находить какой-либо другой способ. Теперь я вижу, что в такой ситуации они очень полезны. Можете ли вы сказать мне, в каких ситуациях вы на них полагаетесь? когда они вам особенно пригодятся? Спасибо.   -  person AidKulesh    schedule 11.02.2015


Ответы (2)


Думаю, это правильно, если спрашивающий ошибся насчет replace = Y вместо obs = 12.

/*Get number of obs so we can build a temporary array to hold the dataset*/
data _null_;
    set have nobs= nobs;
    call symput("nobs",nobs);
    stop;
run;

data want;
    /*Load the dataset into a temporary array*/
    array dates[2,&NOBS] _temporary_;
    if _n_ = 1 then do _n_ = 1 by 1 until(eof);
        set have end = eof;
        dates[1,_n_] = maxdate;
        dates[2,_n_] = 0;
    end;

    set have;

    length replacement $1;

    replacement = 'N';
    do i = 1 to _n_ - 1 until(replacement = 'Y');
        if dates[2,i] = 0 and 0 <= mindate - dates[1,i] <= 30 then do;
            replacement = 'Y';
            dates[2,i] = _n_;
            replaces = i;
        end;
    end;
    drop i; 
run;

Вы можете использовать хэш-объект + хеш-итератор вместо временного массива, если хотите. Я также добавил дополнительную переменную replaces, чтобы показать, какую предыдущую строку заменяет каждая строка.

person user667489    schedule 10.02.2015
comment
Огромное спасибо. Это именно то, что я искал. И да, я ошибся насчет Obs = 12, так как это действительно больше 30 дней. - person AidKulesh; 11.02.2015
comment
Хорошо, я никогда не рассматривал do * by * until или перегрузку _N_. - person SRSwift; 11.02.2015

Вот решение с использованием SQL и хеш-таблиц. Это не оптимально, но это был первый метод, который пришел в голову.

/* Join the input with its self */
proc sql;
    create table b as
    select 
        a1.obs, 
        a2.obs as obs2
    from a as a1
    inner join a as a2
        /* Set the replacement criteria */
        on a1.maxdate < a2.mindate <= a1.maxdate + 30
    order by a2.obs, a1.obs;
quit;
/* Create a mapping for replacements */
data c;
    set b;
    /* Create two empty hash tables so we can look up the used observations */
    if _N_ = 1 then do;
        declare hash h();
        h.definekey("obs");
        h.definedone(); 
        declare hash h2();
        h2.definekey("obs2");
        h2.definedone();
    end;
    /* Check if we've already used this observation as a replacement */
    if h2.find() then do;
        /* Check if we've already replaced his observation  */
        if h.find() then do;
            /* Add the observations to the hash table and output */
            h2.add();
            h.add();
            output;
        end;
    end;
run;
/* Combine the replacement map with the original data */
proc sql;
    select 
        a.*, 
        ifc(c.obs, "Y", "N") as Replace, 
        c.obs as Replaces
    from a
    left join c
        on a.obs = c.obs2
    order by a.obs;
quit;

Это можно упростить несколькими способами:

  • Даты можно перенести через первые proc sql
  • Выражения if можно комбинировать
  • Последнее соединение можно заменить небольшой дополнительной логикой на этапе данных.
person SRSwift    schedule 10.02.2015
comment
Огромное спасибо. Я не думал об использовании хеш-таблиц, но вижу, как они здесь пригодятся. - person AidKulesh; 11.02.2015