2016-09-04 14 views
10

Wewnątrz każdego id, chciałbym zachować wiersze, które są co najmniej 91 dni od siebie. W mojej ramce danych df poniżej, id=1 ma 5 wierszy i id=2 ma 1 wiersz.Jak filtrować wiersze na podstawie różnicy dat między wierszami w R?

Dla id=1, chciałbym zachować tylko 1., 3. i 5. wiersz.

Dzieje się tak, ponieważ jeśli porównamy pierwszą datę i drugą datę, różnią się one o 32 dni. Usuń więc drugą datę. Porównujemy pierwszą i trzecią datę i różnią się one o 152 dni. Tak więc trzymamy 3 datę.

Teraz zamiast pierwszej daty jako odniesienia używamy 3. daty. Trzecia data i czwarta data różnią się o 61 dni. Usuń więc czwartą datę. Porównujemy 3 datę i 5 datę, a różnią się one o 121 dni. Tak więc trzymamy 5. randkę.

Ostatecznie daty, w których się zatrzymujemy, to daty 1, 3 i 5. Jeśli chodzi o id=2, jest tylko jeden wiersz, więc trzymamy to. Pożądany wynik jest pokazany w 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) 

mogę myśleć tylko o zaczynając grupowania df przez id następująco:

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

Jednak nie jestem pewien, w jaki sposób dalej stąd. Czy powinienem wykonać funkcję filter lub slice? Jeśli tak to jak?

Odpowiedz

3

alternatywę, która wykorzystuje slice z dplyr jest określenie funkcji rekurencyjnej następujące:

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))) 
} 

ten działa na kolumnie date wyjściowego w ind = 1. Następnie znajduje następny indeks ind.next, który jest indeksem, dla którego data jest większa niż 90 dni (co najmniej 91 dni) od daty indeksowanej przez ind. Zauważ, że jeśli nie ma takich ind.next, ind.next==NA i właśnie wrócimy ind. W przeciwnym razie wywołujemy rekursywnie f zaczynając od ind.next i zwracamy jego wynik połączony z ind. Końcowym wynikiem tego wywołania funkcji są indeksy wierszy rozdzielone co najmniej o 91 dni.

Za pomocą tej funkcji możemy zrobić:

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 

Użycie tej funkcji zakłada, że ​​kolumna date jest posortowana w kolejności rosnącej przez każdą z grup id. Jeśli nie, możemy posortować daty przed krojeniem. Nie jestem pewien co do skuteczności tego lub niebezpieczeństw rekurencyjnych połączeń w R. Mam nadzieję, David Arenburg lub inni mogą komentować to.


Jak sugeruje David Arenburg, to lepiej konwertować date do klasy Date pierwszy zamiast przez grupy:

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 
+0

Może przekonwertować na dzień spełnienia wymogu 'klasy pierwszej zamiast robić to przez grupy –

+0

@DavidArenburg: Dziękuję, twoje komentarze są zawsze bardzo doceniane. Dokonałem edycji. – aichao

13

Oto próba za pomocą toczenia łączy w data.table które wierzę powinien być skuteczny

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 

To daje dwie dodatkowe kolumny, ale nie jest to nic wielkiego IMO. Logiczne jest to sensowne i przetestowałem to z powodzeniem w różnych scenariuszach, ale może potrzebować dodatkowych testów sprawdzających.

Powiązane problemy