J'ai besoin de remodeler ma table large en format long mais en conservant plusieurs champs pour chaque enregistrement, par exemple:
dw <- read.table(header=T, text='
sbj f1.avg f1.sd f2.avg f2.sd blabla
A 10 6 50 10 bA
B 12 5 70 11 bB
C 20 7 20 8 bC
D 22 8 22 9 bD
')
# Now I want to melt this table, keeping both AVG and SD as separate fields for each measurement, to get something like this:
# sbj var avg sd blabla
# A f1 10 6 bA
# A f2 50 10 bA
# B f1 12 5 bB
# B f2 70 11 bB
# C f1 20 7 bC
# C f2 20 8 bC
# D f1 22 8 bD
# D f2 22 9 bD
J'ai une connaissance de base de l'utilisation de melt
et reshape
, mais il n'est pas évident pour moi comment appliquer un tel remodelage dans mon cas. Je serais reconnaissant pour tout conseil ou pointer vers un autre SO poste si quelque chose de similaire a déjà été demandé.
reshape
le fait avec les arguments appropriés.
varying
répertorie les colonnes qui existent au format large, mais qui sont divisées en plusieurs lignes au format long. v.names
est l'équivalent au format long. Entre les deux, un mappage est créé.
De ?reshape
:
De plus, la tentative n'est pas tentée si v.names est donné explicitement. Notez que l'ordre des variables varie comme x.1, y.1, x.2, y.2.
Compte tenu de ces varying
et v.names
arguments, reshape
est assez intelligent pour voir que j'ai spécifié que l'index est avant le point ici (c'est-à-dire, l'ordre 1.x, 1.y, 2.x, 2.y). Notez que les données d'origine ont les colonnes dans cet ordre, nous pouvons donc spécifier varying=2:5
pour cet exemple de données, mais ce n'est pas sûr en général.
Étant donné les valeurs de times
et v.names
, reshape
divise les colonnes varying
sur un .
caractère (l'argument sep
par défaut) pour créer les colonnes dans la sortie.
times
spécifie les valeurs à utiliser dans la colonne var
créée et v.names
sont collés sur ces valeurs pour obtenir les noms de colonne au format large pour le mappage au résultat.
Enfin, idvar
est spécifié comme étant la colonne sbj
, qui identifie les enregistrements individuels au format large (merci @thelatemail).
reshape(dw, direction='long',
varying=c('f1.avg', 'f1.sd', 'f2.avg', 'f2.sd'),
timevar='var',
times=c('f1', 'f2'),
v.names=c('avg', 'sd'),
idvar='sbj')
## sbj blabla var avg sd
## A.f1 A bA f1 10 6
## B.f1 B bB f1 12 5
## C.f1 C bC f1 20 7
## D.f1 D bD f1 22 8
## A.f2 A bA f2 50 10
## B.f2 B bB f2 70 11
## C.f2 C bC f2 20 8
## D.f2 D bD f2 22 9
Une autre option en utilisant le nouveau package tidyr
de Hadley.
library(tidyr)
library(dplyr)
dw <- read.table(header=T, text='
sbj f1.avg f1.sd f2.avg f2.sd blabla
A 10 6 50 10 bA
B 12 5 70 11 bB
C 20 7 20 8 bC
D 22 8 22 9 bD
')
dw %>%
gather(v, value, f1.avg:f2.sd) %>%
separate(v, c("var", "col")) %>%
arrange(sbj) %>%
spread(col, value)
Cela semble faire ce que vous voulez, sauf que le f
est supprimé des éléments dans time
.
reshape(dw, idvar = "sbj", varying = list(c(2,4),c(3,5)), v.names = c("ave", "sd"), direction = "long")
sbj blabla time ave sd
A.1 A bA 1 10 6
B.1 B bB 1 12 5
C.1 C bC 1 20 7
D.1 D bD 1 22 8
A.2 A bA 2 50 10
B.2 B bB 2 70 11
C.2 C bC 2 20 8
D.2 D bD 2 22 9
melt
de la version> = 1.9.6 de data.table
, le fait en spécifiant l'index de colonne dans measure.vars
en tant que list
.
melt(setDT(dw), measure.vars=list(c(2,4), c(3,5)),
variable.name='var', value.name=c('avg', 'sd'))[,
var:= paste0('f',var)][order(sbj)]
# sbj blabla var avg sd
#1: A bA f1 10 6
#2: A bA f2 50 10
#3: B bB f1 12 5
#4: B bB f2 70 11
#5: C bC f1 20 7
#6: C bC f2 20 8
#7: D bD f1 22 8
#8: D bD f2 22 9
Ou vous pouvez utiliser la nouvelle fonction patterns
:
melt(setDT(dw),
measure = patterns("avg", "sd"),
variable.name = 'var', value.name = c('avg', 'sd'))
# sbj blabla var avg sd
# 1: A bA 1 10 6
# 2: B bB 1 12 5
# 3: C bC 1 20 7
# 4: D bD 1 22 8
# 5: A bA 2 50 10
# 6: B bB 2 70 11
# 7: C bC 2 20 8
# 8: D bD 2 22 9
Pour ajouter aux options disponibles ici, vous pouvez également envisager merged.stack
de mon package "splitstackshape":
library(splitstackshape)
merged.stack(dw, var.stubs = c("avg", "sd"), sep = "var.stubs", atStart = FALSE)
# sbj blabla .time_1 avg sd
# 1: A bA f1. 10 6
# 2: A bA f2. 50 10
# 3: B bB f1. 12 5
# 4: B bB f2. 70 11
# 5: C bC f1. 20 7
# 6: C bC f2. 20 8
# 7: D bD f1. 22 8
# 8: D bD f2. 22 9
Vous pouvez également faire un peu plus de nettoyage sur le ".time_1"
variable, comme ceci.
merged.stack(dw, var.stubs = c("avg", "sd"),
sep = "var.stubs", atStart = FALSE)[, .time_1 := sub(
".", "", .time_1, fixed = TRUE)][]
# sbj blabla .time_1 avg sd
# 1: A bA f1 10 6
# 2: A bA f2 50 10
# 3: B bB f1 12 5
# 4: B bB f2 70 11
# 5: C bC f1 20 7
# 6: C bC f2 20 8
# 7: D bD f1 22 8
# 8: D bD f2 22 9
Vous remarquerez l'utilisation du atStart = FALSE
argument. C'est parce que vos noms sont un peu dans un ordre différent que les fonctions liées au remodelage semblent aimer. En général, le "talon" devrait venir en premier, puis les "heures", comme ceci:
dw2 <- dw
setnames(dw2, gsub("(.*)\\.(.*)", "\\2.\\1", names(dw2)))
names(dw2)
# [1] "sbj" "avg.f1" "sd.f1" "avg.f2" "sd.f2" "blabla"
Si les noms étaient dans ce format, les deux R de base reshape
et merged.stack
bénéficier d'une syntaxe plus directe:
merged.stack(dw2, var.stubs = c("avg", "sd"), sep = ".")
reshape(dw2, idvar = c("sbj", "blabla"), varying = 2:5,
sep = ".", direction = "long")