reshaping data.frame จากรูปแบบกว้างถึงยาว


164

ฉันมีปัญหาในการแปลงdata.frameจากตารางกว้างเป็นตารางยาว ในขณะนี้ดูเหมือนว่านี้:

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

ตอนนี้ฉันต้องการที่จะเปลี่ยนนี้เป็นเวลานานdata.frame data.frameบางสิ่งเช่นนี้

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

ฉันดูแล้วลองใช้melt()และreshape()ฟังก์ชั่นขณะที่บางคนแนะนำคำถามที่คล้ายกัน อย่างไรก็ตามจนถึงตอนนี้ฉันได้รับผลลัพธ์ที่ยุ่งเหยิงเท่านั้น

ถ้าเป็นไปได้ฉันอยากจะใช้reshape()ฟังก์ชั่นนี้เพราะมันดูดีกว่านิดหน่อยที่จะจัดการ


2
ไม่ทราบว่าเป็นปัญหาหรือไม่ แต่ฟังก์ชั่นในแพ็คเกจ reshape ละลายและร่าย (และทำการหล่อใหม่)
Eduardo Leoni

1
และแพ็คเกจ reshape นั้นถูกแทนที่โดย reshape2
IRTFM

5
และตอนนี้ reshape2 ถูกแทนที่โดย tidyr
drhagen

คำตอบ:


93

reshape()ใช้เวลาในขณะที่จะได้รับใช้เช่นเดียวกับที่/melt castนี่คือวิธีแก้ปัญหาที่มีการก่อร่างใหม่สมมติว่ากรอบข้อมูลของคุณเรียกว่าd:

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

153

โซลูชั่นทางเลือกสามทาง:

1) ด้วย :

คุณสามารถใช้meltฟังก์ชั่นเดียวกับในreshape2แพ็คเกจ (ซึ่งเป็นการขยายและปรับปรุงการใช้งาน) meltจากdata.tableยังมีพารามิเตอร์เพิ่มเติมว่าฟังก์ชั่จากmelt reshape2ตัวอย่างเช่นคุณสามารถระบุชื่อของคอลัมน์ตัวแปรได้:

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

ซึ่งจะช่วยให้:

> 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

สัญลักษณ์ทางเลือกบางประการ:

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) ด้วย :

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

สัญลักษณ์ทางเลือกบางประการ:

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) ด้วย :

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

สัญลักษณ์ทางเลือกบางตัวที่ให้ผลลัพธ์เหมือนกัน:

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

หมายเหตุ:

  • ถูกยกเลิก จะมีการเปลี่ยนแปลงที่จำเป็นเพื่อเก็บไว้ใน CRAN เท่านั้น (ที่มา )
  • หากคุณต้องการที่จะไม่รวมNAค่าคุณสามารถเพิ่มna.rm = TRUEไปmeltเช่นเดียวกับgatherฟังก์ชั่น

ปัญหาอีกประการหนึ่งของข้อมูลคือค่าจะถูกอ่านโดย R เป็นค่าอักขระ (ซึ่งเป็นผลมาจาก,ตัวเลข) คุณสามารถซ่อมแซมได้ด้วยgsubและas.numeric:

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

หรือโดยตรงกับdata.tableหรือ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)))

ข้อมูล:

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)

คำตอบที่ดีเพียงเตือนความจำเล็ก ๆ อีกหนึ่ง: อย่าใส่ตัวแปรใด ๆ นอกเหนือจากidและtimeในกรอบข้อมูลของคุณmeltไม่สามารถบอกได้ว่าคุณต้องการทำอะไรในกรณีนี้
Jason Goal

1
@ JasonGoal คุณช่วยอธิบายเกี่ยวกับสิ่งนั้นได้ไหม? ในขณะที่ฉันตีความความคิดเห็นของคุณก็ไม่น่าจะมีปัญหา เพียงแค่ระบุทั้งสองและid.vars measure.vars
Jaap

แล้วมันก็ดีสำหรับฉันไม่รู้id.varsและmeasure.varsสามารถระบุได้ในตัวเลือกแรกขอโทษสำหรับความยุ่งเหยิงมันเป็นความผิดของฉัน
Jason Goal

