R: IF statement evaluating expression despite condition being FALSE? - r

I've got a large function in R and the users have the ability to not include/specify an object. If they DO, the code checks to make sure the names in that object match the names in another. If they DON'T, there's no need to do that checking. The code line is:
if(exists("grids")) if(!all(expvarnames %in% names(grids))) {stop("Not all expvar column names found as column names in grids")}
But I'm getting the following error:
Error in match(x, table, nomatch = 0L) : argument "grids" is missing, with no default
Well in this trial run, grids is SUPPOSED to be missing. If I try
if(exists("grids")) print("yay")
Then nothing prints, i.e. the absence of grids means the expression isn't evaluated, which is as I'd expect. So can anyone think why R seems to be evaluating the subsequent IF statement in the main example? Should I slap another set of curly brackets around the second one??
Thanks!
Edit: more problems. Removing "grids," from the functions list of variables means it works if there's no object called grids and you don't specify it in the call (i.e. function(x,grids=whatever)). And keeping "grids," IN the functions list of variables means it works if there IS an object called grids and you do specify it in the call.
Please see this: http://i.imgur.com/9mr1Lwi.png
using exists(grids) is out because exists wants "quotes" and without em everything fails. WITH them ("grids"), I need to decide whether to keep "grids," in the functions list. If I don't, but I specify it in the call (function(x,grids=whatever)) then I get unused argument fail. If I DO, but don't specify it in the call because grids doesn't exist and I don't want to use it, I get match error, grids missing no default.
How do I get around this? Maybe list it in the function variables list as grids="NULL", then rather than if(exists("grids")) do if(grids!="NULL")
I still don't know why the original match problem is happening though. Match is from the expvarnames/grids names checker, which is AFTER if(exists("grids")) which evaluates to FALSE. WAaaaaaaiiiiittttt..... If I specify grids in the function variables list, i.e. simply putting function(x,grids,etc){do stuff}, does that mean the function CREATES an object called grids, within its environment?
Man this is so f'd up....
testfun <- function(x,grids)
{if(exists("grids")) globalgrids<<-grids
print(x+1)}
testfun(1) # Error in testfun(1) : argument "grids" is missing, with no default
testfun <- function(x,grids)
{if(exists("grids")) a<<-c(1,2,3)
print(x+1)}
testfun(1) #2 (and globally assigns a)
So in the first example, the function seems to have created an object called "grids" because exists("grids") evaluates to true. But THEN, ON THE SAME LINE, when asked to do something with grids, it says it doesn't exist! Schroedinger's object?!
This is proven in example 2: grids evaluates true and a is globally assigned then the function does its thing. Madness. Complete madness. Does anyone know WHY this ridiculousness is going on? And is the best solution to use my grids="NULL" default in the functions variables list?
Thanks.
Reproducible example, if you want to but I've already done it for every permutation:
testfun <- function(x,grids)
{if(exists("grids")) if(!all(expvarnames %in% names(grids))) {stop("Not all expvar column names found as column names in grids")}
print(x+1)}
testfun(1)
testfun(x=1,grids=grids)
grids<-data.frame(c(1,2,3),c(1,2,3),c(1,2,3))
expvarnames <- c("a","b","c")
colnames(grids) <- c("a","b","c")

Solution
Adapting your example use:
testfun <- function(x,grids = NULL)
{
if(!is.null(grids)){
if(!all(expvarnames %in% names(grids))){
stop("Not all expvar column names found as column names in grids")
}
print(x+1)
}
}
Using this testfun(1) will return nothing. By specifying a default argument in the function as NULL the function then checks for this (i.e. no argument specified) and then doesn't continue the function if so.
The Reason the Problem Occurs
We go through each of the examples:
testfun <- function(x,grids)
{if(exists("grids")) globalgrids<<-grids
print(x+1)}
testfun(1) # Error in testfun(1) : argument "grids" is missing, with no default
Here we call the function testfun, giving only the x argument. testfun knows it needs two arguments, and so creates local variables x and grids. We have then given an argument to x and so it assigns the value to x. There is no argument to grids, however the variable has still been created, even though no value has been assigned to it. So grids exists, but has no value.
From this exists("grids") will be TRUE, but when we try to do globalgrids<<-grids we will get an error as grids has not been assigned a value, and so we can't assign anything to globalgrids.
testfun <- function(x,grids)
{if(exists("grids")) a<<-c(1,2,3)
print(x+1)}
testfun(1) #2 (and globally assigns a)
This, however is fine. grids exists as in the previous case, and we never actually try and access the value stored in grids, which would cause an error as we have not assigned one.
In the solution, we simply set a default value for grids, which means we can always get something whenever we try and access the variable. Unlike in the previous cases, we will get NULL, not that nothing is stored there.
The main point of this is that when you declare arguments in your function, they are created each time you use the function. They exist. However, if you don't assign them values in your function call then they will exist, but have no value. Then when you try and use them, their lack of values will throw an error.

