Định hình lại data.frame từ định dạng rộng sang dài


164

Tôi có một số rắc rối để chuyển đổi data.frametừ một bàn rộng sang một bàn dài. Tại thời điểm này, nó trông như thế này:

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

Bây giờ tôi muốn chuyển đổi nó data.framethành dài data.frame. Một cái gì đó như thế này:

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

Tôi đã xem xét và đã thử sử dụng melt()và các reshape()chức năng như một số người đã gợi ý trong các câu hỏi tương tự. Tuy nhiên, cho đến nay tôi chỉ nhận được kết quả lộn xộn.

Nếu có thể tôi muốn làm điều đó với reshape()chức năng vì nó trông đẹp hơn một chút để xử lý.


2
Không biết đó có phải là vấn đề không, nhưng các chức năng trong gói định hình lại bị tan chảy và bị bó bột (và đúc lại.)
Eduardo Leoni

1
Và gói định hình lại đã được thay thế bằng cách định hình lại2.
IRTFM

5
Và bây giờ reshape2 đã được thay thế bởi tidyr.
drhagen

Câu trả lời:


93

reshape()phải mất một thời gian để làm quen, chỉ là melt/ cast. Đây là một giải pháp với việc định hình lại, giả sử khung dữ liệu của bạn được gọi là d:

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

153

Ba giải pháp thay thế:

1) Với :

Bạn có thể sử dụng meltchức năng tương tự như trong reshape2gói (đây là một triển khai mở rộng & được cải thiện). melttừ data.tablecũng có nhiều tham số mà hàm-hàm melttừ reshape2. Ví dụ, bạn cũng có thể chỉ định tên của cột biến:

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

cung cấp cho:

> 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

Một số ký hiệu thay thế:

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) Với :

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

Một số ký hiệu thay thế:

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) Với :

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

Một số ký hiệu thay thế cho kết quả tương tự:

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

GHI CHÚ:

  • Đã nghỉ hưu. Chỉ những thay đổi cần thiết để giữ nó trên CRAN sẽ được thực hiện. ( nguồn )
  • Nếu bạn muốn loại trừ NAcác giá trị, bạn có thể thêm na.rm = TRUEvào meltcũng như các gatherhàm.

Một vấn đề khác với dữ liệu là các giá trị sẽ được R đọc dưới dạng giá trị ký tự (là kết quả của ,các số). Bạn có thể sửa nó với gsubas.numeric:

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

Hoặc trực tiếp với data.tablehoặc 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)))

Dữ liệu:

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)

câu trả lời tuyệt vời, chỉ một lời nhắc nhỏ nữa: không đặt bất kỳ biến nào ngoài idtimetrong khung dữ liệu của bạn, meltkhông thể cho biết bạn muốn làm gì trong trường hợp này.
Mục tiêu Jason

1
@JasonGoal Bạn có thể nói rõ hơn về điều đó? Khi tôi diễn giải bạn nhận xét, nó không phải là một vấn đề. Chỉ cần xác định cả id.varsmeasure.vars.
Jaap

, sau đó điều đó tốt cho tôi, không biết id.varsmeasure.varscó thể được chỉ định trong phương án đầu tiên, xin lỗi vì sự lộn xộn, đó là lỗi của tôi.
Mục tiêu Jason

Xin lỗi để đăng bài này - ai đó có thể giải thích cho tôi tại sao 3 hoạt động không? Tôi đã thử nó và nó hoạt động, nhưng tôi không hiểu dplyr đang làm gì khi thấy -c(var1, var2)...

1
@ReputableMisnomer Khi tidyr thấy -c(var1, var2)nó bỏ qua các biến này khi chuyển đổi dữ liệu từ định dạng rộng sang định dạng dài.
Jaap

35

Sử dụng gói định hình lại :

#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

Với tidyr_1.0.0, một lựa chọn khác làpivot_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

dữ liệu

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
Điều này cần nhiều upvote. Theo Blog Tidyverse gather đang được nghỉ hưu và pivot_longerbây giờ là cách chính xác để thực hiện điều này.
Evan Rosica

16

Vì câu trả lời này được gắn thẻ , Tôi cảm thấy sẽ hữu ích khi chia sẻ một lựa chọn khác từ cơ sở R : stack.

Tuy nhiên, lưu ý rằng stacknó không hoạt động với factors - nó chỉ hoạt động nếu is.vectorTRUE, và từ tài liệu cho is.vector, chúng tôi thấy rằng:

is.vectortrả về TRUEnếu x là một vectơ của chế độ đã chỉ định không có thuộc tính nào ngoài tên . Nó trả về FALSEkhác.

Tôi đang sử dụng dữ liệu mẫu từ câu trả lời của @ Jaap , trong đó các giá trị trong các cột năm là factors.

Đây là stackcách tiếp cận:

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

Đây là một ví dụ khác cho thấy việc sử dụng gathertừ tidyr. Bạn có thể chọn các cột gatherbằng cách loại bỏ chúng riêng lẻ (như tôi làm ở đây) hoặc bằng cách bao gồm các năm bạn muốn rõ ràng.

Lưu ý rằng, để xử lý dấu phẩy (và được thêm vào nếu check.names = FALSEkhông được đặt), tôi cũng đang sử dụng dplyrbiến đổi parse_numbertừ readrđể chuyển đổi các giá trị văn bản trở lại thành số. Đây là tất cả các phần tidyversevà vì vậy có thể được tải cùng vớilibrary(tidyverse)

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

Trả về:

   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

Đây là một giải pháp:

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

Để thực hiện truy vấn mà không cần nhập mọi thứ, bạn có thể sử dụng như sau:

Cảm ơn G. Grothendieck đã thực hiện nó.

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

Thật không may, tôi không nghĩ rằng PIVOTUNPIVOTsẽ làm việc cho R SQLite. Nếu bạn muốn viết lên truy vấn của mình một cách tinh vi hơn, bạn cũng có thể xem qua các bài đăng này:

Sử dụng sprintfviết lên các truy vấn sql    Hoặc truyền    biến chosqldf


0

Bạn cũng có thể sử dụng cdatagói sử dụng khái niệm bảng điều khiển (biến đổi):

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

Tôi hiện đang khám phá gói đó và thấy nó khá dễ tiếp cận. Nó được thiết kế cho các biến đổi phức tạp hơn nhiều và bao gồm cả các thông tin ngược. Có một hướng dẫn có sẵn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.