Umformen von data.frame vom Breitformat zum Langformat

164

Ich habe einige Probleme, meine data.framevon einem breiten Tisch in einen langen Tisch umzuwandeln . Im Moment sieht es so aus:

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

Jetzt möchte ich dies data.framein eine lange verwandeln data.frame. Etwas wie das:

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

Ich habe mir die melt()und die reshape()Funktionen angesehen und bereits versucht, sie zu verwenden, wie einige Leute in ähnlichen Fragen vorgeschlagen haben. Bisher bekomme ich jedoch nur unordentliche Ergebnisse.

Wenn es möglich ist, würde ich es gerne mit der reshape()Funktion machen, da sie etwas besser aussieht.

Mropa
quelle
2
Ich weiß nicht, ob das das Problem war, aber die Funktionen im Umformpaket sind Schmelzen und Gießen (und Neufassen)
Eduardo Leoni
1
Und das Umformungspaket wurde durch Umformung2 ersetzt.
IRTFM
5
Und jetzt wurde reshape2 von tidyr abgelöst.
Drhagen

Antworten:

93

reshape()zu, wie dauert eine Weile , gewöhnungs melt/ cast. Hier ist eine Lösung mit Umformung, vorausgesetzt, Ihr Datenrahmen heißt d:

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

Drei alternative Lösungen:

1) Mit ::

Sie können dieselbe meltFunktion wie im reshape2Paket verwenden (eine erweiterte und verbesserte Implementierung). meltfrom data.tablehat auch mehr Parameter als die melt-Funktion von reshape2. Sie können beispielsweise auch den Namen der Variablenspalte angeben:

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

was gibt:

> 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

Einige alternative Notationen:

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

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

Einige alternative Notationen:

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

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

Einige alternative Notationen, die das gleiche Ergebnis liefern:

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

ANMERKUNGEN:

  • Ist im Ruhestand. Es werden nur Änderungen vorgenommen, die erforderlich sind, um es auf CRAN zu halten. ( Quelle )
  • Wenn Sie NAWerte ausschließen möchten, können Sie sowohl na.rm = TRUEdie meltals auch die gatherFunktionen hinzufügen .

Ein weiteres Problem mit den Daten besteht darin, dass die Werte von R als Zeichenwerte gelesen werden (als Ergebnis der ,in den Zahlen). Sie können das mit gsubund reparieren as.numeric:

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

Oder direkt mit data.tableoder 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)))

Daten:

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)
Jaap
quelle
Tolle Antwort, nur noch eine kleine Erinnerung: Fügen Sie keine anderen Variablen als idund timein Ihren Datenrahmen ein, und meltsagen Sie nicht, was Sie in diesem Fall tun möchten.
Jason Goal
1
@ JasonGoal Könnten Sie das näher erläutern? Während ich Ihren Kommentar interpretiere, sollte es kein Problem sein. Geben Sie einfach sowohl das id.varsals auch das anmeasure.vars .
Jaap
, dann ist das gut für mich, weiß nicht id.varsund das measure.varskann in der ersten Alternative angegeben werden, sorry für das Durcheinander, es ist meine Schuld.
Jason Goal
Tut mir leid, diesen Beitrag zu nekrotisieren - könnte mir jemand erklären warum 3 funktioniert? Ich habe es getestet und es funktioniert, aber ich verstehe nicht, was dplyr tut, wenn es sieht -c(var1, var2)...
1
@ReputableMisnomer Wenn Tidyr sieht-c(var1, var2) dass diese Variablen beim Transformieren der Daten von einem breiten in ein langes Format werden.
Jaap
35

Verwenden des Umformungspakets :

#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"]))
Shane
quelle
18

Mit tidyr_1.0.0ist eine andere Optionpivot_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

Daten

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))
akrun
quelle
1
Dies erfordert mehr Upvotes. Laut Tidyverse wird Blog gather in den Ruhestand versetzt und pivot_longerist nun der richtige Weg, dies zu erreichen.
Evan Rosica
16

Da diese Antwort mit markiert ist Ich hielt es für nützlich, eine andere Alternative von Basis R zu teilen : stack.

Beachten Sie jedoch, dass stackdies mit factors nicht funktioniert - es funktioniert nur, wenn dies der Fall is.vectorist TRUE, und aus der Dokumentation für is.vectorfinden wir Folgendes:

is.vectorGibt zurück, TRUEwenn x ein Vektor des angegebenen Modus ist, der keine anderen Attribute als Namen enthält . Es wird FALSEanders zurückgegeben.

Ich verwende die Beispieldaten aus der Antwort von @ Jaap , wobei die Werte in den Jahresspalten factors sind.

Hier ist der stackAnsatz:

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
A5C1D2H2I1M1N2O1R2T1
quelle
11

Hier ist ein weiteres Beispiel, das die Verwendung von gatherfrom zeigt tidyr. Sie können die Spalten auswählen, gatherindem Sie sie entweder einzeln entfernen (wie hier) oder indem Sie die gewünschten Jahre explizit angeben.

Beachten Sie, dass check.names = FALSEich zur Behandlung der Kommas (und der X-Zeichen, wenn diese nicht gesetzt sind) auch die dplyrMutation mit parse_numbervon verwende readr, um die Textwerte wieder in Zahlen umzuwandeln. Diese sind alle Teil der tidyverseund können daher zusammen mit geladen werdenlibrary(tidyverse)

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

Kehrt zurück:

   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
Mark Peterson
quelle
4

Hier ist ein Lösung:

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

Um die Abfrage durchzuführen, ohne alles einzugeben, können Sie Folgendes verwenden:

Vielen Dank an G. Grothendieck für die Umsetzung.

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

Leider glaube ich das nicht PIVOTund UNPIVOTwürde dafür arbeiten R SQLite. Wenn Sie Ihre Anfrage differenzierter schreiben möchten, können Sie sich auch die folgenden Beiträge ansehen:

Verwenden sprintfvon SQL-Abfragen    oder    Übergeben von Variablen ansqldf

M--
quelle
0

Sie können auch das cdataPaket verwenden, das das Konzept der (Transformations-) Steuertabelle verwendet:

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

Ich erkunde gerade dieses Paket und finde es ziemlich zugänglich. Es ist für viel kompliziertere Transformationen ausgelegt und beinhaltet die Rücktransformation. Es ist ein Tutorial verfügbar.

Karsten W.
quelle