Membentuk kembali data.frame dari format lebar ke panjang

164

Saya memiliki beberapa masalah untuk mengkonversi data.framedari tabel lebar ke meja panjang. Saat ini terlihat seperti ini:

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

Sekarang saya ingin mengubah ini data.framemenjadi panjang data.frame. Sesuatu seperti ini:

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

Saya telah melihat dan sudah mencoba menggunakan melt()dan reshape()fungsinya karena beberapa orang menyarankan dalam pertanyaan serupa. Namun, sejauh ini saya hanya mendapatkan hasil yang berantakan.

Jika mungkin saya ingin melakukannya dengan reshape()fungsi karena terlihat sedikit lebih baik untuk ditangani.

mropa
sumber
2
Tidak tahu apakah itu masalahnya, tetapi fungsi-fungsi dalam paket membentuk kembali meleleh dan dilemparkan (dan disusun kembali.)
Eduardo Leoni
1
Dan paket pembentukan kembali telah digantikan oleh pembentukan ulang2.
IRTFM
5
Dan sekarang bentuk2 telah digantikan oleh tidyr.
drhagen

Jawaban:

93

reshape()perlu waktu untuk terbiasa, sama seperti melt/ cast. Ini adalah solusi dengan membentuk kembali, dengan asumsi frame data Anda disebut d:

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

Tiga solusi alternatif:

1) Dengan :

Anda dapat menggunakan meltfungsi yang sama seperti dalam reshape2paket (yang merupakan implementasi yang diperluas & ditingkatkan). meltfrom data.tablejuga memiliki lebih banyak parameter dari melt-fungsi reshape2. Misalnya, Anda juga dapat menentukan nama kolom-variabel:

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

pemberian yang mana:

> 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

Beberapa notasi alternatif:

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

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

Beberapa notasi alternatif:

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

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

Beberapa notasi alternatif yang memberikan hasil yang sama:

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

CATATAN:

  • sudah pensiun. Hanya perubahan yang diperlukan untuk tetap menggunakan CRAN yang akan dibuat. ( sumber )
  • Jika Anda ingin mengecualikan NAnilai, Anda dapat menambahkan na.rm = TRUEke meltserta gatherfungsi.

Masalah lain dengan data adalah bahwa nilai akan dibaca oleh R sebagai nilai karakter (sebagai akibat dari ,angka-angka). Anda dapat memperbaikinya dengan gsubdan as.numeric:

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

Atau langsung dengan data.tableatau 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)
Jaap
sumber
jawaban yang bagus, hanya satu pengingat kecil: jangan letakkan variabel apa pun selain iddan timedalam bingkai data Anda, melttidak bisa memberi tahu apa yang ingin Anda lakukan dalam kasus ini.
Jason Goal
1
@JasonGoal Bisakah Anda menguraikan itu? Saat saya menafsirkan komentar Anda, itu seharusnya tidak menjadi masalah. Cukup tentukan baik id.varsdan measure.vars.
Jaap
, maka itu bagus untuk saya, tidak tahu id.varsdan measure.varsdapat ditentukan dalam alternatif pertama, maaf untuk kekacauan, ini salahku.
Jason Goal
Maaf necro posting ini - dapatkah seseorang menjelaskan kepada saya mengapa 3 berfungsi? Saya telah mengujinya dan berfungsi, tetapi saya tidak mengerti apa yang dilakukan dplyr ketika ia melihat -c(var1, var2)...
1
@ReputableMisnomer Ketika tidyr melihatnya -c(var1, var2)menghapus variabel-variabel ini ketika mengubah data dari format lebar ke panjang.
Jaap
35

Menggunakan paket membentuk kembali :

#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
sumber
18

Dengan tidyr_1.0.0, opsi lain adalahpivot_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

data

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
sumber
1
Ini membutuhkan lebih banyak upvotes. Menurut Tidyverse Blog gather sedang pensiun dan pivot_longersekarang cara yang tepat untuk mencapai ini.
Evan Rosica
16

Karena jawaban ini ditandai dengan , Saya merasa itu akan berguna untuk berbagi alternatif lain dari basis R: stack.

Catatan, bagaimanapun, itu stacktidak bekerja dengan factors - hanya berfungsi jika is.vectorada TRUE, dan dari dokumentasi untuk is.vector, kami menemukan bahwa:

is.vectorkembali TRUEjika x adalah vektor dari mode yang ditentukan tidak memiliki atribut selain nama . Ia mengembalikan FALSEsebaliknya.

Saya menggunakan data sampel dari jawaban @ Jaap , di mana nilai dalam kolom tahun adalah factors.

Inilah stackpendekatannya:

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
sumber
11

Berikut adalah contoh lain yang menunjukkan penggunaan gatherdari tidyr. Anda dapat memilih kolom untuk gathermenghapusnya satu per satu (seperti yang saya lakukan di sini), atau dengan memasukkan tahun yang Anda inginkan secara eksplisit.

Perhatikan bahwa, untuk menangani koma (dan X ditambahkan jika check.names = FALSEtidak disetel), saya juga menggunakan dplyrmutasi dengan parse_numberdari readruntuk mengubah nilai teks kembali ke angka. Ini semua adalah bagian dari tidyversedan jadi dapat dimuat bersamalibrary(tidyverse)

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

Pengembalian:

   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
sumber
4

Ini a larutan:

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

Untuk membuat kueri tanpa mengetik semuanya, Anda dapat menggunakan yang berikut ini:

Terima kasih kepada G. Grothendieck untuk menerapkannya.

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

Sayangnya, saya tidak memikirkan itu PIVOTdan UNPIVOTakan berhasil R SQLite. Jika Anda ingin menulis kueri Anda dengan cara yang lebih canggih, Anda juga dapat melihat posting ini:

Menggunakan sprintfpenulisan kueri sql    Atau    Lulus variabel kesqldf

M--
sumber
0

Anda juga dapat menggunakan cdatapaket, yang menggunakan konsep tabel kontrol (transformasi):

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

Saya sedang mengeksplorasi paket itu dan merasa cukup mudah diakses. Ia dirancang untuk transformasi yang jauh lebih rumit dan mencakup transformasi balik. Ada tutorial yang tersedia.

Karsten W.
sumber