2013-04-25 5 views
18

Ecco un semplice esempio del mio problema:R come posso calcolare la differenza tra le righe in un frame di dati

> df <- data.frame(ID=1:10,Score=4*10:1) 
> df 
     ID Score 
    1 1 40 
    2 2 36 
    3 3 32 
    4 4 28 
    5 5 24 
    6 6 20 
    7 7 16 
    8 8 12 
    9 9  8 
    10 10  4 
    > diff(df) 

Error in r[i1] - r[-length(r):-(length(r) - lag + 1L)] : 
    non-numeric argument to binary operator 

Qualcuno può dirmi perché si verifica questo errore?

risposta

19

Forse siete alla ricerca di qualcosa di simile:

> tail(df, -1) - head(df, -1) 
    ID Score 
2 1 -4 
3 1 -4 
4 1 -4 
5 1 -4 
6 1 -4 
7 1 -4 
8 1 -4 
9 1 -4 
10 1 -4 

È possibile sottrarre o aggiungere due data.frame s insieme se sono le stesse dimensioni. Quindi, quello che stiamo facendo qui è sottrarre uno data.frame che manca la prima riga (tail(df, -1)) e uno che manca l'ultima riga (head(df, -1)) e sottraendoli.

+1

+1 Bel trucco con testa e croce. –

+0

+1 immaginativo. Non penserei mai che 'head' con -1 restituisca tutto tranne la prima riga. Clever –

25

diff vuole una matrice o un vettore piuttosto che un frame di dati. Prova

data.frame(diff(as.matrix(df))) 
+0

+1 nice - Suppongo che abbia senso poiché una matrice è solo un vettore con un secondo attributo dim. –

+0

+1. Questa sarà, di gran lunga, la soluzione più veloce qui. – A5C1D2H2I1M1N2O1R2T1

+1

Per riferimento, alcuni [benchmark] (http://rpubs.com/mrdwab/bmft) su un set di dati di 1000000 righe per 5 colonne. – A5C1D2H2I1M1N2O1R2T1

8

Perché df funziona su vettore o matrice. È possibile utilizzare Applica per applicare la funzione tra le colonne in questo modo:

apply(df , 2 , diff) 
    ID Score 
2 1 -4 
3 1 -4 
4 1 -4 
5 1 -4 
6 1 -4 
7 1 -4 
8 1 -4 
9 1 -4 
10 1 -4 

Sembra improbabile che si desidera calcolare la differenza di ID sequenziali, così si potrebbe scegliere di applicare su tutte le colonne tranne il primo come così:

apply(df[-1] , 2 , diff) 

Oppure si potrebbe usare data.table (non che aggiunge qualcosa qui voglio solo davvero per iniziare ad usarlo!), e mi sono di nuovo partendo dal presupposto che non si desidera applicare diff alla colonna ID:

DT <- data.table(df) 
DT[ , list(ID,Score,Diff=diff(Score)) ] 
    ID Score Diff 
1: 1 40 -4 
2: 2 36 -4 
3: 3 32 -4 
4: 4 28 -4 
5: 5 24 -4 
6: 6 20 -4 
7: 7 16 -4 
8: 8 12 -4 
9: 9  8 -4 
10: 10  4 -4 

E grazie a @AnandaMahto una sintassi alternativa che dà più flessibilità di scegliere quali colonne di eseguire sul potrebbe essere:

DT[, lapply(.SD, diff), .SDcols = 1:2] 

Qui .SDcols = 1:2 significa che si desidera applicare la funzione diff alle colonne 1 e 2 Se si dispone di 20 colonne e non si desidera applicarlo all'ID, è possibile utilizzare .SDcols=2:20 come esempio.

+0

+1 per le modifiche. Renderà meno confusa per i futuri visitatori di questa domanda. – A5C1D2H2I1M1N2O1R2T1

3

Mi piacerebbe mostrare un modo alternativo per fare questo genere di cose anche se spesso ho la sensazione che non sia apprezzato farlo in questo modo: usando sql.

sqldf(paste("SELECT a.ID,a.Score" 
      ,"  , a.Score - (SELECT b.Score" 
      ,"     FROM df b" 
      ,"     WHERE b.ID < a.ID" 
      ,"     ORDER BY b.ID DESC" 
      ,"     ) diff" 
      ," FROM df a" 
      ) 
    ) 

Il codice sembra complicato ma non lo è e ha un certo vantaggio, come si può vedere i risultati:

ID Score diff 
1 1 40 <NA> 
2 2 36 -4.0 
3 3 32 -4.0 
4 4 28 -4.0 
5 5 24 -4.0 
6 6 20 -4.0 
7 7 16 -4.0 
8 8 12 -4.0 
9 9  8 -4.0 
10 10  4 -4.0 

Un vantaggio è che si utilizza il dataframe originale (senza conversione in altra classi) e ottieni un frame di dati (mettilo in res < - ....). Un altro vantaggio è che hai ancora tutte le righe. E il terzo vantaggio è che puoi facilmente prendere in considerazione i fattori di raggruppamento.Per esempio:

df2 <- data.frame(ID=1:10,grp=rep(c("v","w"), each=5),Score=4*10:1) 

sqldf(paste("SELECT a.ID,a.grp,a.Score" 
      ,"  , a.Score - (SELECT b.Score" 
      ,"     FROM df2 b" 
      ,"     WHERE b.ID < a.ID" 
      ,"       AND a.grp = b.grp" 
      ,"     ORDER BY b.ID DESC" 
      ,"     ) diff" 
    ," FROM df2 a" 
    ) 
) 


    ID grp Score diff 
1 1 v 40 <NA> 
2 2 v 36 -4.0 
3 3 v 32 -4.0 
4 4 v 28 -4.0 
5 5 v 24 -4.0 
6 6 w 20 <NA> 
7 7 w 16 -4.0 
8 8 w 12 -4.0 
9 9 w  8 -4.0 
10 10 w  4 -4.0 
4

L'aggiunta di questo qualche anno più tardi per Completezza è possibile utilizzare un semplice [.data.frame subseting al fine di raggiungere anche questo

df[-1, ] - df[-nrow(df), ] 
# ID Score 
# 2 1 -4 
# 3 1 -4 
# 4 1 -4 
# 5 1 -4 
# 6 1 -4 
# 7 1 -4 
# 8 1 -4 
# 9 1 -4 
# 10 1 -4 
+0

Hai già pubblicato. Quindi, puoi aggiungere quelli – akrun

+0

@akrun, hai avuto delle cose carine lì. Il dplyr ti porterà un sacco di upvotes a lungo termine, garantito. Si prega di inviare –

+0

Ok, allora, anche se è contro i miei principi :-) – akrun

4

Un'altra opzione utilizzando dplyr sarebbe utilizzando mutate_each per scorrere tutte le colonne, ottenere la differenza della colonna (.) con il lag della colonna (.) e rimuovere l'elemento NA nella parte superiore con na.omit()

library(dplyr) 
df %>% 
    mutate_each(funs(. - lag(.))) %>% 
    na.omit() 

O con shift da data.table. Convertire il 'data.frame' a 'data.table' (setDT(df)), ciclo attraverso le colonne (lapply(.SD, ..) ) and get the difference between the column ( x ) and the lag ( spostamento by default gives the lag as type = "lag" `). Rimuovere la prima osservazione, cioè l'elemento NA.

library(data.table) 
setDT(df)[, lapply(.SD, function(x) (x- shift(x))[-1])]