Как написать функцию вместо цикла for с использованием диапазонов дат

#r #plyr #dplyr

#r #plyr #dplyr

Вопрос:

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

 # first generate a vector of dates and repeat it 4 times
beg.date <- rep(seq.Date(as.Date("2008-01-01"),as.Date("2013-12-31"),by="day"),4)
length(beg.date) # 8768
length(beg.date)/4 # 2192 dates (6 years)
# generate a vector of judges of same length
x <- factor(LETTERS[1:4]); judge <- rep(x, each=2192)
# cbind them as df
data <- cbind.data.frame(judge, beg.date)
# create end date exactly 30 days later for each case
data$end.date <- as.Date(data$beg.date   30)
#sort by beg.date and add caseid variable
data  <- data[order(data$beg.date),]; data$caseid <- 1:8768
#reorder columns
data <- data[c(4,1,2,3)]
# reorder rows by judge and by end dates
data <- data[order(data$judge, data$end.date),]
  

Вот как выглядят данные:

   caseid judge   beg.date   end.date
1      1     A 2008-01-01 2008-01-31
2      5     A 2008-01-02 2008-02-01
3      9     A 2008-01-03 2008-02-02
4     13     A 2008-01-04 2008-02-03
5     17     A 2008-01-05 2008-02-04
6     21     A 2008-01-06 2008-02-05
  

Итак, я хочу вычислить, какое 30-дневное отставание судьи и коэффициент завершения были в день вынесения решения по делу. Я выяснил, как создать интервал дат (окно) и определить количество обращений, которые начались или закончились в этом окне. И я могу применять ее на постоянной основе к набору данных judge, используя неуклюжий for loop.

 a <- data
comprate <- numeric()
ratio <- numeric()
for (j in c("A","B","C","D")){
  x=a[a$judge==j,]
for(i in 1:nrow(x)){
  y <- new_interval((x$end.date[i]-ddays(30)),x$end.date[i])
  x$comprate[i] <- length(x$end.date[x$end.date %within% y==T])
  x$ratio[i]  <- length(x$beg.date[x$beg.date %within% y==T])/x$comprate[i]
  }
comprate  <- append(comprate, x$comprate, after=length(comprate))
ratio  <- append(ratio, x$ratio, after=length(ratio))
}
a$comprate <- comprate
a$ratio <- ratio
  

Это работает с небольшим набором выборочных данных, но данные моего проекта содержат более 6 миллионов наблюдений (случаев). Я знаю, что есть способ сделать это с помощью ddply or dplyr , но это просто выше моих сил. Может ли кто-нибудь мне помочь?

Большое спасибо. Кен

Некоторые последующие вопросы:


Спасибо @MrFlick за полезный ответ. Позвольте мне посмотреть, понимаю ли я (или, пожалуйста, помогите мне понять), как работает решение:

 dt[, comprate:=sapply(end.date, function(i) 
    sum(between(as.numeric(i)-as.numeric(end.date),0,30))), by=judge]
  

В этом кодовом блоке:
1 переменная comprate создается путем применения sapply(etc.) выражения by=judge .
2 sapply применяет function(i) к каждому элементу end.date и возвращает упрощенный результат.
3 function(i) принимает в качестве входных данных первый элемент end.date , sum возвращает сумму логических истинных значений логического вектора between(etc.) .

Я в порядке, я думаю до этого, но после этого я запутался в том, как between работает и какие именно значения включаются и оцениваются. Так что же именно as.numeric(i)-as.numeric(end.date) делается? Я получаю as.numeric часть — это просто извлечение целого числа, которое представляет количество дней после контрольной даты.

Итак, ‘as.numeric(i)’ извлекает целочисленное значение i-th элемента end.date ?
Тогда что - as.numeric(end.date) делается?

Ответ №1:

Я бы не стал слишком расстраиваться, эти проблемы с перемещением окон немного сложны.

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

 library(data.table)
dt<-setDT(data)
setkey(dt, judge, end.date)
dt[, comprate:=sapply(end.date, function(i) 
    sum(between(as.numeric(i)-as.numeric(end.date),0,30))), by=judge]

