I am writing a function that pastes arguments and returns empty string if an argument is evaluated with error. It is easy for two arguments:
paste_clean_ab <- function(a,b){
a <- tryCatch(a, error = function(cond) {warning(cond); return(NULL);})
b <- tryCatch(b, error = function(cond) {warning(cond); return(NULL);})
if (is.null(a) | is.null(b)) return('');
return(paste0(a,b))
}
Works as expected:
paste_clean_ab('A', 'B')
# "AB"
var1='VAR1'
paste_clean_ab('A=', var1)
# "A=VAR1"
paste_clean_ab('A ', non_existing_variable)
# ""
# Warning message:
# In doTryCatch(return(expr), name, parentenv, handler) :
# object 'non_existing_variable' not found
# vectorized
paste_clean_ab(letters[1:5], var1)
# "aVAR1" "bVAR1" "cVAR1" "dVAR1" "eVAR1"
Importantly, it also works in data.table
:
require(data.table)
data(iris)
dt.iris <- data.table(iris)
dt.iris[,Label1:=paste_clean_ab('Species: ',as.character(Species))]
dt.iris[,Label2:=paste_clean_ab('Species: ',non_existing_variable)]
# Warning message:
# In doTryCatch(return(expr), name, parentenv, handler) :
# object 'non_existing_variable' not found
Now, I want to extend this function to work with any number of arguments. Here is my solution for now:
paste_clean <- function(...){
arglistS <- as.list(substitute(list(...)))
ret <- ''
for (arg in arglistS){
argVal <- tryCatch(eval(arg), error = function(cond) {warning(cond); return(NULL);})
# Skipping first element of the resulting list:
if (isTRUE(attr(argVal, 'class')=='result') & class(arg)=='name') next; # R v 4.1
if (identical(argVal, .Primitive('list'))) next; # R v 4.2+
if (is.null(argVal)) return('')
ret <- paste0(ret,argVal);
}
return(ret)
}
It works fine with any ordinary variables:
paste_clean('A','B','C')
# "ABC"
var1='VAR1'
var2='VAR2'
paste_clean('A=',var1, '; B=',var2)
# "A=VAR1; B=VAR2"
paste_clean('A=',non_existing_variable,var2)
# ""
# Warning message:
# In eval(arg) : object 'non_existing_variable' not found
dt.iris[,Label3:=paste_clean('var1: ',var1)] # "var1: VAR1"
But I can't address any existing columns in the data.table:
dt.iris[,Label4:=paste_clean('Species: ', Species)]
# Warning message:
# In eval(arg) : object 'Species' not found
What should I change in my tryCatch()
to make it evaluate in the right context?
You could specify evaluation environment : parent.frame()
.
This seems to work with the examples you provided, but isn't fully bulletproof as I got an error instead of the console mode warning using reprex::reprex()
to verify my answer.
paste_clean <- function(...){
arglistS <- as.list(substitute(list(...)))
#arglistS <- list(...)
ret <- ''
for (arg in arglistS){
argVal <- tryCatch(eval(arg,parent.frame()), error = function(cond) {warning(cond); return(NULL);})
# Skipping first element of the resulting list:
if (isTRUE(attr(argVal, 'class')=='result') & class(arg)=='name') next; # R v 4.1
if (identical(argVal, .Primitive('list'))) next; # R v 4.2+
if (is.null(argVal)) return('')
ret <- paste0(ret,argVal);
}
return(ret)
}
paste_clean('A','B','C')
#> [1] "ABC"
var1='VAR1'
var2='VAR2'
paste_clean('A=',var1, '; B=',var2)
#> [1] "A=VAR1; B=VAR2"
paste_clean('A=',non_existing_variable,var2)
#> [1] ""
# Warning message:
# In eval(arg, parent.frame()) : object 'non_existing_variable' not found
dt.iris[,Label4:=paste_clean('Species: ', Species)][,Label4]
#> [1] "Species: setosa" "Species: setosa" "Species: setosa"
#> [4] "Species: setosa" "Species: setosa" "Species: setosa"
...