สรุป dplyr: เทียบเท่ากับ“ .drop = FALSE” เพื่อให้กลุ่มที่มีความยาวเป็นศูนย์ในเอาต์พุต


101

เมื่อใช้summariseกับplyrของddplyฟังก์ชั่นประเภทที่ว่างเปล่าจะลดลงโดยปริยาย .drop = FALSEคุณสามารถเปลี่ยนพฤติกรรมนี้โดยการเพิ่ม อย่างไรก็ตามสิ่งนี้ใช้ไม่ได้เมื่อใช้summariseกับdplyrไฟล์. มีวิธีอื่นในการเก็บหมวดหมู่ว่างไว้ในผลลัพธ์หรือไม่?

นี่คือตัวอย่างข้อมูลปลอม

library(dplyr)

df = data.frame(a=rep(1:3,4), b=rep(1:2,6))

# Now add an extra level to df$b that has no corresponding value in df$a
df$b = factor(df$b, levels=1:3)

# Summarise with plyr, keeping categories with a count of zero
plyr::ddply(df, "b", summarise, count_a=length(a), .drop=FALSE)

  b    count_a
1 1    6
2 2    6
3 3    0

# Now try it with dplyr
df %.%
  group_by(b) %.%
  summarise(count_a=length(a), .drop=FALSE)

  b     count_a .drop
1 1     6       FALSE
2 2     6       FALSE

ไม่ตรงกับสิ่งที่ฉันหวังไว้ มีdplyrวิธีการเพื่อให้ได้ผลลัพธ์เช่นเดียวกับ.drop=FALSEในplyrหรือไม่?


คำตอบ:


30

เนื่องจากdplyr 0.8 group_byได้รับ.dropอาร์กิวเมนต์ที่ทำในสิ่งที่คุณขอ:

df = data.frame(a=rep(1:3,4), b=rep(1:2,6))
df$b = factor(df$b, levels=1:3)

df %>%
  group_by(b, .drop=FALSE) %>%
  summarise(count_a=length(a))

#> # A tibble: 3 x 2
#>   b     count_a
#>   <fct>   <int>
#> 1 1           6
#> 2 2           6
#> 3 3           0

หมายเหตุเพิ่มเติมอีกประการหนึ่งที่ควรคำนึงถึงคำตอบของ @Moody_Mudskipper: การใช้.drop=FALSEสามารถให้ผลลัพธ์ที่ไม่คาดคิดได้เมื่อตัวแปรการจัดกลุ่มอย่างน้อยหนึ่งตัวแปรไม่ได้ถูกเข้ารหัสเป็นปัจจัย ดูตัวอย่างด้านล่าง:

library(dplyr)
data(iris)

# Add an additional level to Species
iris$Species = factor(iris$Species, levels=c(levels(iris$Species), "empty_level"))

# Species is a factor and empty groups are included in the output
iris %>% group_by(Species, .drop=FALSE) %>% tally

#>   Species         n
#> 1 setosa         50
#> 2 versicolor     50
#> 3 virginica      50
#> 4 empty_level     0

# Add character column
iris$group2 = c(rep(c("A","B"), 50), rep(c("B","C"), each=25))

# Empty groups involving combinations of Species and group2 are not included in output
iris %>% group_by(Species, group2, .drop=FALSE) %>% tally

#>   Species     group2     n
#> 1 setosa      A         25
#> 2 setosa      B         25
#> 3 versicolor  A         25
#> 4 versicolor  B         25
#> 5 virginica   B         25
#> 6 virginica   C         25
#> 7 empty_level <NA>       0

# Turn group2 into a factor
iris$group2 = factor(iris$group2)

# Now all possible combinations of Species and group2 are included in the output, 
#  whether present in the data or not
iris %>% group_by(Species, group2, .drop=FALSE) %>% tally

#>    Species     group2     n
#>  1 setosa      A         25
#>  2 setosa      B         25
#>  3 setosa      C          0
#>  4 versicolor  A         25
#>  5 versicolor  B         25
#>  6 versicolor  C          0
#>  7 virginica   A          0
#>  8 virginica   B         25
#>  9 virginica   C         25
#> 10 empty_level A          0
#> 11 empty_level B          0
#> 12 empty_level C          0

Created on 2019-03-13 by the reprex package (v0.2.1)

ฉันได้เพิ่มหมายเหตุเพิ่มเติมในคำตอบของคุณ โปรดอย่าลังเลที่จะลบหากคุณไม่ชอบการแก้ไข
eipi10

ฉันได้ยื่นปัญหาเกี่ยวกับเรื่องนี้ใน github เพื่อค้นหาว่านี่เป็นจุดบกพร่องหรือพฤติกรรมที่ตั้งใจไว้
eipi10

@ eipi10 สั้นกว่าเล็กน้อยคือการใช้count:iris %>% count(Species, group2, .drop=FALSE)
Tjebo

60

ปัญหายังคงเปิดอยู่ แต่ในระหว่างนี้โดยเฉพาะอย่างยิ่งเนื่องจากข้อมูลของคุณได้รับการพิจารณาแล้วคุณสามารถใช้completeจาก "tidyr" เพื่อรับสิ่งที่คุณอาจกำลังมองหา:

