Вычисление промежуточной суммы с движущимся временным окном - PullRequest
0 голосов
/ 08 февраля 2019

Мои данные

Я работаю над набором данных заклинаний в следующем формате:

cls
clear all
set more off

input id spellnr  str7 bdate_str  str7 edate_str  employed
       1    1         2008m1          2008m9          1  
       1    2        2008m12          2009m8          0   
       1    3        2009m11          2010m9          1  
       1    4        2010m10          2011m9          0  
       ///
       2    1         2007m4         2009m12          1
       2    2         2010m4          2011m4          1
       2    3         2011m6          2011m8          0
end

* translate to Stata monthly dates
gen bdate = monthly(bdate_str,"YM")
gen edate = monthly(edate_str,"YM")
drop *_str
format %tm bdate edate

list, sepby(id)

Соответствует:

     +---------------------------------------------+
     | id   spellnr   employed     bdate     edate |
     |---------------------------------------------|
  1. |  1         1          1    2008m1    2008m9 |
  2. |  1         2          0   2008m12    2009m8 |
  3. |  1         3          1   2009m11    2010m9 |
  4. |  1         4          0   2010m10    2011m9 |
     |---------------------------------------------|
  5. |  2         1          1    2007m4   2009m12 |
  6. |  2         2          1    2010m4    2011m4 |
  7. |  2         3          0    2011m6    2011m8 |
     +---------------------------------------------+

Здесь у данного человека (id) может быть несколько заклинаний (spellnr) двух типов (unempl: 1 для безработицы; 0 для занятости).даты начала и окончания каждого заклинания определяются bdate и edate, соответственно.

Представьте, что данные уже очищены и таковы, что никакие заклинания не перекрываются друг с другом,Между любыми двумя заклинаниями могут быть «пропущенные» периоды.Это отражено в фиктивном наборе данных, приведенном выше.

Мой вопрос:

Для каждого периода безработицы мне нужно вычислить количество месяцев, потраченных на занятость за последние 6 месяцев, 12 месяцев и 24 месяца.

Обратите внимание, что, что важно, каждый id может входить и выходить из работы, и все прошлые заклинания занятости должны быть приняты во внимание (не только последнее).

В моем примере это привело бы к следующему желаемому выводу:

     +--------------------------------------------------------------+
     | id   spellnr   employed     bdate     edate   m6   m24   m48 |
     |--------------------------------------------------------------|
  1. |  1         1          1    2008m1    2008m9    .     .     . |
  2. |  1         2          0   2008m12    2009m8    4     9     9 |
  3. |  1         3          1   2009m11    2010m9    .     .     . |
  4. |  1         4          0   2010m10    2011m9    6    11    20 |
     |--------------------------------------------------------------|
  5. |  2         1          1    2007m4   2009m12    .     .     . |
  6. |  2         2          1    2010m4    2011m4    .     .     . |
  7. |  2         3          0    2011m6    2011m8    5    20    44 |
     +--------------------------------------------------------------+

Моя (рабочая) попытка:

Следующий код возвращает желаемый результат,

* expand each spell to one observation per time unit (here "months"; works also for days)
expand edate-bdate+1
bysort id spellnr: gen spell_date = bdate + _n - 1
format %tm spell_date
list, sepby(id spellnr)

* fill-in empty months (not covered by spells)
xtset id spell_date, monthly 
tsfill

* compute cumulative time spent in employment and lagged values
bysort id (spell_date): gen cum_empl = sum(employed) if employed==1
bysort id (spell_date): replace cum_empl = cum_empl[_n-1] if cum_empl==.
bysort id (spell_date): gen lag_7  = L7.cum_empl  if employed==0  
bysort id (spell_date): gen lag_24 = L25.cum_empl if employed==0
bysort id (spell_date): gen lag_48 = L49.cum_empl if employed==0
qui replace lag_7=0  if lag_7==.  & employed==0  // fix computation for first spell of each "id" (if not enough time to go back with "L.")
qui replace lag_24=0 if lag_24==. & employed==0  
qui replace lag_48=0 if lag_48==. & employed==0  

* compute time spent in employment in the last 6, 24, 48 months, at the beginning of each unemployment spell
bysort id (spell_date): gen m6  = cum_empl - lag_7  if employed==0  
bysort id (spell_date): gen m24 = cum_empl - lag_24 if employed==0
bysort id (spell_date): gen m48 = cum_empl - lag_48 if employed==0
qui drop if (spellnr==.)
qui bysort id spellnr (spell_date): keep if _n == 1
drop spell_date cum_empl lag_*

list

Это прекрасно работает, но становится совершенно неэффективным при использовании (нескольких миллионов) ежедневных данных.Можете ли вы предложить какой-либо альтернативный подход, который не предусматривает расширение набора данных?

На словах я делаю выше:

  1. Я расширяю данные, чтобы иметь одну строку в месяц;
  2. Я заполняю «промежутки» между заклинаниями с помощью -tsfill-
  3. I Вычисляю время работы, потраченное на работу, и использую операторы запаздывания для получения трех интересующих величин.

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


РАБОТА С РЕШЕНИЯМИ

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

expand 500000
bysort id spellnr: gen new_id = _n
drop id 
rename new_id id

, который создает набор данных с 500 000 идентификаторов (всего 3 500 000 заклинаний).Первое решение в значительной степени доминирует над теми, которые используют joinby или rangejoin (см. Также комментарии к принятому ответу ниже).

1 Ответ

0 голосов
/ 10 февраля 2019

Приведенный ниже код может сэкономить некоторое время выполнения.

bys id (employed): gen tag = _n if !employed
sum tag, meanonly
local maxtag = `r(max)'

foreach i in 6 24 48 {
gen m`i' = .

    forval d = 1/`maxtag' {
    by id: gen x = 1 + min(bdate[`d'],edate) - max(bdate[`d']-`i',bdate) if employed
    egen y = total(x*(x>0)), by(id)
    replace m`i' = y if tag == `d'
    drop x y
    }
}
sort id bdate

Та же логика вместе с -rangejoin- (ssc) также заслуживает попытки.Пожалуйста, предоставьте некоторую обратную связь после тестирования с вашими (большими) фактическими данными.

preserve
    keep if employed
    replace employed = 0
    tempfile em
    save `em'
restore

foreach i in 6 24 48 {
gen _bd = bdate - `i'
rangejoin edate _bd bdate using `em', by(id employed) p(_)

egen m`i' = total(_edate - max(_bd,_bdate)+1) if !employed, by(id bdate)
bys id bdate: keep if _n==1
drop _*
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...