0

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")) 
hrbrmstr
  • 77,368
  • 11
  • 139
  • 205
Funkwecker
  • 766
  • 13
  • 22
  • 5
    Use `mget()` as in `dat[, mget(col)]` or `.SD` along with `.SDcols` as in `dat[, .SD, .SDcols=cols]` where `cols = "v"`. Allowing the same function argument to have a symbol (of column) *and* as a character vector is inviting trouble and ambiguity. – Arun Sep 11 '16 at 14:06

1 Answers1

3

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.

hrbrmstr
  • 77,368
  • 11
  • 139
  • 205