【问题标题】:Count occurrences of factors across multiple columns in grouped dataframe计算分组数据框中多列中因子的出现次数
【发布时间】:2022-08-22 22:46:51
【问题描述】:

我有以下数据框,并希望按 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 个值列的情况的解决方案,但我不知道如何将它们推广到更多列。

    标签: r dataframe reshape


    【解决方案1】:

    你可以把col1&col2叠在一起,统计每个组合的个数,然后把表格变换成宽表格。

    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
    

    【讨论】:

    • 有两件事值得注意并且非常有帮助。 1. id_cols 和 2. names_sort 参数的使用。
    • 也许是更通用的基础版本:table(cbind(df["grp"], do.call(paste0, stack(df[-1])[2:1])))
    • @GKi 我忽略了 cbind() 将回收 grp 到堆叠数据的长度。感谢帮助!
    【解决方案2】:

    使用来自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
    

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

    【讨论】:

      【解决方案3】:

      meltspread 你走在正确的轨道上。这是一个整洁的解决方案。我首先使用pivot_longer 泛化到任意数量的列,然后使用pivot_wider 返回所需的输出格式。输出数据框中的列顺序取决于数据。如果这是一个问题,只需将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
      

      【讨论】:

        【解决方案4】:

        另一种可能的解决方案,基于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
        

        【讨论】:

          【解决方案5】:

          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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-01-11
            • 1970-01-01
            • 2021-11-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-04-24
            • 2015-09-09
            相关资源
            最近更新 更多