使用dplyr对多列进行汇总?

190

我在使用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))

这为我提供了“a”列的每个“grp”组的平均值。

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

我想要的是像这样的东西

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

298
在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

1
这很好,但如果我只想将函数(即paste)应用于最后一列,而对于其他列,我只想取第一个元素或保持不变,该怎么办? - biocyberman
1
我的意思是,像select中的行为一样会很好:summarize(df, a:c, d=paste(d, collaspe =',' )。我只想添加更多原始列以供参考。 - biocyberman
1
purrr 和 dplyr 有什么区别? - Mostafa90
1
我该如何在这种情况下向mean函数添加参数?将mean(na.rm = TRUE)转换为df %>% group_by(grp) %>% summarise_each(funs(mean)) - piotr
9
建议使用funs(mean(., na.rm = TRUE))而非funs(mean) - Artem Klevtsov
显示剩余11条评论

67
我们可以使用 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

我能否使用summarise_at函数将每一列应用于不同的函数,例如对于列a只应用mean函数,对于列b只应用sd函数? - dondapati
1
在您的情况下,您可以使用 summarise。例如,summarise(a_mean = mean(a), b_sd = sd(b)) - Keiku
4
如果我想对第1-13列进行平均值计算,对第14-30列进行标准差计算,对第31-100列进行求和计算,但不想把它们全部列出来,该怎么办? - Arthur Yip
2
我点赞了你的评论,因为我昨天发布了这个问题R summarise_at动态条件:对于某些列求平均值,对于其他列求和 - phili_b

42

您可以简单地向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

2
太好了!如果列名和数量未知,这样的事情是否可能呢?例如,有3或6个固定列而不是4个? - Daniel
4
我相信这是 dplyr 中的一个待办事项(就像 plyr 中的 colwise),这里提供了一个相当笨拙的当前解决方案:https://dev59.com/D2Ei5IYBdhLWcg3wZ7y8#21296364。 - Stephen Henderson
非常感谢你们两个!我可能只会使用一个循环来迭代所有列。 - Daniel
13
dplyr现在有了summarise_each函数,它可以对每一列进行操作。 - rrs
1
dplyrdplyr 1.0.0版本起已经用summarise(across(....取代了summarise_* - NelsonGon

7
为了完整起见:使用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

1
需要在大型数据集上进行测试。 - Artem Klevtsov
1
ddply 不在 dplyr 中,它在 plyr 中。 - Axeman

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关于整洁数据总是长格式的定义。通常,您不想使观察结果增加,而是希望每个观察结果都有一行。 - Daniel
我并不反对。每个人都有自己的偏好,对于一些人来说,广泛的方法更可取,无论是从更直观的角度还是因为实际上存在结构性原因,你不想使用长格式。对我而言,我的偏好是长格式,因为随着我更多地使用 dplyr,长格式使事情变得更加容易。 - Matt Dancho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接