2012-02-27 10 views
10

Mam zestaw danych z kilkoma ocenami czasowymi dla każdego uczestnika. Chcę wybrać ostatnią ocenę dla każdego uczestnika. Mój zestaw danych wygląda następująco:wybierz ostatnią obserwację z danych podłużnych

ID week outcome 
1 2 14 
1 4 28 
1 6 42 
4 2 14 
4 6 46 
4 9 64 
4 9 71 
4 12 85 
9 2 14 
9 4 28 
9 6 51 
9 9 66 
9 12 84 

Chcę zaznaczyć tylko ostatni obserwacji/oceny dla każdego uczestnika, ale mam tylko kilka tygodni jako wskaźnik dla każdego uczestnika. Jak to jest możliwe do zrobienia w R (lub Excel?)

góry dzięki,

Niki

+1

Jak na bok, upewnij się, że robisz coś sensownego z te dane; samo uwzględnienie ostatniej dostępnej oceny może prowadzić do bardzo błędnych wniosków, w zależności od tego, dlaczego brakuje danych i czego szukasz. – Aaron

Odpowiedz

11

Oto jedno podejście base-R:

do.call("rbind", 
     by(df, INDICES=df$ID, FUN=function(DF) DF[which.max(DF$week), ])) 
    ID week outcome 
1 1 6  42 
4 4 12  85 
9 9 12  84 

Alternatywnie, Pakiet data.table oferuje zwięzły i wyrazisty język do wykonywania operacji związanych z ramkami danych tego typu:

library(data.table) 
dt <- data.table(df, key="ID") 

dt[, .SD[which.max(outcome), ], by=ID] 
#  ID week outcome 
# [1,] 1 6  42 
# [2,] 4 12  85 
# [3,] 9 12  84 

# Same but much faster. 
# (Actually, only the same as long as there are no ties for max(outcome)..) 
dt[ dt[,outcome==max(outcome),by=ID][[2]] ] # same, but much faster. 

# If there are ties for max(outcome), the following will still produce 
# the same results as the method using .SD, but will be faster 
i1 <- dt[,which.max(outcome), by=ID][[2]] 
i2 <- dt[,.N, by=ID][[2]] 
dt[i1 + cumsum(i2) - i2,] 

Wreszcie, tutaj jest plyr -na rozwiązanie

library(plyr) 

ddply(df, .(ID), function(X) X[which.max(X$week), ]) 
# ID week outcome 
# 1 1 6  42 
# 2 4 12  85 
# 3 9 12  84 
+0

Ładne odpowiedzi. Próbowałem wymyślić, jak to zrobić z plyr lub agregacja i nie, ponieważ nie mogę dowiedzieć się, jak zwrócić wynik, hacking to razem. +1 –

+0

@TylerRinker - Czy przyjrzałbyś się rozwiązaniu plyr, które właśnie dodałem? Zwykle używam base-R lub data.table, więc może brakować oczywistych ulepszeń. Dzięki! –

+0

działa dobrze. Ładnie zrobione –

2

Inną opcją w bazie: df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ]

+0

wielkie dzięki, pierwszy działa idealnie. za pomocą drugiego dostaję replikowane przypadki, nie mam pojęcia dlaczego. – user1236418

+0

Właśnie tutaj dla zainteresowania ... Trzymałbym się z Joshem! Spojrzę na problem z duplikatami .. – jbaums

+0

'grep' nie był odpowiedni do dokładnego dopasowania liczb.' X == df $ ID' ma lepszą pracę. – jbaums

1

Próbowałem użyć rozłamu i Tapply nieco więcej, aby stać się bardziej zapoznać się z nimi. Wiem, że to pytanie zostało już odebrane, ale pomyślałem, że dodam jeszcze jedną solodowanie za pomocą podziału (przepraszam za brzydotę, jestem bardziej niż otwarty na informacje zwrotne w celu poprawy, myślę, że być może było zastosowanie do strojenia w celu zmniejszenia kodu):

