Ryan
Ryan

Reputation: 59

R if/else in function

I'm trying to get the following function to work for windsorizing attributes but I can't get the if elseif working in a function. It gives the following error: "the condition has length > 1 and only the first element will be used". I'm hoping someone can suggest a solution or alternative.

Example:

x <- data.frame(runif(100, 0, 100))
colnames(x) <- "test"

WINSORIZE <- function(x){
  WIN_MEAN <- mean(x)
  WIN_SD <- sd(x)
  WIN_UPPER <- sum(WIN_MEAN + (3 * WIN_SD))
  WIN_LOWER <- sum(WIN_MEAN - (3 * WIN_SD))
  if(x > WIN_UPPER){ 
    WIN_UPPER
  } else if (x < WIN_LOWER) {WIN_LOWER
    } else x
}

WINSORIZE(x$test)

Upvotes: 0

Views: 1885

Answers (1)

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 9865

Solution

Use R's immanent vectorized ability. Select by [ and change the value by <- assignment.

This solution is very R-ish:

winsorize <- function(x) {
  m <- mean(x)
  s <- sd(x)
  u <- m + 3 * s
  l <- m - 3 * s
  x[ x > u ] <- u      # select elements > u and assign to them u in situ
  x[ x < l ] <- l      # select elements < l and assign to them l in situ
  x                    # return the resulting vector
}

And also this solution is very R-ish with the already vectorized ifelse() function:

winsorize <- function(x) {
  m <- mean(x)
  s <- sd(x)
  u <- m + 3 * s
  l <- m - 3 * s
  ifelse( x > u, u, ifelse( x < l, l, x))
}

Solution with sapply()

Another possibility is to use sapply(x, ...) to apply your if-else constructs on each element of x.

winsorize <- function(x){
  m <- mean(x)
  s <- sd(x)
  upper <- m + 3 * s
  lower <- m - 3 * s
  # apply your if-else construct on each individual element (el) of x
  # using `sapply()`
  sapply(x, function(el) if(el > upper){ 
    upper
  } else if (el < lower) {
    lower
  } else {
    el})
}

Or the same with ifelse():

winsorize <- function(x){
  m <- mean(x)
  s <- sd(x)
  upper <- m + 3 * s
  lower <- m - 3 * s
  sapply(x, function(el) 
   ifelse(el > upper, upper, ifelse(el < lower, lower, el))
}

Solution with Vectorize()

Or make a function out of your if-else construct, vectorize this function using Vectorize() before you apply it on x:

winsorize <- function(x){
  m <- mean(x)
  s <- sd(x)
  upper <- m + 3 * s
  lower <- m - 3 * s
  # define function for one element
  winsorize.one.element <- function(el) {
    if(el > upper){ upper } else if (el < lower) { lower } else { el}
  }
  # Vectorize this function
  winsorize.elements <- Vectorize(winsorize.one.element)
  # Apply the vectorized function to the vector and return the result
  winsorize.elements(x)
}

This winsorize.one.element function can be written neater by ifelse, but although ifelse is vectorized

Upvotes: 1

Related Questions