Используйте SAS для подсчета количества субъектов, которым необходимы контрольные визиты до заданной даты.

У меня есть следующий набор данных:

Data test;
Input id$ visit$ enrdate : mmddyy10. Vsdate : mmddyy10. ;
Format enrdate mmddyy10. Vsdate mmddyy10.; 
Cards;
ABC01 00 1/2/2020 1/2/2020
ABC02 00 5/16/2020 5/16/2020
ABC02 06 5/16/2019 11/12/2019
CDC01 00 8/20/2019 8/20/2019
CDC01 06 8/20/2019 2/16/2020
EFG01 00 5/20/2020 5/20/2020
EFG02 00 12/2/2018 12/2/2018
EFG02 02 12/2/2018 1/31/2019
EFG02 06 12/2/2018 5/31/2019
EFG02 12 12/2/2018 12/2/2019
EFG03 00 3/3/2019 3/3/2019
EFG03 12 3/3/2019 3/2/2020
GFF04 00 6/2/2019 6/2/2019
GFF04 06 6/2/2019     .
;
Run;

Я хочу сделать следующее:

Подсчитайте, скольким из зачисленных в настоящее время участников все еще нужно посетить 6-месячный визит (v6), скольким нужны их 12-месячные (v12), скольким - 18-месячные (v18) и скольким - 24-месячные. (v24).

Например, из фиктивного набора данных мы можем видеть, что всем 7 участникам по-прежнему понадобятся их v18 и v24, поскольку ни у кого из них уже не было этих посещений, в то время как участникам ABC01 и EFG01 также нужны их v6 и v12 в дополнение к v18 и v24. Участникам ABC02 и CDC01 нужны только их v12 в дополнение к v18 и v24 и т. Д. Участник GFF04, с другой стороны, должен быть исключен из списка участников, которым по-прежнему нужен их v6, поскольку его визит должен был состояться где-то в декабре. 2019, но никогда не делал. Тем не менее, я все еще хочу считать, что ему нужны его версии 12, 18 и 24.

В общем, мы также хотим исключить участников, чьи даты регистрации были давно, но у них никогда не было других последующих посещений после этого. Например, если участник имеет enrdate = 20.01.2018, но после этого не было других посещений, то мы не будем считать его нуждающимся в каких-либо других посещениях, поскольку он, скорее всего, выбыл, поскольку сейчас июнь 2020 года и единственный визит, который у нас есть для него, - это один визит в зачисление.

Кроме того, если участнику было введено следующее посещение, но не предыдущее, мы не будем считать этого участника нуждающимся в этом предыдущем посещении (т. Е. Если участник уже имел, скажем, v12, но не v6, то он не будет засчитан как все еще нужна эта версия 6 и так далее; т.е. участник EFG03)

Наконец, я хочу, чтобы крайний срок был 19 августа 2021 года. Это означает, что такой участник, как CDC01, который был зачислен 20 августа 2019 года, должен будет получить свой v24 20 августа 2021 года, но это уже прошло после ограничения 19 августа 2021 года, поэтому этот участник будут учитываться только как требующие v12 месяцев и v18. И так далее.

Это то, что я делал до сих пор, но теперь я застрял и не знаю, как продолжить кодирование с учетом всех перечисленных выше условий.

Data long;
Set test;
Where visit in (“00”, “06”, “12”, “18”, “24”);
If vsdate ne .;
Run;
Proc sort data=long out=longsort;
By id;
Run;

Data wide;
Set longsort;
By id;
Keep id enrdate vsdate00-vsdate24;
Retain vsdate00-vsdate24;
ARRAY avsdate(00:24) vsdate00-vsdate24;
if first.ID then do;
do i = 00 to 24;
avsdate (i) =.;
end;
    end;
avsdate(visit)=vsdate;
if last.ID then output;
run;
data wide_0;
set wide (keep=ID ENRDATE VSDATE00 VSDATE06 VSDATE12 VSDATE18 VSDATE24);
run;
data wide_final;
set wide_0;
attrib 
vsdate00 format=mmddyy10. Informat=anydtdte.
vsdate06 format=mmddyy10. Informat=anydtdte.
Vsdate12 format=mmddyy10. Informat=anydtdte.
Vsdate18 format=mmddyy10. Informat=anydtdte.
Vsdate24 format=mmddyy10. Informat=anydtdte.
;

Если кто-нибудь может помочь мне с предложениями / образцами кода, это было бы очень полезно!

Спасибо!