sdf <-with(df, split(df, ID)) 
max.week <- sapply(seq_along(sdf), function(x) which.max(sdf[[x]][, 'week'])) 
data.frame(t(mapply(function(x, y) y[x, ], max.week, sdf))) 

Pomyślałem również, dlaczego mamy 7 odpowiedzi tutaj, że dojrzała do punktu odniesienia. Wyniki mogą być zaskakujące (za pomocą rbenchmark z R2.14.1 na Win 7 maszynie):

# library(rbenchmark) 
# benchmark(
#  DATA.TABLE= {dt <- data.table(df, key="ID") 
#   dt[, .SD[which.max(outcome),], by=ID]}, 
#  DO.CALL={do.call("rbind", 
#   by(df, INDICES=df$ID, FUN=function(DF) DF[which.max(DF$week),]))}, 
#  PLYR=ddply(df, .(ID), function(X) X[which.max(X$week), ]), 
#  SPLIT={sdf <-with(df, split(df, ID)) 
#   max.week <- sapply(seq_along(sdf), function(x) which.max(sdf[[x]][, 'week'])) 
#   data.frame(t(mapply(function(x, y) y[x, ], max.week, sdf)))}, 
#  MATCH.INDEX=df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ], 
#  AGGREGATE=df[cumsum(aggregate(week ~ ID, df, which.max)$week), ], 
#  #WHICH.MAX.INDEX=df[sapply(unique(df$ID), function(x) which.max(x==df$ID)), ], 
#  BRYANS.INDEX = df[cumsum(as.numeric(lapply(split(df$week, df$ID), 
#   which.max))), ], 
#  SPLIT2={sdf <-with(df, split(df, ID)) 
#   df[cumsum(sapply(seq_along(sdf), function(x) which.max(sdf[[x]][, 'week']))), 
#   ]}, 
#  TAPPLY=df[tapply(seq_along(df$ID), df$ID, function(x){tail(x,1)}),], 
# columns = c("test", "replications", "elapsed", "relative", "user.self","sys.self"), 
# order = "test", replications = 1000, environment = parent.frame()) 

      test replications elapsed relative user.self sys.self 
6 AGGREGATE   1000 4.49 7.610169  2.84  0.05 
7 BRYANS.INDEX   1000 0.59 1.000000  0.20  0.00 
1 DATA.TABLE   1000 20.28 34.372881  11.98  0.00 
2  DO.CALL   1000 4.67 7.915254  2.95  0.03 
5 MATCH.INDEX   1000 1.07 1.813559  0.51  0.00 
3   PLYR   1000 10.61 17.983051  5.07  0.00 
4  SPLIT   1000 3.12 5.288136  1.81  0.00 
8  SPLIT2   1000 1.56 2.644068  1.28  0.00 
9  TAPPLY   1000 1.08 1.830508  0.88  0.00 

Edit1: pominąłem które rozwiązanie MAX, ponieważ nie zwraca poprawne wyniki i wrócił agregatu rozwiązanie jako dobrze, że chciałem użyć (komplementy Bryana Goodricha) i zaktualizowanej wersji split, SPLIT2, używając cumsum (podobał mi się ten ruch).

Edycja 2: Dason również włączył rozwiązanie tapply, które rzuciłem w test, który również wypadł całkiem dobrze.

+0

Mimo to, aby rozwiązanie tapply działało zgodnie z tym, czego chciał OP, technicznie musiałbyś sortować według tygodnia, jeśli nie był on jeszcze posortowany. W tym przypadku było sortowane według tygodnia, no cóż. – Dason

+1

Huh? Ta tabela pojawia się powtórz każdy test 1000 razy na bardzo małym zbiorze danych. Dlaczego wyniki jakiegokolwiek zastosowania (w jakikolwiek sposób) w praktyce? Musisz uruchomić test _single_ dla każdej metody na zbiorze danych _large_. To jest ważne. –

2