ขออภัยที่ necro โพสต์นี้ - ใครบางคนสามารถอธิบายให้ฉันทำไม 3 งาน? ฉันได้ทดสอบแล้วว่าใช้งานได้ แต่ฉันไม่เข้าใจว่า dplyr กำลังทำอะไรเมื่อเห็น-c(var1, var2)...

1
@ReputableMisnomer เมื่อtidyrเห็น-c(var1, var2)ว่าละเว้นตัวแปรเหล่านี้เมื่อเปลี่ยนข้อมูลจากรูปแบบกว้างเป็นยาว
Jaap

35

ใช้แพคเกจ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"]))

18

ด้วยtidyr_1.0.0ตัวเลือกอื่นคือ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

ข้อมูล

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
สิ่งนี้ต้องการ upvotes มากขึ้น ตามบล็อกของ Tidyverse gatherจะถูกยกเลิกและpivot_longerตอนนี้เป็นวิธีที่ถูกต้องในการทำสิ่งนี้ให้สำเร็จ
Evan Rosica

16

เนื่องจากคำตอบนี้ถูกแท็กด้วย ผมรู้สึกว่ามันจะเป็นประโยชน์ในการร่วมกันอีกทางเลือกหนึ่งจากฐาน stackR:

อย่างไรก็ตามโปรดทราบว่าstackมันใช้งานไม่ได้กับfactors - ใช้ได้เฉพาะถ้าis.vectorเป็นTRUEและจากเอกสารประกอบสำหรับis.vectorเราพบว่า:

is.vectorผลตอบแทนTRUEถ้า x เป็นเวกเตอร์ของโหมดที่ระบุไม่มีคุณลักษณะอื่น ๆ นอกเหนือจากชื่อ มันกลับมาเป็นFALSEอย่างอื่น

ฉันกำลังใช้ข้อมูลตัวอย่างจากคำตอบของ @ Jaapโดยที่ค่าในคอลัมน์ปีเป็นfactors

นี่คือ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

นี่เป็นอีกตัวอย่างหนึ่งที่แสดงให้เห็นการใช้งานจากgather tidyrคุณสามารถเลือกคอลัมน์ที่ต้องการgatherโดยลบออกทีละรายการ (อย่างที่ฉันทำที่นี่) หรือรวมถึงปีที่คุณต้องการอย่างชัดเจน

โปรดทราบว่าเพื่อจัดการเครื่องหมายจุลภาค (และเพิ่ม X หากcheck.names = FALSEไม่ได้ตั้งค่า) ฉันยังใช้การdplyrกลายพันธุ์ด้วยparse_numberจากreadrเพื่อแปลงค่าข้อความกลับเป็นตัวเลข ทั้งหมดนี้เป็นส่วนหนึ่งของtidyverseและสามารถโหลดพร้อมกันได้library(tidyverse)

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

ผลตอบแทน:

   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

นี่คือ สารละลาย:

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

ในการสร้างแบบสอบถามโดยไม่ต้องพิมพ์ทุกอย่างคุณสามารถใช้สิ่งต่อไปนี้:

ขอบคุณG. Grothendieckสำหรับการนำไปใช้

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

แต่น่าเสียดายที่ผมไม่คิดว่าPIVOTและจะทำงานให้UNPIVOT R SQLiteหากคุณต้องการที่จะเขียนแบบสอบถามของคุณในลักษณะที่ซับซ้อนมากขึ้นคุณยังสามารถดูโพสต์เหล่านี้:

ใช้การsprintfเขียนแบบสอบถาม sql    หรือ    ส่งผ่านตัวแปรไปsqldf


0

คุณยังสามารถใช้cdataแพคเกจซึ่งใช้แนวคิดของ (การเปลี่ยนแปลง) ตารางการควบคุม:

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

ฉันกำลังสำรวจแพคเกจนั้นและพบว่าสามารถเข้าถึงได้ค่อนข้าง มันถูกออกแบบมาสำหรับการแปลงที่ซับซ้อนมากขึ้นและรวมถึงการเปลี่ยนรูปหลัง มีการสอนที่มีอยู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.