วิธีการปรับรูปแบบข้อมูลจากรูปแบบยาวเป็นแบบกว้าง


263

ฉันมีปัญหาในการจัดเรียงเฟรมข้อมูลต่อไปนี้:

set.seed(45)
dat1 <- data.frame(
    name = rep(c("firstName", "secondName"), each=4),
    numbers = rep(1:4, 2),
    value = rnorm(8)
    )

dat1
       name  numbers      value
1  firstName       1  0.3407997
2  firstName       2 -0.7033403
3  firstName       3 -0.3795377
4  firstName       4 -0.7460474
5 secondName       1 -0.8981073
6 secondName       2 -0.3347941
7 secondName       3 -0.5013782
8 secondName       4 -0.1745357

ฉันต้องการที่จะก่อร่างใหม่เพื่อให้ตัวแปร "ชื่อ" ที่ไม่ซ้ำกันแต่ละรายการเป็นชื่อแถวโดยมี "ค่า" เป็นข้อสังเกตตามแถวนั้นและ "ตัวเลข" เป็นชื่อ เรียงจากนี้:

     name          1          2          3         4
1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
5 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

ฉันได้ดูmeltและcastและสิ่งอื่น ๆ อีกสองสามอย่าง แต่ดูเหมือนไม่มีใครทำงาน


3
เป็นไปได้ที่ซ้ำกันของReshape data frame สามคอลัมน์ไปยังเมทริกซ์
Frank

4
@ Frank: นี่เป็นชื่อที่ดีกว่ามาก แบบยาวและแบบกว้างเป็นคำมาตรฐานที่ใช้ ไม่พบคำตอบอื่น ๆ โดยค้นหาจากคำเหล่านั้น
smci

อีกหนึ่งคำถาม: จะเปลี่ยนกลับอย่างไร
HappyLiang

คำตอบ:


255

ใช้reshapeฟังก์ชั่น:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")

13
1 และคุณไม่จำเป็นต้องพึ่งพาแพคเกจภายนอกตั้งแต่มาพร้อมกับreshape statsไม่ต้องพูดถึงว่ามันเร็วกว่า! =)
aL3xa

@indra_patil - ฉันน่าจะใช้แพ็คเกจ reshape2 ตามที่ระบุไว้ในคำตอบอื่น คุณสามารถสร้างคำถามใหม่ที่เฉพาะกับกรณีการใช้งานของคุณและโพสต์ถ้าคุณไม่สามารถคิด
ไล่

5
reshapeเป็นตัวอย่างที่โดดเด่นสำหรับ API ฟังก์ชั่นที่น่ากลัว มันอยู่ใกล้กับไร้ประโยชน์มาก
NoBackingDown

14
reshapeความคิดเห็นและข้อโต้แย้งชื่อที่คล้ายกันไม่ได้ทุกสิ่งที่เป็นประโยชน์ อย่างไรก็ตามฉันพบว่าสำหรับยาวไปกว้างคุณต้องให้data =data.frame ของคุณidvar= ตัวแปรที่ระบุกลุ่มของคุณv.names= ตัวแปรที่จะกลายเป็นหลายคอลัมน์ในรูปแบบกว้างtimevar= ตัวแปรที่มีค่าที่จะผนวกเข้า จะv.namesอยู่ในรูปแบบกว้างและdirection = wide sep = "_"ชัดเจนเพียงพอหรือไม่ ;)
Brian D

3
ฉันจะบอกว่าฐาน R ยังคงชนะคะแนนโหวตโดยประมาณ 2 ถึง 1
vonjd

129

ใหม่ (ในปี 2014) tidyrแพคเกจยังทำอย่างนี้ก็มีgather()/ spread()เป็นข้อกำหนดสำหรับ/meltcast

แก้ไข:ตอนนี้ในปี 2019 tidyr v 1.0 ได้เปิดตัวและตั้งค่าspreadและgatherบนเส้นทางที่เลิกใช้แล้วแทนที่จะเลือกpivot_widerและpivot_longerซึ่งคุณสามารถหาคำอธิบายได้ในคำตอบนี้ อ่านต่อไปถ้าคุณต้องการที่แวบเข้ามาในชีวิตสั้น ๆ spread/gatherของ

library(tidyr)
spread(dat1, key = numbers, value = value)

จากGitHub ,

