How can I plot a graph of a function using multiple conditions? - r

I’m going to make a continuous graph with conditional log, in particular:
f <- function(x) {
if (0 < x && x <= 2) {
return (log(x));
} else {
return (x^2*log(x));
}
}
Now, I want to plot it:
ggplot(data.frame(x=seq(0,5,length.out=1000)), aes(x)) + geom_function(fun=f)
This code shows the graph fine on the part that is from x = 2 on, but for some reasons even on x between 0 and 2, it uses the second function x^2*log(x), not the first one.
This is more easily seen if I change the seq to seq(0, 2.1, length.out=1000).
But why does this happen and how can I fix it? I think I'm fine with the if statement with multiple conditions...

if is not vectorized. try ifelse instead:
f <- function(x) {
ifelse(test = 0 < x & x <= 2,
yes = log(x),
no = (x^2) * log(x))
}
df <- data.frame(x = seq(0, 5, length.out = 10000))
ggplot(df, aes(x)) +
geom_function(fun = f)
Vectorized means that ifelse takes a logical vector and applies the corresponding logic to each element of the vector, whether test was TRUE or FALSE.
if instead can handle just a single TRUE or FALSE. In case a vector is provided, only the first element of the vector is considered. Hence you probably received the following warning:
#> Warning messages:
#> 1: In 0 < x && x <= 2 : 'length(x) = 1000 > 1' in coercion to 'logical(1)'

Related

Function should not return a value if it doesn't exist in the function

I'm writing a function that contains some conditions. The returning value of the function will depend on such a condition in a way that the returning value might not even exist within the function based on the condition. In that case, I'd expect the function to throw an error. However, this only works as long as the supposed-to-return value does not also exist in the global environment. If it does, the value from the global environment is returned, which I find confusing.
What am I missing here? What am I doing wrong?
Example:
xyz <- function(x = NULL, y = NULL)
{
if (x+y > 10) {z <- x + y}
return(z)
}
If I now run test <- xyz(20, 30) I get the correct result (test = 50).
If I run test <- xyz(2, 3) I also correctly get an error:
Error in xyz(2, 3) : object 'z' not found
However, now I'm creating a z value in the global environment.
z <- 3.14
When I'm now running my function test <- xyz(2, 3) I get 3.14 as the result.
I was expecting that the function will only return the value of z if it exists inside the function. How can I do that?
Thanks
If a free variable (one referred to but not defined) in a function is accessed then it looks in the environment where the function was defined and then in the parent environment of that and so on.
Use get("z", inherits = FALSE) to look for z only in the current environment or check whether it exists exists("z", inherits = FALSE) in the current environment only.
Another possibility is to always give z a value:
z <- if (length(x) && length(y) && x + y > 10) x + y
In that case z will have the value NULL if the condition is false because the default else leg is NULL. Returning NULL invisibly would be about as close as you can get to not returning a value.
xyz2 <- function(x = NULL, y = NULL) {
z <- if (length(x) && length(y) && x + y > 10) x + y
# other processing can go here
if (is.null(z)) invisible(z) else z
}
xyz2(1, 2)
xyz2(5, 9)
## [1] 14
Why not just throw an informative error, based on the condition rather than z?
xyz <- function(x = NULL, y = NULL)
{
if ( x + y <= 10 ) {
stop("The sum of x and y must be greater than 10.")
}
return(x + y)
}
xyz(20, 30)
# [1] 50
xyz(2, 3)
# Error in xyz(2, 3) : The sum of x and y must be greater than 10.
I like duckmayr's idea, but here's how you can implement while still using z:
xyz <- function(x = NULL, y = NULL) {
z = x + y
if (z <= 10 ) {
stop("The sum of x and y must be greater than 10.")
}
return(z)
}

How to Create a function that will return the sum of 2 integers?

