【问题标题】:Group by multiple columns in dplyr, using string vector input使用字符串向量输入在 dplyr 中按多列分组
【发布时间】:2014-02-08 03:14:53
【问题描述】:

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

【问题讨论】:

  • 刚到这里,因为它是顶级谷歌。您可以使用group_by_ 现在在vignette("nse") 中解释
  • @kungfujam:这似乎只按第一列分组,而不是按列对
  • 您需要使用.dots。这是改编自@hadley 下面的答案的解决方案:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
  • 已将完整代码放在下面的答案中
  • 正如有人在评论的回答中指出的那样,目的是不需要硬编码的列名。

标签: r dplyr r-faq


【解决方案1】:

为了完整地编写代码,这里是哈德利用新语法回答的更新:

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 &lt;- lapply(names(df)[-3], function(x) as.symbol(x)) 更新解决方案以创建.dots 参数
  • 试图整理这些答案,.dots= 是关键的一步。如果有人对group_by 通话中为什么需要这样做有很好的理解,你能编辑这个答案吗?现在有点不可思议。
  • vignette("nse") 表示可以接受三种引用方式:公式、引用和字符。除非您担心它将从哪个环境中提取,否则您可能会选择 group_by_(.dots=grp_cols)
【解决方案2】:

自从发布此问题以来,dplyr 添加了group_by (documentation 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 中进行了一些分组(有时可能会在稍后通过惊喜来吸引人们)。如果您想绝对避免意外的分组行为,您可以随时在汇总后将%&gt;% ungroup 添加到您的管道中。

【讨论】:

  • 更新到0.7.0 是否也使quote-unquote 系统在多列中可用?
  • 您也可以将.dots 参数用于group_by(),例如:data %&gt;% group_by(.dots = columns) %&gt;% summarize(value = mean(value))
  • one_of() 的调用在这里有什么作用吗?我认为在这种情况下这是多余的,因为表达式包含在对 vars() 的调用中。
  • @Khashir 是的,这个答案仍然有效@knowah 你是对的,在这种情况下调用one_of() 是多余的
  • @Sos 要使用 select 语法跨多个列应用函数,请参阅新的 across 函数:dplyr.tidyverse.org/reference/across.html 在您的情况下,它看起来像 summarize(across(all_of(c(''value_A", "value_B")), mean))
【解决方案3】:

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 解决了它。谢谢!如果它有助于开发:这种情况对我来说非常常见。汇总其他变量的每个组合的数值结果。
  • 显然这只适用于这个特定的例子而不是其他的。
  • 我最初将此标记为答案,但对 dplyr 的更新允许 kungfujam 的答案起作用。
  • regroup 也已弃用(至少从 0.4.3 版开始)。
【解决方案4】:

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

下面的 sn-p 干净利落地解决了@sharoz 最初提出的问题(注意需要写出.dots 参数):

# Given data and columns from the OP

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

(请注意,dplyr 现在使用 %&gt;% 运算符,而 %.% 已弃用)。

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

      但是你不能传入 unevalated 不是数据对象中变量名称的东西。

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

      【讨论】:

      • @Arun 谢谢你。我没有注意到这一点,但这也很有意义。我在这方面添加了一条注释,引用了您和您的评论。
      • 不幸的是,我不能依赖对列名进行硬编码。我正在尝试这样做,而不必指定它们。
      【解决方案7】:

      使用 dplyr 1.0.0 中的 cross() 更新

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

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

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

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

      【讨论】:

      • .dots恕我直言相比,这是一种更直观的方式。
      【解决方案8】:
      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))
      

      【讨论】:

        【解决方案9】:

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

        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 = ...) 结合使用来实现此目的。

        【讨论】:

          【解决方案10】:

          使用.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 vignette on programming,它解释了代词、准引用、quosures 和 tidyeval。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-07-06
            • 2017-11-28
            • 2016-04-01
            • 2015-09-24
            • 2020-03-31
            • 1970-01-01
            相关资源
            最近更新 更多