[英]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 使用內部方法來查找您通過...
參數傳入的內容。
上面的所有答案仍然有效,帶有 .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_")
)
這基本上展示了如何將grep
與group_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.