Как передать все возможное i, j и by во вложенных функциях? - PullRequest
4 голосов
/ 20 февраля 2020

Я работаю над пакетом, который использует data.table внутри. В этом пакете у меня есть функция count_by, которая вычисляет количество различных идентификаторов для определенной c переменной в data.table по группам. С некоторой помощью ( R data.table: как передать «все возможное» в функцию? ) Я получил это для работы, как и ожидалось:

library(data.table)
#> Warning: package 'data.table' was built under R version 3.6.2

# create example data
sample_dt <- data.table(
    id = sort(rep(LETTERS[1:3], 3)),
    year = rep(2018L:2020L),
    x = runif(9)
)
sample_dt[id == "B" & year < 2020, x := NA_real_]

# define inner function
count_by <- function(DT, id_var, val_var, by = NULL) {
    id_var <- as.character(substitute(id_var))
    val_var <- as.character(substitute(val_var))

    eval(substitute(
        DT[!is.na(get(val_var)), .(distinct_ids = uniqueN(get(id_var))), by = by]
    ))
}

# test inner function -> works as expected
(reference <- count_by(sample_dt, id_var = id, val_var = x, by = year))
#>    year distinct_ids
#> 1: 2018            2
#> 2: 2019            2
#> 3: 2020            3

identical(count_by(sample_dt, "id", x, year)       , reference)
#> [1] TRUE
identical(count_by(sample_dt, "id", "x", year)     , reference)
#> [1] TRUE
identical(count_by(sample_dt, "id", x, "year")     , reference)
#> [1] TRUE
identical(count_by(sample_dt, "id", x, c("year"))  , reference)
#> [1] TRUE
identical(count_by(sample_dt, "id", "x", "year")   , reference)
#> [1] TRUE
identical(count_by(sample_dt, "id", "x", c("year")), reference)
#> [1] TRUE
identical(count_by(sample_dt, id, "x", year)       , reference)
#> [1] TRUE
identical(count_by(sample_dt, id, "x", "year")     , reference)
#> [1] TRUE
identical(count_by(sample_dt, id, "x", c("year"))  , reference)
#> [1] TRUE
identical(count_by(sample_dt, id, x, "year")       , reference)
#> [1] TRUE
identical(count_by(sample_dt, id, x, c("year"))    , reference)
#> [1] TRUE

Создано в 2020-02-20 пакетом Представить (v0.3.0)

Теперь я хотел бы использовать функцию count_by() в другой функции (минимальный пример ниже):

# define wrapper function
wrapper <- function(data, id_var, val_var, by = NULL) {
    data <- as.data.table(data)
    count_by(data, id_var, val_var, by)
}

# test wrapper function
wrapper(sample_dt, id_var = id, val_var = x, by = year)
#> Error in .(distinct_ids = uniqueN(get("id_var"))): could not find function "."

Создано в 2020-02-20 пакетом Представить (v0.3.0)

Отладка count_by() привести к Обратите внимание, что если count_by() вызывается из wrapper(), substitute(DT[...]) также заменяет DT на data:

Browse[2]> substitute(
+         DT[!is.na(get(val_var)), .(distinct_ids = uniqueN(get(id_var))), by = by]
+     )
data[!is.na(get("val_var")), .(distinct_ids = uniqueN(get("id_var"))), 
    by = by]

Поскольку data недоступно в функциональной среде count_by() оно оценивается как utils::data, что затем приводит к ошибке. Это проясняет проблему, но я не могу придумать решение.

Мне нужно подставить целое выражение DT[...], чтобы by работал правильно (см. R data.table: Как передать «все возможное» в функцию? или передача переменных и имен в функцию data.table ). Но я не могу заменить все выражение так, чтобы DT не заменялось.

Каково решение этой дилеммы?

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

@ chinsoon12, большое спасибо! Вы отвечаете, почти сделал это! Мне все еще нужно преобразовать id_var и val_var в символ, а затем get() в вызове data.table - в противном случае передача строк в id_var и val_var не работает.

Но оценка на более высоком уровне является необходимой ключевой идеей. Вот полный ответ для использования в будущем:

# define inner function
count_by <- function(DT, id_var, val_var, by = NULL) {
    id_var <- as.character(substitute(id_var))
    val_var <- as.character(substitute(val_var))

    substitute(
        DT[!is.na(get(val_var)), .(distinct_ids = uniqueN(get(id_var))), by = by]
    )
}

# define wrapper function
wrapper <- function(data, id_var, val_var, by = NULL) {
    data <- as.data.table(data)
    expr <- eval(substitute(count_by(data, id_var, val_var, by)))
    eval(expr)
}

# test wrapper function
(reference <- (wrapper(sample_dt, id_var = id, val_var = x, by = year)))
#>    year distinct_ids
#> 1: 2018            2
#> 2: 2019            2
#> 3: 2020            3

identical(wrapper(sample_dt, "id", x, year)       , reference)
#> [1] TRUE
identical(wrapper(sample_dt, "id", "x", year)     , reference)
#> [1] TRUE
identical(wrapper(sample_dt, "id", x, "year")     , reference)
#> [1] TRUE
identical(wrapper(sample_dt, "id", x, c("year"))  , reference)
#> [1] TRUE
identical(wrapper(sample_dt, "id", "x", "year")   , reference)
#> [1] TRUE
identical(wrapper(sample_dt, "id", "x", c("year")), reference)
#> [1] TRUE
identical(wrapper(sample_dt, id, "x", year)       , reference)
#> [1] TRUE
identical(wrapper(sample_dt, id, "x", "year")     , reference)
#> [1] TRUE
identical(wrapper(sample_dt, id, "x", c("year"))  , reference)
#> [1] TRUE
identical(wrapper(sample_dt, id, x, "year")       , reference)
#> [1] TRUE
identical(wrapper(sample_dt, id, x, c("year"))    , reference)
#> [1] TRUE

# test expression in by
wrapper(sample_dt, "id", x, by = .(year_2019 = year > 2019L))
#>    year_2019 distinct_ids
#> 1:     FALSE            2
#> 2:      TRUE            3

Создано в 2020-02-20 пакетом Представить (v0.3.0)

1 голос
/ 20 февраля 2020

Получение NSE из картинки работает для этого конкретного примера и сильно упрощает. Но тогда вы должны передать аргументы в виде строки:

count_by <- function(DT, id_var, val_var, by = NULL) {
    DT[!is.na(get(val_var)), .(distinct_ids = uniqueN(get(id_var))), by = by]
}

wrapper <- function(data, id_var, val_var, by = NULL) {
    count_by(data, id_var, val_var, by)
}

wrapper(sample_dt, id_var = "id", val_var = "x", by = "year")

#    year distinct_ids
# 1: 2018            2
# 2: 2019            2
# 3: 2020            3
...