dplyr meringkas: Setara dengan ".drop = FALSE" untuk menyimpan grup dengan panjang keluaran nol


97

Bila menggunakan summarisedengan plyr's ddplyfungsi, kategori kosong dijatuhkan oleh default. Anda dapat mengubah perilaku ini dengan menambahkan .drop = FALSE. Namun, ini tidak berfungsi saat digunakan summarisedengan dplyr. Apakah ada cara lain untuk menjaga kategori kosong dalam hasil?

Berikut contoh data palsu.

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

Tidak persis seperti yang kuharapkan. Apakah ada dplyrmetode untuk mencapai hasil yang sama seperti .drop=FALSEdi plyr?


Jawaban:


26

Sejak dplyr 0.8 group_by memperoleh .dropargumen yang melakukan apa yang Anda minta:

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

Satu catatan tambahan untuk disertakan dengan jawaban @ Moody_Mudskipper: Penggunaan .drop=FALSEdapat memberikan hasil yang berpotensi tidak terduga ketika satu atau lebih variabel pengelompokan tidak dikodekan sebagai faktor. Lihat contoh di bawah ini:

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)

Saya telah menambahkan catatan tambahan untuk jawaban Anda. Silakan hapus jika Anda tidak suka hasil editnya.
— eipi10

Saya telah mengajukan masalah tentang ini di github untuk mencari tahu apakah ini bug atau perilaku yang diinginkan.
— eipi10

@ eipi10 sedikit lebih pendek adalah penggunaan count:iris %>% count(Species, group2, .drop=FALSE)
— Tjebo

59

Masalahnya masih terbuka, tetapi sementara itu, terutama karena data Anda sudah difaktorkan, Anda dapat menggunakan completedari "tidyr" untuk mendapatkan apa yang mungkin Anda cari:

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

Jika Anda ingin nilai pengganti menjadi nol, Anda perlu menentukannya dengan 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
Butuh banyak waktu bagi saya untuk membenturkan kepala ke dinding untuk mengetahuinya jadi saya akan menyebutkannya di sini ... Jika Anda mengelompokkan berdasarkan 2 variabel, dan mereka adalah karakter daripada faktor, Anda perlu menggunakan ungroup()sebelum Anda menyelesaikannya. Jika Anda pernah melihat completetidak benar-benar menyelesaikan, ungroupmungkin diperlukan.
— williamsurles

Bagaimana Jika Anda memiliki lebih banyak variabel pengelompokan? Saya mendapatkan sejumlah besar baris (lebih banyak dari kerangka data asli saya) jika saya menggunakan semua variabel pengelompokan dari group_by
— TobiO

1
Saya menemukannya: Anda harus menggunakan nesting :-) Jadi taruh semua Variabel yang tidak boleh digabungkan di antara mereka complete(variablewithdroppedlevels, nesting(var1,var2,var3))(sebenarnya dalam bantuan karena completesaya masih butuh waktu untuk mengetahuinya
— TobiO

20

solusi dplyr:

Pertama buat df dikelompokkan

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

kemudian kami meringkas level-level yang terjadi dengan menghitung dengan n()

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

kemudian kami menggabungkan hasil kami ke dalam bingkai data yang berisi semua tingkat faktor:

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

Akhirnya, dalam kasus ini karena kita melihat hitungan, NAnilainya diubah menjadi 0.

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

Ini juga dapat diimplementasikan secara fungsional, lihat jawaban: Tambahkan baris ke data yang dikelompokkan dengan dplyr?

Sebuah retasan:

Saya pikir saya akan memposting peretasan mengerikan yang berfungsi dalam kasus ini demi kepentingan. Saya benar-benar ragu Anda harus benar-benar melakukan ini tetapi ini menunjukkan bagaimana group_by()menghasilkan atribut seolah-olah df$bvektor karakter bukan faktor dengan level. Selain itu, saya tidak berpura-pura memahami ini dengan benar - tetapi saya berharap ini membantu saya belajar - inilah satu-satunya alasan saya mempostingnya!

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

tentukan nilai "out-of-bounds" yang tidak boleh ada dalam set data.

oob_val <- nrow(by_b)+1

ubah atribut menjadi "trik" summarise():

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

lakukan ringkasannya:

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

indeks dan ganti semua kemunculan oob_val

res[res == oob_val] <- 0

yang memberikan tujuan:

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

b count_a
1 1       6
2 2       6
3 3       0

11

ini tidak persis seperti yang ditanyakan dalam pertanyaan, tetapi setidaknya untuk contoh sederhana ini, Anda bisa mendapatkan hasil yang sama menggunakan xtabs, misalnya:

menggunakan dplyr:

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

atau lebih pendek:

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

hasil (sama dalam kedua kasus):

  b Freq
1 1    6
2 2    6
3 3    0
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.