2015-09-04 27 views
7

Sono un nuovo arrivato relativamente a R, quindi mi dispiace se c'è una risposta ovvia a questo. Ho esaminato altre domande e penso che "applicare" sia la risposta, ma non riesco a capire come usarlo in questo caso.Modi più efficienti per utilizzare R rispetto ai cicli "for"

Ho un sondaggio longitudinale in cui i partecipanti sono invitati ogni anno. In alcuni anni non riescono a prendere parte e, a volte, muoiono. Ho bisogno di identificare quali partecipanti hanno preso parte a una "serie" coerente sin dall'inizio del sondaggio (cioè se si fermano, si fermano per sempre).

Ho fatto questo con un ciclo 'for', che funziona bene nell'esempio qui sotto. Ma ho molti anni e molti partecipanti, e il ciclo è molto lento. C'è un approccio più veloce che potrei usare?

Nell'esempio, TRUE significa che hanno partecipato a quell'anno. Il loop crea due vettori - 'finalyear' per l'ultimo anno in cui hanno preso parte e 'streak' per mostrare se sono stati completati tutti gli anni prima dell'ultimo anno (cioè i casi 1, 3 e 5).

dat <- data.frame(ids = 1:5, "1999" = c(T, T, T, F, T), "2000" = c(T, F, T, F, T), "2001" = c(T, T, T, T, T), "2002" = c(F, T, T, T, T), "2003" = c(F, T, T, T, F)) 
finalyear <- NULL 
streak <- NULL 
for (i in 1:nrow(dat)) { 
    x <- as.numeric(dat[i,2:6]) 
    y <- max(grep(1, x)) 
    finalyear[i] <- y 
    streak[i] <- sum(x) == y 
} 
dat$finalyear <- finalyear 
dat$streak <- streak 

Grazie!

+0

un sacco di risposte - qualcuno vuole per creare un set di dati più grandi e li punto di riferimento? Quanto è grande il set di dati in modo che sia possibile creare un banco di prova rappresentativo per il benchmarking? – Spacedman

+0

Ci sono circa 250.000 casi e 25 anni. Tutte le risposte qui sotto risolvono il mio problema - grazie a tutti! Se le persone sono interessate, potrei creare un set di dati rappresentativo per testare approcci diversi. –

risposta

4

potremmo usare max.col e rowSums come un approccio vectorized.

dat$finalyear <- max.col(dat[-1], 'last') 

Se ci sono file senza TRUE valori, possiamo fare sicuri di restituire 0 per quella riga moltiplicando con la doppia negazione di rowSums. Lo FALSE sarà forzato a 0 e moltiplicando con 0 si ottiene 0 per quella riga.

dat$finalyear <- max.col(dat[-1], 'last')*!!rowSums(dat[-1]) 

Poi, creiamo la colonna 'striscia' confrontando il rowSums di colonne 2: 6 con quello di 'finalyear'

dat$streak <- rowSums(dat[,2:6])==dat$finalyear 
dat 
# ids X1999 X2000 X2001 X2002 X2003 finalyear streak 
#1 1 TRUE TRUE TRUE FALSE FALSE   3 TRUE 
#2 2 TRUE FALSE TRUE TRUE TRUE   5 FALSE 
#3 3 TRUE TRUE TRUE TRUE TRUE   5 TRUE 
#4 4 FALSE FALSE TRUE TRUE TRUE   5 FALSE 
#5 5 TRUE TRUE TRUE TRUE FALSE   4 TRUE 

o di un codice di una sola riga (che potrebbe rientrare in un -line, ma ha deciso di rendere evidente da 2 linee) suggerito da @ColonelBeauvel

library(dplyr) 
mutate(dat, finalyear=max.col(dat[-1], 'last'), 
      streak=rowSums(dat[-1])==finalyear) 
+1

La migliore risposta concisa e vettoriale. +1 –

+0

@ColonelBeauvel Grazie, stavo per cambiare il tuo, ma sembra essere cancellato. – akrun

