Remodeler data.frame du format large au format long


164

J'ai du mal à convertir ma data.frametable large en table longue. Pour le moment, cela ressemble à ceci:

Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246

Maintenant, je voudrais transformer cela data.frameen un long data.frame. Quelque chose comme ça:

Code Country        Year    Value
AFG  Afghanistan    1950    20,249
AFG  Afghanistan    1951    21,352
AFG  Afghanistan    1952    22,532
AFG  Afghanistan    1953    23,557
AFG  Afghanistan    1954    24,555
ALB  Albania        1950    8,097
ALB  Albania        1951    8,986
ALB  Albania        1952    10,058
ALB  Albania        1953    11,123
ALB  Albania        1954    12,246

Je l' ai regardé et déjà essayé d' utiliser la melt()et les reshape()fonctions que certaines personnes ont laissé entendre dans des questions similaires. Cependant, jusqu'à présent je n'obtiens que des résultats désordonnés.

Si c'est possible, j'aimerais le faire avec la reshape()fonction car elle semble un peu plus agréable à manipuler.


2
Je ne sais pas si c'était le problème, mais les fonctions du package reshape sont fondues et moulées (et refondues.)
Eduardo Leoni

1
Et le package reshape a été remplacé par reshape2.
IRTFM

5
Et maintenant reshape2 a été remplacé par tidyr.
drhagen

Réponses:


93

reshape()prend un certain temps pour s'y habituer, tout comme melt/ cast. Voici une solution avec remodelage, en supposant que votre bloc de données est appelé d:

reshape(d, 
        direction = "long",
        varying = list(names(d)[3:7]),
        v.names = "Value",
        idvar = c("Code", "Country"),
        timevar = "Year",
        times = 1950:1954)

153

Trois solutions alternatives:

1) Avec :

Vous pouvez utiliser la même meltfonction que dans le reshape2package (qui est une implémentation étendue et améliorée). meltfrom data.tablea également plus de paramètres que la fonction melt-fonction reshape2. Vous pouvez par exemple également spécifier le nom de la variable-colonne:

library(data.table)
long <- melt(setDT(wide), id.vars = c("Code","Country"), variable.name = "year")

qui donne:

> long
    Code     Country year  value
 1:  AFG Afghanistan 1950 20,249
 2:  ALB     Albania 1950  8,097
 3:  AFG Afghanistan 1951 21,352
 4:  ALB     Albania 1951  8,986
 5:  AFG Afghanistan 1952 22,532
 6:  ALB     Albania 1952 10,058
 7:  AFG Afghanistan 1953 23,557
 8:  ALB     Albania 1953 11,123
 9:  AFG Afghanistan 1954 24,555
10:  ALB     Albania 1954 12,246

Quelques notations alternatives:

melt(setDT(wide), id.vars = 1:2, variable.name = "year")
melt(setDT(wide), measure.vars = 3:7, variable.name = "year")
melt(setDT(wide), measure.vars = as.character(1950:1954), variable.name = "year")

2) Avec :

library(tidyr)
long <- wide %>% gather(year, value, -c(Code, Country))

Quelques notations alternatives:

wide %>% gather(year, value, -Code, -Country)
wide %>% gather(year, value, -1:-2)
wide %>% gather(year, value, -(1:2))
wide %>% gather(year, value, -1, -2)
wide %>% gather(year, value, 3:7)
wide %>% gather(year, value, `1950`:`1954`)

3) Avec :

library(reshape2)
long <- melt(wide, id.vars = c("Code", "Country"))

Quelques notations alternatives qui donnent le même résultat:

# you can also define the id-variables by column number
melt(wide, id.vars = 1:2)

# as an alternative you can also specify the measure-variables
# all other variables will then be used as id-variables
melt(wide, measure.vars = 3:7)
melt(wide, measure.vars = as.character(1950:1954))

REMARQUES:

  • est à la retraite. Seules les modifications nécessaires pour le conserver sur CRAN seront effectuées. ( source )
  • Si vous souhaitez exclure des NAvaleurs, vous pouvez ajouter na.rm = TRUEaux meltfonctions ainsi qu'aux gatherfonctions.

Un autre problème avec les données est que les valeurs seront lues par R en tant que valeurs de caractères (en conséquence de la ,dans les nombres). Vous pouvez réparer cela avec gsubet as.numeric:

long$value <- as.numeric(gsub(",", "", long$value))