person R. Simian    schedule 25.06.2020    source источник
comment
Я думаю, это много в одном вопросе. Я бы посоветовал опубликовать его на community.sas.com и включить ваши образцы данных в виде текста, а не изображения. Предпочтительно в качестве шага данных. Это не особо сложная проблема, но правильно рассчитать даты для любого конкретного момента времени сложно. Также неплохо посмотреть на распределение, когда люди возвращаются, т.е. 10% в 5-й месяц, 80% в 6-й месяц, 5% в 7-й месяц и 5% никогда. Или вы можете просто выяснить, когда они должны быть в посещении - добавьте их и сделайте расчеты, чтобы получить время для посещения.   -  person Reeza    schedule 26.06.2020
comment
На самом деле мне интересно, не сможет ли опция SPARSE в PROC FREQ дать вам все посещения, а затем вычислить даты по мере того, как вы спускаетесь по записи.   -  person Reeza    schedule 26.06.2020
comment
Чтобы получить лучший ответ, часто помогает упростить вопрос, исключив экономическое обоснование и оставив только проблему программирования. Желательно создать аналогичную проблему, используя примеры наборов данных в библиотеке SASHELP, например SASHELP.CARS   -  person Dirk Horsten    schedule 26.06.2020
comment
Мне кажется, что с вашей проблемой было бы легче справиться, если бы вы свернули всю информацию, которая у вас есть для одного идентификатора, в одно наблюдение - тогда вам также нужно будет закодировать все свои исключения для идентификации пропущенных посещений. Вы думали об этом?   -  person user190080    schedule 26.06.2020
comment
@ user190080, вы имеете ввиду преобразование данных из длинных в широкие? Это то, что я уже сделал, но теперь я застрял в том, что делать дальше и как.   -  person R. Simian    schedule 26.06.2020
comment
@Reeza и Дирк Хорстен, спасибо за полезные предложения! Я добавил код для фиктивного набора данных и того, что я сделал до сих пор, в дополнение к сокращению и упрощению текста того, что я хочу сделать. Надеюсь это поможет.   -  person R. Simian    schedule 26.06.2020
comment
В общем, преобразование в широкое - не лучшая идея.   -  person Reeza    schedule 26.06.2020
comment
Это то, что я имел в виду. Я бы также добавил переменную visit, но это, вероятно, зависит от того, как вы хотите фильтровать. И теперь я предполагаю, что вам нужно написать процедуру для проверки каждого наблюдения / идентификатора на пропущенные посещения - в зависимости от сложности вашего дела это действительно может стать утомительным, но, поскольку все идентификаторы независимы друг от друга, я подозреваю, что это правильный путь. Может быть, Риза права и есть способ получше, но это то, как он подходит.   -  person user190080    schedule 26.06.2020
comment
Спасибо за ваш вклад, я уже разобрался. Взял довольно много операторов if, но, наконец, получил то, что мне нужно. Я работал с данными, преобразованными из длинных в широкие, так как я не настолько разбираюсь в SAS и не знаю другого способа сделать это.   -  person R. Simian    schedule 30.06.2020


Ответы (2)


Если вы создаете набор данных со строкой для каждой комбинации идентификатора участника и посещения, вы можете перейти к классификации каждого посещения, которое (еще) не было (пока) посещено. Это упрощает ручную проверку и подсчет различных посещений.

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

Обратите внимание, что набор данных результата будет меняться со временем, поскольку даты сравниваются с today().

* Define test data;
data have;
Input id$ visit$ enrdate : mmddyy10. Vsdate : mmddyy10. ;
Format enrdate mmddyy10. Vsdate mmddyy10.; 
Cards;
ABC01 00 1/2/2020 1/2/2020
ABC02 00 5/16/2019 5/16/2019
ABC02 06 5/16/2019 11/12/2019
CDC01 00 8/20/2019 8/20/2019
CDC01 06 8/20/2019 2/16/2020
EFG01 00 5/20/2020 5/20/2020
EFG02 00 12/2/2018 12/2/2018
EFG02 02 12/2/2018 1/31/2019
EFG02 06 12/2/2018 5/31/2019
EFG02 12 12/2/2018 12/2/2019
EFG03 00 3/3/2019 3/3/2019
EFG03 12 3/3/2019 3/2/2020
GFF04 00 6/2/2019 6/2/2019
GFF04 06 6/2/2019     .
;
run;

* Define the planned visits;
data planned_visits;
Input visit$;
Cards;
00
06
12
18
24
;
run;

