在 dplyr 中按多列分组,使用字符串向量输入

Group by multiple columns in dplyr, using string vector input

提问人:sharoz 提问时间:1/19/2014 最后编辑:NelsonGonsharoz 更新时间:6/23/2021 访问量:121118

问:

我正在尝试将我对 plyr 的理解转移到 dplyr 中,但我无法弄清楚如何按多列分组。

# make data with weird column names that can't be hard coded
data = data.frame(
  asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

# plyr - works
ddply(data, columns, summarize, value=mean(value))

# dplyr - raises error
data %.%
  group_by(columns) %.%
  summarise(Value = mean(value))
#> Error in eval(expr, envir, enclos) : index out of bounds

将 plyr 示例转换为 dplyr 式语法时,我缺少什么?

编辑 2017:Dplyr 已更新,因此可以使用更简单的解决方案。查看当前选择的答案。

DPLYR R-常见问题

评论

3赞 James Owers 1/26/2015
刚到这里,因为它是顶级谷歌。您现在可以使用group_by_vignette("nse")
3赞 sharoz 1/28/2015
@kungfujam:这似乎只按第一列分组,而不是按列对分组
1赞 James Owers 1/28/2015
您需要使用 .以下是根据 @hadley 的回答改编的解决方案:.dotsdf %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
1赞 James Owers 1/28/2015
在下面的答案中输入了完整的代码
1赞 sharoz 1/28/2015
正如有人在评论的回答中指出的那样,目的是不需要硬编码的列名。

答:

10赞 Gavin Simpson 1/19/2014 #1

如果你将对象传递给它(好吧,你不是,但是......),而不是作为字符向量,它就会起作用:

df %.%
    group_by(asdfgfTgdsx, asdfk30v0ja) %.%
    summarise(Value = mean(value))

> df %.%
+   group_by(asdfgfTgdsx, asdfk30v0ja) %.%
+   summarise(Value = mean(value))
Source: local data frame [9 x 3]
Groups: asdfgfTgdsx

  asdfgfTgdsx asdfk30v0ja        Value
1           A           C  0.046538002
2           C           B -0.286359899
3           B           A -0.305159419
4           C           A -0.004741504
5           B           B  0.520126476
6           C           C  0.086805492
7           B           C -0.052613078
8           A           A  0.368410146
9           A           B  0.088462212

你的.dfdata

?group_by说:

 ...: variables to group by. All tbls accept variable names, some
      will also accept functons of variables. Duplicated groups
      will be silently dropped.

我的解释不是指名称的字符版本,而是您将如何在 ; 这里没有引用。或者如何在公式中引用变量:.foo$barbarfoo ~ bar

@Arun还提到你可以做:

df %.%
    group_by("asdfgfTgdsx", "asdfk30v0ja") %.%
    summarise(Value = mean(value))

但是,您不能传入未计算的内容,这些内容不是数据对象中变量的名称。

我认为这是由于 Hadley 用来查找您通过参数传递的内容的内部方法。...

评论

1赞 Gavin Simpson 1/19/2014
@Arun 谢谢你。我没有注意到这一点,但这也很有道理。我在这方面添加了一个注释,引用了你和你的评论。
4赞 sharoz 1/19/2014
不幸的是,我不能依赖对列名进行硬编码。我正在尝试在不指定它们的情况下执行此操作。
57赞 hadley 1/21/2014 #2

dplyr 中对此的支持目前非常薄弱,最终我认为语法将是这样的:

df %.% group_by(.groups = c("asdfgfTgdsx", "asdfk30v0ja"))

但这可能不会在一段时间内出现(因为我需要考虑所有后果)。

同时,您可以使用 ,它需要符号列表:regroup()

library(dplyr)

df <-  data.frame(
  asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

df %.%
  regroup(list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %.%
  summarise(n = n())

如果您有列名的字符向量,则可以使用 和 将它们转换为正确的结构:lapply()as.symbol()

vars <- setdiff(names(df), "value")
vars2 <- lapply(vars, as.symbol)

df %.% regroup(vars2) %.% summarise(n = n())

评论

6赞 sharoz 1/22/2014
as.symbol解决了它。谢谢!如果它有助于开发:这种情况对我来说是一个非常常见的场景。聚合其他变量的每个组合的数值结果。
0赞 Paulo E. Cardoso 4/29/2014
显然,这只适用于这个特定的例子,而不适用于其他例子。
3赞 sharoz 1/28/2015
我最初将其标记为答案,但对 dplyr 的更新允许 kungfujam 的答案起作用。
0赞 Berk U. 6/8/2016
regroup也被弃用(至少从版本 0.4.3 开始)。
17赞 Sebastian Kranz 3/21/2014 #3

在 dplyr 完全支持字符串参数之前,也许这个要点很有用:

https://gist.github.com/skranz/9681509

它包含一堆使用字符串参数的包装函数,如 s_group_by、s_mutate、s_filter 等。您可以将它们与普通的 dplyr 函数混合使用。例如

cols = c("cyl","gear")
mtcars %.%
  s_group_by(cols) %.%  
  s_summarise("avdisp=mean(disp), max(disp)") %.%
  arrange(avdisp)
4赞 Jordan 10/25/2014 #4
data = data.frame(
  my.a = sample(LETTERS[1:3], 100, replace=TRUE),
  my.b = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

group_by(data,newcol=paste(my.a,my.b,sep="_")) %>% summarise(Value=mean(value))
27赞 edward 10/31/2014 #5

现在,通过函数的变体支持列的字符串规范,其名称以下划线结尾。例如,与该函数相对应的是,有一个可以接受字符串参数的函数。这个小插曲详细描述了这些函数的语法。dplyrdplyrgroup_bygroup_by_

以下代码片段干净利落地解决了@sharoz最初提出的问题(请注意,需要写出参数):.dots

# Given data and columns from the OP

data %>%
    group_by_(.dots = columns) %>%
    summarise(Value = mean(value))

(请注意,dplyr 现在使用运算符,并且已弃用)。%>%%.%

105赞 James Owers 1/28/2015 #6

为了完整地编写代码,以下是 Hadley 使用新语法的答案的更新:

library(dplyr)

df <-  data.frame(
    asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# Columns you want to group by
grp_cols <- names(df)[-3]

# Convert character vector to list of symbols
dots <- lapply(grp_cols, as.symbol)

# Perform frequency counts
df %>%
    group_by_(.dots=dots) %>%
    summarise(n = n())

输出:

Source: local data frame [9 x 3]
Groups: asihckhdoydk

  asihckhdoydk a30mvxigxkgh  n
1            A            A 10
2            A            B 10
3            A            C 13
4            B            A 14
5            B            B 10
6            B            C 12
7            C            A  9
8            C            B 12
9            C            C 10

评论

1赞 Gregor Thomas 1/28/2015
这似乎仍然是对列名进行硬编码,只是在公式中。问题的重点是如何使用字符串,这样就不必键入...asihckhdoydk
1赞 James Owers 1/28/2015
更新了用于创建参数的解决方案dots <- lapply(names(df)[-3], function(x) as.symbol(x)).dots
4赞 Andrew 7/9/2015
试图整理这些答案,是关键的一步。如果有人很好地掌握了为什么在通话中需要这样做,你能编辑这个答案吗?现在有点高深莫测。.dots=group_by
13赞 Ari B. Friedman 7/22/2015
vignette("nse")表示有三种可以接受的引用方式:公式、引用和字符。除非你担心它会从哪个环境中拉出来,否则你可能会侥幸逃脱group_by_(.dots=grp_cols)
4赞 tchakravarty 11/7/2016 #7

我想明确说明的是,这里的答案中缺少一个(微小的)情况,即当要分组的变量在管道中游动态生成时:

library(wakefield)
df_foo = r_series(rnorm, 10, 1000)
df_foo %>% 
  # 1. create quantized versions of base variables
  mutate_each(
    funs(Quantized = . > 0)
  ) %>% 
  # 2. group_by the indicator variables
  group_by_(
    .dots = grep("Quantized", names(.), value = TRUE)
    ) %>% 
  # 3. summarize the base variables
  summarize_each(
    funs(sum(., na.rm = TRUE)), contains("X_")
  )

这基本上显示了如何结合使用来实现这一点。grepgroup_by_(.dots = ...)

66赞 Empiromancer 7/7/2017 #8

自从这个问题发布以来,dplyr 添加了 (文档在这里)。这使您可以使用与 相同的函数,如下所示:group_byselect

data = data.frame(
    asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

library(dplyr)
df1 <- data %>%
  group_by_at(vars(one_of(columns))) %>%
  summarize(Value = mean(value))

#compare plyr for reference
df2 <- plyr::ddply(data, columns, plyr::summarize, value=mean(value))
table(df1 == df2, useNA = 'ifany')
## TRUE 
##  27 

示例问题的输出符合预期(请参阅上面与 plyr 的比较和下面的输出):

# A tibble: 9 x 3
# Groups:   asihckhdoydkhxiydfgfTgdsx [?]
  asihckhdoydkhxiydfgfTgdsx a30mvxigxkghc5cdsvxvyv0ja       Value
                     <fctr>                    <fctr>       <dbl>
1                         A                         A  0.04095002
2                         A                         B  0.24943935
3                         A                         C -0.25783892
4                         B                         A  0.15161805
5                         B                         B  0.27189974
6                         B                         C  0.20858897
7                         C                         A  0.19502221
8                         C                         B  0.56837548
9                         C                         C -0.22682998

请注意,由于一次只剥离一层分组,因此在生成的杂乱无章中仍然会进行一些分组(这有时会在以后让人们感到惊讶)。如果希望绝对避免意外的分组行为,则始终可以在汇总后添加到管道中。dplyr::summarize%>% ungroup

评论

0赞 JelenaČuklina 3/19/2018
是否也更新以使引用-取消引用系统具有多个列?0.7.0
4赞 Paul Rougieux 10/19/2018
您还可以将参数用于: ..dotsgroup_by()data %>% group_by(.dots = columns) %>% summarize(value = mean(value))
0赞 knowah 8/15/2019
这里有什么要求吗?我认为在这种情况下它是多余的,因为表达式包含在对 .one_of()vars()
0赞 Empiromancer 8/17/2019
@Khashir是的,这个答案仍然有效@knowah 你是对的,在这种情况下,调用 to 是多余的one_of()
6赞 Empiromancer 7/22/2020
@Sos 要使用语法跨多个列应用函数,请参阅新函数: dplyr.tidyverse.org/reference/across.html 在您的例子中,它看起来像selectacrosssummarize(across(all_of(c(''value_A", "value_B")), mean))
3赞 Paul Rougieux 10/19/2018 #9

使用参数作为函数的字符向量输入的一般示例:.dotsdplyr::group_by

iris %>% 
    group_by(.dots ="Species") %>% 
    summarise(meanpetallength = mean(Petal.Length))

或者没有分组变量的硬编码名称(如 OP 所要求):

iris %>% 
    group_by(.dots = names(iris)[5]) %>% 
    summarise_at("Petal.Length", mean)

以 OP 为例:

data %>% 
    group_by(.dots =names(data)[-3]) %>% 
    summarise_at("value", mean)

另请参阅关于编程的 dplyr 小插曲,其中解释了代词、准引号、quosure 和 tidyeval。

28赞 Agile Bean 2/18/2021 #10

从 dplyr 1.0.0 使用 across() 进行更新

上面的所有答案仍然有效,带有 .dots 参数的解决方案很有说服力。

但是,如果您寻找一种更容易记住的解决方案,那么新的解决方案就会派上用场。它由 Hadley Wickham 于 2020-04-03 发布,可用于和替换作用域变体,如 或 。最重要的是,它非常优雅地用引用/取消引用(例如)取代了繁琐的非标准评估 (NSE)。across()mutate()summarise()_at_all!!! rlang::syms()

因此,解决方案看起来非常可读:across

data %>%
  group_by(across(all_of(columns))) %>%
  summarize(Value = mean(value))

评论

1赞 HBat 9/25/2021
与恕我直言相比,这是一种更直观的方式。.dots
1赞 Paul 11/17/2022
我一直在寻找最新的好(最佳?)做法,这看起来像是