Ou directement avec data.tableou dplyr:

# data.table
long <- melt(setDT(wide),
             id.vars = c("Code","Country"),
             variable.name = "year")[, value := as.numeric(gsub(",", "", value))]

# tidyr and dplyr
long <- wide %>% gather(year, value, -c(Code,Country)) %>% 
  mutate(value = as.numeric(gsub(",", "", value)))

Les données:

wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)

excellente réponse, juste un petit rappel de plus: ne mettez aucune variable autre que idet timedans votre bloc de données, meltne pourrait pas dire ce que vous voulez faire dans ce cas.
Objectif de Jason

1
@JasonGoal Pourriez-vous nous en dire plus? D'après mon interprétation de votre commentaire, cela ne devrait pas poser de problème. Spécifiez simplement le id.varset le measure.vars.
Jaap

, alors c'est bon pour moi, je ne sais pas id.varset le measure.varspeut être spécifié dans la première alternative, désolé pour le désordre, c'est ma faute.
Objectif de Jason

Désolé de necro ce post - quelqu'un pourrait-il m'expliquer pourquoi 3 fonctionne? Je l'ai testé et cela fonctionne, mais je ne comprends pas ce que fait dplyr quand il voit -c(var1, var2)...

1
@ReputableMisnomer Lorsque tidyr voit, -c(var1, var2)il omet ces variables lors de la transformation des données du format large au format long.
Jaap

35

Utilisation du package reshape :

#data
x <- read.table(textConnection(
"Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246"), header=TRUE)

library(reshape)

x2 <- melt(x, id = c("Code", "Country"), variable_name = "Year")
x2[,"Year"] <- as.numeric(gsub("X", "" , x2[,"Year"]))

18

Avec tidyr_1.0.0, une autre option estpivot_longer

library(tidyr)
pivot_longer(df1, -c(Code, Country), values_to = "Value", names_to = "Year")
# A tibble: 10 x 4
#   Code  Country     Year  Value 
#   <fct> <fct>       <chr> <fct> 
# 1 AFG   Afghanistan 1950  20,249
# 2 AFG   Afghanistan 1951  21,352
# 3 AFG   Afghanistan 1952  22,532
# 4 AFG   Afghanistan 1953  23,557
# 5 AFG   Afghanistan 1954  24,555
# 6 ALB   Albania     1950  8,097 
# 7 ALB   Albania     1951  8,986 
# 8 ALB   Albania     1952  10,058
# 9 ALB   Albania     1953  11,123
#10 ALB   Albania     1954  12,246

Les données

df1 <- structure(list(Code = structure(1:2, .Label = c("AFG", "ALB"), class = "factor"), 
    Country = structure(1:2, .Label = c("Afghanistan", "Albania"
    ), class = "factor"), `1950` = structure(1:2, .Label = c("20,249", 
    "8,097"), class = "factor"), `1951` = structure(1:2, .Label = c("21,352", 
    "8,986"), class = "factor"), `1952` = structure(2:1, .Label = c("10,058", 
    "22,532"), class = "factor"), `1953` = structure(2:1, .Label = c("11,123", 
    "23,557"), class = "factor"), `1954` = structure(2:1, .Label = c("12,246", 
    "24,555"), class = "factor")), class = "data.frame", row.names = c(NA, 
-2L))

1
Cela nécessite plus de votes positifs. Selon le blog Tidyverse, il gather est en train d'être retiré et pivot_longerest maintenant la bonne façon d'accomplir cela.
Evan Rosica le

16

Puisque cette réponse est identifiée avec , Je pense qu'il serait utile de partager une autre alternative de la base R: stack.

Notez cependant que stackcela ne fonctionne pas avec factors - cela ne fonctionne que si is.vectorc'est le cas TRUE, et d'après la documentation de is.vector, nous trouvons que:

is.vectorrenvoie TRUEsi x est un vecteur du mode spécifié n'ayant aucun attribut autre que des noms . Il revient FALSEautrement.

J'utilise les exemples de données de la réponse de @ Jaap , où les valeurs dans les colonnes de l'année sont factors.

Voici l' stackapproche:

