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

我正在尝试将我对 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 已经更新,因此有一个更简单的解决方案。

115634 次浏览

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

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$bar中如何引用它们; 这里没有引用 bar。或者如何引用公式中的变量: foo ~ bar

@ Arun 还提到你可以做到:

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

但是您不能传入 未经评估不是数据对象中变量名的内容。

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

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())

在 dplyr 完全支持字符串参数之前,这个 gist 可能是有用的:

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

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 现在使用 %>%操作符,不推荐使用 %.%)。

为了完整地编写代码,下面是 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

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

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 = ...)来实现这一点。

由于发布了这个问题,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添加到管道中。

使用 .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 文件夹

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

以上所有的答案都仍然有效,而带有. dot 参数的解决方案很有趣。

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

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

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