首页 文章

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

提问于
浏览
132

我试图将我对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语法我缺少什么?

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

9 回答

  • 2

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

    自此问题发布以来,dplyr添加了范围版本的 group_bydocumentation 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 一次只剥离一层分组,因此您仍然会在生成的分组中进行一些分组(有时可能会让人感到困惑) . 如果您希望绝对安全地避免意外的分组行为,您可以在汇总后始终将 %>% ungroup 添加到管道中 .

  • 55

    如果你传递对象(好吧,你不是,但是......)而不是作为一个字符向量,它是有效的:

    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用于查找通过 ... 参数传递的内容的内部方法 .

  • 3

    这里的答案中缺少的一个(微小的)案例,我希望明确表示,分组变量是在流水线的中游动态生成的:

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

  • 23

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

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

    # Given data and columns from the OP
    
    data %>%
        group_by_(.dots = columns) %>%
        summarise(Value = mean(value))
    

    (请注意,dplyr现在使用 %>% 运算符,并且不推荐使用 %.% ) .

  • 97

    为了完整地编写代码,这里是关于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

    在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)
    
  • 32
    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))
    
  • 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,它解释了代词,quasiquotation,quosures和tidyeval .

相关问题