Comment remodeler les données du format long au format large

263

J'ai du mal à réorganiser la trame de données suivante:

set.seed(45)
dat1 <- data.frame(
    name = rep(c("firstName", "secondName"), each=4),
    numbers = rep(1:4, 2),
    value = rnorm(8)
    )

dat1
       name  numbers      value
1  firstName       1  0.3407997
2  firstName       2 -0.7033403
3  firstName       3 -0.3795377
4  firstName       4 -0.7460474
5 secondName       1 -0.8981073
6 secondName       2 -0.3347941
7 secondName       3 -0.5013782
8 secondName       4 -0.1745357

Je veux le remodeler de sorte que chaque variable "nom" unique soit un nouveau nom, avec les "valeurs" comme observations le long de cette ligne et les "nombres" comme noms de colonnes. Un peu comme ça:

     name          1          2          3         4
1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
5 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

J'ai regardé meltet castet quelques autres choses, mais aucune ne semble faire le travail.

Steve
la source
3
duplication possible du bloc de données
Frank
4
@Frank: c'est un bien meilleur titre. de longue durée et de grande forme sont les termes standard utilisés. L'autre réponse ne peut être trouvée en recherchant sur ces termes.
smci
encore une question: comment le changer en arrière?
HappyLiang

Réponses:

257

Utilisation de la reshapefonction:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")
Chasse
la source
13
+1 et vous n'avez pas besoin de compter sur des packages externes, car il reshapeest fourni avec stats. Sans oublier que c'est plus rapide! =)
aL3xa
@indra_patil - J'utiliserais probablement le package reshape2 comme indiqué dans l'une des autres réponses. Vous pouvez créer une nouvelle question spécifique à votre cas d'utilisation et la publier si vous ne pouvez pas la comprendre.
Chase
5
reshapeest un exemple exceptionnel pour une horrible API de fonction. C'est très proche de l'inutile.
NoBackingDown
14
Les reshapecommentaires et les noms d'arguments similaires ne sont pas très utiles. Cependant, j'ai constaté que, de long à large, vous devez fournir data =votre data.frame, idvar= la variable qui identifie vos groupes, v.names= les variables qui deviendront plusieurs colonnes au format large, timevar= la variable contenant les valeurs qui seront ajoutées au v.namesformat large,, direction = wideet sep = "_". Suffisamment clair? ;)
Brian D
3
Je dirais que la base R gagne toujours par vote d'environ 2 à 1
vonjd
129

Le nouveau tidyrpackage (en 2014) le fait aussi simplement, avec gather()/ spread()étant les termes de melt/ cast.

Edit: Maintenant, en 2019, tidyr v 1.0 a lancé et défini spreadet gathersur un chemin de dépréciation, préférant à la place pivot_wideret pivot_longer, que vous pouvez trouver décrit dans cette réponse . Lisez la suite si vous voulez un bref aperçu de la brève vie de spread/gather.

library(tidyr)
spread(dat1, key = numbers, value = value)

De github ,

tidyrest un recadrage de reshape2conçu pour accompagner le cadre de données bien rangé, et pour travailler main dans la main avec magrittret dplyrconstruire un pipeline solide pour l'analyse des données.

Tout comme a reshape2fait moins que remodeler, tidyrfait moins que reshape2. Il est conçu spécifiquement pour ranger les données, pas pour le remodelage général qui le reshape2fait, ni pour l'agrégation générale que le remodelage a fait. En particulier, les méthodes intégrées ne fonctionnent que pour les trames de données et tidyrne fournissent aucune marge ni agrégation.

Gregor Thomas
la source
5
Je voulais juste ajouter un lien vers la page du livre de recettes R qui traite de l'utilisation de ces fonctions à partir de tidyret reshape2. Il fournit de bons exemples et explications.
Jake
71

Vous pouvez le faire avec la reshape()fonction ou avec les fonctions melt()/ cast()dans le package de remodelage. Pour la deuxième option, l'exemple de code est

library(reshape)
cast(dat1, name ~ numbers)

Ou en utilisant reshape2

library(reshape2)
dcast(dat1, name ~ numbers)
Ista
la source
2
Il peut être intéressant de noter que le simple fait d'utiliser castou dcastne fonctionnera pas correctement si vous n'avez pas de colonne "valeur" claire. Essayez dat <- data.frame(id=c(1,1,2,2),blah=c(8,4,7,6),index=c(1,2,1,2)); dcast(dat, id ~ index); cast(dat, id ~ index)et vous n'obtiendrez pas ce que vous attendez. Vous devez noter explicitement le value/value.var- cast(dat, id ~ index, value="blah")et dcast(dat, id ~ index, value.var="blah")par exemple.
lemailail
45