I've encountered a simple question but failed to write the code. Grateful if you can help me.
The question: Create a function that will return the sum of 2 integers.
My code:
q1 <- function(a, b) {
if (a == as.integer(a)) {
if (b == as.integer(b)) {
result1 <- a + b
}
}
} else {
result1 <- NA
result1
}
q1(3,6) - suppose it returns 9
q1(3.1,6) - suppose it returns NA
BTW, why can I not write the syntax in this way?
if(is.integer(a) = TRUE){
This uses a function (testInteger) in this answer, slightly edited. It checks if a value is TRUE with the now recommended isTRUE().
testInteger <- function(x){
test <- all.equal(x, as.integer(x), check.attributes = FALSE)
isTRUE(test)
}
q1 <- function(a, b) {
if (testInteger(a) && testInteger(b)) {
a + b
} else {
NA
}
}
q1(3, 6) # returns 9
q1(3.1, 6) # returns NA
Depends on what you decide to be integer. Usually you don't need your integers to be stored as an objects of class integer:
Integer vectors exist so that data can be passed to C or Fortran code which expects them, and so that (small) integer data can be represented exactly and compactly.
So, 3 is integer which is represented in R as numeric (try class(3) to see that). So I think, we don't need to check class of the values passed to your function, but rather check if those numbers are whole numbers with some level of tolerance.
sum_int <- function(..., tol = .Machine$double.eps^.5){
if(any(abs(round(c(...)) - c(...)) > tol)){
return(NA)
}
return(sum(round(c(...))))
}
sum_int(1, 2, 3 + 1e-7)
#NA
sum_int(1, 2, 3 + 1e-15)
#6
sum_int(1, 2, 3 + 1e-15, tol = 0)
#NA
sum_int(1, 2, 3.1, tol = .02)
#NA
sum_int(1, 2, 3.1, tol = .2)
#6
At the first step we grab the list of all arguments passed into the sum_int(). To find if those values are integers we use predefined or defined by user tolerance level (tol is defined the same way as it shown in ?is.integer examples). After that we round our numeric values, to make them whole numbers, and to find fractional parts of those values, as absolute difference between whole parts of the arguments, and arguments themselves. If any of the fractional parts exceed the tolerance level - we return NA. Otherwise we return sum of whole parts of those values.
You had missplaced some brackets and you were not returning anything, check this:
q1 <- function(a,b){
if (a == as.integer(a)){
if (b == as.integer(b)){
result1 <- a + b
}
}
else {
result1 <- NA
}
return(result1);
}
If you want your function to only sum() integers, as in the R object.
Intsum <- function(a,b){
if(is.integer(a) & is.integer(b) == TRUE){
return(sum(a,b))
}
else {
return(NA)
}
}

Creating a function in R but getting a replacement has length zero error

I tried to create a function f and create the function so when a value x is inserted, it spits out a function f from y.But, when I try to run the code to plot, it gives me an error that says that my y_value has no length.
f <- function(x){
if (x<0){
print(y_values<-x*x*x)
}
if(x>0 & x<=1){
print(y_values<-x*x)
}
if(x>1){
print(y_values<-sqrt(x))
}
}
x_values <- seq(-2, 2, by = 0.1)
y_values <- rep(NA, length(x_values))
for (i in seq_along(x_values)) {
x <- x_values[i]
y_values[i] <- f(x)
}
# output
plot(x_values, y_values, type = "l")
Two issues:
From ?print
‘print’ prints its argument and returns it invisibly (via
‘invisible(x)’)
So all your function f does is print the values to the console (instead of returning them).
As per your definition of f, the function does not know how to deal with x=0; so this will create a problem when you store the output of f(0) later.
We can fix these issues by slightly altering f as
f <- function(x) {
y_values <- NA
if (x<0){
y_values<-x*x*x
}
if(x>0 & x<=1){
y_values<-x*x
}
if(x>1){
y_values<-sqrt(x)
}
return(y_values)
}
Then
x_values <- seq(-2, 2, by = 0.1)
y_values <- rep(NA, length(x_values))
for (i in seq_along(x_values)) {
x <- x_values[i]
y_values[i] <- f(x)
}
plot(x_values, y_values, type = "l")
You could also use Vectorize to obtain a vectorised function f2, which allows you to pass x_values as a vector, thereby avoiding the explicit for loop:
f2 <- Vectorize(f)
x_values <- seq(-2, 2, by = 0.1)
y_values <- f2(x_values)
The resulting plot is the same.
I would recommend you explore other methods for coding something like this:
here is one option that doesn't use a for loop. If you are simply working on using for loops then the fix Mauritus Evers made should work for you.
library(tidyverse)
data.frame(x_values = seq(-2, 2, by = 0.1)) %>%
mutate(y_values = case_when(x_values < 0 ~ x_values^3,
x_values>=0 & x_values<=1 ~ x_values^2,
x_values>1 ~ sqrt(x_values))) %>%
ggplot(aes(x_values, y_values)) + geom_point()
note that I changed your code to produce output when x_value = 0.

Setting up a Horner polynomial in R

I am trying to set up a function in R that computes a polynomial
P(x) = c1 + c2*x + c3*x^2 + ... + cn-1*x^n-2 + cn*x^n-1
for various values of x and set coefficients c.
Horner's method is to
Set cn = bn
For i = n-1, n-1, ..., 2, 1, set bi = bi+1*x + ci
Return the output
What I have so far:
hornerpoly1 <- function(x, coef, output = tail(coef,n=1), exp = seq_along(coef)-1) {
for(i in 1:tail(exp,n=1)) {
(output*x)+head(tail(coef,n=i),n=1)
}
}
hornerpoly <- function(x, coef) {
exp<-seq_along(coef)-1
output<-tail(coef,n=1)
if(length(coef)<2) {
stop("Must be more than one coefficient")
}
sapply(x, hornerpoly1, coef, output,exp)
}
I also need to error check on the length of coef, that's what the if statement is for but I am not struggling with that part. When I try to compute this function for x = 1:3 and coef = c(4,16,-1), I get three NULL statements, and I can't figure out why. Any help on how to better construct this function or remedy the null output is appreciated. Let me know if I can make anything more clear.
How about the following:
Define a function that takes x as the argument at which to evaluate the polynomial, and coef as the vector of coefficients in decreasing order of degree. So the vector coef = c(-1, 16, 4) corresponds to P(x) = -x^2 + 16 * x + 4.
The Horner algorithm is implemented in the following function:
f.horner <- function(x, coef) {
n <- length(coef);
b <- rep(0, n);
b[n] <- coef[n];
while (n > 0) {
n <- n - 1;
b[n] <- coef[n] + b[n + 1] * x;
}
return(b[1]);
}
We evaluate the polynomial at x = 1:3 for coef = c(-1, 16, 4):
sapply(1:3, f.horner, c(-1, 16, 4))
#[1] 19 47 83
Some final comments:
Note that the check on the length of coef is realised in the statement while (n > 0) {...}, i.e. we go through the coefficients starting from the last and stop when we reach the first coefficient.
You don't need to save the intermediate b values as a vector in the function. This is purely for (my) educational/trouble-shooting purposes. It's easy to rewrite the code to store bs last value, and then update b every iteration. You could then also vectorise f.horner to take a vector of x values instead of only a scalar.

3-section transformation function in R

I hope this question is new as I couldn't find a solution that worked for me.
I have a vector x that can be filled with any real numbers. The elements in this vector should be transformed by a function in the following way:
x = 0, if x < a
x = (x-a)/(b-a), if a <= x <= b
x = 1, if x > b
This is the function I came up with:
transf <- function(x, a = 0, b = 1){
if(a > b) stop("\nThe lower bound must be smaller than the upper bound!")
if(x < a){
y = 0
}
else if(a <= x <= b){
y = (x-a)/(b-a)
}
else{
y = 1
}
return(y)
print(y)
}
I get a bunch of error messages that I can't quite put together. I also tried replacing else if and else with a simple if, but that didn't work either.
Any help on how to solve this would be very much appreciated
I'd use logical indexing:
i.low <- x < a
i.mid <- x >= a & x <= b
i.high <- x > b
x[i.low] <- 0
x[i.mid] <- (x[i.mid] - a) / (b - a)
x[i.high] <- 1
Your original post didn't say what the errors were, but I'd guess you were passing the vector in as your function argument and the errors were complaining "the condition has length > 1 and only the first element will be used". This is because you're testing the condition of a vector of logicals.

Resources