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?

eipi10
sumber

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)
Moody_Mudskipper
sumber
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
A5C1D2H2I1M1N2O1R2T1
sumber
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
npjc
sumber
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
talat
sumber