I have a function that may end up being nested (Inner) and some other function (in general this function won't be known) that I'm calling Outer, and I would like Inner to be able to produce the same result regardless of the wrapper function (Outerin the below case).
Inner <- function(x,baz,bang){
# code stuff things ...
x.prime = as.character(substitute(x))
return(c(x.prime,y,z))
}
Outer <- function(y){
Inner(y)
}
Inner(a)
# "a" "stuff" "things" , which is what I'm expecting, in particular the "a".
Outer(a)
# "y" .... , but I was expecting to get "a"?
Of course I'm not dead set on using substitute if someone knows of a better method.
Does anyone have any clues how to get Inner to output the same result regardless if it is nested or not?
thanks in advance.
Here is a general outline that should help you solve your problem:
Inner <- function(x) {
my.call <- quote(substitute(x)) # we quote this here because we are going to re-use this expression
var.name <- eval(my.call)
for(i in rev(head(sys.frames(), -1L))) { # First frame doesn't matter since we already substituted for first level, reverse since sys.frames is in order of evaluation, and we want to go in reverse order
my.call[[2]] <- var.name # this is where we re-use it, modified to replace the variable
var.name <- eval(my.call, i)
}
return(var.name)
}
Outer <- function(y) Inner(y)
Outer2 <- function(z) Outer(z)
Now let's run the functions:
Inner(1 + 1)
# 1 + 1
Outer(2 + 2)
# 2 + 2
Outer2(3 + 3)
# 3 + 3
Inner always returns the outermost expression (you don't see y or z ever, just the expression as typed in .GlobalEnv.
The trick here is to use sys.frames(), and repeatedly substitute until we get to the top level.
Note this assumes that all the "Outer" functions just forward their argument on to the next inner one. Things likely get a lot more complicated / impossible if you have something like:
Outer <- function(y) Inner(y + 1)
This code does not check for that type of issue, but you probably should in your code. Also, keep in mind that the assumption here is that your functions will only be called from the R command line. If someone wraps their functions around yours, you might get unexpected results.
Related
The two following functions don't work at the moment, but do work when I write them out in full - not sure why. Any suggestions for fixes would be great.
change_specific_column_name <- function(data.frame,old_column_name,new_column_name){
names(data.frame)[names(data.frame) == old_column_name] <- new_column_name
}
change_specific_observations_name <- function(data.frame, column_name, old_obseration, new_observation){
data.frame$column_name[which(data.frame$column_name == old_obseration)] <- new_observation
}
test_frame <- data.frame(Does=1,This=2,Work=3)
change_specific_column_name(test_frame,"Work","Happen") # this doesn't change the name of the column
names(test_frame)[names(test_frame) == "Work"] <- "Happen" # writing out the function does change the name
Although not exact, you can think of the function's argument as passed-by-value, so it's perspicuous that changes made to the function's formal parameter don't impact the actual argument.
Any suggestions for fixes would be great.
If you really want a function to modify its argument, you could use a technique described e. g. under Call by reference in R; essentially just wrap your assignment in eval.parent(substitute(…)).
change_specific_column_name <- function(data.frame, old_column_name, new_column_name)
eval.parent(substitute(names(data.frame)[names(data.frame) == old_column_name] <- new_column_name))
So I currently have several functions where I want to modify a matrix that was created outside the function, in order to use it as a counting variable for things that happen inside the function. The matrix is named cost_counter, and I want to add to it when certain events occur inside of multiple functions. However, I'd like the solution to still be able to be used in foreach() and be parallelizable.
I know that using <<- is not recommended, however I can't figure out how to use assign() to modify an existing matrix. Example code is below. I've defined the variable cost_counter at the beginning. The function below goes on for longer, but I'm just including the first part for an example of what is happening.
cost_counter <<- matrix(0,nrow = 2, ncol = 12*15)
I0 <- function(){
if (screen[i] == 1){
cost_counter[2,ages[i]] <<- 1 + cost_counter[2,ages[i]] + 1
if(HIV[i] == 1){
if(ages[i] > 35){
if(pv[(i-min_i+1),1] < (1-specP3)){
cost_counter[1,ages[i]] <<- cost_counter[1,ages[i]] + 1
if(contact[i] == 1){return(c(5,0))}
}
}
When I run, error message simply says
"Error in cost_counter[2, ages[i]] <<- cost_counter[2, ages[i]] + 1 :
object 'cost_counter' not found"
I would just like to be able to modify the matrix, and for it to be recognized.
Any help would be appreciated. Thanks!
just at the end f the function re assign the function's matrix to the global env using:
cost_counter <<- cost_counter
Thank you for trying to help. I am happy to be corrected on all R misdemeanors.
I am not sure that I was entirely clear with my earlier post as below, so I will hope to clarify:
In the R console, my calls 'use source (etc)' to a .R file
Code within the .R file uses variables (for e.g. 'extracted info' ) ex1, ex2, ex3. These may hold strings or (a string of) numbers pulled from text.
In line with your guidance I've renamed my function to 'reset' (and ?reset indicates no other occurrences) are in scope. I'm passing both x and y which from outside the function:
#send variables ex1, ex2, ex3 together with location, loc and parse, prs to be reset with 0
reset(x<-c(loc,prs,ex1,ex2,ex3),y<-rep(c(0),length(x))) #repeats 0 in y variable as many times as there are entries for x
reset<-function(x,y){
print(c("resetting ",x," with ", y))
if (length(x) == length(y)) {x <- y
print(paste(x,"=",y),sep="") #both x and y should now be equal (to y)
} else {
paste("list lengths differ: x=",length(x)," y=",length(y),sep="")
}
}
Now both x and y are 0 but ex1, ex2 and ex3 still contain the previous values
I would like ex1, ex2 and ex3 all to be 0 before they are used in a subsequent section of code, so they don't contaminate extracted data with previous values such as:
loc<-str_locate(data[i],"=")
prs<-str_locate(data[i],",")
#extract data from the end of loc to before the occurrence of prs
ex1<-str_sub(data[i],loc[2]+1,prs[1]-1)
#cleanup
#below is simplified for example;
#in reality I wish to send ex1:ex(n) to be reset with values val1:val(n)
The desired outcome would be that back in the Rconsole >ex1 should now return 0.
Hope you can understand my dilemma and possibly help.
Say my code uses some variables to hold data extracted from a string using Stringr str_sub. The variables are temporary in that I use the values to construct other strings then they should be freed up to be used in an upcoming test: i.e. if (test==true){extract<-str_sub(string, start, end)}
For a later test, I would like extract==0; simple enough, but I have a few of these and would like to do it in one fell swoop.
I've used a for loop, but if there is a simpler way, please identify this.
My attempt is using a function:
#For variables loc, prs, ex1 and x2, set all values to 0
x<-assign(x<-c(loc, prs, ex1, ex2),y<-rep(c(0),length(x)))
#Function
assign <- function(x, y) {
if(length(x)==length(y)){
for (i in 1:length(x)){x[i]<-y[i]}
print(c("Assigned",x[i]))
return (x)
} else { print (c("list lengths differ: x=",length(x)," y=",length(y)))
}
}
The problem being that this returns x as 0, but the list of variables retain their values.
I'm a bit of a noob to both r and SO, so although I've benefitted from SO's bountiful advice on numerous occasions, this is my first question, so please be gentle. I have searched this issue, but have not found what I need in a few hours now. Hope you can help.
Beware of naming a function assign. There is already one in base-r and you will create confusion.
There are a couple of problems with your function besides its name. First, you do not need the for-loop to replace x by y, as this is a basic vectorized operation. Just use x <- y ; second, your should wrap your message in paste.
asgn <- function(x, y) {
if(length(x)==length(y)){
## This step is not needed, return(y) is better as #Rick proposed in their now deleted answer
## I am leaving it to show you how the for-loop is not needed
x<-y
return (x)
} else {
print (paste("list lengths differ: x=",length(x)," y=",length(y)))
return(x)
}
}
Then, there are a couple of problems with your function call. You use <- instead of = to specify the arguments. They are only somewhat synonymous for assigning variables, but a function argument is another matter. Finally, you are trying to use x is the definition of y in the arguments (length(x)), but this is not possible, because it is not yet defined, so it is looking for x in the parent environment. You should test your function with length(3) instead.
x<-asgn(x=c(loc, prs, ex1, ex2),y=rep(c(0),length(3)))
I am processing records from a large dataset with varying lengths using data.table[, somefunc(someseries), by=]. The length L of each record someseries could be anything from 1 to 50. I want to handle the following efficiently without needlessly adding an if expression:
For each group, I want the simplest way to access its middle entries someseries[3:(L-2)]
Problem: beware that when L<5, the expression someseries[3:(L-2)] actually misbehaves by inferring backwards direction. This is due to the default "helpful" behavior of [from:to] which uses
seq(from..., to..., by = ((to - from)/(length.out - 1) ...) i.e. infers backwards direction by=-1
In that case I just want somefunc to get passed an empty vector() not someseries[4:2]
But you can't explicitly do seq(... by=1) because that errors if from > to.
Here's a testcase:
set.seed(15)
ragged_arrays <- lapply(ceiling(runif(5,1,5)), function(n) (1:n) )
# indexing with unwanted auto-backwards
lapply(ragged_arrays, function(someseries) someseries[2 : (length(someseries)-2)] )
For the sake of our testcase, somefunc is a function which behaves gracefully when passed an empty vector, e.g. median()
I'm assuming you want to drop the first two and last two elements.
ragged_arrays <- lapply(1:7, seq_len)
lapply(ragged_arrays, function(x) x[seq_along(x) > 2 & rev(seq_along(x)) > 2])
I want to go through a vector, name all variables with i and use i to subset a larger file.
Why this does not work?
x <- c(seq(.1,.9,.1),seq(.9,1,.01))
doplot <- function(y)
{
for (i in unique(y))
{
paste("f_", i, sep = "") <- (F_agg[F_agg$Assort==i,])
}
}
doplot(x)
There are several problems here. First of all, on the left hand side of <- you need a symbol (well, or a special function, but let's not get into that now). So when you do this:
a <- "b"
a <- 15
then a will be set to 15, instead of first evaluating a to be b and then set b to 15.
Then, if you create variables within a function, they will be (by default) local to that function, and destroyed at the end of the function.
Third, it is not good practice to create variables this way. (For details I will not go into now.) It is better to put your data in a named list, and then return the list from the function.
Here is a solution that should work, although I cannot test it, because you did not provide any test data:
doplot <- function(y) {
lapply(unique(y), function(i) {
F_agg[F_agg$Assort == i, ]
})
}