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


157

我试图将我对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已更新,因此可以使用更简单的解决方案。查看当前选择的答案。


3
刚到这里,因为它是顶级Google。您group_by_现在可以在vignette("nse")
James Owers

3
@kungfujam:似乎仅按第一列分组,而不是按两列
分组

1
您需要使用.dots。以下是根据@hadley的答案改编的解决方案:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
James Owers

1
在下面的答案中输入了完整的代码
James Owers 2015年

1
正如有人在评论的答案中指出的那样,其目的是不需要硬编码的列名。
sharoz15年

Answers:


52

自从发布此问题以来,dplyr添加了group_by文档在此处)的作用域版本。这使您可以使用与相同的功能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只剥离一层分组,因此您仍然会在所产生的小标题中进行一些分组(这有时可能会在后来的行列中引起人们的注意)。如果要绝对避免意外的分组行为,则可以%>% ungroup在总结后始终将其添加到管道中。


是否进行更新以0.7.0使多行报价系统也可用?
JelenaČuklina

4
您也可以这样使用.dots参数group_by()data %>% group_by(.dots = columns) %>% summarize(value = mean(value))
Paul Rougieux

打电话在one_of()这里做什么吗?我认为在这种情况下是多余的,因为表达式包含在对的调用中vars()
Knowah

@Khashir是的,此答案仍然有效@knowah是的,one_of()在这种情况下,对的呼叫是多余的
Empiromancer

1
@Sos要使用select语法跨多个列应用一个函数,请参阅新across函数:dplyr.tidyverse.org/reference/across.html就您而言,它看起来像summarize(across(all_of(c(''value_A", "value_B")), mean))
Empiromancer

102

为了完全编写代码,以下是使用新语法对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
这似乎仍在对列名进行硬编码,只是在公式中。问题的重点是如何使用字符串,而不必键入asihckhdoydk...
Gregor Thomas

1
更新了dots <- lapply(names(df)[-3], function(x) as.symbol(x))用于创建.dots参数的解决方案
James Owers 2015年

4
试图整理这些答案.dots=是至关重要的一步。如果有人很好地理解了为什么要求group_by通话,您可以编辑此答案吗?现在有点难以理解。
安德鲁(Andrew)

12
vignette("nse")表示存在三种可接受的报价方式:公式,报价和字符。除非您担心会从哪个环境中抽离,否则您可能可以逃脱group_by_(.dots=grp_cols)
Ari B. Friedman 2015年

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

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

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

6
as.symbol解决它。谢谢!万一它对开发有帮助:这种情况对我来说是一种非常常见的情况。汇总其他变量的每个组合的数值结果。
sharoz 2014年

显然,这仅适用于该特定示例,而不适用于其他示例。
Paulo E. Cardoso 2014年

3
我最初将其标记为答案,但是对dplyr的更新允许功夫贾姆的答案起作用。
sharoz

regroup也已弃用(至少从0.4.3版开始)。
Berk U.

27

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

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

# Given data and columns from the OP

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

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



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使用内部方法来查找您通过...参数传递的内容的缘故。


1
@Arun谢谢你。我没有注意到,但这也很有意义。我在这方面添加了注释,引用了您和您的评论。
加文·辛普森

4
不幸的是,我不能依靠对列名进行硬编码。我正在尝试这样做,而不必指定它们。
sharoz 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))

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 = ...)以实现此目的。


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小插图,其中介绍了代词,准引号,等价和整齐的语言。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.