การเปลี่ยนค่าตัวแปรให้เป็นชื่อคอลัมน์ ตัวระบุซ้ำสำหรับแถวใน tidyr::spread

ฉันกำลังทำงานกับไฟล์ผู้มีสิทธิเลือกตั้งที่ยุ่งเหยิง พิจารณา tibble ต่อไปนี้:

library(dplyr)
library(tidyr)
dat <- tibble(
  id = factor(c("A","B","C","D","E")),
  demographic_info1 = round(rnorm(5),2),
  demographic_info2 = round(rnorm(5),2),
  election_1 = c(NA,"GN2016","GN2016","SE2016","GN2008"),
  election_2 = c(NA,"MT2014","GN2012","GN2016","GN2004"),
  election_3 = c(NA,NA,NA,"MT2014","GN2000"),
  election_4 = c(NA,NA,NA,"GN2012",NA),
  election_5 = c(NA,NA,NA,"MT2010",NA),
)

ซึ่งดูเหมือนว่า:

# A tibble: 5 x 8
      id demographic_info1 demographic_info2 election_1 election_2 election_3 election_4 election_5
  <fctr>             <dbl>             <dbl>      <chr>      <chr>      <chr>      <chr>      <chr>
1      A             -1.50              0.81       <NA>       <NA>       <NA>       <NA>       <NA>
2      B             -1.84             -0.64     GN2016     MT2014       <NA>       <NA>       <NA>
3      C              1.66             -0.10     GN2016     GN2012       <NA>       <NA>       <NA>
4      D              0.91             -0.08     SE2016     GN2016     MT2014     GN2012     MT2010
5      E              0.04             -1.15     GN2008     GN2004     GN2000       <NA>       <NA>
  • id แต่ละตัวคือตัวระบุที่ไม่ซ้ำกันสำหรับผู้มีสิทธิเลือกตั้ง
  • คอลัมน์ demographic_info ทั้งสองคอลัมน์เป็นเพียงตัวเติม เพียงเพื่อแสดงให้เห็นว่าฉันต้องการ รักษา ค่าเหล่านี้เมื่อฉันปรับรูปร่างข้อมูลใหม่

คอลัมน์ election_1 ถึง election_5 คือสิ่งที่ฉันสนใจ ข้อมูลมีโครงสร้างในลักษณะที่ไฟล์รวมการเลือกตั้ง 5 ครั้งล่าสุดที่มีคนเข้าร่วม election_1 คือรายการล่าสุด election_5 คือรายการล่าสุด

โปรดทราบว่าบุคคล A ไม่เคยลงคะแนนเสียง ในขณะที่บุคคล D มักจะลงคะแนนเสียงเสมอ สิ่งที่ฉันต้องการทำคือเปลี่ยนคอลัมน์เหล่านี้ให้เป็นตัวแปรจำนวนหนึ่ง: SE2016, GN2016, MT2014, GN2012 ฯลฯ; นั่นคือค่าทั้งหมดใน election_1 ถึง election_5 ฉันอยากให้แต่ละตัวแปรเหล่านี้เป็นตัวแปรที่เป็น TRUE หรือ FALSE ไม่ว่าบุคคลนั้นจะปรากฏตัวในการสำรวจหรือไม่ ฉันได้ลองใช้รหัสนี้แล้ว:

dat %>% # take data
  gather(election, race, election_1:election_5) %>% # gather by election
  mutate(temp=TRUE) %>% # make new variable that is all TRUE
  select(-election) %>% # drop election variable
  spread(race, temp, fill=FALSE) # spread by this all TRUE variable, fill all NAs as FALSE

อย่างไรก็ตาม spread ส่งข้อผิดพลาด:

Error: Duplicate identifiers for rows (1, 6, 11, 16, 21), (12, 17, 22), (13, 18, 23), (20, 25)

เนื่องจากมีหลายรายการสำหรับแต่ละค่าของตัวแปร race ฉันได้ลอง group_by(id) ก่อนที่จะทำ spread แต่เกิดข้อผิดพลาดเดียวกันนี้

ฉันต้องการให้ผลลัพธ์ tibble มีลักษณะดังนี้:

# A tibble: 5 x 11
      id demographic_info1 demographic_info2 SE2016 GN2016 MT2014 GN2012 MT2010 GN2008 GN2004 GN2000
  <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
