Przekształcanie data.frame z formatu szerokiego na długi

164

Mam problem z przekształceniem mojego data.frameszerokiego stołu w długi. W tej chwili wygląda to tak:

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

Teraz chciałbym przekształcić to data.framew długi data.frame. Coś takiego:

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

Przyjrzałem się i już próbowałem używać funkcji melt()i, reshape()jak sugerowali niektórzy w podobnych pytaniach. Jednak jak dotąd otrzymuję tylko niechlujne wyniki.

Jeśli to możliwe, chciałbym to zrobić za pomocą reshape()funkcji, ponieważ wygląda to trochę przyjemniej w obsłudze.

mropa
źródło
2
Nie wiem, czy to był problem, ale funkcje w pakiecie zmiany kształtu są topione i odlewane (i przekształcane).
Eduardo Leoni
1
Pakiet reshape został zastąpiony przez reshape2.
IRTFM,
5
A teraz reshape2 został zastąpiony przez tidyr.
drhagen

Odpowiedzi:

93

reshape()zajmuje trochę czasu, aby się przyzwyczaić, tak jak melt/ cast. Oto rozwiązanie ze zmianą kształtu, zakładając, że ramka danych nazywa się d:

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

Trzy alternatywne rozwiązania:

1) z :

Możesz użyć tej samej meltfunkcji, co w reshape2pakiecie (która jest rozszerzoną i ulepszoną implementacją). meltfrom data.tablema również więcej parametrów niż meltfunkcja z reshape2. Możesz na przykład określić również nazwę kolumny-zmiennej:

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

co daje:

> 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

Kilka alternatywnych notacji:

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

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

Kilka alternatywnych notacji:

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

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

Kilka alternatywnych notacji, które dają ten sam wynik:

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

UWAGI:

  • jest na emeryturze. Zostaną wprowadzone tylko zmiany niezbędne do utrzymania go w CRAN. ( źródło )
  • Jeśli chcesz wykluczyć NAwartości, możesz dodać na.rm = TRUEdo funkcji, melta także gather.

Innym problemem związanym z danymi jest to, że wartości będą odczytywane przez R jako wartości znakowe (jako wynik ,w liczbach). Możesz to naprawić za pomocą gsubi as.numeric:

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

Lub bezpośrednio za pomocą data.tablelub 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)))

Dane:

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
źródło
świetna odpowiedź, jeszcze jedno małe przypomnienie: nie umieszczaj żadnych zmiennych poza idiw timeramce danych, meltnie możesz powiedzieć, co chcesz zrobić w tym przypadku.
Jason Goal
1
@JasonGoal Czy mógłbyś to rozwinąć? Ponieważ interpretuję Twój komentarz, nie powinno to stanowić problemu. Po prostu określ zarówno plik, jak id.varsi measure.vars.
Jaap,
, to mi dobrze, nie wiem id.varsi measure.varsmożna to określić w pierwszej możliwości, przepraszam za bałagan, to moja wina.
Jason Goal
Przepraszam za necro ten post - czy ktoś mógłby mi wyjaśnić, dlaczego 3 działa? Przetestowałem to i działa, ale nie rozumiem, co robi dplyr, gdy widzi -c(var1, var2)...
1
@ReputableMisnomer Gdy tidyr widzi -c(var1, var2), pomija te zmienne podczas przekształcania danych z formatu szerokiego do długiego.
Jaap
35

Korzystanie z pakietu zmiany kształtu :

#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
źródło
18

Z tidyr_1.0.0, innym rozwiązaniem jestpivot_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

dane

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
źródło
1
To wymaga więcej głosów pozytywnych. Według Tidyverse blog gather jest wycofywany i pivot_longerjest teraz właściwym sposobem, aby to osiągnąć.
Evan Rosica
16

Ponieważ ta odpowiedź jest oznaczona tagiem , To uważa, że byłoby przydatne, aby dzielić inną alternatywę od podstawy R: stack.

Pamiętaj jednak, że stackto nie działa z factors - działa tylko wtedy , gdy is.vectorjest TRUE, a z dokumentacji dla is.vectordowiadujemy się, że:

is.vectorzwraca, TRUEjeśli x jest wektorem określonego trybu i nie posiada atrybutów innych niż nazwy . W FALSEprzeciwnym razie wraca .

Używam przykładowych danych z odpowiedzi @ Jaap , gdzie wartości w kolumnach roku to factors.

Oto stackpodejście:

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
źródło
11

Oto kolejny przykład pokazujący użycie gatherfrom tidyr. Możesz wybrać kolumny dogather , usuwając je pojedynczo (tak jak ja tutaj) lub dołączając jawnie lata, które chcesz.

Zwróć uwagę, że aby obsłużyć przecinki (i znaki X dodane, jeśli check.names = FALSEnie są ustawione), używam również funkcji dplyrmutate z parse_numberz, readraby przekonwertować wartości tekstowe z powrotem na liczby. Są to wszystkie elementy tidyversei można je ładować razem zlibrary(tidyverse)

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

Zwroty:

   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
źródło
4

Tutaj jest rozwiązanie:

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

Aby wykonać zapytanie bez wpisywania wszystkiego, możesz użyć następujących poleceń:

Podziękowania dla G. Grothendiecka za jego wdrożenie.

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

Niestety nie sądzę PIVOTi UNPIVOTmógłbym pracować R SQLite. Jeśli chcesz napisać zapytanie w bardziej wyrafinowany sposób, możesz również zapoznać się z tymi postami:

Używanie sprintfpisania zapytań sql    lub    przekazywanie zmiennych dosqldf

M--
źródło
0

Możesz również użyć cdatapakietu, który wykorzystuje koncepcję (transformacji) tabeli sterującej:

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

Obecnie badam ten pakiet i uważam, że jest całkiem dostępny. Jest przeznaczony do znacznie bardziej skomplikowanych przekształceń i obejmuje transformację wsteczną. Dostępny jest tutorial .

Karsten W.
źródło