Call an object using a function in R - r

I have R objects:
"debt_30_06_2010" "debt_30_06_2011" "debt_30_06_2012" ...
and need to call them using a function:
paste0("debt_",date) ## "date" being another object
The problem is that when I assign the call to another object it takes only the name not the content:
debt_a <- paste0("endeud_", date1)
> debt_a
[1] "debt_30_06_2014"
I've tried to use the function "assign" without success:
assign("debt_a", paste0("debt_", date))
> debt_a
[1] "debt_30_06_2014"
I would like to know there is any method to achieve this task.

We could use get to get the value of the object. If there are multiple objects, use mget. For example, here I am assigning 'debt_a' with the value of 'debt_30_06_2010'
assign('debt_a', get(paste0('debt_', date[1])))
debt_a
#[1] 1 2 3 4 5
mget returns a list. So if we are assigning 'debt_a' to multiple objects,
assign('debt_a', mget(paste0('debt_', date)))
debt_a
#$debt_30_06_2010
#[1] 1 2 3 4 5
#$debt_30_06_2011
#[1] 6 7 8 9 10
data
debt_30_06_2010 <- 1:5
debt_30_06_2011 <- 6:10
date <- c('30_06_2010', '30_06_2011')

I'm not sure if I understood your question correctly, but I suspect that your objects are names of functions, and that you want to construct these names as characters to use the functions. If this is the case, this example might help:
myfun <- function(x){sin(x)**2}
mychar <- paste0("my", "fun")
eval(call(mychar, x = pi / 4))
#[1] 0.5
#> identical(eval(call(mychar, x = pi / 4)), myfun(pi / 4))
#[1] TRUE

Related

How to assign values to an object without directly typing out the object in R?

Let's say I create a list using the assign function:
name <- "test_list"
assign(name, list(a = c(1,2), b = c(3,4)))
Now, let's say I want to assign a new value to test_list without typing it out directly (like in a situation where I want objects with specific names to be generated automatically).
Both of the following attempts didn't work:
1.)
as.name(name)$a[[1]] <- 5
2.)
eval(expr = as.name(name))$a[[1]] <- 5
Any ideas?
We can use
assign(name, `[<-`(get(name), get(name)$a[1], 5))
Or make this more explicit
assign(name, {dat <- get(name); dat$a[1] <- 5; dat})
Or extract the object from the globalenv and assign
.GlobalEnv[[name]]$a[1] <- 5
test_list
#$a
#[1] 5 2
#$b
#[1] 3 4
One approach is with eval(parse(text=expression)), which can often be pressed into service in an emergency. But I would try to avoid it as much as possible.
name <- "test_list"
assign(name, list(a = c(1,2), b = c(3,4)))
eval(parse(text=paste0(name,"$a[[1]] <- 5")))
test_list
$a
[1] 5 2
$b
[1] 3 4

Modify a function call captured in exp

For example, changing cumsum in the output of expr(cumsum(1:3)) to cumprod.
Currently the only thing I can think of is converting the output of expr(cumsum(1:3)) to a string, editing it, then changing it back to a function call.
This seems like a pretty poor solution though and I'm struggling to find a better way.
library(rlang)
f(expr(cumsum(1:4)), cumprod)
# [1] 1 2 6 24
This is basically what I'm trying to achieve. Can you help me find a starting point?
If you just apply gsub to expression R casts it to character vector and does the substitution which you can cast as expression with parse:
y <- 1:4
x <- expression({cumsum(y)})
x.2 <- gsub("cumsum", "cumprod", x)
class(x.2)
# [1] "character"
x.2 <- parse(text = x.2)
eval(x)
# [1] 1 3 6 10
eval(x.2)
# [1] 1 2 6 24
Here is an option using rlang
f <- function(ex, fn) {
ex1 <- as.character(ex)
fn <- enquo(fn)[-1]
eval_tidy(parse_expr(glue::glue('{fn}({ex1[-1]})')))
}
f(expr(cumsum(1:4)), cumprod)
#[1] 1 2 6 24
Note that if you replaced cumsum with cumprod the output would be a vector 4 long, not 24, so we assume you meant to replace it with prod.
We use substitute to substitute cumsum with the value of the cumsum argument and then evaluate the resulting expression.
f here uses no packages -- the input in the question uses expr from rlang but even that is not really needed since we could have used quote(...) in place of expr(...).
f <- function(.x, cumsum) eval.parent(do.call("substitute", list(.x)))
# test
f(expr(cumsum(1:4)), prod)
## [1] 24
f(expr(cumsum(1:4)), cumprod)
## [1] 1 2 6 24
I like #David Arenburg, so I'm posting his answer here and marking it.
It's not clear to me how do you decide which function you want replace (because : is also a function). But if you want to always replace the outer one, you could define the following
function f <- function(x, y) {
tmp <- substitute(x)
tmp[[1]] <- substitute(y)
eval(tmp)
}
and then use it as follows
f(cumsum(1:4), cumprod)
#[1] 1 2 6 24
– David Arenburg

Reference vector from data frame using custom function