Une autre option si les performances sont un problème est d’utiliser data.tablel’extension dereshape2 fonctions de fusion et de coulée de

( Référence: remodelage efficace à l'aide de data.tables )

library(data.table)

setDT(dat1)
dcast(dat1, name ~ numbers, value.var = "value")

#          name          1          2         3         4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814

Et, à partir de data.table v1.9.6, nous pouvons lancer sur plusieurs colonnes

## add an extra column
dat1[, value2 := value * 2]

## cast multiple value columns
dcast(dat1, name ~ numbers, value.var = c("value", "value2"))

#          name    value_1    value_2   value_3   value_4   value2_1   value2_2 value2_3  value2_4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078  0.3672866 -1.6712572 3.190562 0.6590155
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814 -1.6409368  0.9748581 1.476649 1.1515627
SymbolixAU
la source
5
data.tableapproche est la meilleure! très efficace ... vous verrez la différence quand nameest une combinaison de 30-40 colonnes !!
joel.wilson
Et si je voulais prendre le maximum?
T.Fung
@ T.Fung Je ne comprends pas ce que vous demandez. Serait-il préférable d'ouvrir une nouvelle question?
SymbolixAU
@SymbolixAU dans la question op 'nom' et 'nombres' sont des combinaisons uniques. Et si ce n'était pas le cas et je voulais récupérer la valeur maximale pour chaque combinaison après pivotement? Pas un problème si une question trop délicate. Juste matière à réflexion. Je vous remercie.
T.Fung
Très bonne réponse. Je vous remercie. Pour plusieurs colonnes, j'ai obtenu "Erreur dans .subset2 (x, i, exact = exact)", et je pouvais résoudre ce problème en forçant l'utilisation de data.table dcast: voir stackoverflow.com/a/44271092/190791
Timothée HENRY
26

En utilisant votre exemple de trame de données, nous pourrions:

xtabs(value ~ name + numbers, data = dat1)
Jim M.
la source
2
celui-ci est bon, mais le résultat est un tableau de format qui n'est peut-être pas aussi facile à gérer que data.frame ou data.table, les deux ont beaucoup de packages
cloudscomputes
18

Deux autres options:

Paquet de base:

df <- unstack(dat1, form = value ~ numbers)
rownames(df) <- unique(dat1$name)
df

sqldf paquet:

library(sqldf)
sqldf('SELECT name,
      MAX(CASE WHEN numbers = 1 THEN value ELSE NULL END) x1, 
      MAX(CASE WHEN numbers = 2 THEN value ELSE NULL END) x2,
      MAX(CASE WHEN numbers = 3 THEN value ELSE NULL END) x3,
      MAX(CASE WHEN numbers = 4 THEN value ELSE NULL END) x4
      FROM dat1
      GROUP BY name')
mpalanco
la source
1
Au lieu de coder en dur les chiffres, la requête peut être configurée comme ValCol <- unique(dat1$numbers);s <- sprintf("MAX(CASE WHEN numbers = %s THEN value ELSE NULL END) `%s`,", ValCol, ValCol);mquerym <- gsub('.{1}$','',paste(s, collapse = "\n"));mquery <- paste("SELECT name,", mquerym, "FROM dat1", "GROUP BY name", sep = "\n");sqldf(mquery)
M--
13

Utilisation de la aggregatefonction base R :

aggregate(value ~ name, dat1, I)

# name           value.1  value.2  value.3  value.4
#1 firstName      0.4145  -0.4747   0.0659   -0.5024
#2 secondName    -0.8259   0.1669  -0.8962    0.1681
Ronak Shah
la source
11

Avec la version devel de tidyr ‘0.8.3.9000’, il y a pivot_wideret pivot_longerqui est généralisé pour faire le remodelage (long -> wide, wide -> long, respectivement) de 1 à plusieurs colonnes. Utilisation des données de l'OP

- colonne simple longue -> large

library(dplyr)
library(tidyr)
dat1 %>% 
    pivot_wider(names_from = numbers, values_from = value)
# A tibble: 2 x 5
#  name          `1`    `2`    `3`    `4`
#  <fct>       <dbl>  <dbl>  <dbl>  <dbl>
#1 firstName   0.341 -0.703 -0.380 -0.746
#2 secondName -0.898 -0.335 -0.501 -0.175

-> créé une autre colonne pour montrer la fonctionnalité

dat1 %>% 
    mutate(value2 = value * 2) %>% 
    pivot_wider(names_from = numbers, values_from = c("value", "value2"))
# A tibble: 2 x 9
#  name       value_1 value_2 value_3 value_4 value2_1 value2_2 value2_3 value2_4
#  <fct>        <dbl>   <dbl>   <dbl>   <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#1 firstName    0.341  -0.703  -0.380  -0.746    0.682   -1.41    -0.759   -1.49 
#2 secondName  -0.898  -0.335  -0.501  -0.175   -1.80    -0.670   -1.00    -0.349
akrun
la source
8

La reshapefonction de base fonctionne parfaitement bien:

df <- data.frame(
  year   = c(rep(2000, 12), rep(2001, 12)),
  month  = rep(1:12, 2),
  values = rnorm(24)
)
df_wide <- reshape(df, idvar="year", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

  • idvar est la colonne de classes qui sépare les lignes
  • timevar est la colonne des classes à diffuser largement
  • v.names est la colonne contenant des valeurs numériques
  • direction spécifie un format large ou long
  • l' separgument facultatif est le séparateur utilisé entre timevarles noms de classe et v.namesdans la sortie data.frame.

Si aucun idvarn'existe, créez-en un avant d'utiliser la reshape()fonction:

df$id   <- c(rep("year1", 12), rep("year2", 12))
df_wide <- reshape(df, idvar="id", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

N'oubliez pas que cela idvarest nécessaire! La partie timevaret v.namesest facile. La sortie de cette fonction est plus prévisible que certaines des autres, car tout est explicitement défini.

Adam Erickson
la source
7

Il y a nouveau package très puissant de scientifiques de données génie à Win-Vector ( les gens qui ont fait vtreat, seplyret replyr) appelé cdata. Il met en œuvre les principes de «données coordonnées» décrits dans ce document et également dans ce billet de blog . L'idée est que, quelle que soit la façon dont vous organisez vos données, il devrait être possible d'identifier des points de données individuels à l'aide d'un système de «coordonnées de données». Voici un extrait du récent billet de blog de John Mount:

L'ensemble du système est basé sur deux primitives ou opérateurs cdata :: moveValuesToRowsD () et cdata :: moveValuesToColumnsD (). Ces opérateurs ont le pivot, le non-pivot, le codage à chaud, la transposition, le déplacement de plusieurs lignes et colonnes, et de nombreuses autres transformations comme de simples cas spéciaux.

Il est facile d'écrire de nombreuses opérations différentes en termes de primitives cdata. Ces opérateurs peuvent travailler en mémoire ou à grande échelle de données (avec des bases de données et Apache Spark; pour les mégadonnées, utilisez les variantes cdata :: moveValuesToRowsN () et cdata :: moveValuesToColumnsN ()). Les transformations sont contrôlées par une table de contrôle qui est elle-même un diagramme (ou une image de) la transformation.

Nous allons d'abord créer la table de contrôle (voir l' article de blog pour plus de détails), puis effectuer le déplacement des données des lignes vers les colonnes.

library(cdata)
# first build the control table
pivotControlTable <- buildPivotControlTableD(table = dat1, # reference to dataset
                        columnToTakeKeysFrom = 'numbers', # this will become column headers
                        columnToTakeValuesFrom = 'value', # this contains data
                        sep="_")                          # optional for making column names

# perform the move of data to columns
dat_wide <- moveValuesToColumnsD(tallTable =  dat1, # reference to dataset
                    keyColumns = c('name'),         # this(these) column(s) should stay untouched 
                    controlTable = pivotControlTable# control table above
                    ) 
dat_wide

#>         name  numbers_1  numbers_2  numbers_3  numbers_4
#> 1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
#> 2 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
dmi3kno
la source
1

moyen beaucoup plus facile!

devtools::install_github("yikeshu0611/onetree") #install onetree package

library(onetree)
widedata=reshape_toWide(data = dat1,id = "name",j = "numbers",value.var.prefix = "value")
widedata

        name     value1     value2     value3     value4
   firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
  secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

si vous voulez revenir de large à long, ne changez que Wide à Long, et aucun changement dans les objets.

reshape_toLong(data = widedata,id = "name",j = "numbers",value.var.prefix = "value")

        name numbers      value
   firstName       1  0.3407997
  secondName       1 -0.8981073
   firstName       2 -0.7033403
  secondName       2 -0.3347941
   firstName       3 -0.3795377
  secondName       3 -0.5013782
   firstName       4 -0.7460474
  secondName       4 -0.1745357
zhang jing
la source