使用 Dplyr 编程:如何制作可在 for 循环中使用的函数?
Programming with Dplyr: How to make a function that can be used in a for loop?
目标
我正在尝试创建一个函数来创建自定义交叉 table (a.k.a.pivot table)。为此,我使用了 rlang
包,试图坚持 "dplyr" 编程方式。另见 http://dplyr.tidyverse.org/articles/programming.html.
函数
library(tidyverse)
crossTable <- function(
df,
rows,
cols,
vals
){
quoRows = enquo(rows)
quoCols = enquo(cols)
quoVals = enquo(vals)
result <-
df %>%
group_by(!!quoRows, !!quoCols) %>%
summarize(
values = !!quoVals
) %>%
ungroup() %>%
spread(!!quoCols, values)
}
有效方法
在全局环境中调用此函数有效。
a <- crossTable(mpg, manufacturer, cyl, mean(hwy))
a
什么不起作用
我希望能够使用此功能为不同的 rows
、cols
和 vals
自动生成不同的交叉 table,例如通过使用 for 循环或 map
函数之一(相当于基础 apply
函数的 tidyverse)。换句话说,我希望能够做这样的事情:
b <- list()
colVars <- c(cyl, class)
expr <- c(mean(hwy), mean(cty), median(hwy), median(cty))
for(i in seq_along(colVars)){
for(j in seq_along(expr)){
b[[i]][[j]] <- crossTable(mpg, manufacturer, colVars[[i]], expr[[j]])
}
}
对于rows
和cols
我看到有些人使用group_by_at
,但这仍然没有解决vals
的问题。
我的问题
- 如何更改此函数,使其在 for 循环中也能工作(或在使用
map
/apply
调用时)?
- 或者,如果使用 dplyr 方式(使用
enquo
和朋友)很难让它工作,那么 "base" 解决这个问题的方法是什么?
一个选项是将 'expr' 转换为 quosures,然后在循环内,使用 !!
评估参数以及将字符向量 ('colVars') 转换为 sym
('symbol')
colVars <- c("cyl", "class")
b <- vector('list', length(colVars))
expr <- quos(mean(hwy), mean(cty), median(hwy), median(cty))
for(i in seq_along(colVars)){
for(j in seq_along(expr)){
b[[i]][[j]] <- crossTable(mpg, manufacturer, !!rlang::sym(colVars[i]), !! expr[[j]])
}
}
-检查输出
identical(crossTable(mpg, manufacturer, cyl, mean(hwy)), b[[1]][[1]])
#[1] TRUE
identical(crossTable(mpg, manufacturer, cyl, mean(cty)), b[[1]][[2]])
#[1] TRUE
identical(crossTable(mpg, manufacturer, cyl, median(hwy)), b[[1]][[3]])
#[1] TRUE
identical(crossTable(mpg, manufacturer, cyl, median(cty)), b[[1]][[4]])
#[1] TRUE
目标
我正在尝试创建一个函数来创建自定义交叉 table (a.k.a.pivot table)。为此,我使用了 rlang
包,试图坚持 "dplyr" 编程方式。另见 http://dplyr.tidyverse.org/articles/programming.html.
函数
library(tidyverse)
crossTable <- function(
df,
rows,
cols,
vals
){
quoRows = enquo(rows)
quoCols = enquo(cols)
quoVals = enquo(vals)
result <-
df %>%
group_by(!!quoRows, !!quoCols) %>%
summarize(
values = !!quoVals
) %>%
ungroup() %>%
spread(!!quoCols, values)
}
有效方法
在全局环境中调用此函数有效。
a <- crossTable(mpg, manufacturer, cyl, mean(hwy))
a
什么不起作用
我希望能够使用此功能为不同的 rows
、cols
和 vals
自动生成不同的交叉 table,例如通过使用 for 循环或 map
函数之一(相当于基础 apply
函数的 tidyverse)。换句话说,我希望能够做这样的事情:
b <- list()
colVars <- c(cyl, class)
expr <- c(mean(hwy), mean(cty), median(hwy), median(cty))
for(i in seq_along(colVars)){
for(j in seq_along(expr)){
b[[i]][[j]] <- crossTable(mpg, manufacturer, colVars[[i]], expr[[j]])
}
}
对于rows
和cols
我看到有些人使用group_by_at
,但这仍然没有解决vals
的问题。
我的问题
- 如何更改此函数,使其在 for 循环中也能工作(或在使用
map
/apply
调用时)? - 或者,如果使用 dplyr 方式(使用
enquo
和朋友)很难让它工作,那么 "base" 解决这个问题的方法是什么?
一个选项是将 'expr' 转换为 quosures,然后在循环内,使用 !!
评估参数以及将字符向量 ('colVars') 转换为 sym
('symbol')
colVars <- c("cyl", "class")
b <- vector('list', length(colVars))
expr <- quos(mean(hwy), mean(cty), median(hwy), median(cty))
for(i in seq_along(colVars)){
for(j in seq_along(expr)){
b[[i]][[j]] <- crossTable(mpg, manufacturer, !!rlang::sym(colVars[i]), !! expr[[j]])
}
}
-检查输出
identical(crossTable(mpg, manufacturer, cyl, mean(hwy)), b[[1]][[1]])
#[1] TRUE
identical(crossTable(mpg, manufacturer, cyl, mean(cty)), b[[1]][[2]])
#[1] TRUE
identical(crossTable(mpg, manufacturer, cyl, median(hwy)), b[[1]][[3]])
#[1] TRUE
identical(crossTable(mpg, manufacturer, cyl, median(cty)), b[[1]][[4]])
#[1] TRUE