+2

un liner con 'mutate (dat, finalyear = max.col (dat [-1], 'last'), streak = rowSums (dat [-1]) == finalyear)' –

3

Ecco una soluzione con dplyr e tidyr.

gather(data = dat,year,value,-ids) %>% 
    mutate(year=as.integer(gsub("X","",year))) %>% 
    group_by(ids) %>% 
    summarize(finalyear=last(year[value]), 
      streak=!any(value[first(year):finalyear] == FALSE)) 

uscita

ids finalyear streak 
1 1  2001 TRUE 
2 2  2003 FALSE 
3 3  2003 TRUE 
4 4  2003 FALSE 
5 5  2002 TRUE 
1

Ecco una versione base con apply al ciclo sopra le righe e rle per vedere quanto spesso i cambiamenti di stato. La sua condizione sembra essere equivalente allo stato di partenza, come TRUE e solo in continua evoluzione per FALSE al massimo una volta, in modo da testare la rle come più breve di 3 e il primo valore essendo TRUE:

> dat$streak = apply(dat[,2:6],1,function(r){r[1] & length(rle(r)$length)<=2}) 
> 
> dat 
    ids X1999 X2000 X2001 X2002 X2003 streak 
1 1 TRUE TRUE TRUE FALSE FALSE TRUE 
2 2 TRUE FALSE TRUE TRUE TRUE FALSE 
3 3 TRUE TRUE TRUE TRUE TRUE TRUE 
4 4 FALSE FALSE TRUE TRUE TRUE FALSE 
5 5 TRUE TRUE TRUE TRUE FALSE TRUE 

C'è probabilmente carichi di modi di lavorare fuori finalyear, questo appena trova l'ultimo elemento di ogni riga che è TRUE:

> dat$finalyear = apply(dat[,2:6], 1, function(r){max(which(r))}) 
> dat 
    ids X1999 X2000 X2001 X2002 X2003 streak finalyear 
1 1 TRUE TRUE TRUE FALSE FALSE TRUE   3 
2 2 TRUE FALSE TRUE TRUE TRUE FALSE   5 
3 3 TRUE TRUE TRUE TRUE TRUE TRUE   5 
4 4 FALSE FALSE TRUE TRUE TRUE FALSE   5 
5 5 TRUE TRUE TRUE TRUE FALSE TRUE   4 
4

For-loop non sono intrinsecamente male in R, ma sono lento se si cresce vettori iterativamente (come si sta facendo). Ci sono spesso modi migliori per fare le cose. Esempio di una soluzione con solo apply-funzioni:

dat$finalyear <- apply(dat[,2:6],MARGIN=1,function(x){max(which(x))}) 
dat$streak <- apply(dat[,2:7],MARGIN=1,function(x){sum(x[1:5])==x[6]}) 

o l'opzione 2, in base a commento di @Spacedman:

dat$finalyear <- apply(dat[,2:6],MARGIN=1,function(x){max(which(x))}) 
dat$streak <- apply(dat[,2:6],MARGIN=1,function(x){max(which(x))==sum(x)}) 

> dat 
    ids X1999 X2000 X2001 X2002 X2003 finalyear streak 
1 1 TRUE TRUE TRUE FALSE FALSE   3 TRUE 
2 2 TRUE FALSE TRUE TRUE TRUE   5 FALSE 
3 3 TRUE TRUE TRUE TRUE TRUE   5 TRUE 
4 4 FALSE FALSE TRUE TRUE TRUE   5 FALSE 
5 5 TRUE TRUE TRUE TRUE FALSE   4 TRUE 
+0

Neat, ma attenzione dipende dall'aggiunta di 'finalyear' direttamente dopo i dati vero/falso, in questo caso nella colonna 7. – Spacedman

+0

Grazie. Dubitavo che avrei dovuto farlo in questo modo, o chiamare max (che (x)) due volte. Modificherà. – Heroka