Как написать функцию, которая вызывает функцию, которая вызывает data.table?
В пакете data.table
имеется специальный синтаксис, который требует использования выражений в качестве аргументов i
и j
.
Это имеет некоторые последствия для того, как одни функции записи принимают и передают аргументы таблицам данных, как это хорошо объясняется в разделе 1.16 часто задаваемых вопросов.
Но я не могу понять, как сделать этот дополнительный уровень.
Вот пример. Скажем, я хочу написать функцию-обертку foo()
, которая делает конкретное резюме моих данных, а затем вторую обертку plotfoo()
, которая вызывает foo()
и выводит результат:
library(data.table)
foo <- function(data, by){
by <- substitute(by)
data[, .N, by=list(eval(by))]
}
DT <- data.table(mtcars)
foo(DT, gear)
ОК, это работает, потому что я получаю результаты с табулированием:
by N
1: 4 12
2: 3 15
3: 5 5
Теперь я пытаюсь сделать то же самое при написании plotfoo()
, но я терплю неудачу:
plotfoo <- function(data, by){
by <- substitute(by)
foo(data, eval(by))
}
plotfoo(DT, gear)
Но на этот раз я получаю сообщение об ошибке:
Error: evaluation nested too deeply: infinite recursion / options(expressions=)?
ОК, поэтому проблема eval()
. Удалите его:
plotfoo <- function(data, by){
by <- substitute(by)
foo(data, by)
}
plotfoo(DT, gear)
Нет, я получаю новое сообщение об ошибке:
Error in `[.data.table`(data, , .N, by = list(eval(by))) :
column or expression 1 of 'by' or 'keyby' is type symbol. Do not quote column names. Useage: DT[,sum(colC),by=list(colA,month(colB))]
И здесь я остаюсь застрявшим.
Вопрос: Как написать функцию, вызывающую функцию, которая вызывает data.table?
Ответы
Ответ 1
Это будет работать:
plotfoo <- function(data, by) {
by <- substitute(by)
do.call(foo, list(quote(data), by))
}
plotfoo(DT, gear)
# by N
# 1: 4 12
# 2: 3 15
# 3: 5 5
Объяснение:
Проблема заключается в том, что ваш вызов foo()
в plotfoo()
выглядит следующим образом:
foo(data, eval(by))
foo(data, by)
Когда foo
обрабатывает эти вызовы, он покорно substitute
для второго формального аргумента (by
) получает как by
значение символы eval(by)
или by
. Но вы хотите, чтобы значение by
было gear
, как в случае вызова foo(data, gear)
.
do.call()
решает эту проблему, оценивая элементы своего второго аргумента перед построением вызова, который он затем оценивает. В результате, когда вы передаете его by
, он вычисляет его значение (символ gear
) перед построением вызова, который выглядит (по существу) следующим образом:
foo(data, gear)
Ответ 2
Я думаю, ты можешь завязать себя в узлах. Это работает:
library(data.table)
foo <- function(data, by){
by <- by
data[, .N, by=by]
}
DT <- data.table(mtcars)
foo(DT, 'gear')
plotfoo <- function(data, by){
foo(data, by)
}
plotfoo(DT, 'gear')
И этот метод поддерживает передачу символьных значений:
> gg <- 'gear'
> plotfoo <- function(data, by){
+ foo(data, by)
+ }
> plotfoo(DT, gg)
gear N
1: 4 12
2: 3 15
3: 5 5