I'm trying to wrap my head around ellipsis in R. I have a function and want to be able to pass additional arguments to the function as needed, for example whether or not to return a df or similar. Can I not specify variable names? This is a very simplified example and I want to be able to make this optional to keep function calls as easy and clean as possible with multiple possible conditionals within the function for various scenarios.
custom.fun<-function(x, y, z, ...){
a<-sum(x, y, z)
if (exists('return.var') && return.var=='yes'){
return(a)
}
}
A<-custom.fun(1,2,3,return.var='yes')
This returns Null, as it is obviously not passing on return.var.
I guess you can do something similar to this, capture all the optional argument in list and check if any of them have the required name and value.
custom.fun<-function(x, y, z, ...){
opt_args <- list(...)
a <- sum(x, y, z)
if (any(names(opt_args) == 'return.var' & opt_args == 'yes'))
return(a)
else
return('No arg')
}
custom.fun(1,2,3,return.var = 'yes')
#[1] 6
custom.fun(1,2,3,var = 'yes')
#[1] "No arg"
custom.fun(1,2,3,var='no', return.var = 'yes')
#[1] 6
Related
I want the output of one function to be able to set all, or possibly only the needed/given, attributes of another. I want to use the output of myFunction1() on its own, which does some calculations and based on that produces multiple needed values, or in combination with myFunction2(), which is supposed to use those values in a plot or similar. The code would look something like this:
myFunction1() >%> myFunction2()
I'm aware that I can possibly put the function that needs the output inside the first function, like:
myFunction1=function(x, logical){
x=x^2
y=""
if(x>100){
y="hello"
}else{
y="goodbye"
}
if(logical){
return(list(x=x,y=y,logical=logical))
}else{
return(myFunction2(x,y,logical))
}
} ##end myFunction1()
myFunction2=function(x, y, z){
a=paste0(x, y, z)
return(a)
}
or use the output with the $-operator
myOutput = myFunction(1, TRUE)
myOutput2 = myFunction2(myOutput$x, myOutput$y, myOutput$logical)
But is there a way to have a list output (or anything that can contain different data types) be able to set all attributes without the need of addressing the output via $ or index?
(First post so feedback regarding the wrongs would be appreciated aswell)
If the question is asking how to create a pipeline from myFunction1 and myFunction2 assuming we can modify myFunction1 but not myFunction2 then remove the test from myFunction1 and put it into the pipeline as shown.
myFunction1 <- function(x, logical) {
y <- if (x^2 > 100) "hello" else "goodbye"
list(x = x, y = y, logical = logical)
}
myFunction2 <- function(x, y, z) {
paste0(x, y, z)
}
# tests - 3 alternatives
library(magrittr)
# 1 - with
myFunction1(1, TRUE) %>%
with(if (logical) myFunction2(x, y, logical) else .)
## [1] "1goodbyeTRUE"
# 2 - magrittr %$%
myFunction1(1, TRUE) %$%
if (logical) myFunction2(x, y, logical) else .
## [1] "1goodbyeTRUE"
# 3 - do.call
myFunction1(1, TRUE) %>%
{ if (.$logical) do.call("myFunction2", unname(.)) else . }
## [1] "1goodbyeTRUE"
If we can modify both we could have myFunction2 accept a list.
myFunction2 <- function(List) {
if (List$logical) do.call("paste0", List) else List
}
myFunction1(1, TRUE) %>% myFunction2
## [1] "1goodbyeTRUE"
I try to create a loop that makes join to 5 dataframes like this
c <- list(EC_Pop, EC_GDP, EC_Inflation, ST_Tech_Exp, ST_Res_Jour)
for (i in seq_along(c))
{
if (i < 2)
{
EC_New <- c[i] %>%
left_join(c[i+1], by = c("Country","Year"))
}
else if(i > 1 & i < 4)
{
EC_New <- EC_New %>%
left_join(c[i+1], by = c("Country","Year"))
}
else
{
EC_New
}
}
But I have an error : UseMethod ("left_join") error: No Applicable method for 'left_join' applied to object of class "list"
Can somebody explain the reason? It seems very logical for me the way I wrote it...
According to the documentation of left_join, both x and y must be data frames.
Your c is a list, and so is c[i].
However, c[[i]] is a data frame. So change your code to include two square brackets.
EC_New <- c[[i]] %>%
left_join(c[[i+1]], by = c("Country","Year"))
I think you can also replace your code using Reduce:
EC_New2 <- Reduce(left_join, c)
Then check:
identical(EC_New, EC_New2) # should be TRUE
But I'm not sure since I don't have your data. It should work if the common columns are only "Country" and "Year".
And thanks to this answer, you can use the following command if the "Country" and "Year" are not the only common columns.
EC_New2 <- Reduce(function(x, y) left_join(x, y, by=c("Country","Year")), c)
By the way, try not to use function names such as c to name your R objects. While R allows this, it can lead to confusion later. For example, if you want to concatenate x and y but accidentally type c[x, y] instead of c(x, y), R may not return an error but something totally unexpected.
I would like to have a function accept arguments in the usual R way, most of which will have defaults. But I would also like it to accept a list of named arguments corresponding to some or some or all of the formals. Finally, I would like arguments supplied to the function directly, and not through the list, to override the list arguments where they conflict.
I could do this with a bunch of nested if-statements. But I have a feeling there is some elegant, concise, R-ish programming-on-the-language solution -- probably multiple such solutions -- and I would like to learn to use them. To show the kind of solution I am looking for:
> arg_lst <- list(x=0, y=1)
> fn <- function(a_list = NULL, x=2, y=3, z=5, ...){
<missing code>
print(c(x, y, z))
}
> fn(a_list = arg_list, y=7)
Desired output:
x y z
0 7 5
I like a lot about #jdobres's approach, but I don't like the use of assign and the potential scoping breaks.
I also don't like the premise, that a function should be written in a special way for this to work. Wouldn't it be better to write a wrapper, much like do.call, to work this way with any function? Here is that approach:
Edit: solution based off of purrr::invoke
Thinking a bit more about this, purrr::invoke almost get's there - but it will result in an error if a list argument is also passed to .... But we can make slight modifications to the code and get a working version more concisely. This version seems more robust.
library(purrr)
h_invoke = function (.f, .x = NULL, ..., .env = NULL) {
.env <- .env %||% parent.frame()
args <- c(list(...), as.list(.x)) # switch order so ... is first
args = args[!duplicated(names(args))] # remove duplicates
do.call(.f, args, envir = .env)
}
h_invoke(fn, arg_list, y = 7)
# [1] 0 7 5
Original version borrowing heavily from jdobres's code:
hierarchical_do_call = function(f, a_list = NULL, ...){
formal_args = formals() # get the function's defined inputs and defaults
formal_args[names(formal_args) %in% c('f', 'a_list', '...')] = NULL # remove these two from formals
supplied_args <- as.list(match.call())[-1] # get the supplied arguments
supplied_args[c('f', 'a_list')] = NULL # ...but remove the argument list and the function
a_list[names(supplied_args)] = supplied_args
do.call(what = f, args = a_list)
}
fn = function(x=2, y=3, z=5) {
print(c(x, y, z))
}
arg_list <- list(x=0, y=1)
hierarchical_do_call(f = fn, a_list = arg_list, y=7)
# x y z
# 0 7 5
I'm not sure how "elegant" this is, but here's my best attempt to satisfy the OP's requirements. The if/else logic is actually pretty straightforward (no nesting needed, per se). The real work is in collecting and sanitizing the three different input types (formal defaults, the list object, and any supplied arguments).
fn <- function(a_list = NULL, x = 2, y = 3, z = 5, ...) {
formal_args <- formals() # get the function's defined inputs and defaults
formal_args[names(formal_args) %in% c('a_list', '...')] <- NULL # remove these two from formals
supplied_args <- as.list(match.call())[-1] # get the supplied arguments
supplied_args['a_list'] <- NULL # ...but remove the argument list
# for each uniquely named item among the 3 inputs (argument list, defaults, and supplied args):
for (i in unique(c(names(a_list), names(formal_args), names(supplied_args)))) {
if (!is.null(supplied_args[[i]])) {
assign(i, supplied_args[[i]])
} else if (!is.null(a_list[[i]])) {
assign(i, a_list[[i]])
}
}
print(c(x, y, z))
}
arg_lst <- list(x = 0, y = 1)
fn(a_list = arg_lst, y=7)
[1] 0 7 5
With a little more digging into R's meta-programming functions, it's actually possible to pack this hierarchical assignment into its own function, which is designed to operate on the function environment that called it. This makes it easier to reuse this functionality, but it definitely breaks scope and should be considered dangerous.
The "hierarchical assignment" function, mostly the same as before:
hierarchical_assign <- function(a_list) {
formal_args <- formals(sys.function(-1)) # get the function's defined inputs and defaults
formal_args[names(formal_args) %in% c('a_list', '...')] <- NULL # remove these two from formals
supplied_args <- as.list(match.call(sys.function(-1), sys.call(-1)))[-1] # get the supplied arguments
supplied_args['a_list'] <- NULL # ...but remove the argument list
# for each uniquely named item among the 3 inputs (argument list, defaults, and supplied args):
for (i in unique(c(names(a_list), names(formal_args), names(supplied_args)))) {
if (!is.null(supplied_args[[i]])) {
assign(i, supplied_args[[i]], envir = parent.frame())
} else if (!is.null(a_list[[i]])) {
assign(i, a_list[[i]], envir = parent.frame())
}
}
}
And the usage. Note that the the calling function must have an argument named a_list, and it must be passed to hierarchical_assign.
fn <- function(a_list = NULL, x = 2, y = 3, z = 5, ...) {
hierarchical_assign(a_list)
print(c(x, y, z))
}
[1] 0 7 5
I think do.call() does exactly what you want. It accepts a function and a list as arguments, the list being arguments for the functions. I think you will need a wrapper function to create this behavior of "overwriting defaults"
I'm confused how ... works.
tt = function(...) {
return(x)
}
Why doesn't tt(x = 2) return 2?
Instead it fails with the error:
Error in tt(x = 2) : object 'x' not found
Even though I'm passing x as argument ?
Because everything you pass in the ... stays in the .... Variables you pass that aren't explicitly captured by a parameter are not expanded into the local environment. The ... should be used for values your current function doesn't need to interact with at all, but some later function does need to use do they can be easily passed along inside the .... It's meant for a scenario like
ss <- function(x) {
x
}
tt <- function(...) {
return(ss(...))
}
tt(x=2)
If your function needs the variable x to be defined, it should be a parameter
tt <- function(x, ...) {
return(x)
}
If you really want to expand the dots into the current environment (and I strongly suggest that you do not), you can do something like
tt <- function(...) {
list2env(list(...), environment())
return(x)
}
if you define three dots as an argument for your function and want it to work, you need to tell your function where the dots actually go. in your example you are neither defining x as an argument, neither ... feature elsewhere in the body of your function. an example that actually works is:
tt <- function(x, ...){
mean(x, ...)
}
x <- c(1, 2, 3, NA)
tt(x)
#[1] NA
tt(x, na.rm = TRUE)
#[1] 2
here ... is referring to any other arguments that the function mean might take. additionally you have a regular argument x. in the first example tt(x) just returns mean(x), whilst in the second example tt(x, na.rm = TRUE), passes the second argument na.rm = TRUE to mean so tt returns mean(x, na.rm = TRUE).
Another way that the programmers of R use a lot is list(...) as in
tt <- function(...) {
args <- list(...) # As in this
if("x" %in% names(args))
return(args$x)
else
return("Something else.")
}
tt(x = 2)
#[1] 2
tt(y = 1, 2)
#[1] "Something else."
I believe that this is one of their favorite, if not the favorite, way of handling the dots arguments.
I have a series of functions like this one:
otherfunction<-function(x, y){
if(option=="one"){
z<-x+y+var
}
if(option=="two"){
z<-x-y+2*var
}
return(z)
}
Then a master function that defines arguments that need to be passed, along with output of the internal function, on to other internal functions functions, as well as .
master <- function(x, y, option=c("one", "two"), variable=0.1){
w <- otherfunction(x,y)
#(or otherfunction(x,y, option, variable))
v <- otherfunction(w,y)
return(v)
}
I seem to be getting stuck with either "object not found" or "unused arguments" errors.
How do other people deal with having multiple functions that will be called from a master function?
Do I need to turn the values of the arguments in the master function into objects?
Does this need to be done in the global environment?
Do I need to define the "otherfunction"s within the master function?
Do I need to use some kind of "..." argument?
Or is there something else I'm not getting?
Your otherfunction has no way to see the option value from your master function. Functions look for variables in the environment where they are defined, not where they are called. This should work
otherfunction<-function(x, y, option, var){
if(option=="one"){
z<-x+y+var
}
if(option=="two"){
z<-x-y+2*var
}
return(z)
}
master <- function(x, y, option=c("one", "two"), variable=0.1){
w <- otherfunction(x,y, option, variable)
v <- otherfunction(w,y, option, variable)
return(v)
}
master(2,2, "two")
# [1] -1.6
If you wanted to pass through parameter, you could also do something like this with master
master <- function(x, y, ...){
w <- otherfunction(x,y, ...)
v <- otherfunction(w,y, ...)
return(v)
}
master(2,2, option="two", var=0.1)
# [1] -1.6