Mogę grać w tę grę. Przeprowadziłem kilka testów porównawczych różnic między lapply, sapply i przez, między innymi. Wydaje mi się, że im więcej masz kontroli nad typami danych i im bardziej podstawowa operacja, tym szybciej (np. Lapply jest generalnie szybszy niż sapply, a jako numeryczny (lapply (...)) idzie być szybszym, również). Mając to na uwadze, dało to takie same wyniki jak powyżej i może być szybsze niż pozostałe.

df[cumsum(as.numeric(lapply(split(df$week, df$id), which.max))), ] 

Wyjaśnienie: chcemy tylko what.max w tygodniu na każdy identyfikator. To obsługuje zawartość lapply. Potrzebujemy tylko wektora tych względnych punktów, więc utwórz go liczbowo.Wynikiem jest wektor (3, 5, 5). Musimy dodać pozycje poprzednich maksimów. Osiąga się to za pomocą cumsum.

Należy zauważyć, to rozwiązanie nie jest ogólne, gdy używam cumsum. Może to wymagać, aby przed wykonaniem sortować ramkę na identyfikator i tydzień. Mam nadzieję, że rozumiesz, dlaczego (i wiesz, jak używać z (df, porządek (identyfikator, tydzień)) w indeksie wiersza, aby to osiągnąć). W każdym razie może się nie powieść, jeśli nie mamy unikalnego maksimum, ponieważ which.max zajmuje tylko pierwsze. W związku z tym moje rozwiązanie jest trochę pytaniem, ale to oczywiste. Próbujemy wyodrębnić bardzo konkretne informacje dla bardzo konkretnego przykładu. Nasze rozwiązania nie mogą być ogólne (nawet jeśli metody są ważne, aby je ogólnie zrozumieć).

Zostawię go Trinker zaktualizować swoje porównań!

8

Jeśli jesteś po prostu patrząc na ostatnią obserwację za osobę ID, a następnie prosty kod dwóch wiersz powinien to zrobić. Zawsze jestem gotowy na proste rozwiązanie bazowe, kiedy tylko jest to możliwe, podczas gdy zawsze jest wspaniale mieć więcej niż jeden sposób na rozwiązanie problemu.

dat[order(dat$ID,dat$Week),] # Sort by ID and week 
dat[!duplicated(dat$ID, fromLast=T),] # Keep last observation per ID 

    ID Week Outcome 
3 1 6  42 
8 4 12  85 
13 9 12  84 
+0

+1 dla powielonych. To przydatna mała funkcja. – AdamO

+0

To jest odpowiedź wysokiej jakości, która zasługuje na więcej głosów. Jeśli chodzi o "R", doceniam odpowiedzi, które nie wymagają instalowania nowych pakietów itp. Dziękujemy za dodanie dwóch centów. – ChrisP

2

Ta odpowiedź wykorzystuje pakiet data.table. Powinien być bardzo szybki, nawet przy większych zbiorach danych.

setkey(DT, ID, week)    # Ensure it's sorted. 
DT[DT[, .I[.N], by = ID][, V1]] 

Objaśnienie: .I to wektor posiadający lokalizacje całkowitą rzędu dla grupy (w tym przypadku grupa jest ID). .N to wektor długości całkowitej-jeden zawierający liczbę wierszy w grupie. Więc co robimy tutaj jest wydobycie lokalizację ostatnim rzędzie dla każdej grupy, używając „wewnętrzny” DT[.], wykorzystując fakt, że dane są sortowane według ID i week. Następnie używamy tego do podzbioru "zewnętrznego" DT[.].

Dla porównania (bo to nie pisał gdzie indziej), oto jak można generować oryginalne dane, aby można było uruchomić kod:

DT <- 
    data.table(
    ID = c(rep(1, 3), rep(4, 5), rep(9, 5)), 
    week = c(2,4,6, 2,6,9,9,12, 2,4,6,9,12), 
    outcome = c(14,28,42, 14,46,64,71,85, 14,28,51,66,84)) 
Powiązane problemy