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 assunto, TIME
é uma variável de tempo, e X e Y são medições feitas de ID
a TIME
:
> my.df <- data.frame(ID=rep(c("A","B","C"), 5), TIME=rep(1:5, each=3), X=1:15, Y=16:30)
> my.df
ID TIME X Y
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 cabeçalhos 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 Xs 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
-ing 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 uma combinação de funções em reshape2
e / ou plyr
deve ser capaz de fazer isso de forma mais elegante que a minha tentativa, bem como lidar com variáveis de medida múltiplas de forma mais limpa. Algo como cast(my.df, ID ~ TIME, value=c("X","Y"))
, o que não é válido, mas eu não consegui descobrir.
Algum R-wizards pode me ajudar? Obrigado.
Respostas:
14 para resposta № 1Para lidar com várias variáveis como você deseja, você precisa melt
os dados que você tem antes de lançá-lo.
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))))
dá um resultado diferente (todas as entradas são 2) porque o ID
/TIME
combinação não indica uma linha única. Na verdade, existem duas linhas com cada ID
/TIME
combinações. reshape2
assume um valor único para cada combinação possível das variáveis e aplicará uma função de resumo para criar uma única variável, se houver várias entradas. É 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
.
15 para resposta № 2
reshape(my.df,
idvar = "ID",
timevar = "TIME",
direction = "wide")
dá
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
11 for answer № 3
Usando o data.table_1.9.5
, isso pode ser feito sem o melt
como ele pode lidar com vários value.var
colunas. Você pode instalá-lo a partir de 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
5 para resposta № 4
Aqui está uma solução com o próximo pacote, que essencialmente substituiu remodelar e reshape2. Como acontece com esses dois pacotes, a estratégia é tornar o conjunto de dados 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.
Depois de tidyr::gather()
chamada, o conjunto de dados 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. Isto é funcionalmente equivalente ao dplyr::mutate()
e dplyr::select()
combinação acima, quando o remove
O parâmetro é verdadeiro (que é o padrão).
Se você não está acostumado a este tipo de manipulação, o tidyr::unite()
pode ser um pequeno obstáculo, porque é mais uma função que você tem que aprender e lembrar. No entanto, os benefícios incluem (a) código mais conciso (ou seja, quatro linhas são substituídas por uma) e (b) menos lugares para repetir nomes de variáveis (ou seja, você não precisa repetir / modificar variáveis no dplyr::select()
cláusula).
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.