tidyrเป็น reframing ของการreshape2ออกแบบมาเพื่อมาพร้อมกับกรอบข้อมูลที่เป็นระเบียบและในการทำงานร่วมกันกับmagrittrและdplyrเพื่อสร้างท่อส่งที่มั่นคงสำหรับการวิเคราะห์ข้อมูล

เช่นเดียวกับที่reshape2ได้น้อยกว่าก่อร่างใหม่ไม่น้อยกว่าtidyr reshape2มันได้รับการออกแบบมาโดยเฉพาะสำหรับข้อมูลการจัดระเบียบไม่ใช่การปรับแต่งทั่วไปที่reshape2ทำหรือการรวมทั่วไปที่ปรับรูปร่างได้ โดยเฉพาะอย่างยิ่งวิธีการในตัวใช้งานได้กับเฟรมข้อมูลเท่านั้นและtidyrไม่มีขอบหรือการรวม


5
แค่อยากจะเพิ่มการเชื่อมโยงไปที่R ตำราหน้าเว็บที่กล่าวถึงการใช้ฟังก์ชั่นเหล่านี้จากและtidyr reshape2เป็นตัวอย่างและคำอธิบายที่ดี
Jake

71

คุณสามารถทำได้ด้วยreshape()ฟังก์ชั่นหรือกับmelt()/ cast()ฟังก์ชั่นในแพคเกจการก่อร่างใหม่ สำหรับตัวเลือกที่สองโค้ดตัวอย่างคือ

library(reshape)
cast(dat1, name ~ numbers)

หรือการใช้ reshape2

library(reshape2)
dcast(dat1, name ~ numbers)

2
มันอาจจะคุ้มค่าที่จะสังเกตเห็นว่าการใช้castหรือdcastจะไม่ได้ผลหากคุณไม่มีคอลัมน์ "ค่า" ที่ชัดเจน ลองdat <- data.frame(id=c(1,1,2,2),blah=c(8,4,7,6),index=c(1,2,1,2)); dcast(dat, id ~ index); cast(dat, id ~ index)และคุณจะไม่ได้รับสิ่งที่คุณคาดหวัง คุณต้องจดบันทึกvalue/value.var- cast(dat, id ~ index, value="blah")และdcast(dat, id ~ index, value.var="blah")ตัวอย่างเช่น
thelatemail

45

อีกทางเลือกหนึ่งหากประสิทธิภาพเป็นสิ่งที่น่ากังวลก็คือการใช้data.tableส่วนขยายของreshape2ฟังก์ชั่นละลาย & dcast

( การอ้างอิง: การเปลี่ยนรูปที่มีประสิทธิภาพโดยใช้ data.tables )

library(data.table)

setDT(dat1)
dcast(dat1, name ~ numbers, value.var = "value")

#          name          1          2         3         4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814

และจาก data.table v1.9.6 เราสามารถส่งหลายคอลัมน์ได้

## add an extra column
dat1[, value2 := value * 2]

## cast multiple value columns
dcast(dat1, name ~ numbers, value.var = c("value", "value2"))

#          name    value_1    value_2   value_3   value_4   value2_1   value2_2 value2_3  value2_4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078  0.3672866 -1.6712572 3.190562 0.6590155
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814 -1.6409368  0.9748581 1.476649 1.1515627

5
data.tableวิธีการที่ดีที่สุด! มีประสิทธิภาพมาก ... คุณจะเห็นความแตกต่างเมื่อnameมีการรวมกันของคอลัมน์ 30-40 !!
joel.wilson

ถ้าฉันต้องการที่จะได้สูงสุด
T.Fung

@ T.Fung ฉันไม่เข้าใจสิ่งที่คุณถาม อาจจะดีที่สุดที่จะเปิดคำถามใหม่?
SymbolixAU

@SymbolixAU ในคำถาม 'ชื่อ' และ 'ตัวเลข' ของ op เป็นชุดค่าผสมที่ไม่ซ้ำกัน เกิดอะไรขึ้นถ้าพวกเขาไม่ได้และฉันต้องการที่จะดึงค่าสูงสุดสำหรับการรวมกันหลังจากหมุนเหวี่ยง? ไม่เป็นปัญหาหากคำถามเที่ยวยุ่งยิ่งเกินไป เพียงแค่อาหารเพื่อความคิด ขอบคุณ.
T.Fung

คำตอบที่ดี ขอบคุณ. สำหรับหลายคอลัมน์ฉันได้รับ "ข้อผิดพลาดใน. ย่อย 2 (x, i, แน่นอน = แน่นอน)" และสามารถแก้ไขได้โดยบังคับให้ใช้ data.table dcast: ดูstackoverflow.com/a/44271092/190791
Timothée HENRY