1      A             -0.91             -0.56  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
2      B              1.24             -1.78  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE
3      C              0.61              0.11  FALSE   TRUE  FALSE   TRUE  FALSE  FALSE  FALSE  FALSE
4      D              2.43             -0.53   TRUE   TRUE   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE
5      E             -1.40             -1.23  FALSE  FALSE  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE

person Mark White    schedule 19.08.2017    source แหล่งที่มา
comment
คุณต้องสร้างคอลัมน์ลำดับตามกลุ่มก่อนที่จะดำเนินการ spread   -  person akrun    schedule 19.08.2017
comment
@akrun ตัวแปร id ไม่นับเป็นคอลัมน์ลำดับหรือไม่ ไม่แน่ใจครับ ตามมาครับ   -  person Mark White    schedule 19.08.2017


คำตอบ (3)


tidyr จัดเตรียมไวยากรณ์บางอย่างสำหรับจัดการกับปัญหานี้

# set up
library(dplyr)
library(tidyr)
dat <- tibble(
  id = factor(c("A","B","C","D","E")),
  demographic_info1 = round(rnorm(5),2),
  demographic_info2 = round(rnorm(5),2),
  election_1 = c(NA,"GN2016","GN2016","SE2016","GN2008"),
  election_2 = c(NA,"MT2014","GN2012","GN2016","GN2004"),
  election_3 = c(NA,NA,NA,"MT2014","GN2000"),
  election_4 = c(NA,NA,NA,"GN2012",NA),
  election_5 = c(NA,NA,NA,"MT2010",NA)
)

สิ่งที่เราต้องการในที่สุดคือ TRUE หรือ FALSE สำหรับผู้มีสิทธิเลือกตั้งทุกคน (5) x การเลือกตั้ง (8) การจับคู่ เมื่อเรารวบรวมข้อมูลในรูปแบบยาว เราจะเห็นเฉพาะชุดข้อมูลของผู้มีสิทธิเลือกตั้ง x ชุดค่าผสมการเลือกตั้ง ที่มีอยู่ ในชุดข้อมูล

d_votes <- dat %>%
  gather("variable", "election", election_1:election_5) %>%
  select(-variable) %>%
  mutate(voted = TRUE)
d_votes
#> # A tibble: 25 x 5
#>        id demographic_info1 demographic_info2 election voted
#>    <fctr>             <dbl>             <dbl>    <chr> <lgl>
#>  1      A              0.76             -0.23     <NA>  TRUE
#>  2      B             -0.80              0.08   GN2016  TRUE
#>  3      C             -0.33              1.60   GN2016  TRUE
#>  4      D             -0.50             -1.27   SE2016  TRUE
#>  5      E             -1.03              0.59   GN2008  TRUE
#>  6      A              0.76             -0.23     <NA>  TRUE
#>  7      B             -0.80              0.08   MT2014  TRUE
#>  8      C             -0.33              1.60   GN2012  TRUE
#>  9      D             -0.50             -1.27   GN2016  TRUE
#> 10      E             -1.03              0.59   GN2004  TRUE
#> # ... with 15 more rows

count(d_votes, election)
#> # A tibble: 9 x 2
#>   election     n
#>      <chr> <int>
#> 1   GN2000     1
#> 2   GN2004     1
#> 3   GN2008     1
#> 4   GN2012     2
#> 5   GN2016     3
#> 6   MT2010     1
#> 7   MT2014     2
#> 8   SE2016     1
#> 9     <NA>    13

เราจำเป็นต้องสร้างทุกการผสมผสานระหว่างผู้มีสิทธิเลือกตั้งและการเลือกตั้ง ฟังก์ชัน expand() ของ tidyr จะสร้างการรวมตัวแปรทั้งหมดจากคอลัมน์/เวกเตอร์ของข้อมูลที่แตกต่างกัน (มันทำงานเหมือนฟังก์ชันพื้นฐาน expand.grid() ดังนั้นชื่อ expand() จึงชวนให้นึกถึง)

d_possible_votes <- d_votes %>%
  expand(nesting(id, demographic_info1, demographic_info2),
         election)
