Как фильтровать строки по разнице дат между строками в R?

Внутри каждого id я хотел бы сохранить строки, по крайней мере, на 91 день. В моем кадре df ниже id=1 имеет 5 строк и id=2 имеет 1 строку.

Для id=1, я хотел бы сохранить только 1-й, 3-й и 5-й строки.

Это связано с тем, что если мы сравним 1-ю дату и 2-ю дату, они будут отличаться на 32 дня. Итак, удалите вторую дату. Мы переходим к сравнению 1-й и 3-й даты, и они отличаются на 152 дня. Итак, мы сохраняем 3-й день.

Теперь, вместо использования 1-й даты в качестве ссылки, мы используем 3-ю дату. 3-я дата и 4-я дата отличаются на 61 день. Итак, удалите 4-ю дату. Мы переходим к сравнению 3-й даты и 5-й даты, и они отличаются на 121 день. Итак, мы сохраняем 5-ю дату.

В конце концов, даты, которые мы сохраняем, - это 1, 3 и 5 даты. Что касается id=2, то есть только одна строка, поэтому мы сохраняем это. Желаемый результат показан в dfnew.

df <- read.table(header = TRUE, text = "
id  var1  date        
 1  A     2006-01-01 
 1  B     2006-02-02 
 1  C     2006-06-02 
 1  D     2006-08-02 
 1  E     2007-12-01 
 2  F     2007-04-20 
",stringsAsFactors=FALSE)

dfnew <- read.table(header = TRUE, text = "
id  var1  date        
 1  A     2006-01-01 
 1  C     2006-06-02 
 1  E     2007-12-01 
 2  F     2007-04-20 
",stringsAsFactors=FALSE)

Я могу думать только о начале группировки df на id следующим образом:

library(dplyr)
dfnew <- df %>% group_by(id)

Однако я не уверен, как продолжить здесь. Должен ли я продолжить функцию filter или slice? Если да, то как?

Ответ 1

Альтернативой, использующей slice из dplyr, является определение следующей рекурсивной функции:

library(dplyr)
f <- function(d, ind=1) {
  ind.next <- first(which(difftime(d,d[ind], units="days") > 90))
  if (is.na(ind.next))
    return(ind)
  else
    return(c(ind, f(d,ind.next)))
}

Эта функция работает с столбцом date, начиная с ind = 1. Затем он находит следующий индекс ind.next, который является индексом first, для которого дата больше 90 дней (не менее 91 дня) с даты, проиндексированной ind. Обратите внимание, что если таких ind.next, ind.next==NA нет, и мы просто возвращаем ind. В противном случае мы рекурсивно вызываем f начиная с ind.next и возвращаем его результат, конкатенированный с помощью ind. Конечным результатом этого вызова функции являются индексы строк, разделенные не менее 91 днями.

С помощью этой функции мы можем сделать:

result <- df %>% group_by(id) %>% slice(f(as.Date(date, format="%Y-%m-%d")))
##Source: local data frame [4 x 3]
##Groups: id [2]
##
##     id  var1       date
##  <int> <chr>      <chr>
##1     1     A 2006-01-01
##2     1     C 2006-06-02
##3     1     E 2007-12-01
##4     2     F 2007-04-20

Использование этой функции предполагает, что столбец date сортируется в порядке возрастания каждой группой id. Если нет, мы можем просто отсортировать даты до нарезки. Не уверен в эффективности этого или об опасностях рекурсивных звонков в Р. Надеюсь, Дэвид Аренбург или другие могут прокомментировать это.


Как было предложено Дэвидом Аренбургом, лучше преобразовать date в класс Date сначала вместо группы:

result <- df %>% mutate(date=as.Date(date, format="%Y-%m-%d")) %>%
                 group_by(id) %>% slice(f(date))
##Source: local data frame [4 x 3]
##Groups: id [2]
##
##     id  var1       date
##  <int> <chr>     <date>
##1     1     A 2006-01-01
##2     1     C 2006-06-02
##3     1     E 2007-12-01
##4     2     F 2007-04-20

Ответ 2

Здесь попытка использования скользящих соединений в data.table, которая, как мне кажется, должна быть эффективной

library(data.table)
# Set minimum distance
mindist <- 91L 
# Make sure it is a real Date
setDT(df)[, date := as.IDate(date)] 
# Create a new column with distance + 1 to roll join too
df[, date2 := date - (mindist + 1L)] 
# Perform a rolling join per each value in df$date2 that has atleast 91 difference from df$date
unique(df[df, on = c(id = "id", date = "date2"), roll = -Inf], by = c("id", "var1"))
#    id var1       date      date2 i.var1     i.date
# 1:  1    A 2005-10-01 2005-10-01      A 2006-01-01
# 2:  1    C 2006-03-02 2006-03-02      C 2006-06-02
# 3:  1    E 2007-08-31 2007-08-31      E 2007-12-01
# 4:  2    F 2007-01-18 2007-01-18      F 2007-04-20

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