Home > Back-end >  R replace list elements from within a function
R replace list elements from within a function

Time:01-21

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’.
  •  Tags:  
  • Related