171

我试图将我对 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-esque 语法时,我缺少什么?

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

4

10 回答 10

105

为了完整地编写代码,这里是使用新语法对 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
于 2015-01-27T23:58:42.300 回答
61

由于发布了这个问题,dplyr 添加了group_by文档here)的范围版本。这使您可以使用与 一起使用的相同功能select,如下所示:

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只剥离一层分组,因此您仍然会在生成的 tibble 中进行一些分组(有时可能会在以后通过惊喜来吸引人们)。如果您想绝对避免意外的分组行为,您可以%>% ungroup在汇总后随时添加到您的管道中。

于 2017-07-06T16:46:52.930 回答
58

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()and将它们转换为正确的结构as.symbol()

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

df %.% regroup(vars2) %.% summarise(n = n())
于 2014-01-20T20:42:30.640 回答
27

现在通过名称以下划线结尾dplyr的函数的变体支持in 列的字符串规范。dplyr例如,对应于group_by函数,有一个group_by_函数可以接受字符串参数。这个小插图详细描述了这些函数的语法。

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

# Given data and columns from the OP

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

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

于 2014-10-31T04:55:53.383 回答
17

在 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)
于 2014-03-21T08:04:42.697 回答
17

使用 dplyr 1.0.0 中的 cross() 进行更新

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

但是,如果您寻找更容易记住的解决方案,那么新的解决方案across()就派上用场了。它由 Hadley Wickham 于 2020 年 4 月 3 日发布,可用于mutate()summarise()替换范围变体,如_ator _all。最重要的是,它非常优雅地用引用/取消引用(例如!!! rlang::syms().

所以解决方案across看起来非常可读:

data %>%
  group_by(across(all_of(columns))) %>%
  summarize(Value = mean(value))
于 2021-02-18T03:47:01.907 回答
11

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

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

df你在哪里data

?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$barbar这里没有引用。或者您如何在公式中引用变量:foo ~ bar.

@Arun 还提到您可以这样做:

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

但是您不能传入未评估的不是数据对象中变量名称的东西。

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

于 2014-01-18T19:54:39.133 回答
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))
于 2014-10-24T16:53:22.850 回答
4

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

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_")
  )

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

于 2016-11-07T07:40:12.613 回答
3

.dots将参数用作函数的字符向量输入的一般示例dplyr::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 小插图,它解释了代词、准引用、quosures 和 tidyeval。

于 2018-10-19T08:22:42.030 回答