I'm trying to call a vector "a" from a data frame "df" using a function. I know I could do this just fine with the following:
> df$a
[1] 1 2 3
But I'd like to use a function where both the data frame and vector names are input separately as arguments. This is the best that I've come up with:
show_vector <- function(data.set, column) {
data.set$column
}
But here's how it goes when I try it out:
> show_vector(df, a)
NULL
How could I change this function in order to successfully reference vector df$a where the names of both are input to a function as arguments?
It's actually possible to do this without passing the column name as a string (in other words, you can pass in the unquoted column name:
show_vector <- function(data.set, column) {
eval(substitute(column), envir = data.set)
}
Usage example:
df <- data.frame(a = 1:3, b = 4:6)
show_vector(df, b)
# 4 5 6
I've wondered about this kind of thing a lot in the past and haven't found an easy fix. The best I've come up with is this:
df <- data.frame(c(1, 2, 3), c(4, 5, 6))
colnames(df) <- c("A", "B")
test <- function(dataframe, columnName) {
return(dataframe[, match(columnName, colnames(dataframe))])
}
test(df, "A")
Your code would work if you only put the column name in quotes i.e. show_vector(df, "a")
Other multiple ways to do this:
Using base functionality
func <- function(df, cname){
return(df[, grep(cname, colnames(df))])
}
Or even
func <- function(df, cname){
return(df[, cname])
}
You can use substitute to capture the input vector name as it is then use `as.character to make it as a character.
show_vector <- function(data.set, column) {
data.set[,as.character(substitute(column))]
}
Now lets take a look:
(dat=data.frame(a=1:3,b=4:6,c=10:12))
a b c
1 1 4 10
2 2 5 11
3 3 6 12
show_vector(dat,a)
[1] 1 2 3
show_vector(dat,"a")
[1] 1 2 3
It works.
we can also write a simple one where we just input a character string:
show_vector1 <- function(data.set, column) {
data.set[,column]
}
show_vector1(dat,"a")
[1] 1 2 3
Although this will not work if the column name is not a character:
show_vector1(dat,a)
**Show Traceback
Rerun with Debug
Error in `[.data.frame`(data.set, , column) : undefined columns selected**

Regex in R lists to call specific function

It is of course possible to store functions in a list to call it.
It is also possible to name that list entry to have a better access to it later.
Now I need the list item name to be a regular expression like this:
funcList <- list("^\\+[0-9]{1,3}$"=lead, "^\\-[0-9]{1,3}$"=lag)
a <- funcList$"+12"(a,12) # this will fire function "lead"
a <- funcList$"-4"(a,-4) # this will fire function "lag"
a <- funcList$"^\\+[0-9]{1,3}$"(a,12) # this works of course but is not what I want...
Of course this is not working correctly and I am getting the error "Error: attempt to apply non-function" because it is not used as regex but as a normal string value.
Is it possible to do what I need?
You could use the names of the array as parameters for grepl:
funcList <- list("^\\+[0-9]{1,3}$"=lead, "^\\-[0-9]{1,3}$"=lag)
f1 <- funcList[sapply(names(funcList), function(x) grepl(x,"+12"))][[1]]
f2 <- funcList[sapply(names(funcList), function(x) grepl(x,"-4"))][[1]]
> f1(seq(1,10))
[1] 2 3 4 5 6 7 8 9 10 NA
> f2(seq(1,10))
[1] NA 1 2 3 4 5 6 7 8 9
I think you can map strings like "+4" and "-12" to lead/lag more straightforwardly like:
set.seed(123)
df = data.frame(
x = sample(1:20, 10)
)
shifted = function(x, shift) {
direction = substr(shift, 1, 1)
amount = as.integer(substr(shift, 2, nchar(shift)))
if (direction == "+") {
return(lead(x, amount))
} else {
return(lag(x, amount))
}
}
df %>%
mutate(
plus4 = shifted(x, "+4"),
minus3 = shifted(x, "-3")
)
You could use regex within the shifted function if you need to do more validation of the "+4" strings, but I prefer not to go for complicated regexes unless they're definitely needed.

Using apply and multi argument functions

I want to apply a function over a data frame. The function takes V1 as arg1 and V2 as arg2 and I want to write the result to V3 or some other vector.
Is there an easy and compact way to do this? I've posted a (non-working) example below.
Thanks
Stu
my.func <- function(X, Y) {
return(X + Y)
}
a <- c(1,2,3)
b <- c(4,5,6)
my.df <- data.frame(a, b)
apply(my.df, 1, my.func, X="a", Y="b")
mapply() is made for this.
Either of the following will do the job. The advantage of the second approach is that it scales nicely to functions that take an arbitrary number of arguments.
mapply(my.func, my.df[,1], my.df[,2])
# [1] 5 7 9
do.call(mapply, c(FUN=list(my.func), unname(my.df)))
# [1] 5 7 9
I feel this would be better approached using with than mapply if you're calling elements inside a data.frame:
with(my.df,my.func(X=a,Y=b))
#[1] 5 7 9
It's still quite a clean method even if you need to do the explicit conversion from a matrix:
with(data.frame(my.mat),my.func(X=a,Y=b))
#[1] 5 7 9
There isn't really any need for an *apply function here. Vectorization would suffice:
my.df$c <- my.df$a + my.df$b
# a b c
#1 1 4 5
#2 2 5 7
#3 3 6 9
Your apply solution can't work the way you have written it because apply does not pass a named vector through to your function: e.g.
colnames(my.df)
#[1] "a" "b"
apply( my.df , 1 , colnames )
#NULL
For your example, rowSums(my.df) will do the job. For more complicated tasks, you can use the mapply function. For example: mapply(my.func, my.df[a], my.df[b]).
Alternatively, you could rewrite your function to take a vector argument:
my.otherfunc <- function(x) sum(x)
apply(my.df, 1, my.otherfunc)
It's important to understand that when apply feeds each row or column into the function, it's sending one vector, not a list of separate entries. So you should give it a function with a single (vector) argument.

Resources