提问人:sharoz 提问时间:1/19/2014 最后编辑:NelsonGonsharoz 更新时间:6/23/2021 访问量:121118
在 dplyr 中按多列分组,使用字符串向量输入
Group by multiple columns in dplyr, using string vector input
问:
我正在尝试将我对 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 已更新,因此可以使用更简单的解决方案。查看当前选择的答案。
答:
如果你将对象传递给它(好吧,你不是,但是......),而不是作为字符向量,它就会起作用:
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())
评论
as.symbol
解决了它。谢谢!如果它有助于开发:这种情况对我来说是一个非常常见的场景。聚合其他变量的每个组合的数值结果。
regroup
也被弃用(至少从版本 0.4.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)
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
评论
asihckhdoydk
dots <- lapply(names(df)[-3], function(x) as.symbol(x))
.dots
.dots=
group_by
vignette("nse")
表示有三种可以接受的引用方式:公式、引用和字符。除非你担心它会从哪个环境中拉出来,否则你可能会侥幸逃脱group_by_(.dots=grp_cols)
我想明确说明的是,这里的答案中缺少一个(微小的)情况,即当要分组的变量在管道中游动态生成时:
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 = ...)
自从这个问题发布以来,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
.dots
group_by()
data %>% group_by(.dots = columns) %>% summarize(value = mean(value))
one_of()
vars()
one_of()
select
across
summarize(across(all_of(c(''value_A", "value_B")), mean))
使用参数作为函数的字符向量输入的一般示例:.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 小插曲,其中解释了代词、准引号、quosure 和 tidyeval。
从 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))
评论
.dots
下一个:按名称重命名多个列
评论
group_by_
vignette("nse")
.dots
df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())