Search code examples
rdata.tablense

How to pass a string as a parameter to a function which expects a variable in R


The first call to the function f works, the second does not. How can I pass a String ("v") to the function f so that the function works as exspected?

library(data.table)

f<-function(t,x) t[,deparse(substitute(x)),with=F]

dat<-data.table(v="a")

f(dat,v)
#    v
# 1: a

f(dat,eval(parse(text="v")))
# Error in `[.data.table`(t, , deparse(substitute(x)), with = F) : 
#   column(s) not found: eval(parse(text = "v")) 

Solution

  • It won't be a one-liner anymore but you can test for what you're passing in:

    library(data.table)
    library(purrr)
    
    dat <- data.table(v="a")
    
    f <- function(dt, x) {
    
      # first, see if 'x' is a variable holding a string with a column name
    
      seval <- safely(eval)
      res <- seval(x, dt, parent.frame())
    
      # if it is, then get the value, otherwise substitute() it
    
      if ((!is.null(res$result)) && inherits(res$result, "character")) {
        y <- res$result
      } else {
        y <- substitute(x)
      }
    
      # if it's a bare name, then we deparse it, otherwise we turn
      # the string into name and then deparse it
    
      if (inherits(y, "name")) {
        y <- deparse(y) 
      } else if (inherits(y, "character")) {
        y <- deparse(as.name(x))
      }
    
      dt[, y, with=FALSE]
    
    }
    
    f(dat,v)
    ##    v
    ## 1: a
    
    f(dat, "v")
    ##    v
    ## 1: a
    
    V <- "v"
    f(dat, V)
    ##    v
    ## 1: a
    
    f(dat, VVV)
    #> throws an Error
    

    I switched it from t to dt since I don't like using the names of built-in functions (like t()) as variable names unless I really have to. It can introduce subtle errors in larger code blocks that can be frustrating to debug.

    I'd also move the safely() call outside the f() function to save a function call each time you run f(). You can use old-school try() instead, if you like, but you have to check for try-error which may break some day. You could also tryCatch() wrap it, but the safely() way just seems cleaner to me.