library(tidyr)
df %>%
  group_by(b) %>%
  summarise(count_a=length(a)) %>%
  complete(b)
# Source: local data frame [3 x 2]
# 
#        b count_a
#   (fctr)   (int)
# 1      1       6
# 2      2       6
# 3      3      NA

หากคุณต้องการให้ค่าทดแทนเป็นศูนย์คุณต้องระบุด้วยfill:

df %>%
  group_by(b) %>%
  summarise(count_a=length(a)) %>%
  complete(b, fill = list(count_a = 0))
# Source: local data frame [3 x 2]
# 
#        b count_a
#   (fctr)   (dbl)
# 1      1       6
# 2      2       6
# 3      3       0

11
ฉันต้องใช้หัวโขกกับกำแพงหลายครั้งเพื่อหาสิ่งนี้ดังนั้นฉันจะพูดถึงที่นี่ ... ถ้าคุณจัดกลุ่มตาม 2 ตัวแปรและเป็นตัวอักษรแทนที่จะเป็นตัวประกอบคุณจะต้องใช้ungroup()ก่อนที่จะดำเนินการเสร็จสมบูรณ์ หากคุณเคยสังเกตเห็นว่าcompleteไม่เสร็จสมบูรณ์ungroupอาจจำเป็น
williamsurles

จะเกิดอะไรขึ้นถ้าคุณมีตัวแปรการจัดกลุ่มมากกว่านี้? ฉันได้รับแถวจำนวนมาก (มากกว่าดาต้าเฟรมเดิมของฉันมาก) ถ้าฉันใช้ตัวแปรการจัดกลุ่มทั้งหมดจาก group_by ของฉัน
TobiO

1
ฉันคิดออกแล้ว: คุณต้องใช้การซ้อน :-) ดังนั้นใส่ตัวแปรทั้งหมดที่ไม่ควรรวมเข้าด้วยกันcomplete(variablewithdroppedlevels, nesting(var1,var2,var3))(จริงๆแล้วมันอยู่ในความช่วยเหลือcompleteแต่ฉันยังต้องใช้เวลาสักพักกว่าจะคิดออก
TobiO

20

โซลูชัน dplyr:

ก่อนอื่นให้จัดกลุ่ม df

by_b <- tbl_df(df) %>% group_by(b)

จากนั้นเราจะสรุประดับที่เกิดขึ้นโดยการนับด้วย n()

res <- by_b %>% summarise( count_a = n() )

จากนั้นเรารวมผลลัพธ์ของเราลงในกรอบข้อมูลที่มีทุกระดับปัจจัย:

expanded_res <- left_join(expand.grid(b = levels(df$b)),res)

ในที่สุดในกรณีนี้เนื่องจากเรากำลังดูการนับNAค่าจึงเปลี่ยนเป็น 0

final_counts <- expanded_res[is.na(expanded_res)] <- 0

สิ่งนี้สามารถนำไปใช้งานได้เช่นกันดูคำตอบ: เพิ่มแถวในข้อมูลที่จัดกลุ่มด้วย dplyr?

แฮ็ค:

ฉันคิดว่าฉันจะโพสต์การแฮ็กที่น่ากลัวซึ่งใช้ได้ในกรณีนี้เพื่อประโยชน์ของผลประโยชน์ ฉันสงสัยอย่างจริงจังว่าคุณควรทำสิ่งนี้จริง ๆ แต่มันแสดงให้เห็นว่าการgroup_by()สร้าง atrributes ราวกับว่าdf$bเป็นเวกเตอร์อักขระไม่ใช่ปัจจัยที่มีระดับ นอกจากนี้ฉันไม่ได้แสร้งทำเป็นเข้าใจสิ่งนี้อย่างถูกต้อง - แต่ฉันหวังว่านี่จะช่วยให้ฉันเรียนรู้ - นี่เป็นเหตุผลเดียวที่ฉันโพสต์!

by_b <- tbl_df(df) %>% group_by(b)

กำหนดค่า "นอกขอบเขต" ที่ไม่มีอยู่ในชุดข้อมูล

oob_val <- nrow(by_b)+1

แก้ไขแอตทริบิวต์เป็น "เคล็ดลับ" summarise():

attr(by_b, "indices")[[3]] <- rep(NA,oob_val)
attr(by_b, "group_sizes")[3] <- 0
attr(by_b, "labels")[3,] <- 3

สรุป:

res <- by_b %>% summarise(count_a = n())

ดัชนีและแทนที่การเกิดขึ้นทั้งหมดของ oob_val

res[res == oob_val] <- 0

ซึ่งให้วัตถุประสงค์:

> res
Source: local data frame [3 x 2]

b count_a
1 1       6
2 2       6
3 3       0

11

นี่ไม่ใช่สิ่งที่ถามในคำถาม แต่อย่างน้อยสำหรับตัวอย่างง่ายๆนี้คุณจะได้ผลลัพธ์เดียวกันโดยใช้ xtabs ตัวอย่างเช่น:

ใช้ dplyr:

df %>%
  xtabs(formula = ~ b) %>%
  as.data.frame()

หรือสั้นกว่า:

as.data.frame(xtabs( ~ b, df))

ผลลัพธ์ (เท่ากันในทั้งสองกรณี):

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