In R I'd like to replace some elements in a list using the $ notation:
# functions
replaceNonNull <- function(x, value) {
if(!is.null(x)){
thisx <- deparse(substitute(x))
print(paste0("replacing ", thisx, " with '",value,"'"))
#x <<- value
assign(thisx, value, envir = .GlobalEnv)
}
}
mylist = list("a"=1:3)
replaceNonNull(mylist$a,"456");mylist$a
However after running replaceNonNull, a new variable is created with name 'mylist$a'. How can I change the a value in the list instead?
CodePudding user response:
The problem you're having is that the first argument of assign is:
x - a variable name, given as a character string.
But even outside the function, this doesn't work.
assign(mylist$a,0)
#Error in assign(mylist$a, 0) : invalid first argument
assign("mylist$a",0)
mylist
#$a
#[1] 1 2 3
However, you can use $<-, like this:
> mylist$a <- 0
> mylist$a
[1] 0
One approach, then is to create that expression and evaluate it:
mylist = list("a"=1:3)
myexpression <- deparse(substitute(mylist$a))
myexpression
#[1] "mylist$a"
library(rlang)
expr(!!parse_expr(myexpression) <- 0)
#mylist$a <- 0
eval(expr(!!parse_expr(myexpression) <- 0))
mylist$a
#[1] 0
Obviously use <<- inside the function.
CodePudding user response:
Maybe you want something like this:
replaceNonNull <- function(x, el, value, env = globalenv()) {
if (!is.null(x[[el]])) {
nx <- deparse(substitute(x))
nv <- deparse(substitute(value))
cat("replacing value of", sQuote(el), "in", sQuote(nx), "with", sQuote(nv), "\n")
env[[nx]][[el]] <- value
}
}
mylist <- list(a = 1:3)
replaceNonNull(mylist, "a", 4:6)
## replacing value of ‘a’ in ‘mylist’ with ‘4:6’
mylist$a
## [1] 4 5 6
replaceNonNull(mylist, "b", 4:6)
mylist$b
## NULL
Nonstandard evaluation is a dangerous game, so you should be aware of the limitations. Here, x must be the name of a variable bound in env (hence not a call to the $ operator). Otherwise, you will continue to see unexpected behaviour:
mylist <- list(zzz = list(a = 1:3))
replaceNonNull(mylist$zzz, "a", 4:6)
## replacing value of ‘a’ in ‘mylist$zzz’ with ‘4:6’
mylist$zzz$a
## [1] 1 2 3
`mylist$zzz`
## $a
## [1] 4 5 6
You can avoid unintended assignments by adding a test:
replaceNonNull <- function(x, el, value, env = globalenv()) {
nx <- deparse(substitute(x))
if (!exists(nx, env, mode = "list")) {
stop("There is no list in ", sQuote("env"), " named ", sQuote(nx), ".")
}
if (!is.null(x[[el]])) {
nv <- deparse(substitute(value))
cat("replacing value of", sQuote(el), "in", sQuote(nx), "with", sQuote(nv), "\n")
env[[nx]][[el]] <- value
}
}
rm(`mylist$zzz`) # clean up after last example
replaceNonNull(mylist$zzz, "a", 4:6)
## Error in replaceNonNull(mylist$zzz, "a", 4:6) :
## There is no list in ‘env’ named ‘mylist$zzz’.