setkey(dt, judge, beg.date)
dt[, newcase:=sapply(end.date, function(i) 
    sum(between(as.numeric(i)-as.numeric(beg.date),0,30))), by=judge]

dt[, ratio:= newcase/comprate]
a<-as.data.frame(dt)
  

Итак, мы используем setDT() для превращения data в объект data.table. Затем мы устанавливаем ключ, который добавляет индекс в таблицу. Далее мы используем специальный синтаксис data.table для добавления новых столбцов. Здесь для каждого судьи мы вычисляем количество конечных дат за последние 30 дней. Похоже, вы использовали lubridate это раньше. Здесь, поскольку значения даты хранятся как количество дней, прошедших с контрольной даты, я просто преобразую в числовое значение и делаю вычитание самостоятельно. Затем я переупорядочиваю и вычисляю количество новых обращений. Я делаю один дополнительный шаг, чтобы вычислить соотношение. Затем я конвертирую данные обратно в data.frame (но вы могли бы с таким же успехом сохранить их как data.table).

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


Дальнейшее объяснение

Итак, давайте поработаем с простым вектором

 x<-c(1,3,6,9,10,15)
  

мы можем использовать их как даты в числовой форме. Когда мы делаем

 sapply(x, function(i) i-x)

#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    0    2    5    8    9   14
# [2,]   -2    0    3    6    7   12
# [3,]   -5   -3    0    3    4    9
# [4,]   -8   -6   -3    0    1    6
# [5,]   -9   -7   -4   -1    0    5
# [6,]  -14  -12   -9   -6   -5    0
  

Что мы делаем, так это берем каждое значение x по одному за раз (как i ) и находим разницу с любым другим значением в x . Каждое x значение генерирует один из приведенных выше столбцов. Теперь я могу добавить значение between, чтобы увидеть, есть ли различия, скажем, между 0 и 10.

 sapply(x, function(i) between(i-x, 1, 10))

#       [,1]  [,2]  [,3]  [,4]  [,5]  [,6]
# [1,]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
# [2,] FALSE  TRUE  TRUE  TRUE  TRUE FALSE
# [3,] FALSE FALSE  TRUE  TRUE  TRUE  TRUE
# [4,] FALSE FALSE FALSE  TRUE  TRUE  TRUE
# [5,] FALSE FALSE FALSE FALSE  TRUE  TRUE
# [6,] FALSE FALSE FALSE FALSE FALSE  TRUE
  

Итак, мы используем between (из data.table пакета), чтобы ограничить результаты определенным окном в прошлом. Теперь, вместо того, чтобы возвращать столбец для каждого x значения, мы берем sum() значение между значениями, которое превратит все ИСТИННЫЕ значения в 1, а ЛОЖНЫЕ в 0

 sapply(x, function(i) sum(between(i-x, 0, 10)))
# [1] 1 2 3 4 5 4
  

Комментарии:

1. Спасибо за щедрый ответ. Сейчас я разбираюсь с этим. И освоение нового набора навыков.

2. Я в порядке, я думаю, до определенного момента (смотрите Мои последующие вопросы в моем первоначальном вопросе). Но после этого я запутался в том, как between работает и какие именно значения включаются и оцениваются. Так что же именно as.numeric(i)-as.numeric(end.date) делается? Я получаю as.numeric часть — это просто извлечение целого числа, которое представляет количество дней после контрольной даты. Итак, ‘as.numeric(i)’ извлекает целочисленное значение первого элемента end.date ? Тогда что - as.numeric(end.date) делает?

3. @kmayeaux Я добавил несколько дополнительных объяснений и простой пример, чтобы более наглядно показать, как это работает.

4. большое спасибо за урок. Таким образом, повышение скорости заключается в выполнении этого в виде матрицы, а не в сканировании каждого элемента end.date , чтобы увидеть, находится ли он в интервале, а затем в суммировании T — а затем повторении этого для каждого значения end.date ?

5. На самом деле, повышение скорости происходит за счет использования data.table , которое способно выполнять эту операцию быстрее за счет индексации данных. Приведенное выше описание на самом деле о том, почему вы ожидаете, что эти функции будут работать. Фактическая реализация того, как они выполняются, может отличаться в data.table самих вызовах.