计算分组数据框中多列因素的出现次数

Count occurrences of factors across multiple columns in grouped dataframe

我有以下数据框,想按 grp 列分组以查看每个列值在每个组中出现的数量。

> data.frame(grp = unlist(strsplit("aabbccca", "")), col1=unlist(strsplit("ABAABBAB", "")), col2=unlist(strsplit("BBCCCCDD", "")))
  grp col1 col2
1   a    A    B
2   a    B    B
3   b    A    C
4   b    A    C
5   c    B    C
6   c    B    C
7   c    A    D
8   a    B    D

想要的结果:

  grp col1A col1B col2B col2C col2D
1   a    1    2     2     0     1
2   b    2    0     0     2     0
3   c    1    2     0     2     1

如果我只查看 grpcol1 列,使用 table() 很容易解决这个问题,当只有 2 列时,我可以合并 table(df[c('grp', 'col1')])table(df[c('grp', 'col2')])。但是,随着因子列数量的增加,这会变得非常麻烦,如果 col1col2.

之间存在共享值,则会出现问题

请注意,dplyr 的计数不起作用,因为它会寻找 col1 和 col2 的独特组合。

我试过使用 tidyr 融化和传播数据框,但没有成功

> pivot_longer(df, c(col1, col2), names_to= "key", values_to = "val") %>% pivot_wider("grp", names_from = c("key", "val"), values_from = 1, values_fn = sum)
Error in `stop_subscript()`:
! Can't subset columns that don't exist.
x Column `grp` doesn't exist.

我可以找到很多适用于我有 1 个组列和 1 个值列的情况的解决方案,但我不知道如何将它们推广到更多列。

您在 meltspread 方面走在了正确的轨道上。这是一个 tidyverse 解决方案。我首先使用 pivot_longer 泛化到任意数量的列,然后使用 pivot_wider 到 return 到所需的输出格式。输出数据框中列的顺序取决于数据。如果这是一个问题,只需将 select 附加到管道的末尾即可获得所需的顺序。 (或者使用@DarrenTsai 的回答中的 names_sort。)

library(tidyverse)

d %>% 
  pivot_longer(
    starts_with("col"),
    names_to="Column",
    values_to="Value"
  ) %>% 
  group_by(grp, Column, Value) %>% 
  summarise(N=n(), .groups="drop") %>% 
  group_by(grp) %>% 
  pivot_wider(
    id_cols=grp,
    values_from=N,
    names_from=c(Column, Value),
    names_sep="",
    values_fill=0
  ) %>%
  ungroup()
# A tibble: 3 × 6
  grp   col1A col1B col2B col2D col2C
  <chr> <int> <int> <int> <int> <int>
1 a         1     2     2     1     0
2 b         2     0     0     0     2
3 c         1     2     0     1     2

可以把col1col2叠在一起,统计每个组合的个数,然后把table变宽。

library(dplyr)
library(tidyr)

df %>%
  pivot_longer(col1:col2) %>%
  count(grp, name, value) %>%
  pivot_wider(grp, names_from = c(name, value), names_sort = TRUE,
              values_from = n, values_fill = 0)

# A tibble: 3 x 6
  grp   col1_A col1_B col2_B col2_C col2_D
  <chr>  <int>  <int>  <int>  <int>  <int>
1 a          1      2      2      0      1
2 b          2      0      0      2      0
3 c          1      2      0      2      1

一个base解决方案(感谢@GKi提炼代码):

table(cbind(df["grp"], col=do.call(paste0, stack(df[-1])[2:1])))

   col
grp col1A col1B col2B col2C col2D
  a     1     2     2     0     1
  b     2     0     0     2     0
  c     1     2     0     2     1

使用 reshape2 包中的 recast

reshape2::recast(df, grp~variable+value,id.var = 'grp', fun = length)

  grp col1_A col1_B col2_B col2_C col2_D
1   a      1      2      2      0      1
2   b      2      0      0      2      0
3   c      1      2      0      2      1

在基础 R 中你可以这样做:

with(df, cbind(table(grp, paste0('col1_', col1)), table(grp, paste0('col2_', col2))))

  col1_A col1_B col2_B col2_C col2_D
a      1      2      2      0      1
b      2      0      0      2      0
c      1      2      0      2      1

如果您有很多列,请考虑这样做:

do.call(cbind, Map(function(x, y) table(df$grp, paste(x,y, sep = '_')),
                        names(df)[-1], df[,-1]))

  col1_A col1_B col2_B col2_C col2_D
a      1      2      2      0      1
b      2      0      0      2      0
c      1      2      0      2      1

然后您可以将其转换为数据框

data.table中,我们可以像下面这样使用dcast + melt

dcast(
    melt(setDT(df), id.vars = "grp")[
        , value := paste(variable, value, sep = "_")
    ], grp ~ value
)

生产

   grp col1_A col1_B col2_B col2_C col2_D
1:   a      1      2      2      0      1
2:   b      2      0      0      2      0
3:   c      1      2      0      2      1

另一种可能的解决方案,基于 tidyr::pivot_longer 后跟 tidyr::pivot_wider 并使用 values_fn = length:

library(tidyverse)

df %>% 
  pivot_longer(c(col1, col2)) %>% 
  mutate(name = str_c(name, value)) %>% 
  pivot_wider(grp, values_fn = length, values_fill = 0, names_sort = T)

#> # A tibble: 3 x 6
#>   grp   col1A col1B col2B col2C col2D
#>   <chr> <int> <int> <int> <int> <int>
#> 1 a         1     2     2     0     1
#> 2 b         2     0     0     2     0
#> 3 c         1     2     0     2     1