Как передать «все возможное» в функцию? - PullRequest
4 голосов
/ 18 февраля 2020

Я пытаюсь использовать data.table в пользовательской функции в пакете, над которым я работаю. Я хотел бы, чтобы эта функция работала как data.table, насколько это возможно. Это означает, например, что моя функция также имеет аргумент by, который передается в базовый вызов data.table внутри функции. Пользователь должен свободно передавать что-либо в «my» by, что возможно непосредственно в data.table.

Ссылаясь на ?data.table, это включает в себя:

  1. Имя столбца без кавычек: например, DT [,. (Sa = sum (a)), by = x ]
  2. список () выражений имен столбцов: например, DT [,. (Sa = sum (a)), by =. (X = x> 0, y)]
  3. одиночная строка символов, содержащая имена столбцов, разделенных запятыми (где пробелы являются значительными, поскольку имена столбцов могут содержать пробелы даже в начале или в конце): например, DT [, sum (a), by = "x, y, z"]
  4. символьный вектор имен столбцов: например, DT [, sum (a), by = c ("x", "y")]
  5. или формы startcol: endcol : например, DT [, sum (a), by = x: z]

Вот минимальный (частично) рабочий пример, чтобы прояснить мои намерения:

library(data.table)
#> Warning: package 'data.table' was built under R version 3.6.2
sample_dt <- data.table(a = 1:5, b = 5:1)

count_by <- function(dt, by = NULL) {
    by <- substitute(by)
    dt[, .N, by = eval(by, dt, parent.frame())]
}

count_by(sample_dt)               
#>    N
#> 1: 5
count_by(sample_dt, by = a)       # refers to 1 from the list above
#>    by N
#> 1:  1 1
#> 2:  2 1
#> 3:  3 1
#> 4:  4 1
#> 5:  5 1
count_by(sample_dt, by = list(a)) # refers to 2 from the list above
#>    a N
#> 1: 1 1
#> 2: 2 1
#> 3: 3 1
#> 4: 4 1
#> 5: 5 1
count_by(sample_dt, by = "a")     # refers to 3 from the list above
#>    a N
#> 1: 1 1
#> 2: 2 1
#> 3: 3 1
#> 4: 4 1
#> 5: 5 1
count_by(sample_dt, by = c("a"))  # refers to 4 from the list above
#> Error in `[.data.table`(dt, , .N, by = eval(by, dt, parent.frame())): 'by' appears to evaluate to column names but isn't c() or key(). Use by=list(...) if you can. Otherwise, by=evalc("a") should work. This is for efficiency so data.table can detect which columns are needed.
count_by(sample_dt, by = a:b)     # refers to 5 from the list above
#>    a b N
#> 1: 1 5 1
#> 2: 2 4 1
#> 3: 3 3 1
#> 4: 4 2 1
#> 5: 5 1 1

Создано в 2020-02-18 с помощью пакета prex (v0.3.0)

За исключением случая 4, все работает, как и ожидалось, с помощью простой замены и оценка в правильном контексте. Итак, мой вопрос:

Как я могу создавать функции, которые используют data.table внутренне и mimi c оригинальный by пользовательский интерфейс точно?


Информация о сеансе

devtools::session_info()
#> - Session info ---------------------------------------------------------------
#>  setting  value                       
#>  version  R version 3.6.1 (2019-07-05)
#>  os       Windows 10 x64              
#>  system   x86_64, mingw32             
#>  ui       RTerm                       
#>  language (EN)                        
#>  collate  German_Germany.1252         
#>  ctype    German_Germany.1252         
#>  tz       Europe/Berlin               
#>  date     2020-02-18                  
#> 
#> - Packages -------------------------------------------------------------------
#>  package     * version date       lib source        
#>  assertthat    0.2.1   2019-03-21 [1] CRAN (R 3.6.2)
#>  backports     1.1.5   2019-10-02 [1] CRAN (R 3.6.1)
#>  callr         3.4.1   2020-01-24 [1] CRAN (R 3.6.2)
#>  cli           2.0.1   2020-01-08 [1] CRAN (R 3.6.2)
#>  crayon        1.3.4   2017-09-16 [1] CRAN (R 3.6.2)
#>  data.table  * 1.12.8  2019-12-09 [1] CRAN (R 3.6.2)
#>  desc          1.2.0   2018-05-01 [1] CRAN (R 3.6.2)
#>  devtools      2.2.1   2019-09-24 [1] CRAN (R 3.6.2)
#>  digest        0.6.23  2019-11-23 [1] CRAN (R 3.6.2)
#>  ellipsis      0.3.0   2019-09-20 [1] CRAN (R 3.6.2)
#>  evaluate      0.14    2019-05-28 [1] CRAN (R 3.6.2)
#>  fansi         0.4.1   2020-01-08 [1] CRAN (R 3.6.2)
#>  fs            1.3.1   2019-05-06 [1] CRAN (R 3.6.2)
#>  glue          1.3.1   2019-03-12 [1] CRAN (R 3.6.2)
#>  highr         0.8     2019-03-20 [1] CRAN (R 3.6.2)
#>  htmltools     0.4.0   2019-10-04 [1] CRAN (R 3.6.2)
#>  knitr         1.27    2020-01-16 [1] CRAN (R 3.6.2)
#>  magrittr      1.5     2014-11-22 [1] CRAN (R 3.6.2)
#>  memoise       1.1.0   2017-04-21 [1] CRAN (R 3.6.2)
#>  pkgbuild      1.0.6   2019-10-09 [1] CRAN (R 3.6.2)
#>  pkgload       1.0.2   2018-10-29 [1] CRAN (R 3.6.2)
#>  prettyunits   1.1.1   2020-01-24 [1] CRAN (R 3.6.2)
#>  processx      3.4.1   2019-07-18 [1] CRAN (R 3.6.2)
#>  ps            1.3.0   2018-12-21 [1] CRAN (R 3.6.2)
#>  R6            2.4.1   2019-11-12 [1] CRAN (R 3.6.2)
#>  Rcpp          1.0.3   2019-11-08 [1] CRAN (R 3.6.2)
#>  remotes       2.1.0   2019-06-24 [1] CRAN (R 3.6.2)
#>  rlang         0.4.4   2020-01-28 [1] CRAN (R 3.6.2)
#>  rmarkdown     2.1     2020-01-20 [1] CRAN (R 3.6.2)
#>  rprojroot     1.3-2   2018-01-03 [1] CRAN (R 3.6.2)
#>  sessioninfo   1.1.1   2018-11-05 [1] CRAN (R 3.6.2)
#>  stringi       1.4.4   2020-01-09 [1] CRAN (R 3.6.2)
#>  stringr       1.4.0   2019-02-10 [1] CRAN (R 3.6.2)
#>  testthat      2.3.1   2019-12-01 [1] CRAN (R 3.6.2)
#>  usethis       1.5.1   2019-07-04 [1] CRAN (R 3.6.2)
#>  withr         2.1.2   2018-03-15 [1] CRAN (R 3.6.2)
#>  xfun          0.12    2020-01-13 [1] CRAN (R 3.6.2)
#>  yaml          2.2.1   2020-02-01 [1] CRAN (R 3.6.2)
#> 
#> [1] C:/Program Files/R/library

1 Ответ

4 голосов
/ 18 февраля 2020

Есть ли конкретная причина для использования eval внутри data.table? Я думаю, что это было бы лучше:

count_by <- function(dt, by = NULL) {
  eval(substitute(dt[, .N, by = by]))
}

Он проходит все тестовые случаи (конечно). Даже первый, где ваша функция завершается с именем столбца by.

...