2012-02-27 9 views
10

Ho un set di dati con diverse valutazioni del tempo per ogni partecipante. Voglio selezionare l'ultima valutazione per ogni partecipante. Il mio set di dati simile a questo:selezionare l'ultima osservazione da dati longitudinali

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 

voglio selezionare solo l'ultima osservazione/valutazione per ogni partecipante, ma ho solo il numero di settimane come un indicatore per ogni partecipante. Come è possibile fare in R (o Excel?)

grazie in anticipo,

niki

+1

Per inciso, si prega di assicurarsi che si sta facendo qualcosa di ragionevole con questi dati; solo prendere l'ultima valutazione disponibile può portare a inferenze molto sbagliate, a seconda del motivo per cui manchi i dati e quello che stai cercando. – Aaron

risposta

11

Ecco un approccio di 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 

In alternativa, il pacchetto data.table offre un linguaggio sintetico ed espressivo per l'esecuzione di manipolazioni di frame di dati di questo tipo:

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,] 

Infine, ecco una soluzione basata su plyr

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

Belle risposte. Stavo cercando di pensare a come farlo con plyr o aggregato e fallire perché non riesco a capire come restituire il risultato senza hackerarlo insieme. +1 –

+0

@TylerRinker - Vorresti dare un'occhiata alla soluzione plyr che ho appena aggiunto? Per lo più uso la base-R o data.table, quindi potrebbe mancare qualche ovvio miglioramento ad esso. Grazie! –

+0

funziona bene. Ben fatto –

2

Un'altra opzione a base di: df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ]

+0

grazie mille, il primo funziona perfettamente. usando il secondo ottengo alcuni casi replicati, nessun indizio perché. – user1236418

+0

Solo qui per interesse ... Mi terrei con Josh's! Vedremo comunque il problema dei duplicati. – jbaums

+0

'grep' non era proprio appropriato per la corrispondenza esatta dei numeri ..' x == df $ ID fa un lavoro migliore. – jbaums

1

Ho cercato di utilizzare dividere e Tapply un po 'di più per diventare più conoscerli So che questa domanda ha già avuto una risposta, ma ho pensato di aggiungere un'altra soluzione usando split (scusate la bruttezza, sono più che aperto al feedback per il miglioramento, pensavo forse ci fosse un uso per diminuire il codice):

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

Ho anche capito perché abbiamo 7 risposte qui era maturo per un punto di riferimento. I risultati potrebbero sorprendervi (usando rbenchmark con R2.14.1 su una macchina Win 7):

# 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: ho omesso il quale soluzione MAX in quanto non restituisce i risultati corretti e sono tornato una soluzione aggregato beh che volevo usare (complimenti di Bryan Goodrich) e una versione aggiornata di split, SPLIT2, usando cumsum (mi piaceva quella mossa).

Modifica 2: Anche Dason ha suonato con una soluzione filettata che ho lanciato nel test che è andato molto bene.

+0

Anche se per fare in modo che la soluzione tapply funzioni per quello che l'OP voleva, tecnicamente dovresti ordinare per settimana se non fosse già stato ordinato. In questo caso è stato ordinato per settimana quindi vabbè. – Dason

+1

Eh? Questa tabella appare ripetere ogni test 1000 volte su un set di dati molto piccolo. Perché i risultati di qualsiasi utilizzo (in qualsiasi modo) nella pratica? È necessario eseguire un test _single_ di ciascun metodo, su un set di dati _large_. _Questo è ciò che conta –

2

Posso giocare a questo gioco. Ho eseguito alcuni benchmark sulle differenze tra lapply, sapply e di, tra le altre cose. Mi sembra che più controlli i tipi di dati e più l'operazione è di base, più veloce è (ad esempio, lapply è generalmente più veloce di sapply e as.numeric (lapply (...)) sta andando essere più veloce, anche). Con questo in mente, questo ha prodotto gli stessi risultati di cui sopra e potrebbe essere più veloce del resto.

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

Spiegazione: vogliamo solo which.max nella settimana per ogni ID. Che gestisce il contenuto di lapply. Abbiamo solo bisogno del vettore di questi punti relativi, quindi rendilo numerico.Il risultato è il vettore (3, 5, 5). Abbiamo bisogno di aggiungere le posizioni dei massimi precedenti. Ciò è possibile con cumsum.

Si noti che questa soluzione non è generale quando utilizzo cumsum. Potrebbe richiedere che prima dell'esecuzione ordiniamo il frame su id e settimana. Spero che tu capisca perché (e sappia usare con (df, order (id, week)) nell'indice di riga per ottenere quello). In ogni caso, può ancora fallire se non abbiamo un massimo univoco, perché which.max prende solo il primo. Pertanto, la mia soluzione è un po 'una domanda di accattonaggio, ma è ovvio. Stiamo cercando di estrarre informazioni molto specifiche per un esempio molto specifico. Le nostre soluzioni non possono essere generali (anche se i metodi sono importanti per capire in generale).

Lo lascio al trinker per aggiornare i suoi confronti!

8

Se stai cercando l'ultimo ID di osservazione per persona, è necessario un semplice codice a due righe. Sono sempre pronto per una semplice soluzione di base quando possibile, mentre è sempre bello avere più di un modo per risolvere un problema.

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 per duplicato. Questa è una piccola funzione utile. – AdamO

+0

Questa è una risposta di alta qualità che merita più voti positivi. Quando si tratta di 'R', apprezzo le risposte che non implicano l'installazione di nuovi pacchetti, ecc. Grazie per aver aggiunto i tuoi due centesimi. – ChrisP

2

Questa risposta utilizza il pacchetto data.table. Dovrebbe essere molto veloce, anche con set di dati più grandi.

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

Spiegazione: .I è un vettore intero tenendo le posizioni delle righe per il gruppo (in questo caso il gruppo è ID). .N è un vettore intero di lunghezza uno contenente il numero di righe nel gruppo. Quindi, quello che stiamo facendo qui è quello di estrarre la posizione dell'ultima riga per ogni gruppo, utilizzando il "interno" DT[.], utilizzando il fatto che i dati sono ordinati in base a ID e week. Successivamente lo usiamo per suddividere il "esterno" DT[.].

Per confronto (perché non è pubblicato altrove), ecco come è possibile generare i dati originali in modo da poter eseguire il codice:

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)) 
Problemi correlati