web-dev-qa-db-fra.com

Remodelage de data.frame du format large au format long

J'ai quelques difficultés à convertir mon data.frame D'une table large à une table longue. Pour le moment ça ressemble à ça:

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, j'aimerais transformer ce data.frame En 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

J'ai déjà étudié et essayé d'utiliser les fonctions melt() et reshape(), comme certaines personnes le suggéraient dans des questions similaires. Cependant, jusqu'à présent, je n'ai que des résultats en désordre.

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

131
mropa

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

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

Trois solutions alternatives:

1: avec reshape2

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

donnant:

   Code     Country variable  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 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))

2: avec data.table

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

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

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

: avec tidyr

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

Si vous voulez exclure les valeurs NA, vous pouvez ajouter na.rm = TRUE aux fonctions melt ainsi qu'aux fonctions gather.


Un autre problème avec les données est que les valeurs seront lues par R comme des valeurs de caractère (à la suite du , dans les chiffres). Vous pouvez réparer ça avec gsub et as.numeric:

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

Ou directement avec data.table ou 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)))

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

Utiliser le 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"]))
33
Shane

Puisque cette réponse est étiquetée avec r-faq , j’ai pensé qu’il serait utile de partager une autre alternative à partir de la base R: stack.

Notez cependant que stack ne fonctionne pas avec factors - il ne fonctionne que si is.vector est TRUE et de la documentation pour is.vector, on constate que:

is.vector retourne TRUE si x est un vecteur du mode spécifié ne comportant aucun attribut autre que les noms . Il retourne FALSE sinon.

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'approche stack:

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 gather de tidyr. Vous pouvez sélectionner les colonnes à gather en les supprimant individuellement (comme je le fais ici) ou en incluant explicitement les années souhaitées.

Notez que, pour gérer les virgules (et les X ajoutés si check.names = FALSE N'est pas défini), j'utilise également la mutation de dplyr avec parse_number De readr pour convertir le texte retourne aux nombres. Ils font tous partie de tidyverse et peuvent donc être chargés avec library(tidyverse)

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

Résultats:

   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
10
Mark Peterson

Voici une solution sqldf :

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 créer la requête sans tout saisir, vous pouvez utiliser les éléments suivants:

Merci à G. Grothendieck pour l’avoir appliqué.

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 que PIVOT et UNPIVOT fonctionneraient pour RSQLite. Si vous souhaitez rédiger votre requête de manière plus sophistiquée, vous pouvez également consulter ces publications:

tilisation de sprintf écriture de requêtes SQL OuPasser les variables à sqldf

2
M--