Converti i dati dal formato lungo al formato wide con più colonne di indicatori

Ho difficoltà a trovare il modo più elegante e flessibile per passare i dati dal formato lungo al formato grande quando ho più di una variabile di misura che voglio portare avanti.

Ad esempio, ecco una semplice cornice dati in formato lungo. ID è l’object, TIME è una variabile temporale e X e Y sono misure fatte di ID alle TIME :

 > my.df  my.df ID TIME XY 1 A 1 1 16 2 B 1 2 17 3 C 1 3 18 4 A 2 4 19 5 B 2 5 20 6 C 2 6 21 7 A 3 7 22 8 B 3 8 23 9 C 3 9 24 10 A 4 10 25 11 B 4 11 26 12 C 4 12 27 13 A 5 13 28 14 B 5 14 29 15 C 5 15 30 

Se volessi semplicemente convertire i valori di TIME in intestazioni di colonna contenenti l’inclusione X, so che posso usare cast dal pacchetto di risagoma (o dcast da reshape2):

 > cast(my.df, ID ~ TIME, value="X") ID 1 2 3 4 5 1 A 1 4 7 10 13 2 B 2 5 8 11 14 3 C 3 6 9 12 15 

Ma quello che voglio veramente fare è anche portare Y come un’altra variabile di misura, e fare in modo che i nomi delle colonne riflettano sia il nome della variabile di misura che il valore temporale:

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5 1 A 1 4 7 10 13 16 19 22 25 28 2 B 2 5 8 11 14 17 20 23 26 29 3 C 3 6 9 12 15 18 21 24 27 30 

(FWIW, non mi interessa se tutte le X sono prima seguite dalle Y, o se sono intercalate come X_1 , Y_1 , X_2 , Y_2 , ecc.)

Posso avvicinarmi a questo facendo cast due volte i dati lunghi e unire i risultati, anche se i nomi delle colonne hanno bisogno di un po ‘di lavoro, e dovrei modificarlo se dovessi aggiungere una terza o quarta variabile oltre a X e Y :

 merge( cast(my.df, ID ~ TIME, value="X"), cast(my.df, ID ~ TIME, value="Y"), by="ID", suffixes=c("_X","_Y") ) 

Sembra che una combinazione di funzioni in reshape2 e / o plyr dovrebbe essere in grado di farlo in modo più elegante del mio tentativo, oltre a gestire più variabili di misura in modo più pulito. Qualcosa come cast(my.df, ID ~ TIME, value=c("X","Y")) , che non è valido. Ma non sono stato in grado di capirlo.

I maghi della R possono aiutarmi? Grazie.

Per gestire più variabili come vuoi, devi melt i dati che hai prima di lanciarli.

 library("reshape2") dcast(melt(my.df, id.vars=c("ID", "TIME")), ID~variable+TIME) 

che dà

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5 1 A 1 4 7 10 13 16 19 22 25 28 2 B 2 5 8 11 14 17 20 23 26 29 3 C 3 6 9 12 15 18 21 24 27 30 

EDIT basato sul commento:

Il quadro di dati

 num.id = 10 num.time=10 my.df < - data.frame(ID=rep(LETTERS[1:num.id], num.time), TIME=rep(1:num.time, each=num.id), X=1:(num.id*num.time), Y=(num.id*num.time)+1:(2*length(1:(num.id*num.time)))) 

dà un risultato diverso (tutte le voci sono 2) perché la combinazione ID / TIME non indica una riga univoca. In realtà, ci sono due righe con ciascuna combinazione ID / TIME . reshape2 assume un singolo valore per ogni ansible combinazione di variabili e applicherà una funzione di riepilogo per creare una singola variabile se ci sono più voci. Ecco perché c'è l'avvertimento

 Aggregation function missing: defaulting to length 

Puoi ottenere qualcosa che funzioni se aggiungi un'altra variabile che interrompe quella ridondanza.

 my.df$cycle < - rep(1:2, each=num.id*num.time) dcast(melt(my.df, id.vars=c("cycle", "ID", "TIME")), cycle+ID~variable+TIME) 

Funziona perché cycle / ID / ora definisce ora in modo univoco una riga in my.df

  reshape(my.df, idvar = "ID", timevar = "TIME", direction = "wide") 

  ID X.1 Y.1 X.2 Y.2 X.3 Y.3 X.4 Y.4 X.5 Y.5 1 A 1 16 4 19 7 22 10 25 13 28 2 B 2 17 5 20 8 23 11 26 14 29 3 C 3 18 6 21 9 24 12 27 15 30 

Usando data.table_1.9.5 , questo può essere fatto senza la melt in quanto può gestire più colonne value.var . Puoi installarlo da here

  library(data.table) dcast(setDT(my.df), ID~TIME, value.var=c('X', 'Y')) # ID 1_X 2_X 3_X 4_X 5_X 1_Y 2_Y 3_Y 4_Y 5_Y #1: A 1 4 7 10 13 16 19 22 25 28 #2: B 2 5 8 11 14 17 20 23 26 29 #3: C 3 6 9 12 15 18 21 24 27 30 

Ecco una soluzione con il pacchetto tidyr , che ha sostanzialmente sostituito rimodella e rimodella2 . Come con questi due pacchetti, la strategia è quella di rendere il set di dati più lungo prima e poi più ampio.

 library(magrittr); requireNamespace("tidyr"); requireNamespace("dplyr") my.df %>% tidyr::gather_(key="variable", value="value", c("X", "Y")) %>% # Make it even longer. dplyr::mutate( # Create the spread key. time_by_variable = paste0(variable, "_", TIME) ) %>% dplyr::select(ID, time_by_variable, value) %>% # Retain these three. tidyr::spread(key=time_by_variable, value=value) # Spread/widen. 

Dopo la tidyr::gather() , il set di dati intermedio è:

 ID TIME variable value 1 A 1 X 1 2 B 1 X 2 3 C 1 X 3 ... 28 A 5 Y 28 29 B 5 Y 29 30 C 5 Y 30 

Il risultato finale è:

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5 1 A 1 4 7 10 13 16 19 22 25 28 2 B 2 5 8 11 14 17 20 23 26 29 3 C 3 6 9 12 15 18 21 24 27 30 

tidyr::unite() è un’alternativa, suggerita da @JWilliman. Questo è funzionalmente equivalente alla dplyr::mutate() e dplyr::select() sopra, quando il parametro remove è true (che è il valore predefinito).

Se non sei abituato a questo tipo di manipolazione, il tidyr::unite() può essere un piccolo ostacolo perché è un’altra funzione che devi imparare e ricordare. Tuttavia, i suoi vantaggi includono (a) codice più conciso ( cioè , quattro linee sono sostituite da una) e (b) meno punti per ripetere i nomi delle variabili ( cioè , non è necessario ripetere / modificare le variabili nel dplyr::select() clausola).

 my.df %>% tidyr::gather_(key="variable", value="value", c("X", "Y")) %>% # Make it even longer. tidyr::unite("time_by_variable", variable, TIME, remove=T) %>% # Create the spread key `time_by_variable` while simultaneously dropping `variable` and `TIME`. tidyr::spread(key=time_by_variable, value=value) # Spread/widen.