sam
sam

Reputation: 21

trying to write a piecewise function in R

I'm fairly new to R and want to write a piecewise function , my code is not working , if you can help please comment :

   a<-rnorm(1000)

   ifelse(0<a & a<=.3,C=sqrt(30*a),NA)
   ifelse(.3<a & a<=.7,C=5*a+15/10),NA)
   ifelse(.7<a & a<=1,c=8-sqrt(-30*a+30),NA)

thank you

Upvotes: 1

Views: 281

Answers (2)

G. Grothendieck
G. Grothendieck

Reputation: 269556

Regarding the code in the question:

  1. The assignment should be done outside the ifelse, not inside

  2. ifelse will first evaluate both legs for all values so if you pass a negative a it will try to take the square root of a negative number even if that leg is not chosen. As a result it is better to avoid using ifelse entirely here.

  3. nested ifelse's can be hard to follow.

The solutions below avoid ifelse. The first one uses if ... else ... . Unlike ifelse it only evaluates legs that are actually used. It only works for scalars but we can use Vectorize to transform the scalar function that we write into a function which accepts vectors.

The second solution separates out the values of a which are between 0 and 1 from the others so that we never attempt to take a square root of a negative number and so NA's are not involved in the main computation. It uses the idiom cond1 * y1 + cond2 * y2 + ... where y1, y2, ... are numeric non-NA expressions and cond1, cond2, ... are mutually exclusive conditions. For each vector component only one of the conditions is TRUE so R multiplies the corresponding expression by 1 keeping it and the others by 0 eliminating them.

1) Use if/else and Vectorize it. This works even for negative values of a without issuing warnings which might otherwise be issued due to negative square roots and no packages are used.

f <- Vectorize(function(a)
  if (0 < a & a <= 0.3) sqrt(30 * a)
  else if (0.3 < a & a <= 0.7) 5 * a + 15 / 10
  else if (0.7 < a & a <= 1) 8 - sqrt(-30 * a + 30)
  else NA)

a <- seq(0, 1, 0.01)
c <- f(a)

head(c)
## [1]        NA 0.5477226 0.7745967 0.9486833 1.0954451 1.2247449

tail(c)
## [1] 6.775255 6.904555 7.051317 7.225403 7.452277 8.000000

plot(c ~ a, type = "l")

(continued after image) screenshot

2) Another approach is to do it in a vectorized manner but handle the values leading to an NA separately so that it never attempts to take a square root of a negative number. Again this uses no packages.

f2 <- function(x) {
  ix <- x > 0 & x <= 1
  a <- x[ix]
  replace(NA * x, ix, (0 < a & a <= 0.3) * sqrt(30 * a) +
    (0.3 < a & a <= 0.7) * (5 * a + 15 / 10) +
    (0.7 < a & a <= 1) * (8 - sqrt(-30 * a + 30)))
}

# test
c2 <- f(a)
identical(c, c2)
## [1] TRUE

Upvotes: 2

Konrad Rudolph
Konrad Rudolph

Reputation: 545588

Three issues:

  1. When used directly as part of a function call’s argument list, = isn’ the assignment operator, it’s the syntax for named arguments.
  2. Instead of attempting to assign inside the ifelse() function call argument list, assign the result
  3. You are currently performing three distinct calculations. Really you only want to perform one. To solve this, you need to nest your calls:
c = ifelse(
    0 < a & a <= 0.3, sqrt(30 * a),
    ifelse(
        0.3 < a & a <= 0.7, 5 * a + 15 / 10,
        ifelse(0.7 < a & a <= 1, 8 - sqrt(-30 * a + 30), NA)
    )
)

However, this isn’t very readable. You can use case_when() from the ‘dplyr’ package instead to make the code more readable:

c = case_when(
    0 < a & a <= 0.3 ~ sqrt(30 * a),
    0.3 < a & a <= 0.7 ~ 5 * a + 15 / 10
    0.7 < a & a <= 1 ~ 8 - sqrt(-30 * a + 30)
    TRUE ~ NA
)

Both of these solutions have the unfortunate side-effect of raising warnings due to NaN values (which are caused by the code calculating square roots of negative numbers). See G. Grothendieck’s answer for ways around this issue.

Upvotes: 5

Related Questions