d_possible_votes
#> # A tibble: 40 x 4
#>        id demographic_info1 demographic_info2 election
#>    <fctr>             <dbl>             <dbl>    <chr>
#>  1      A              0.76             -0.23   GN2000
#>  2      A              0.76             -0.23   GN2004
#>  3      A              0.76             -0.23   GN2008
#>  4      A              0.76             -0.23   GN2012
#>  5      A              0.76             -0.23   GN2016
#>  6      A              0.76             -0.23   MT2010
#>  7      A              0.76             -0.23   MT2014
#>  8      A              0.76             -0.23   SE2016
#>  9      B             -0.80              0.08   GN2000
#> 10      B             -0.80              0.08   GN2004
#> # ... with 30 more rows

โปรดทราบว่าขณะนี้เรามีการเลือกตั้ง 8 ครั้ง x 5 รหัส = 40 แถว

เราใช้ฟังก์ชัน nesting() เพื่อถือว่าแต่ละ (id, demographic_info1, demographic_info2) ชุด/แถวเป็นหน่วยเดียว ข้อมูลประชากร ซ้อน ภายในรหัส การขยายให้ชุดค่าผสมทั้งหมด 40 รายการของ (id, demographic_info1, demographic_info2) x election

หากเรารวมการโหวตที่สังเกตได้เข้ากับการโหวตที่เป็นไปได้ คอลัมน์ voted จะถูกเติมด้วยค่า TRUE หรือ NA ฟังก์ชัน replace_na() ของ tidyr สามารถแก้ไขค่า NA เหล่านั้นได้

d_possible_votes <- d_possible_votes %>%
  left_join(d_votes) %>%
  replace_na(list(voted = FALSE))
#> Joining, by = c("id", "demographic_info1", "demographic_info2", "election")
d_possible_votes
#> # A tibble: 40 x 5
#>        id demographic_info1 demographic_info2 election voted
#>    <fctr>             <dbl>             <dbl>    <chr> <lgl>
#>  1      A              0.76             -0.23   GN2000 FALSE
#>  2      A              0.76             -0.23   GN2004 FALSE
#>  3      A              0.76             -0.23   GN2008 FALSE
#>  4      A              0.76             -0.23   GN2012 FALSE
#>  5      A              0.76             -0.23   GN2016 FALSE
#>  6      A              0.76             -0.23   MT2010 FALSE
#>  7      A              0.76             -0.23   MT2014 FALSE
#>  8      A              0.76             -0.23   SE2016 FALSE
#>  9      B             -0.80              0.08   GN2000 FALSE
#> 10      B             -0.80              0.08   GN2004 FALSE
#> # ... with 30 more rows

ตอนนี้เราสามารถกระจายการเลือกตั้งและบรรลุกรอบข้อมูลที่ต้องการได้

spread(d_possible_votes, election, voted)
#> # A tibble: 5 x 11
#>       id demographic_info1 demographic_info2 GN2000 GN2004 GN2008 GN2012 GN2016 MT2010 MT2014 SE2016
#> * <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
#> 1      A              0.76             -0.23  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
#> 2      B             -0.80              0.08  FALSE  FALSE  FALSE  FALSE   TRUE  FALSE   TRUE  FALSE
#> 3      C             -0.33              1.60  FALSE  FALSE  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE
#> 4      D             -0.50             -1.27  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE   TRUE   TRUE
#> 5      E             -1.03              0.59   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE

รูปแบบของการสร้างการรวมกันของตัวระบุ การรวมข้อมูลจริง และการแก้ไขค่าที่หายไปนั้นเป็นเรื่องปกติมาก มากจนทำให้ tidyr มีฟังก์ชัน complete() เพื่อทำทั้งสามอย่างพร้อมกัน

d_votes %>%
  complete(nesting(id, demographic_info1, demographic_info2),
           election, fill = list(voted = FALSE)) %>%
  spread(election, voted)
