Как передать список имен без столбцов в `lapply` (чтобы я мог использовать его с функцией`dplyr`)
Я пытаюсь написать функцию в tidyverse/dplyr
что я хочу в конечном итоге использовать с lapply
(или же map
). (Я работал над этим, чтобы ответить на этот вопрос, но натолкнулся на интересный результат / тупик. Пожалуйста, не отмечайте это как дубликат - этот вопрос является расширением / отступлением от ответов, которые вы видите там.)
Есть
1) способ получить список переменных в кавычках для работы внутри функции dplyr
(и не использовать устаревшие SE_
функции) или есть
2) некоторый способ передать список строк без кавычек через lapply
или жеmap
Я использовалProgramming in Dplyr
Виньетка для построения того, что я считаю, является функцией, наиболее соответствующей текущему стандарту работы с 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 отчетов для генерации, все рушится. (Хотя по общему признанию код, требуемый в этой последней строке функции - чтобы вернуть строку, с помощью которой затем заполняется столбец - должен быть достаточно подсказкой, чтобы я заблудился в неверном направлении.)
#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 сделал здесь /questions/10642712/r-help-funktsiya-dlya-neskolkih-stolbtsov-frejma-dannyih/10642729#10642729 есть ли способ заставить эту функцию работать с 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
3 ответа
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 Альтернативный подход - переписать report
используя пакет wrapr, который является альтернативой 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)
база - по
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
преобразовать предложение в строку, которую затем можно использовать в 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)
Теперь, чтобы ответить на ваш вопрос о том, "как передать список строк без кавычек через lapply
или же map
заставить его работать внутри dplyr
функции ", я предлагаю два способа сделать это.
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. Перепишите свой report
функция размещения lapply
или же map
внутри так, чтобы 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
, который преобразует ...
к списку предложений. Затем их кормят в 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
Я на самом деле не фанат 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)