26

โดยใช้ตัวอย่างดาต้าเฟรมของคุณเราสามารถ:

xtabs(value ~ name + numbers, data = dat1)

2
อันนี้เป็นสิ่งที่ดี แต่ผลที่ได้คือรูปแบบตารางซึ่งอาจไม่ใช่เรื่องง่ายที่จะจัดการเป็น data.frame หรือ data.table ทั้งสองมีแพคเกจมากมาย
cloudscomputes

18

อีกสองตัวเลือก:

แพคเกจฐาน:

df <- unstack(dat1, form = value ~ numbers)
rownames(df) <- unique(dat1$name)
df

sqldf แพคเกจ:

library(sqldf)
sqldf('SELECT name,
      MAX(CASE WHEN numbers = 1 THEN value ELSE NULL END) x1, 
      MAX(CASE WHEN numbers = 2 THEN value ELSE NULL END) x2,
      MAX(CASE WHEN numbers = 3 THEN value ELSE NULL END) x3,
      MAX(CASE WHEN numbers = 4 THEN value ELSE NULL END) x4
      FROM dat1
      GROUP BY name')

1
แทนที่จะ hardcoding หมายเลขแบบสอบถามสามารถตั้งค่าเช่นนี้ValCol <- unique(dat1$numbers);s <- sprintf("MAX(CASE WHEN numbers = %s THEN value ELSE NULL END) `%s`,", ValCol, ValCol);mquerym <- gsub('.{1}$','',paste(s, collapse = "\n"));mquery <- paste("SELECT name,", mquerym, "FROM dat1", "GROUP BY name", sep = "\n");sqldf(mquery)
M--

13

ใช้aggregateฟังก์ชั่นbase R :

aggregate(value ~ name, dat1, I)

# name           value.1  value.2  value.3  value.4
#1 firstName      0.4145  -0.4747   0.0659   -0.5024
#2 secondName    -0.8259   0.1669  -0.8962    0.1681

11

ด้วยรุ่น devel ของtidyr ‘0.8.3.9000’มีpivot_widerและpivot_longerซึ่งเป็นทั่วไปที่จะทำการปรับแต่ง (ยาว -> กว้าง - กว้าง -> ยาวตามลำดับ) จาก 1 ถึงหลายคอลัมน์ ใช้ข้อมูลของ OP

- คอลัมน์เดี่ยวยาว -> กว้าง

library(dplyr)
library(tidyr)
dat1 %>% 
    pivot_wider(names_from = numbers, values_from = value)
# A tibble: 2 x 5
#  name          `1`    `2`    `3`    `4`
#  <fct>       <dbl>  <dbl>  <dbl>  <dbl>
#1 firstName   0.341 -0.703 -0.380 -0.746
#2 secondName -0.898 -0.335 -0.501 -0.175

-> สร้างคอลัมน์อื่นเพื่อแสดงฟังก์ชั่น

dat1 %>% 
    mutate(value2 = value * 2) %>% 
    pivot_wider(names_from = numbers, values_from = c("value", "value2"))
# A tibble: 2 x 9
#  name       value_1 value_2 value_3 value_4 value2_1 value2_2 value2_3 value2_4
#  <fct>        <dbl>   <dbl>   <dbl>   <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#1 firstName    0.341  -0.703  -0.380  -0.746    0.682   -1.41    -0.759   -1.49 
#2 secondName  -0.898  -0.335  -0.501  -0.175   -1.80    -0.670   -1.00    -0.349

8

reshapeฟังก์ชันพื้นฐานทำงานได้อย่างสมบูรณ์แบบ:

df <- data.frame(
  year   = c(rep(2000, 12), rep(2001, 12)),
  month  = rep(1:12, 2),
  values = rnorm(24)
)
df_wide <- reshape(df, idvar="year", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

ที่ไหน

  • idvar เป็นคอลัมน์ของคลาสที่แยกแถว
  • timevar เป็นคอลัมน์ของคลาสที่ต้องใช้ในการคัดเลือก
  • v.names คือคอลัมน์ที่มีค่าตัวเลข
  • direction ระบุรูปแบบกว้างหรือยาว
  • ตัวเลือกsepอาร์กิวเมนต์เป็นแยกที่ใช้ในระหว่างtimevarชื่อชั้นและในการส่งออกv.namesdata.frame

หากไม่มีidvarอยู่ให้สร้างขึ้นก่อนใช้reshape()ฟังก์ชัน:

df$id   <- c(rep("year1", 12), rep("year2", 12))
df_wide <- reshape(df, idvar="id", timevar="month", v.names="values", direction="wide", sep="_")
df_wide

แค่จำไว้ว่า idvarต้องมี! timevarและv.namesส่วนหนึ่งเป็นเรื่องง่าย ผลลัพธ์ของฟังก์ชั่นนี้สามารถคาดเดาได้มากกว่าบางฟังก์ชั่นอื่น ๆ เนื่องจากทุกอย่างถูกกำหนดไว้อย่างชัดเจน


7

มีแพคเกจใหม่ที่มีประสิทธิภาพมากจากนักวิทยาศาสตร์ข้อมูลอัจฉริยะที่ Win-เวกเตอร์ (คนที่ทำให้vtreat, seplyrและreplyr) cdataที่เรียกว่า มันใช้หลักการ "ข้อมูลประสานงาน" ที่อธิบายไว้ในเอกสารนี้และในโพสต์บล็อกนี้ แนวคิดก็คือไม่ว่าคุณจะจัดระเบียบข้อมูลของคุณอย่างไรมันเป็นไปได้ที่จะระบุจุดข้อมูลแต่ละจุดโดยใช้ระบบของ "data data" นี่คือข้อความที่ตัดตอนมาจากโพสต์บล็อกล่าสุดโดย John Mount:

ทั้งระบบขึ้นอยู่กับพื้นฐานสองประการหรือตัวดำเนินการ cdata :: moveValuesToRowsD () และ cdata :: moveValuesToColumnsD () ตัวดำเนินการเหล่านี้มี pivot, un-pivot, การเข้ารหัสแบบ one-hot, การย้าย, การย้ายหลายแถวและคอลัมน์และการแปลงอื่น ๆ อีกมากมายเป็นกรณีพิเศษอย่างง่าย

ง่ายต่อการเขียนการดำเนินการต่าง ๆ ในแง่ของดั้งเดิม cdata ผู้ประกอบการเหล่านี้สามารถทำงานในหน่วยความจำหรือขนาดใหญ่ข้อมูล (กับฐานข้อมูลและ Apache Spark; สำหรับข้อมูลขนาดใหญ่ใช้ cdata :: moveValuesToRowsN () และ cdata :: moveValuesToColumnsN () การแปลงรูปนั้นถูกควบคุมโดยตารางควบคุมที่ตัวเองเป็นไดอะแกรมของ (หรือรูปภาพ) การแปลงรูป

ก่อนอื่นเราจะสร้างตารางควบคุม (ดูโพสต์บล็อกเพื่อดูรายละเอียด) จากนั้นทำการย้ายข้อมูลจากแถวไปยังคอลัมน์

library(cdata)
# first build the control table
pivotControlTable <- buildPivotControlTableD(table = dat1, # reference to dataset
                        columnToTakeKeysFrom = 'numbers', # this will become column headers
                        columnToTakeValuesFrom = 'value', # this contains data
                        sep="_")                          # optional for making column names

# perform the move of data to columns
dat_wide <- moveValuesToColumnsD(tallTable =  dat1, # reference to dataset
                    keyColumns = c('name'),         # this(these) column(s) should stay untouched 
                    controlTable = pivotControlTable# control table above
                    ) 
dat_wide

#>         name  numbers_1  numbers_2  numbers_3  numbers_4
#> 1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
#> 2 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

1

วิธีที่ง่ายกว่ามาก!

devtools::install_github("yikeshu0611/onetree") #install onetree package

library(onetree)
widedata=reshape_toWide(data = dat1,id = "name",j = "numbers",value.var.prefix = "value")
widedata

        name     value1     value2     value3     value4
   firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
  secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

หากคุณต้องการกลับจากกว้างเป็นยาวเปลี่ยนเฉพาะกว้างเป็นยาวและไม่มีการเปลี่ยนแปลงในวัตถุ

reshape_toLong(data = widedata,id = "name",j = "numbers",value.var.prefix = "value")

        name numbers      value
   firstName       1  0.3407997
  secondName       1 -0.8981073
   firstName       2 -0.7033403
  secondName       2 -0.3347941
   firstName       3 -0.3795377
  secondName       3 -0.5013782
   firstName       4 -0.7460474
  secondName       4 -0.1745357
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.