R - параметр функции, то есть список функций - проверить параметр без оценки? - PullRequest
0 голосов
/ 16 января 2020

РЕДАКТИРОВАТЬ: Первоначальные резонансы предполагают, что моя статья сосредоточила внимание людей на вопросах передового опыта, а не на вопросах техники. Однако я хотел бы сосредоточиться на технической проблеме, используя приведенный ниже пример игрушек:

Если человек передает список параметру функции, как вы можете захватить и проверить отдельные элементы этого списка? не рискуя ошибками системы, пытающейся вызвать / оценить эти элементы?

Например, если пользователь передает параметру список функций, которые могут или не могут быть подходящими, или загруженные связанные пакеты, как функция может безопасно проверить, какие функции были запрошены?


Скажем, я хотел бы построить функцию, которая выполняет итерации других функций, которые могут быть применены. Фактический пример будет вызывать различные функции моделирования, но вот игрушечный пример, который легче увидеть:

newfunc <- function(func.list){
  lapply(func.list, 
         function(f){
           f(letters)
         }
  )
}

Скажем, среди функций newfun c () могут быть функции nchar () и length (). Если мы предоставим их, мы получим следующее:

newfunc(
  func.list = list(nchar, length)
)


[[1]]
 [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

[[2]]
[1] 26

Но, скажем, newfun c () также способен принимать что-то вроде str_to_upper (), которое приходит из пакета stringr . Передача str_to_upper () работает нормально, но только , если stringr был предварительно загружен:

newfunc(
  func.list = list(nchar, length, str_to_upper)
)

Error in lapply(func.list, function(f) f(letters)) : 
  object 'str_to_upper' not found


require(stringr)

newfunc(func.list = list(nchar, length, str_to_upper))
[[1]]
 [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

[[2]]
[1] 26

[[3]]
 [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O"
[16] "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"


Я хотел бы поместить код в функцию, которая может исследовать элементы списка и определяют, нужно ли загружать какие-либо пакеты (например, stringr ). Кроме того, я хотел бы проверить, относятся ли перечисленные функции к приемлемому набору (поэтому он ловит, если кто-то передает mean() или, что еще хуже, rcorr() из незагруженного Hmis c).

# This works here but is undesireable:
newfunc(func.list = list(nchar, length, str_to_upper, mean))

# This creates issues no matter what:
newfunc(func.list = list(nchar, length, str_to_upper, rcorr))
require(Hmisc)
newfunc(func.list = list(nchar, length, str_to_upper, rcorr))

Я знаю, как сделать что-то вроде func.list.test <- deparse(substitute(func.list), чтобы получить буквальный текст параметра, но я не знаю, как это сделать на отдельных элементах, не рискуя вызвать ошибку, если какая-то функция не ' не присутствует.

(и я не хочу использовать хакерский путь манипулирования строками для всего разбитого вывода func.list.test)

Идеально для этого варианта использования я хотел бы знать, если это можно сделать с помощью базовых методов R. Тем не менее, не стесняйтесь объяснять, как это сделать, используя новые подходы, такие как аккуратная оценка / цитаты, если это лучший / единственный способ (хотя я знаю, что мое знакомство с ними в настоящее время довольно ограничено).

Любая помощь будет принята с благодарностью .

1 Ответ

1 голос
/ 30 января 2020

Вот чистая base функция, которая использует find(), чтобы определить, какая функция используется, и help.search(), чтобы найти любые установленные пакеты, которые могут иметь функцию:

resolve <- function( func.list )
{
  ## Disassemble the supplied list of functions (lfs)
  lf <- as.list(substitute( func.list ))[-1]
  lfs <- lapply( lf, deparse )
  lfs <- setNames( lfs, lfs )

  ## Find functions (ff) in the loaded namespaces
  ff <- lapply( lfs, find )

  ## Existing functions (fex) are listed in the order of masking
  ##   The first element is used by R in the absence of explicit ::
  fex <- subset( ff, lapply(ff, length) > 0 )
  fex <- lapply( fex, `[`, 1 )

  ## Search for empty entries (ee) among installed packages
  ee <- names(subset( ff, lapply(ff, length) < 1 ))
  ee <- setNames( ee, ee )
  eeh <- lapply( ee, function(e)
      help.search( apropos = paste0("^", e, "$"),
                  fields = "name", ignore.case=FALSE )$matches$Package )

  ## Put everything together
  list( existing = fex, to_load = eeh )
}

Пример использования:

resolve(func.list = list(nchar, length, str_to_upper, lag, between))
# List of 2
#  $ existing:List of 3
#   ..$ nchar : chr "package:base"
#   ..$ length: chr "package:base"
#   ..$ lag   : chr "package:stats"
#  $ to_load :List of 2
#   ..$ str_to_upper: chr "stringr"
#   ..$ between     : chr [1:3] "data.table" "dplyr" "rex"

library(dplyr)
resolve(func.list = list(nchar, length, str_to_upper, lag, between))
# List of 2
#  $ existing:List of 4
#   ..$ nchar  : chr "package:base"
#   ..$ length : chr "package:base"
#   ..$ lag    : chr "package:dplyr"
#   ..$ between: chr "package:dplyr"
#  $ to_load :List of 1
#   ..$ str_to_upper: chr "stringr"

library(data.table)
resolve(func.list = list(nchar, length, str_to_upper, lag, between))
# List of 2
#  $ existing:List of 4
#   ..$ nchar  : chr "package:base"
#   ..$ length : chr "package:base"
#   ..$ lag    : chr "package:dplyr"
#   ..$ between: chr "package:data.table"
#  $ to_load :List of 1
#   ..$ str_to_upper: chr "stringr"
...