* List and categorize all visits for all participants;
proc sql;
   create table id_visit_list as 
      select a.id
            ,a.enrdate
            ,b.visit
            ,intnx('month',a.enrdate,input(b.visit,best.),'s') as planned_date format mmddyy10.
            ,c.vsdate
            ,case
               when not missing(vsdate) then "Visit attended"
               when calculated planned_date + 7 < today() then "Visit skipped"
               when calculated planned_date > '19AUG2021'D then "Visit after cut-off"
               when max(vsdate) + 365 < today() then "Likely drop-out"
               else "Scheduled visit"
            end as visit_status 
      from (select distinct id, enrdate from have) as a
      left join (select distinct visit from planned_visits) as b
         on 1
      left join have as c
         on a.id = c.id and b.visit = c.visit
      group by a.id
   ;
quit;

* Count number of scheduled visits;
proc sql;
   create table want as 
      select distinct visit
                     ,sum(visit_status = "Scheduled visit") as count
      from id_visit_list
      group by visit
   ;
quit;
person Jeppe Rømer Juul    schedule 05.07.2020
comment
Йеппе Рёмер Юул, большое спасибо, это очень помогло! - person R. Simian; 02.09.2020

Вы можете заполнить массив во время цикла по группе, а затем выполнить любой алгоритм, который вы хотите оттуда. Вопрос, по-видимому, ищет посещения в 24, 18, 12 и 6 месяцах и перестает искать, когда произошло посещение (представляющих наибольший интерес) людей.

Пример:

Предположим, visit - числовая переменная. Значение visit может использоваться как индекс массива. Это известно как прямая адресация.

Рассмотрим два массива:

  • months - это массив, роль которого заключается в хранении статического списка интересующих месяцев.
  • visits - это массив с прямым адресом, который отслеживает, какие посещения имели место для id

Каждая группа id обрабатывается с использованием метода DOW, в котором оператор SET находится внутри явного цикла DO.

Таблица периодичности необходимых месяцев (т. Е. Количество) поддерживается в объекте hash.

Данные:

Data have;
Input id$ visit enrdate : mmddyy10. Vsdate : mmddyy10. ;
Format enrdate mmddyy10. Vsdate mmddyy10. visit z2.; 
Cards;
ABC01 00 1/2/2020 1/2/2020
ABC02 00 5/16/2020 5/16/2020
ABC02 06 5/16/2019 11/12/2019
CDC01 00 8/20/2019 8/20/2019
CDC01 06 8/20/2019 2/16/2020
EFG01 00 5/20/2020 5/20/2020
EFG02 00 12/2/2018 12/2/2018
EFG02 02 12/2/2018 1/31/2019
EFG02 06 12/2/2018 5/31/2019
EFG02 12 12/2/2018 12/2/2019
EFG03 00 3/3/2019 3/3/2019
EFG03 12 3/3/2019 3/2/2020
GFF04 00 6/2/2019 6/2/2019
GFF04 06 6/2/2019     .
;

Код:


data _null_;
  array months[1:4] _temporary_  (6,12,18,24);  * visit months of interest;
  array visits[0:24] _temporary_; * array for direct addressed have visit visits for by group.;

  if _n_ = 1 then do;
    declare hash counts (ordered: 'A');
    counts.defineKey('month');
    counts.defineData('month', 'count');
    counts.defineDone();
  end;

  * track visits that occurred;
  do _n_ = 1 by 1 until (last.id);
    set have end=done;
    by id;

    if visit <= hbound(visits) then do;
      visits(visit) = 1;
      flagged_count = sum(flagged_count,1);
    end;
  end;

  * presume enroll date is the same for all rows in the group;
  * at the end of the explicit loop `enrdate` will be available;

  * go from high to low months counting the needed months;

  do _n_ = hbound(months) to lbound(months) by -1;
    month = months(_n_);
  
    if missing(visits(month)) then do;
      * check if needed visit is in date span of interest;

      * compute the expected visit date;
      vsdate = intnx('month', enrdate, month, 'SAMEDAY');

      * if needed visit date is AFTER a cut off date perhaps an earlier one is before;
      if vsdate > '19AUG2021'D then CONTINUE;

      * if needed visit date is from a 'foggy' enrollment date then skip the id;
      if vsdate < '01JAN2020'D and missing(flagged_count) then LEAVE;
      
      * update counts;
      if counts.find() ne 0 then count = 1; else count + 1;
      counts.replace();
    end;
    else
      leave;  /* ignore all non-visits prior to a flagged visit */
  end;

  call missing(of visits[*]);

  if done then do;
    counts.output(dataset: 'want(label="Counts for needed visits")');
  end;

  keep id need;
run;

Вывод
 введите описание изображения здесь

person Richard    schedule 01.07.2020
comment
Спасибо, Ричард, это было очень полезно! - person R. Simian; 02.09.2020