Home > Software design >  Extract the elements of list as individual arguments within custom function in r
Extract the elements of list as individual arguments within custom function in r

Time:10-16

I have a problem where I'm trying to pass a list of arguments to a custom function, but I want the first part of the function to check if certain arguments are NULL, and then assign the value of zero if that is true. I've found a similar question here, but it isn't helping me figure out my particular problem. I'm trying to pass 4 arguments (aa, bb, dd, and ff) to the function, and then check if cc and ee are NULL. If so, then I want to first part of the function to define the NULL values (cc and ee in this case) as zero. I think the problem lies in unpacking, or unlisting x so that the function recognizes the arguments that are being passed.

Consider the following code:

test <- list(aa = 2, bb = 0.5, dd = 2, ff = 16)

testfunc <- function(x, aa=NULL, bb=NULL, cc=NULL, dd=NULL, ee=NULL, ff=NULL) {
          
  unlist(x, recursive = TRUE, use.names = TRUE)
  
  if(!is.null(aa)) aa <- 0
  if(!is.null(bb)) bb <- 0
  if(!is.null(cc)) cc <- 0
  if(!is.null(dd)) dd <- 0
  if(!is.null(ee)) ee <- 0
  if(!is.null(ff)) ff <- 0

  q <- list(aa, bb, cc, dd, ee, ff)
  
  return(q)
}

testfunc(test)

This code works but it returns all NULL values. If I remove all of the NULL calls when I define the function, then I get the following error, "Error in testfunc(test) : object 'aa' not found"

testfunc <- function(x) {
          
  unlist(x, recursive = TRUE, use.names = TRUE)
  
  if(!is.null(aa)) aa <- 0
  if(!is.null(bb)) bb <- 0
  if(!is.null(cc)) cc <- 0
  if(!is.null(dd)) dd <- 0
  if(!is.null(ee)) ee <- 0
  if(!is.null(ff)) ff <- 0

  q <- list(aa, bb, cc, dd, ee, ff)
  
  return(q)
}

testfunc(test)

Maybe it would help to define test as a dataframe instead of a list? Any help is appreciated!

CodePudding user response:

Is this what you're looking for? The crux is to wrap formals() in list(), and thus obtain a named list of all ("formal") parameters for testfunc().

My interpretation of your question is that you want to pass a named list as the x parameter to testfunc(), which contains named values to override other parameters passed to testfunc() alongside x. This list might be "incomplete", in that it does not necessarily name every other parameter (like cc and ee). Those that are not overridden are left as they are; unless they possess the default value of NULL, in which case they are converted to 0.

Using the formals() as above, we can define a testfunc()

testfunc <- function(x, aa = NULL, bb = NULL, cc = NULL, dd = NULL, ee = NULL, ff = NULL) {
  # Capture what values were passed to what parameters of 'testfunc()'.
  params <- as.list(formals())
  param_names <- names(params)
  
  # For convenience, record the named variables listed under 'x'.
  x_names <- names(x)
  
  
  # Process each parameter (besides 'x' itself).
  for(p_name in param_names[param_names != "x"]) {
    # If the parameter is named in 'x', override it with the value from 'x'.
    if(p_name %in% x_names) {
      assign(p_name, x[[p_name]])
    }
    # Otherwise, leave the parameter alone...unless it is NULL and should be
    # replaced with 0.
    else if(is.null(get(p_name))) {
      assign(p_name, 0)
    }
  }
  
  
  # ...
  # Any further operations; where parameters can be referenced like any other parameter
  # in any other function, rather than having to be 'unlist'ed from 'x'.
  # ...
  
  
  # Explicitly name the parameters in the output list.
  q <- list(
    aa = aa,
    bb = bb,
    cc = cc,
    dd = dd,
    ee = ee,
    ff = ff
  )
  
  return(q)
}

such that this call

test <- list(aa = 2, bb = 0.5, dd = 2, ff = 16)

testfunc(test)

yields the following list as output:

$aa
[1] 2

$bb
[1] 0.5

$cc
[1] 0

$dd
[1] 2

$ee
[1] 0

$ff
[1] 16

Note

I have given priority to x. That is, I have assumed you want to keep any NULLs that were explicitly assigned by name in x. So explicitly giving x a NULL element like aa here

test <- list(aa = NULL, bb = 0.5, dd = 2, ff = 16)
testfunc(test)

will yield a NULL for aa in the output:

$aa
NULL

$bb
[1] 0.5

$cc
[1] 0

$dd
[1] 2

$ee
[1] 0

$ff
[1] 16

If you want to override this behavior, and ensure that any NULL is replaced by 0 in the output, then simply change the else if to an if, in line 18 of the function definition:

    # ...
    if(is.null(get(p_name))) {
      assign(p_name, 0)
    }
    # ...

Tips

Your original solution failed because you used !is.null() rather than is.null(); so you avoided overriding any NULLs with 0.

If you were expecting unlist(x) to populate the environment of testfunc() with the contents of x, you are sadly mistaken. All unlist() does is "flatten" x into a (homogenized) vector of its atomic components; so we get

x <- list(
  a = 1,
  list(
    b = TRUE,
    "hi"
  )
)

unlist(x, recursive = TRUE, use.names = TRUE)

#>     a      b        
#>   "1" "TRUE"   "hi" 

homogenized into a (named) character vector. Your code inertly returns this value and moves on.

Only with something like assign("a", 1) (and so forth) could you achieve the effect of actually populating these values as named variables in your environment.

CodePudding user response:

What about something like this:

test <- list(aa = 2, bb = 0.5, dd = 2, ff = 16)

testfunc <- function(x, aa=NULL, bb=NULL, cc=NULL, dd=NULL, ee=NULL, ff=NULL) {
          
  unlist(x, recursive = TRUE, use.names = TRUE)
  
  if(!is.null(aa)) aa <- 0
  if(!is.null(bb)) bb <- 0
  if(!is.null(cc)) cc <- 0
  if(!is.null(dd)) dd <- 0
  if(!is.null(ee)) ee <- 0
  if(!is.null(ff)) ff <- 0

  q <- list(aa, bb, cc, dd, ee, ff)
  
  return(q)
}

testfunc(test)
# $aa
# [1] 2
# 
# $bb
# [1] 0.5
# 
# $cc
# [1] 0
# 
# $dd
# [1] 2
# 
# $ee
# [1] 0
# 
# $ff
# [1] 16
  • Related