Converter dados do formato longo para o formato amplo com várias colunas de medida

Estou com dificuldades para descobrir a maneira mais elegante e flexível de trocar dados de formato longo para formato amplo quando tenho mais de uma variável de medida que desejo acompanhar.

Por exemplo, aqui está um quadro de dados simples em formato longo. ID é o sujeito, TIME é uma variável de tempo e X e Y são medidas feitas de ID às 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 eu quisesse apenas transformar os valores de TIME em headers de coluna contendo o include X, eu sei que posso usar o cast do pacote reshape (ou dcast de 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 

Mas o que eu realmente quero fazer é trazer Y como outra variável de medida e ter os nomes das colunas refletindo o nome da variável de medida e o valor de tempo:

  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, eu realmente não me importo se todos os X’s são primeiro seguidos pelos Y’s, ou se eles são intercalados como X_1 , Y_1 , X_2 , Y_2 , etc.)

Eu posso chegar perto disso, cast os dados longos duas vezes e mesclando os resultados, embora os nomes das colunas precisem de algum trabalho, e eu precisaria ajustá-los se eu precisasse adicionar uma terceira ou quarta variável além de X e Y :

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

Parece que algumas combinações de funções em reshape2 e / ou plyr devem ser capazes de fazer isso de forma mais elegante que minha tentativa, bem como manipular variables ​​de medida múltiplas de forma mais limpa. Algo como cast(my.df, ID ~ TIME, value=c("X","Y")) , que não é válido. Mas eu não fui capaz de descobrir.

Algum R-wizards pode me ajudar? Obrigado.

Para lidar com várias variables ​​como você deseja, você precisa melt os dados que você tem antes de lançá-los.

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

que 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 com base no comentário:

O quadro de dados

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

fornece um resultado diferente (todas as inputs são 2) porque a combinação ID / TIME não indica uma linha exclusiva. Na verdade, existem duas linhas com cada combinação de ID / TIME . reshape2 assume um único valor para cada combinação possível das variables ​​e aplicará uma function de resumo para criar uma única variável, se houver várias inputs. É por isso que há o aviso

 Aggregation function missing: defaulting to length 

Você pode obter algo que funcione se você adicionar outra variável que interrompe essa redundância.

 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) 

Isso funciona porque cycle / ID / time agora define exclusivamente uma linha em 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 o data.table_1.9.5 , isso pode ser feito sem o melt pois ele pode manipular várias colunas value.var . Você pode instalá-lo 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 

Aqui está uma solução com o pacote tidyr , que basicamente substituiu reshape e reshape2 . Como acontece com esses dois pacotes, a estratégia é tornar o dataset mais longo primeiro e depois mais amplo.

 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. 

Após a tidyr::gather() , o dataset intermediário é:

 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 

O resultado final é:

  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() é uma alternativa sugerida por @JWilliman. Isso é funcionalmente equivalente à dplyr::mutate() e dplyr::select() acima, quando o parâmetro remove é true (que é o padrão).

Se você não está acostumado com este tipo de manipulação, o tidyr::unite() pode ser um pequeno obstáculo, porque é mais uma function que você tem que aprender e lembrar. No entanto, seus benefícios incluem (a) código mais conciso ( ou seja , quatro linhas são substituídas por um) e (b) menos lugares para repetir nomes de variables ​​( ou seja , você não precisa repetir / modificar variables ​​no dplyr::select() cláusula dplyr::select() ).

 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.