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

sha*_*roz 145 r r-faq 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
Run Code Online (Sandbox Code Playgroud)

将plyr示例翻译成dplyr-esque语法我错过了什么?

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

Jam*_*ers 100

为了完整地编写代码,这里是关于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())
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

  • `vignette("nse")`表示有三种方法可以接受:公式,引用和字符.除非你担心它会从哪个环境中获取,否则你可以通过`group_by _(.dots = grp_cols)来逃避它. (12认同)
  • 试图对这些答案进行分类,`.dots =`是至关重要的一步.如果有人能够很好地理解为什么在'group_by`电话中需要它,你能编辑这个答案吗?现在它有点难以理解. (4认同)

had*_*ley 57

在dplyr中对此的支持目前相当薄弱,最终我认为语法将类似于:

df %.% group_by(.groups = c("asdfgfTgdsx", "asdfk30v0ja"))
Run Code Online (Sandbox Code Playgroud)

但那可能不会存在一段时间(因为我需要考虑所有后果).

在此期间,您可以使用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())
Run Code Online (Sandbox Code Playgroud)

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

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

df %.% regroup(vars2) %.% summarise(n = n())
Run Code Online (Sandbox Code Playgroud)

  • `as.symbol`解决了它.谢谢!如果它有助于开发:这种情况对我来说非常普遍.在其他变量的每个组合上汇总数值结果. (6认同)
  • 我最初将此标记为答案,但对dplyr的更新允许kungfujam的工作答案. (3认同)

Emp*_*cer 43

自从发布此问题以来,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 
Run Code Online (Sandbox Code Playgroud)

您的示例问题的输出符合预期(请参阅上面的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
Run Code Online (Sandbox Code Playgroud)

请注意,由于一次dplyr::summarize只剥离一层分组,因此您仍然会在生成的分组中进行一些分组(有时可能会让人感到困惑).如果您希望绝对安全地避免意外分组行为,则可以%>% ungroup在汇总后始终添加到管道中.

  • @Sos要使用“select”语法在多个列上应用函数,请参阅新的“across”函数:https://dplyr.tidyverse.org/reference/across.html 在您的情况下,它看起来像“summarize(跨(all_of(c(''value_A”,“value_B”)),平均值))` (5认同)
  • 您也可以对group_by()使用.dots参数,例如:data%&gt;%group_by(.dots = columns)%&gt;%summary(value = mean(value))。 (3认同)
  • @Empironmancer:这个答案是否仍然有效? (2认同)

edw*_*ard 25

dplyr现在,通过dplyr名称以下划线结尾的函数变体支持列中的字符串规范.例如,对应于该group_by函数,存在group_by_可以采用字符串参数的函数.这个插图详细描述了这些函数的语法.

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

# Given data and columns from the OP

data %>%
    group_by_(.dots = columns) %>%
    summarise(Value = mean(value))
Run Code Online (Sandbox Code Playgroud)

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


小智 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)
Run Code Online (Sandbox Code Playgroud)


Rei*_*son 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
Run Code Online (Sandbox Code Playgroud)

这里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.
Run Code Online (Sandbox Code Playgroud)

我解释的不是名字的字符版本,而是你如何引用它们foo$bar; bar这里没有引用.或者你如何引用公式中的变量:foo ~ bar.

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

df %.%
    group_by("asdfgfTgdsx", "asdfk30v0ja") %.%
    summarise(Value = mean(value))
Run Code Online (Sandbox Code Playgroud)

但是你无法传递一些未评估的东西,它不是数据对象中变量的名称.

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

  • 不幸的是,我不能依赖硬编码列名.我试图这样做而不必指定它们. (4认同)

Agi*_*ean 5

从 dplyr 1.0.0 使用 cross() 更新

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

但是,如果您正在寻找一个更容易记住的解决方案,那么新的across()就派上用场了。它是由哈德利韦翰公布2020年4月3日,可在使用mutate()summarise()并更换范围的变体像_at_all。最重要的是,它非常优雅地用引用/取消引用(例如!!! rlang::syms().

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

data %>%
  group_by(across(all_of(columns))) %>%
  summarize(Value = mean(value))
Run Code Online (Sandbox Code Playgroud)