【问题标题】:Summarizing multiple columns with dplyr? [duplicate]用 dplyr 总结多列? [复制]
【发布时间】:2025-11-24 12:35:01
【问题描述】:

我在使用 dplyr 语法时遇到了一些困难。我有一个包含不同变量和一个分组变量的数据框。现在我想使用 R 中的 dplyr 计算每个组中每一列的平均值。

df <- data.frame(
    a = sample(1:5, n, replace = TRUE), 
    b = sample(1:5, n, replace = TRUE), 
    c = sample(1:5, n, replace = TRUE), 
    d = sample(1:5, n, replace = TRUE), 
    grp = sample(1:3, n, replace = TRUE)
)
df %>% group_by(grp) %>% summarise(mean(a))

这给了我“grp”指示的每个组的“a”列的平均值。

我的问题是:是否可以一次获得每个组中每列的平均值?还是我必须为每一列重复df %&gt;% group_by(grp) %&gt;% summarise(mean(a))

我想要的是类似的东西

df %>% group_by(grp) %>% summarise(mean(a:d)) # "mean(a:d)" does not work

【问题讨论】:

    标签: r dplyr aggregate


    【解决方案1】:

    dplyr (>=1.00) 中,您可以在summarise 中使用across(everything() 将函数应用于所有变量:

    library(dplyr)
    
    df %>% group_by(grp) %>% summarise(across(everything(), list(mean)))
    #> # A tibble: 3 x 5
    #>     grp     a     b     c     d
    #>   <int> <dbl> <dbl> <dbl> <dbl>
    #> 1     1  3.08  2.98  2.98  2.91
    #> 2     2  3.03  3.04  2.97  2.87
    #> 3     3  2.85  2.95  2.95  3.06
    

    或者,purrrlyr 包提供相同的功能:

    library(purrrlyr)
    df %>% slice_rows("grp") %>% dmap(mean)
    #> # A tibble: 3 x 5
    #>     grp     a     b     c     d
    #>   <int> <dbl> <dbl> <dbl> <dbl>
    #> 1     1  3.08  2.98  2.98  2.91
    #> 2     2  3.03  3.04  2.97  2.87
    #> 3     3  2.85  2.95  2.95  3.06
    

    也不要忘记data.table(使用keyby 对排序组进行排序):

    library(data.table)
    setDT(df)[, lapply(.SD, mean), keyby = grp]
    #>    grp        a        b        c        d
    #> 1:   1 3.079412 2.979412 2.979412 2.914706
    #> 2:   2 3.029126 3.038835 2.967638 2.873786
    #> 3:   3 2.854701 2.948718 2.951567 3.062678
    

    让我们尝试比较性能。

    library(dplyr)
    library(purrrlyr)
    library(data.table)
    library(bench)
    set.seed(123)
    n <- 10000
    df <- data.frame(
      a = sample(1:5, n, replace = TRUE), 
      b = sample(1:5, n, replace = TRUE), 
      c = sample(1:5, n, replace = TRUE), 
      d = sample(1:5, n, replace = TRUE), 
      grp = sample(1:3, n, replace = TRUE)
    )
    dt <- setDT(df)
    mark(
      dplyr = df %>% group_by(grp) %>% summarise(across(everything(), list(mean))),
      purrrlyr = df %>% slice_rows("grp") %>% dmap(mean),
      data.table = dt[, lapply(.SD, mean), keyby = grp],
      check = FALSE
    )
    #> # A tibble: 3 x 6
    #>   expression      min   median `itr/sec` mem_alloc `gc/sec`
    #>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
    #> 1 dplyr        2.81ms   2.85ms      328.        NA     17.3
    #> 2 purrrlyr     7.96ms   8.04ms      123.        NA     24.5
    #> 3 data.table 596.33µs 707.91µs     1409.        NA     10.3
    

    【讨论】:

    • 这很好,但是如果我只想将函数(即paste)应用于最后一列,而对于其他列,我只想取第一个元素或保留为-是?
    • 我的意思是,select 中的行为会很棒:summarize(df, a:c, d=paste(d, collaspe =',' )。只是想放更多原创专栏供参考
    • 请问 purrr 和 dplyr 的区别?
    • 在这种情况下如何将参数添加到 mean 函数?mean(na.rm = TRUE) 到:df %&gt;% group_by(grp) %&gt;% summarise_each(funs(mean))
    • @piotr: funs(mean(., na.rm = TRUE)) 而不是 funs(mean)
    【解决方案2】:

    我们可以在dplyr 0.7.4 上使用summarize_atsummarize_allsummarize_if 进行总结。我们可以使用varsfuns 参数设置多个列和函数,如下代码所示。 funs 公式的左侧分配给汇总变量的后缀。在dplyr 0.7.4中,summarise_each(和mutate_each)已经被弃用了,所以我们不能使用这些函数。

    options(scipen = 100, dplyr.width = Inf, dplyr.print_max = Inf)
    
    library(dplyr)
    packageVersion("dplyr")
    # [1] ‘0.7.4’
    
    set.seed(123)
    df <- data_frame(
      a = sample(1:5, 10, replace=T), 
      b = sample(1:5, 10, replace=T), 
      c = sample(1:5, 10, replace=T), 
      d = sample(1:5, 10, replace=T), 
      grp = as.character(sample(1:3, 10, replace=T)) # For convenience, specify character type
    )
    
    df %>% group_by(grp) %>% 
      summarise_each(.vars = letters[1:4],
                     .funs = c(mean="mean"))
    # `summarise_each()` is deprecated.
    # Use `summarise_all()`, `summarise_at()` or `summarise_if()` instead.
    # To map `funs` over a selection of variables, use `summarise_at()`
    # Error: Strings must match column names. Unknown columns: mean
    

    您应该更改为以下代码。以下代码都具有相同的结果。

    # summarise_at
    df %>% group_by(grp) %>% 
      summarise_at(.vars = letters[1:4],
                   .funs = c(mean="mean"))
    
    df %>% group_by(grp) %>% 
      summarise_at(.vars = names(.)[1:4],
                   .funs = c(mean="mean"))
    
    df %>% group_by(grp) %>% 
      summarise_at(.vars = vars(a,b,c,d),
                   .funs = c(mean="mean"))
    
    # summarise_all
    df %>% group_by(grp) %>% 
      summarise_all(.funs = c(mean="mean"))
    
    # summarise_if
    df %>% group_by(grp) %>% 
      summarise_if(.predicate = function(x) is.numeric(x),
                   .funs = funs(mean="mean"))
    # A tibble: 3 x 5
    # grp a_mean b_mean c_mean d_mean
    # <chr>  <dbl>  <dbl>  <dbl>  <dbl>
    # 1     1   2.80   3.00    3.6   3.00
    # 2     2   4.25   2.75    4.0   3.75
    # 3     3   3.00   5.00    1.0   2.00
    

    您还可以拥有多种功能。

    df %>% group_by(grp) %>% 
      summarise_at(.vars = letters[1:2],
                   .funs = c(Mean="mean", Sd="sd"))
    # A tibble: 3 x 5
    # grp a_Mean b_Mean      a_Sd     b_Sd
    # <chr>  <dbl>  <dbl>     <dbl>    <dbl>
    # 1     1   2.80   3.00 1.4832397 1.870829
    # 2     2   4.25   2.75 0.9574271 1.258306
    # 3     3   3.00   5.00        NA       NA
    

    【讨论】:

    • 我是否可以将每个列与每个函数一起应用,即,对于列 a 仅应用 mean 和对于列 b 仅应用 sd 并使用 summaise_at
    • @user7462639 在您的情况下,您可以使用summarise。即summarise(a_mean = mean(a), b_sd = sd(b))
    • 但是如果我想对第 1-13 列表示,sd 表示第 14-30 列,sum 表示第 31-100 列,并且不想全部列出呢?
    • 我赞成你的评论,因为我昨天发布了这个问题R summarise_at dynamically by condition : mean for some columns, sum for others
    【解决方案3】:

    您可以简单地将更多参数传递给summarise

    df %>% group_by(grp) %>% summarise(mean(a), mean(b), mean(c), mean(d))
    

    来源:本地数据框 [3 x 5]

      grp  mean(a)  mean(b)  mean(c) mean(d)
    1   1 2.500000 3.500000 2.000000     3.0
    2   2 3.800000 3.200000 3.200000     2.8
    3   3 3.666667 3.333333 2.333333     3.0
    

    【讨论】:

    • 太棒了!如果列名和计数未知,甚至可以做这样的事情吗?例如。有 3 个或 6 个而不是 4 个固定列?
    • 这是dplyr 中的 TODO 我相信(如plyr colwise),请参阅此处以获取当前相当尴尬的解决方案:*.com/a/21296364/1527403
    • 非常感谢你们!我可能只使用一个循环来迭代所有列。
    • dplyr 现在有summarise_each,它将在每一列上运行
    • dplyr 现在已经用 summarise(across(.... 取代 summarise_* dplyr 1.0.0
    【解决方案4】:

    为了完整性:使用 dplyr v0.2 ddplycolwise 也会这样做:

    > ddply(df, .(grp), colwise(mean))
      grp        a    b        c        d
    1   1 4.333333 4.00 1.000000 2.000000
    2   2 2.000000 2.75 2.750000 2.750000
    3   3 3.000000 4.00 4.333333 3.666667
    

    但它更慢,至少在这种情况下:

    > microbenchmark(ddply(df, .(grp), colwise(mean)), 
                      df %>% group_by(grp) %>% summarise_each(funs(mean)))
    Unit: milliseconds
                                                expr      min       lq     mean
                    ddply(df, .(grp), colwise(mean))     3.278002 3.331744 3.533835
     df %>% group_by(grp) %>% summarise_each(funs(mean)) 1.001789 1.031528 1.109337
    
       median       uq      max neval
     3.353633 3.378089 7.592209   100
     1.121954 1.133428 2.292216   100
    

    【讨论】:

    • 需要对大型数据集进行测试。
    • ddply 不在dplyr 中,它在plyr 中。
    【解决方案5】:

    所有示例都很棒,但我想我会再添加一个以展示以“整洁”格式工作如何简化事情。现在数据框是“宽”格式,这意味着变量“a”到“d”以列表示。要获得“整洁”(或长)格式,您可以使用tidyr 包中的gather(),它将“a”到“d”列中的变量转换为行。然后使用group_by()summarize() 函数来获得每个组的平均值。如果您想以宽格式显示数据,只需额外调用spread() 函数即可。


    library(tidyverse)
    
    # Create reproducible df
    set.seed(101)
    df <- tibble(a   = sample(1:5, 10, replace=T), 
                 b   = sample(1:5, 10, replace=T), 
                 c   = sample(1:5, 10, replace=T), 
                 d   = sample(1:5, 10, replace=T), 
                 grp = sample(1:3, 10, replace=T))
    
    # Convert to tidy format using gather
    df %>%
        gather(key = variable, value = value, a:d) %>%
        group_by(grp, variable) %>%
        summarize(mean = mean(value)) %>%
        spread(variable, mean)
    #> Source: local data frame [3 x 5]
    #> Groups: grp [3]
    #> 
    #>     grp        a     b        c        d
    #> * <int>    <dbl> <dbl>    <dbl>    <dbl>
    #> 1     1 3.000000   3.5 3.250000 3.250000
    #> 2     2 1.666667   4.0 4.666667 2.666667
    #> 3     3 3.333333   3.0 2.333333 2.333333
    

    【讨论】:

    • 这是另一个值得记住的好方法。只有一件事:我不同意 Hadley 对整齐数据始终采用长格式的定义。通常,您不想将观察结果相乘,而是希望每次观察结果显示一行。
    • 我不反对。每个人都有偏好,对于某些人来说,从更直观的角度来看,宽方法更可取,或者因为实际上存在结构上的原因,您不想要长格式。对我来说,我更喜欢长格式,因为当我开始使用 dplyr 时,长格式让事情变得更容易。