#> # A tibble: 5 x 11
#>       id demographic_info1 demographic_info2 GN2000 GN2004 GN2008 GN2012 GN2016 MT2010 MT2014 SE2016
#> * <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
#> 1      A              0.76             -0.23  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
#> 2      B             -0.80              0.08  FALSE  FALSE  FALSE  FALSE   TRUE  FALSE   TRUE  FALSE
#> 3      C             -0.33              1.60  FALSE  FALSE  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE
#> 4      D             -0.50             -1.27  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE   TRUE   TRUE
#> 5      E             -1.03              0.59   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE
person TJ Mahr    schedule 21.08.2017
comment
ขอบคุณสำหรับคำอธิบายที่ดีเกี่ยวกับวิธีการทำงานของ complete ฉันไม่รู้ถึงฟังก์ชั่นนั้น! คุณคิดอย่างไรกับโซลูชันของฉันเมื่อเปรียบเทียบ โค้ดของฉันรู้สึกหรูหราน้อยลง แต่ microbenchmark บอกว่ามันทำงานเร็วกว่า (เร็วกว่าประมาณ 6 มิลลิวินาที ซึ่งอาจมีความหมายมากเมื่อฉันรันบนไฟล์ขนาด 1GB) - person Mark White; 21.08.2017

เราสามารถใช้ group_by บน 'id' เพื่อสร้างตัวแปรลำดับได้ เนื่องจาก 'id' ถูกทำซ้ำและลบออกในภายหลังหลังจาก spread

dat %>%
   gather(election, race, election_1:election_5) %>%
   mutate(temp=TRUE)%>% group_by(id) %>%
   mutate(i1 = row_number()) %>% 
   select(-election) %>%
   spread(race, temp, fill=FALSE) %>%
   select(-i1)
person akrun    schedule 19.08.2017
comment
ยอดเยี่ยมขอบคุณ เพียงเพื่อให้แน่ใจว่าฉันเข้าใจเหตุใดจึงใช้งานได้: i1 ระบุตัวระบุที่ไม่ซ้ำกันภายในแต่ละกลุ่ม... - person Mark White; 19.08.2017
comment
@MarkWhite ใช่เนื่องจากมี 5 แถวสำหรับแต่ละ 'id' เช่น dat %>% gather(election, race, election_1:election_5) %>% mutate(temp=TRUE)%>% count(id) %>% .$n# [1] 5 5 5 5 5 ดังนั้น 'i1' จึงสร้างลำดับ 1, 2, ,3, ..5 สำหรับแต่ละ 'id' และมันสามารถช่วยในการแพร่กระจาย - person akrun; 19.08.2017
comment
เมื่อมองแวบที่สอง การใช้สิ่งนี้กับชุดข้อมูลจริงของฉัน: โค้ดด้านบนสร้างห้าแถวสำหรับทุก ๆ id; หนึ่งแถวสำหรับทุก ๆ ฟิลด์ election_* - person Mark White; 19.08.2017
comment
ฉันเคยเห็นคำตอบอื่นๆ ของคุณเช่นกัน เช่นนี้ (stackoverflow.com/a/43259735/7903456) ดูเหมือนว่าควรจะใช้งานได้ตามวัตถุประสงค์ของฉัน แต่ด้วยเหตุผลบางอย่าง ฉันจึงได้รับแถวใหม่สำหรับทุกๆ ฟิลด์ election_* - person Mark White; 20.08.2017

ปัญหาคือมีรายการที่ซ้ำกันสำหรับค่า NA ฉันแก้ไขปัญหาตัวระบุที่ซ้ำกัน และ ปัญหาหลายแถวจากคำตอบของ Akrun โดยรับเพียง unique แถว จากนั้นจัดกลุ่มด้วย id:

dat %>%
  gather(election, race, election_1:election_5) %>%
  mutate(temp=TRUE) %>%
  select(-election) %>%
  unique() %>% # GET RID OF DUPLICATE NA ENTRIES
  group_by(id) %>% 
  spread(race, temp, fill=FALSE) %>%
  select(-`<NA>`)

# A tibble: 5 x 11
# Groups:   id [5]
      id demographic_info1 demographic_info2 GN2000 GN2004 GN2008 GN2012 GN2016 MT2010 MT2014 SE2016
* <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
1      A             -1.19             -0.94  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
2      B              1.41             -0.62  FALSE  FALSE  FALSE  FALSE   TRUE  FALSE   TRUE  FALSE
3      C             -0.21              1.62  FALSE  FALSE  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE
4      D              1.51              0.09  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE   TRUE   TRUE
5      E              0.65             -2.09   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE
person Mark White    schedule 19.08.2017