web-dev-qa-db-fra.com

Convertir des données de format long en format large avec plusieurs colonnes de mesure

J'ai du mal à trouver le moyen le plus élégant et le plus flexible de passer des données du format long au format large lorsque j'ai plus d'une variable de mesure à apporter.

Par exemple, voici un simple bloc de données au format long. ID est le sujet, TIME est une variable de temps, et X et Y sont des mesures faites de ID à 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

Si je voulais simplement transformer les valeurs de TIME en en-têtes de colonne contenant l'include X, je sais que je peux utiliser cast() à partir du package 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

Mais ce que je veux vraiment faire, c'est aussi apporter Y comme une autre variable de mesure, et faire en sorte que les noms de colonne reflètent à la fois le nom de la variable de mesure et la valeur de temps:

  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, peu m'importe si tous les X sont d'abord suivis par les Y, ou s'ils sont entrelacés comme X_1, Y_1, X_2, Y_2, Etc.)

Je peux me rapprocher de cela en transtypé - en multipliant les données longues par deux et en fusionnant les résultats, bien que les noms des colonnes aient besoin d'un peu de travail, et j'aurais besoin de les modifier si j'avais besoin d'ajouter un 3ème ou 4ème variable en plus de X et Y:

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

On dirait qu'une combinaison de fonctions dans reshape2 Et/ou plyr devrait être en mesure de le faire plus élégamment que ma tentative, ainsi que de gérer plus proprement plusieurs variables de mesure. Quelque chose comme cast(my.df, ID ~ TIME, value=c("X","Y")), qui n'est pas valide. Mais je n'ai pas pu le comprendre.

35
colonel.triq

Afin de gérer plusieurs variables comme vous le souhaitez, vous devez melt les données que vous avez avant de les caster.

library("reshape2")

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

qui donne

  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 basé sur le commentaire:

Le bloc de données

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

donne un résultat différent (toutes les entrées sont 2) car la combinaison ID/TIME n'indique pas une ligne unique. En fait, il y a deux lignes avec chacune des combinaisons ID/TIME. reshape2 suppose une valeur unique pour chaque combinaison possible de variables et appliquera une fonction récapitulative pour créer une seule variable s'il existe plusieurs entrées. Voilà pourquoi il y a l'avertissement

Aggregation function missing: defaulting to length

Vous pouvez obtenir quelque chose qui fonctionne si vous ajoutez une autre variable qui rompt cette redondance.

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)

Cela fonctionne car cycle/ID/time définit désormais uniquement une ligne dans my.df.

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

donne

  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
20
Seth

En utilisant le data.table_1.9.5, cela peut être fait sans melt car il peut gérer plusieurs value.var Colonnes. Vous pouvez l'installer à 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
14
akrun

Remarque -Sept 2019 : dans tidyr , l'approche gather() + spread() (décrit dans cette réponse) a plus ou moins été remplacé par l'approche pivot_wider() (décrite dans ` cette nouvelle réponse tidyr ). Pour des informations actuelles sur la transition, voir vignette pivotante .


Voici une solution avec le package tidyr , qui a essentiellement remplacé reshape et reshape2 . Comme avec ces deux packages, la stratégie consiste à allonger d'abord l'ensemble de données, puis à l'étendre.

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.

Après l'appel tidyr::gather() , l'ensemble de données intermédiaire est:

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

Le résultat final est:

  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() est une alternative, suggérée par @JWilliman. Ceci est fonctionnellement équivalent à la combinaison dplyr::mutate() et dplyr::select() ci-dessus, lorsque le paramètre remove est vrai ( qui est la valeur par défaut).

Si vous n'êtes pas habitué à ce type de manipulation, la tidyr::unite() peut être un petit obstacle car c'est une fonction de plus que vous devez apprendre et mémoriser. Cependant, ses avantages incluent (a) un code plus concis ( c'est-à-dire , quatre lignes sont remplacées par une) et (b) moins d'endroits pour répéter les noms de variables ( ie , vous n'avez pas à répéter/modifier les variables dans la clause 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.
7
wibeasley

La fonction pivot_wider() est l'approche de 2ème génération de tidyr (publiée dans tidyr 1.0.0).

library(magrittr); requireNamespace("tidyr");

my.df %>%
  tidyr::pivot_wider(
    names_from  = c(TIME), # Can accommodate more variables, if needed.
    values_from = c(X, Y)
  )

Résultat :

# A tibble: 3 x 11
  ID      X_1   X_2   X_3   X_4   X_5   Y_1   Y_2   Y_3   Y_4   Y_5
  <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
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

Ceci est probablement préférable à approche tidyr précédente (qui utilise une combinaison de gather() et spread() =).

Plus de capacités sont décrites dans la vignette pivotante . Cet exemple est particulièrement concis car vos spécifications souhaitées correspondent aux valeurs par défaut de id_cols Et names_sep.

0
wibeasley