如何将未加引号的列名列表输入“lapply”(以便我可以将其与“dplyr”函数一起使用)
How to feed a list of unquoted column names into `lapply` (so that I can use it with a `dplyr` function)
我正在尝试在 tidyverse/dplyr
中编写一个函数,我希望最终将其与 lapply
(或 map
)一起使用。 (我一直在研究 ,但发现了一个有趣的 result/dead-end。请不要将其标记为重复 - 这个问题是 extension/departure 来自您所看到的答案那里。)
有吗
1) 一种获取引用变量列表以在 dplyr 函数中工作的方法
(并且不使用已弃用的 SE_
函数) 或者是否存在
2) 通过 lapply
或 map
提供未加引号的字符串列表的某种方式
我使用了Programming in Dplyr
vignette 构建了我认为最符合当前标准的功能
与 NSE 合作。
样本数据:
sample_data <-
read.table(text = "REVENUEID AMOUNT YEAR REPORT_CODE PAYMENT_METHOD INBOUND_CHANNEL AMOUNT_CAT
1 rev-24985629 30 FY18 S Check Mail 25,50
2 rev-22812413 1 FY16 Q Other Canvassing 0.01,10
3 rev-23508794 100 FY17 Q Credit_card Web 100,250
4 rev-23506121 300 FY17 S Credit_card Mail 250,500
5 rev-23550444 100 FY17 S Credit_card Web 100,250
6 rev-21508672 25 FY14 J Check Mail 25,50
7 rev-24981769 500 FY18 S Credit_card Web 500,1e+03
8 rev-23503684 50 FY17 R Check Mail 50,75
9 rev-24982087 25 FY18 R Check Mail 25,50
10 rev-24979834 50 FY18 R Credit_card Web 50,75
", header = TRUE, stringsAsFactors = FALSE)
报告生成函数
report <- function(report_cat){
report_cat <- enquo(report_cat)
sample_data %>%
group_by(!!report_cat, YEAR) %>%
summarize(num=n(),total=sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!report_cat) %>%
mutate(REPORT_CATEGORY := as.character(quote(!!report_cat))[2])
}
生成单个报告的效果很好:
> report(REPORT_CODE)
# A tibble: 7 x 5
# Groups: REPORT_VALUE [4]
REPORT_VALUE YEAR num total REPORT_CATEGORY
<chr> <chr> <int> <int> <chr>
1 J FY14 1 25 REPORT_CODE
2 Q FY16 1 1 REPORT_CODE
3 Q FY17 1 100 REPORT_CODE
4 R FY17 1 50 REPORT_CODE
5 R FY18 2 75 REPORT_CODE
6 S FY17 2 400 REPORT_CODE
7 S FY18 2 530 REPORT_CODE
当我尝试设置要生成的所有 4 份报告的列表时,一切都崩溃了。 (虽然不可否认,函数最后一行所需的代码 - return 一个字符串,然后用它填充列 - 应该足够线索,我走错了方向。)
#the other reports
cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")
# Applying and Mapping attempts
lapply(cat.list, report)
map_df(cat.list, report)
这导致:
> lapply(cat.list, report)
Error in (function (x, strict = TRUE) :
the argument has already been evaluated
> map_df(cat.list, report)
Error in (function (x, strict = TRUE) :
the argument has already been evaluated
我还尝试将字符串列表转换为名称,然后再交给 apply
和 map
:
library(rlang)
cat.names <- lapply(cat.list, sym)
lapply(cat.names, report)
map_df(cat.names, report)
> lapply(cat.names, report)
Error in (function (x, strict = TRUE) :
the argument has already been evaluated
> map_df(cat.names, report)
Error in (function (x, strict = TRUE) :
the argument has already been evaluated
无论如何,我问这个问题的原因是我认为我已经按照当前记录的标准编写了函数,但最终我看不出有什么办法可以利用 apply
甚至是具有这种功能的 purrr::map
家族。除了重写函数以使用 names
就像 useR 在这里完成 有没有办法让这个函数与 apply
或 map
?
我希望看到这样的结果:
# A tibble: 27 x 5
# Groups: REPORT_VALUE [16]
REPORT_VALUE YEAR num total REPORT_CATEGORY
<chr> <chr> <int> <int> <chr>
1 J FY14 1 25 REPORT_CODE
2 Q FY16 1 1 REPORT_CODE
3 Q FY17 1 100 REPORT_CODE
4 R FY17 1 50 REPORT_CODE
5 R FY18 2 75 REPORT_CODE
6 S FY17 2 400 REPORT_CODE
7 S FY18 2 530 REPORT_CODE
8 Check FY14 1 25 PAYMENT_METHOD
9 Check FY17 1 50 PAYMENT_METHOD
10 Check FY18 2 55 PAYMENT_METHOD
# ... with 17 more rows
我不是真正的 dplyr 爱好者,但这里的价值在于如何使用 library(data.table)
代替:
setDT(sample_data)
gen_report <- function(report_cat){
sample_data[ , .(num = .N, total = sum(AMOUNT), REPORT_CATEGORY = report_cat),
by = .(REPORT_VALUE = get(report_cat), YEAR)]
}
gen_report('REPORT_CODE')
lapply(cat.list, gen_report)
as.name
会将字符串转换为名称,然后可以传递给 report
:
lapply(cat.list, function(x) do.call("report", list(as.name(x))))
字符参数 另一种方法是重写 report
以便它接受字符串参数:
report_ch <- function(colname) {
report_cat <- rlang::sym(colname) # as.name(colname) would also work here
sample_data %>%
group_by(!!report_cat, YEAR) %>%
summarize(num = n(), total = sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!report_cat) %>%
mutate(REPORT_CATEGORY = colname)
}
lapply(cat.list, report_ch)
wrapr 另一种方法是使用 wrapr 包重写 report
,它是 rlang/tidyeval:
的替代方法
library(dplyr)
library(wrapr)
report_wrapr <- function(colname)
let(c(COLNAME = colname),
sample_data %>%
group_by(COLNAME, YEAR) %>%
summarize(num = n(), total = sum(AMOUNT)) %>%
rename(REPORT_VALUE = COLNAME) %>%
mutate(REPORT_CATEGORY = colname)
)
lapply(cat.list, report_wrapr)
当然,如果您使用不同的框架,这整个问题就会消失,例如
plyr
library(plyr)
report_plyr <- function(colname)
ddply(sample_data, c(REPORT_VALUE = colname, "YEAR"), function(x)
data.frame(num = nrow(x), total = sum(x$AMOUNT), REPORT_CATEOGRY = colname))
lapply(cat.list, report_plyr)
sqldf
library(sqldf)
report_sql <- function(colname, envir = parent.frame(), ...)
fn$sqldf("select [$colname] REPORT_VALUE,
YEAR,
count(*) num,
sum(AMOUNT) total,
'$colname' REPORT_CATEGORY
from sample_data
group by [$colname], YEAR", envir = envir, ...)
lapply(cat.list, report_sql)
base - by
report_base_by <- function(colname)
do.call("rbind",
by(sample_data, sample_data[c(colname, "YEAR")], function(x)
data.frame(REPORT_VALUE = x[1, colname],
YEAR = x$YEAR[1],
num = nrow(x),
total = sum(x$AMOUNT),
REPORT_CATEGORY = colname)
)
)
lapply(cat.list, report_base_by)
data.table data.table 包提供了另一种选择,但另一个答案已经涵盖了。
更新: 添加了其他选项。
首先要指出的是,在您最初的 report
函数中,您可以使用 quo_name
将 quosure 转换为字符串,然后您可以在 mutate
中使用它,例如以下:
library(dplyr)
library(rlang)
report <- function(report_cat){
report_cat <- enquo(report_cat)
sample_data %>%
group_by(!!report_cat, YEAR) %>%
summarize(num=n(),total=sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!report_cat) %>%
mutate(REPORT_CATEGORY = quo_name(report_cat))
}
report(REPORT_CODE)
现在,为了解决您 "how to feed a list of unquoted strings through lapply
or map
to make it work inside dplyr
functions" 的问题,我提出了两种解决方法。
1。使用 rlang::sym
解析您的字符串并在输入 lapply
或 map
时取消引用它
library(purrr)
cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")
map_df(cat.list, ~report(!!sym(.)))
或者使用 syms
你可以一次解析一个向量的所有元素:
map_df(syms(cat.list), ~report(!!.))
结果:
# A tibble: 27 x 5
# Groups: REPORT_VALUE [16]
REPORT_VALUE YEAR num total REPORT_CATEGORY
<chr> <chr> <int> <int> <chr>
1 J FY14 1 25 REPORT_CODE
2 Q FY16 1 1 REPORT_CODE
3 Q FY17 1 100 REPORT_CODE
4 R FY17 1 50 REPORT_CODE
5 R FY18 2 75 REPORT_CODE
6 S FY17 2 400 REPORT_CODE
7 S FY18 2 530 REPORT_CODE
8 Check FY14 1 25 PAYMENT_METHOD
9 Check FY17 1 50 PAYMENT_METHOD
10 Check FY18 2 55 PAYMENT_METHOD
# ... with 17 more rows
2。通过将 lapply
或 map
放在 中重写您的 report
函数,以便 report
可以执行 NSE
report <- function(...){
report_cat <- quos(...)
map_df(report_cat, function(x) sample_data %>%
group_by(!!x, YEAR) %>%
summarize(num=n(),total=sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!x) %>%
mutate(REPORT_CATEGORY = quo_name(x)))
}
通过将 map_df
放在 report
中,您可以利用 quos
,它将 ...
转换为 quosures 列表。然后将它们送入 map_df
并使用 !!
一个一个取消引用。
report(REPORT_CODE, PAYMENT_METHOD, INBOUND_CHANNEL, AMOUNT_CAT)
这样写的另一个优点是您还可以提供一个字符串符号向量并使用 !!!
拼接它们,如下所示:
report(!!!syms(cat.list))
结果:
# A tibble: 27 x 5
# Groups: REPORT_VALUE [16]
REPORT_VALUE YEAR num total REPORT_CATEGORY
<chr> <chr> <int> <int> <chr>
1 J FY14 1 25 REPORT_CODE
2 Q FY16 1 1 REPORT_CODE
3 Q FY17 1 100 REPORT_CODE
4 R FY17 1 50 REPORT_CODE
5 R FY18 2 75 REPORT_CODE
6 S FY17 2 400 REPORT_CODE
7 S FY18 2 530 REPORT_CODE
8 Check FY14 1 25 PAYMENT_METHOD
9 Check FY17 1 50 PAYMENT_METHOD
10 Check FY18 2 55 PAYMENT_METHOD
# ... with 17 more rows
我正在尝试在 tidyverse/dplyr
中编写一个函数,我希望最终将其与 lapply
(或 map
)一起使用。 (我一直在研究
有吗
1) 一种获取引用变量列表以在 dplyr 函数中工作的方法
(并且不使用已弃用的 SE_
函数) 或者是否存在
2) 通过 lapply
或 map
我使用了Programming in Dplyr
vignette 构建了我认为最符合当前标准的功能
与 NSE 合作。
样本数据:
sample_data <-
read.table(text = "REVENUEID AMOUNT YEAR REPORT_CODE PAYMENT_METHOD INBOUND_CHANNEL AMOUNT_CAT
1 rev-24985629 30 FY18 S Check Mail 25,50
2 rev-22812413 1 FY16 Q Other Canvassing 0.01,10
3 rev-23508794 100 FY17 Q Credit_card Web 100,250
4 rev-23506121 300 FY17 S Credit_card Mail 250,500
5 rev-23550444 100 FY17 S Credit_card Web 100,250
6 rev-21508672 25 FY14 J Check Mail 25,50
7 rev-24981769 500 FY18 S Credit_card Web 500,1e+03
8 rev-23503684 50 FY17 R Check Mail 50,75
9 rev-24982087 25 FY18 R Check Mail 25,50
10 rev-24979834 50 FY18 R Credit_card Web 50,75
", header = TRUE, stringsAsFactors = FALSE)
报告生成函数
report <- function(report_cat){
report_cat <- enquo(report_cat)
sample_data %>%
group_by(!!report_cat, YEAR) %>%
summarize(num=n(),total=sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!report_cat) %>%
mutate(REPORT_CATEGORY := as.character(quote(!!report_cat))[2])
}
生成单个报告的效果很好:
> report(REPORT_CODE) # A tibble: 7 x 5 # Groups: REPORT_VALUE [4] REPORT_VALUE YEAR num total REPORT_CATEGORY <chr> <chr> <int> <int> <chr> 1 J FY14 1 25 REPORT_CODE 2 Q FY16 1 1 REPORT_CODE 3 Q FY17 1 100 REPORT_CODE 4 R FY17 1 50 REPORT_CODE 5 R FY18 2 75 REPORT_CODE 6 S FY17 2 400 REPORT_CODE 7 S FY18 2 530 REPORT_CODE
当我尝试设置要生成的所有 4 份报告的列表时,一切都崩溃了。 (虽然不可否认,函数最后一行所需的代码 - return 一个字符串,然后用它填充列 - 应该足够线索,我走错了方向。)
#the other reports
cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")
# Applying and Mapping attempts
lapply(cat.list, report)
map_df(cat.list, report)
这导致:
> lapply(cat.list, report) Error in (function (x, strict = TRUE) : the argument has already been evaluated > map_df(cat.list, report) Error in (function (x, strict = TRUE) : the argument has already been evaluated
我还尝试将字符串列表转换为名称,然后再交给 apply
和 map
:
library(rlang)
cat.names <- lapply(cat.list, sym)
lapply(cat.names, report)
map_df(cat.names, report)
> lapply(cat.names, report) Error in (function (x, strict = TRUE) : the argument has already been evaluated > map_df(cat.names, report) Error in (function (x, strict = TRUE) : the argument has already been evaluated
无论如何,我问这个问题的原因是我认为我已经按照当前记录的标准编写了函数,但最终我看不出有什么办法可以利用 apply
甚至是具有这种功能的 purrr::map
家族。除了重写函数以使用 names
就像 useR 在这里完成 apply
或 map
?
我希望看到这样的结果:
# A tibble: 27 x 5 # Groups: REPORT_VALUE [16] REPORT_VALUE YEAR num total REPORT_CATEGORY <chr> <chr> <int> <int> <chr> 1 J FY14 1 25 REPORT_CODE 2 Q FY16 1 1 REPORT_CODE 3 Q FY17 1 100 REPORT_CODE 4 R FY17 1 50 REPORT_CODE 5 R FY18 2 75 REPORT_CODE 6 S FY17 2 400 REPORT_CODE 7 S FY18 2 530 REPORT_CODE 8 Check FY14 1 25 PAYMENT_METHOD 9 Check FY17 1 50 PAYMENT_METHOD 10 Check FY18 2 55 PAYMENT_METHOD # ... with 17 more rows
我不是真正的 dplyr 爱好者,但这里的价值在于如何使用 library(data.table)
代替:
setDT(sample_data)
gen_report <- function(report_cat){
sample_data[ , .(num = .N, total = sum(AMOUNT), REPORT_CATEGORY = report_cat),
by = .(REPORT_VALUE = get(report_cat), YEAR)]
}
gen_report('REPORT_CODE')
lapply(cat.list, gen_report)
as.name
会将字符串转换为名称,然后可以传递给 report
:
lapply(cat.list, function(x) do.call("report", list(as.name(x))))
字符参数 另一种方法是重写 report
以便它接受字符串参数:
report_ch <- function(colname) {
report_cat <- rlang::sym(colname) # as.name(colname) would also work here
sample_data %>%
group_by(!!report_cat, YEAR) %>%
summarize(num = n(), total = sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!report_cat) %>%
mutate(REPORT_CATEGORY = colname)
}
lapply(cat.list, report_ch)
wrapr 另一种方法是使用 wrapr 包重写 report
,它是 rlang/tidyeval:
library(dplyr)
library(wrapr)
report_wrapr <- function(colname)
let(c(COLNAME = colname),
sample_data %>%
group_by(COLNAME, YEAR) %>%
summarize(num = n(), total = sum(AMOUNT)) %>%
rename(REPORT_VALUE = COLNAME) %>%
mutate(REPORT_CATEGORY = colname)
)
lapply(cat.list, report_wrapr)
当然,如果您使用不同的框架,这整个问题就会消失,例如
plyr
library(plyr)
report_plyr <- function(colname)
ddply(sample_data, c(REPORT_VALUE = colname, "YEAR"), function(x)
data.frame(num = nrow(x), total = sum(x$AMOUNT), REPORT_CATEOGRY = colname))
lapply(cat.list, report_plyr)
sqldf
library(sqldf)
report_sql <- function(colname, envir = parent.frame(), ...)
fn$sqldf("select [$colname] REPORT_VALUE,
YEAR,
count(*) num,
sum(AMOUNT) total,
'$colname' REPORT_CATEGORY
from sample_data
group by [$colname], YEAR", envir = envir, ...)
lapply(cat.list, report_sql)
base - by
report_base_by <- function(colname)
do.call("rbind",
by(sample_data, sample_data[c(colname, "YEAR")], function(x)
data.frame(REPORT_VALUE = x[1, colname],
YEAR = x$YEAR[1],
num = nrow(x),
total = sum(x$AMOUNT),
REPORT_CATEGORY = colname)
)
)
lapply(cat.list, report_base_by)
data.table data.table 包提供了另一种选择,但另一个答案已经涵盖了。
更新: 添加了其他选项。
首先要指出的是,在您最初的 report
函数中,您可以使用 quo_name
将 quosure 转换为字符串,然后您可以在 mutate
中使用它,例如以下:
library(dplyr)
library(rlang)
report <- function(report_cat){
report_cat <- enquo(report_cat)
sample_data %>%
group_by(!!report_cat, YEAR) %>%
summarize(num=n(),total=sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!report_cat) %>%
mutate(REPORT_CATEGORY = quo_name(report_cat))
}
report(REPORT_CODE)
现在,为了解决您 "how to feed a list of unquoted strings through lapply
or map
to make it work inside dplyr
functions" 的问题,我提出了两种解决方法。
1。使用 rlang::sym
解析您的字符串并在输入 lapply
或 map
时取消引用它
library(purrr)
cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")
map_df(cat.list, ~report(!!sym(.)))
或者使用 syms
你可以一次解析一个向量的所有元素:
map_df(syms(cat.list), ~report(!!.))
结果:
# A tibble: 27 x 5
# Groups: REPORT_VALUE [16]
REPORT_VALUE YEAR num total REPORT_CATEGORY
<chr> <chr> <int> <int> <chr>
1 J FY14 1 25 REPORT_CODE
2 Q FY16 1 1 REPORT_CODE
3 Q FY17 1 100 REPORT_CODE
4 R FY17 1 50 REPORT_CODE
5 R FY18 2 75 REPORT_CODE
6 S FY17 2 400 REPORT_CODE
7 S FY18 2 530 REPORT_CODE
8 Check FY14 1 25 PAYMENT_METHOD
9 Check FY17 1 50 PAYMENT_METHOD
10 Check FY18 2 55 PAYMENT_METHOD
# ... with 17 more rows
2。通过将 lapply
或 map
放在 中重写您的 report
函数,以便 report
可以执行 NSE
report <- function(...){
report_cat <- quos(...)
map_df(report_cat, function(x) sample_data %>%
group_by(!!x, YEAR) %>%
summarize(num=n(),total=sum(AMOUNT)) %>%
rename(REPORT_VALUE = !!x) %>%
mutate(REPORT_CATEGORY = quo_name(x)))
}
通过将 map_df
放在 report
中,您可以利用 quos
,它将 ...
转换为 quosures 列表。然后将它们送入 map_df
并使用 !!
一个一个取消引用。
report(REPORT_CODE, PAYMENT_METHOD, INBOUND_CHANNEL, AMOUNT_CAT)
这样写的另一个优点是您还可以提供一个字符串符号向量并使用 !!!
拼接它们,如下所示:
report(!!!syms(cat.list))
结果:
# A tibble: 27 x 5
# Groups: REPORT_VALUE [16]
REPORT_VALUE YEAR num total REPORT_CATEGORY
<chr> <chr> <int> <int> <chr>
1 J FY14 1 25 REPORT_CODE
2 Q FY16 1 1 REPORT_CODE
3 Q FY17 1 100 REPORT_CODE
4 R FY17 1 50 REPORT_CODE
5 R FY18 2 75 REPORT_CODE
6 S FY17 2 400 REPORT_CODE
7 S FY18 2 530 REPORT_CODE
8 Check FY14 1 25 PAYMENT_METHOD
9 Check FY17 1 50 PAYMENT_METHOD
10 Check FY18 2 55 PAYMENT_METHOD
# ... with 17 more rows