> a <- c(1,2,3,4)
> b <- c(2,4,6,8)
> if(exists("a")) if(!all(a %in% b)) {stop("Not all a in b")}
Error: Not all a in b
> rm(a)
> if(exists("a")) if(!all(a %in% b)) {stop("Not all a in b")}
>
When a does not exist, the expression does not evaluate, as expected. Before testing your first expression, make sure that grids does not exist by running rm(grids) in the console.

Richard Scriven's comment got me thinking: grids was an argument in my function but was optional, so maybe shouldn't be specified (like anything in "..." optional functions). I commented it out and it worked. Hooray, cheers everyone.

Related

Strange as.Date() behavior

I'm using R 4.2.1 with all packages updated to the latest version.
The two lines below differ only in the order of the elements in a concatenated vector, yet the output is completely different.
as.Date(c(Sys.Date(), "2020-09-09"))
as.Date(c("2020-09-09", Sys.Date()))
The output is:
> as.Date(c(Sys.Date(), "2020-09-09"))
[1] "2022-09-16" "2020-09-09"
> as.Date(c("2020-09-09", Sys.Date()))
[1] "2020-09-09" NA
The first line correctly coerces the system date as a string, and the second line coerces it first as a numeric value and then as a string, but I have never before run into a situation where coercion in R depends on the order of elements in a vector...
Can someone explain to me why coercion rules behave this way and where I can read more about it...
And what can I do in a situation when the type of elements inside c() is not known a priori?
Thank you!
The default c() unclasses each argument before combining them (unclass(Sys.Date()) is 19251 [as of today]); this is because "all attributes except names are removed" by (at least the default version of) c(), which includes the class.
The reason for the difference in orders is that c() is an S3 generic function, which means that it dispatches on the class of its first argument, so c(<date>, <character>) calls c.Date(), while c(<character>, <date>) calls the generic version of c() (which falls through to a primitive function in C which I don't want to bother digging through).
The code of c.Date:
function (..., recursive = FALSE)
.Date(c(unlist(lapply(list(...), function(e) unclass(as.Date(e))))))
in other words, it coerces everything to a date, then unclasses it, then turns the vector back to dates once everything is concatenated ...
A possible workaround/solution is to call c.Date() explicitly, if you know that's what you want ...

Why the code only works with numbers and not letters?

I have to use the code bellow but I don't completely understand how it works. Why it won't work if I change du.4 by du.f and then use the f when calling the function? For some reason it only works with numbers and I do not undarstand why.
This is the error that it is giving in the case of du.f
Error in paste("Meth1=", nr, ".ps", sep = "") : object 'f' not found
du.4 <- function(u,v,a){(exp(a)*(-1+exp(a*v)))/(-exp(a)+exp(a+a*u)-exp(a*(u+v))+exp(a+a*v))}
plotmeth1 <- function(data1,data2,alpha,nr) {
psfile <-paste("Meth1=",nr,".ps",sep="")
diffmethod <-paste("du.",nr,sep="")
title=paste("Family",nr)
alphavalue <-paste("alpha=",round(alpha,digits=3),sep="")
#message=c("no message")
postscript(psfile)
data3<-sort(eval(call(diffmethod,data1,data2,alpha)))
diffdata <-data3[!is.na(data3)]
#if(length(data3)>length(diffdata))
#{message=paste("Family ",nr,"contains NA!")}
tq <-((1:length(diffdata))/(length(diffdata)+1))
plot(diffdata,tq,main=title,xlab="C1[F(x),G(y)]",ylab="U(0,1)",type="l")
legend(0.6,0.3,c(alphavalue))
abline(0,1)
#dev.off()
}
In R, a dot is used as just another character in identifiers. It is often used for clarity but doesn't have a formal function in defining the part after the dot as being in a name-space given by the part of the identifier before the dot. In something like du.f you can't refer to the function by f alone, even if your computation is inside of an environment named du. You can of course define a function named du.4 and then use 4 all by itself, but when you do so you are using the number 4 as just a number and not as a reference to the function. For example, if
du.4 <- function(u,v,a){(exp(a)*(-1+exp(a*v)))/(-exp(a)+exp(a+a*u)-exp(a*(u+v))+exp(a+a*v))}
Then du.4(1,2,3) evaluates to 21.08554 but attempting to use 4(1,2,3) throws the error
Error: attempt to apply non-function
In the case of your code, you are using paste to assemble the function name as a string to be passed to eval. It makes sense to paste the literal number 4 onto the string 'du.' (since the paste will convert 4 to the string '4') but it doesn't make sense to paste an undefined f onto 'du.'. It does, however, make sense to paste the literal string 'f' onto 'du.', so that the function call plotmeth1 (data1, data2, alpha, 'f') will work even though plotmeth1 (data1, data2, alpha, f) will fail.
See this question for more about the use of the dot in R identifiers.

Preserve a promise in R

I want to, essentially, pass a value untouched through a function. So in the following example (in Rstudio):
example_function <- function(datain){
as.environment("package:utils")$View(datain)
}
I want the inner function to act as if I'm passing it the original object, in particular so the name which appears in the View window will have the name of the original object (X, say) rather than datain which is what currently occurs.
With deparse(substitute(datain)) you can get the original name of the argument passed.
Then, to accomplish what you asked for, you can simply do
example_function <- function(datain){
as.environment("package:utils")$View(datain, deparse(substitute(datain)))
}
Now the View window will be titled appropriately as you wanted.
However note that "I want the inner function to act as if I'm passing it the original object" request of yours is not possible in R. R does not support pass-by-reference. There are some workarounds, but if you only needed if for naming the View, the above fix should be fine.
You can also use get for this.
example_function <- function(datain){
as.environment("package:utils")$View(get(datain),datain)
}
in this case you don't pass the variable but rather the name of the variable as a string.
example_function("X")

Unexpected R behavior with function parameter

Am R newb. I coded a function that uses 3 parameters. In my code i use one of the parameters to help me read files from a directory. There are 100 files in the directory. The code works fine when I pass it all the function parameters and specify the files i want to read.
functionX(var1, var2, id) and functionX(var1, var2, id = 1:100)
## Below is the first line of code for me that uses "id".
sub.file.names <- file.names[id] ### Get file names
The odd thing is that when a value for "id" is not passed to the function initially (or set with a 1:100 default), the code seems to read all the file names anyway. And it does so even though a value for "id" has never been established.
It's as if R somehow treats the two functions below the same when the user omits passing a value to "id" when executing the function ... eg, functionx("var1", "var2") ## and does not pass any id variable
functionx(var1, var2, id)
functionx(var1, var2, id = 1:100)
Any pointers on why this is happening would be great to know. I feel the answer is obvious, but have not been able to figure it out.
Let me try to explain what is happening with a simple example. Consider the following function
foo = function(i){
LETTERS[i]
}
When you try foo(), you will notice that the function returns all 26 uppercase letters. Why does that happen? Well, everything in R is a function. So when you say LETTERS[i], you are essentially calling the function [. So, the function call is
`[`(LETTERS, i)
Since i is missing, this call is executed as [(LETTERS) (essentially LETTERS[]) which returns all elements of the vector. Note that this occurs because the [ function allows for the i argument to be missing while calling it. Check ?[
If you want the function to act differently when id is missing, either check for missing(id), or explicitly set it to NULL as default. So, if you do
foo2 = function(i = NULL){
LETTERS[i]
}
foo2() will return a zero length character vector.

R Finding name of assignee variable from inside called function

Say that I have the following function:
myfun <- function(x){
assignee <- get_assignee_name()
message(paste0("output assigned to ",assignee,"!"))
x
}
my_var <- myfun(7)
output assigned to my_var!
I am looking for something that does the job of get_assignee_name() above.
It should be a function or a few lines of code that is called from inside a function, it examines the call that it occurs inside of and returns the object that is being assigned to as a character, if one exists. So in the example
answer <- myfun(10)
the message "output assigned to answer!" should appear.
I am aware that this seems unnecessarily complicated, but it is just an example to demonstrate the point for a more complicated use case. I am also aware that of ways around this involving passing the variable name into the function as a parameter and then calling assign. This won't solve the problem in this case, where this should be transparent to the user
This is not possible because <- is a primitive and quite special function. It is not part of the call stack. You would need to use assign instead:
myfun <- function(x){
assignee <- sys.call(1)
if (assignee[[1]] == quote(assign))
message(paste0("output assigned to ",assignee[[2]],"!"))
x
}
assign("my_var", myfun(7))
#output assigned to my_var!
my_var
#[1] 7
However, I suggest you reconsider how you approach your actual issue.

Resources