cbind(wide[1:2], stack(lapply(wide[-c(1, 2)], as.character)))
##    Code     Country values  ind
## 1   AFG Afghanistan 20,249 1950
## 2   ALB     Albania  8,097 1950
## 3   AFG Afghanistan 21,352 1951
## 4   ALB     Albania  8,986 1951
## 5   AFG Afghanistan 22,532 1952
## 6   ALB     Albania 10,058 1952
## 7   AFG Afghanistan 23,557 1953
## 8   ALB     Albania 11,123 1953
## 9   AFG Afghanistan 24,555 1954
## 10  ALB     Albania 12,246 1954

11

Voici un autre exemple montrant l'utilisation de gatherfrom tidyr. Vous pouvez sélectionner les colonnes gathersoit en les supprimant individuellement (comme je le fais ici), soit en incluant les années que vous souhaitez explicitement.

Notez que, pour gérer les virgules (et les X ajoutés si check.names = FALSEn'est pas défini), j'utilise également dplyrmutate avec parse_numberfrom readrpour convertir les valeurs de texte en nombres. Ceux-ci font tous partie du tidyverseet peuvent donc être chargés aveclibrary(tidyverse)

wide %>%
  gather(Year, Value, -Code, -Country) %>%
  mutate(Year = parse_number(Year)
         , Value = parse_number(Value))

Retour:

   Code     Country Year Value
1   AFG Afghanistan 1950 20249
2   ALB     Albania 1950  8097
3   AFG Afghanistan 1951 21352
4   ALB     Albania 1951  8986
5   AFG Afghanistan 1952 22532
6   ALB     Albania 1952 10058
7   AFG Afghanistan 1953 23557
8   ALB     Albania 1953 11123
9   AFG Afghanistan 1954 24555
10  ALB     Albania 1954 12246

4

Voici un Solution:

sqldf("Select Code, Country, '1950' As Year, `1950` As Value From wide
        Union All
       Select Code, Country, '1951' As Year, `1951` As Value From wide
        Union All
       Select Code, Country, '1952' As Year, `1952` As Value From wide
        Union All
       Select Code, Country, '1953' As Year, `1953` As Value From wide
        Union All
       Select Code, Country, '1954' As Year, `1954` As Value From wide;")

Pour effectuer la requête sans tout taper, vous pouvez utiliser ce qui suit:

Merci à G. Grothendieck pour sa mise en œuvre.

ValCol <- tail(names(wide), -2)

s <- sprintf("Select Code, Country, '%s' As Year, `%s` As Value from wide", ValCol, ValCol)
mquery <- paste(s, collapse = "\n Union All\n")

cat(mquery) #just to show the query
 #> Select Code, Country, '1950' As Year, `1950` As Value from wide
 #>  Union All
 #> Select Code, Country, '1951' As Year, `1951` As Value from wide
 #>  Union All
 #> Select Code, Country, '1952' As Year, `1952` As Value from wide
 #>  Union All
 #> Select Code, Country, '1953' As Year, `1953` As Value from wide
 #>  Union All
 #> Select Code, Country, '1954' As Year, `1954` As Value from wide

sqldf(mquery)
 #>    Code     Country Year  Value
 #> 1   AFG Afghanistan 1950 20,249
 #> 2   ALB     Albania 1950  8,097
 #> 3   AFG Afghanistan 1951 21,352
 #> 4   ALB     Albania 1951  8,986
 #> 5   AFG Afghanistan 1952 22,532
 #> 6   ALB     Albania 1952 10,058
 #> 7   AFG Afghanistan 1953 23,557
 #> 8   ALB     Albania 1953 11,123
 #> 9   AFG Afghanistan 1954 24,555
 #> 10  ALB     Albania 1954 12,246

Malheureusement, je ne pense pas cela PIVOTet UNPIVOTje travaillerais pour R SQLite. Si vous souhaitez rédiger votre requête de manière plus sophistiquée, vous pouvez également consulter ces articles:

Utiliser l' sprintfécriture de requêtes SQL    ou    passer des variables àsqldf


0

Vous pouvez également utiliser le cdatapackage, qui utilise le concept de table de contrôle (de transformation):

# data
wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)

library(cdata)
# build control table
drec <- data.frame(
    Year=as.character(1950:1954),
    Value=as.character(1950:1954),
    stringsAsFactors=FALSE
)
drec <- cdata::rowrecs_to_blocks_spec(drec, recordKeys=c("Code", "Country"))

# apply control table
cdata::layout_by(drec, wide)

J'explore actuellement ce package et je le trouve assez accessible. Il est conçu pour des transformations beaucoup plus compliquées et inclut la rétro-transformation. Il y a un tutoriel disponible.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.