簡體   English   中英

使用字符串向量輸入按 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 已更新,因此可以使用更簡單的解決方案。 查看當前選擇的答案。

為了完整地編寫代碼,這里是使用新語法對 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

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 添加了group_by范圍版本( 文檔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添加到您的管道中。

現在通過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 現在使用%>%運算符,而%.%已棄用)。

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

如果您將對象傳遞給它(好吧,您不是,但是......)而不是作為字符向量,它會起作用:

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 1.0.0 使用 cross() 更新

上面的所有答案仍然有效,帶有 .dots 參數的解決方案很有趣。

但是,如果您尋找更容易記住的解決方案,新的across()會派上用場。 它由 Hadley Wickham 於 2020-04-03 發布,可用於mutate()summarise()並替換_at_all等范圍變體。 最重要的是,它用引用/取消引用非常優雅地替換了繁瑣的非標准評估 (NSE),例如!!! rlang::syms() !!! rlang::syms()

因此,與該解決方案across看上去非常可讀:

data %>%
  group_by(across(all_of(columns))) %>%
  summarize(Value = mean(value))
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))

這里的答案中缺少一個(微小的)案例,我想明確指出,當要分組的變量在管道中動態生成時:

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 = ...)結合使用來實現這一點。

使用.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 小插圖,其中解釋了代詞、准引用、quosures 和 tidyeval。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM