Control Flow

IF

Syntax:

if (condition) true_action
if (condition) true_action else false_action

Return value of if

x <- if (TRUE) 3
x
#> [1] 3
y <- if (FALSE) 3
y
#> NULL
if(NA) 3
#> Error in if (NA) 3: missing value where TRUE/FALSE needed

Note that, however:

is.logical(NA)
#> [1] TRUE

Use case of NULL from if (FALSE)

c() and paste() drop NULL inputs, this allows for a compact expression of certain idioms:

greet <- function(name, birthday = FALSE) {
  paste0(
    "Hi ", name,
    if (birthday) " and HAPPY BIRTHDAY"
  )
}

greet("A")
#> [1] "Hi A"
greet("A", birthday = TRUE)
#> [1] "Hi A and HAPPY BIRTHDAY"

Vectorized IF

x <- 1:10

ifelse(x %% 5 == 0, "XXX", as.character(x))
#>  [1] "1"   "2"   "3"   "4"   "XXX" "6"   "7"   "8"   "9"   "XXX"

Test Vectorized IF

library(emo)
x <- 1:10

If x can be divided by:

  • 3 return πŸ§€

  • 5 return πŸš—

  • otherwise, return x

if with Loop

f_loopif <- function(x) {
  
  out <- character(length(x))
  
  for (i in seq_along(x)) {
    
    out[i] <- if (x[i] %% 3 == 0) {
      
      ji("cheese")
      
    } else if (x[i] %% 5 == 0) {
      
      ji("car")
      
    } else {
      x[i]
    }
    
  }

  out
  
}

f_loopif(x)
#>  [1] "1"  "2"  "πŸ§€" "4"  "πŸš—" "πŸ§€" "7"  "8"  "πŸ§€" "πŸš—"

ifelse approach

f_ifelse <- function(x) {
  
  ifelse(x %% 3 == 0, ji("cheese"),
    ifelse(x %% 5 == 0, ji("car"), x)
  )
  
}

f_ifelse(x)
#>  [1] "1"  "2"  "πŸ§€" "4"  "πŸš—" "πŸ§€" "7"  "8"  "πŸ§€" "πŸš—"

Logical Subsetting

f_subset <- function(x) {
  
  out <- character(length(x))
  
  # Assign value to location where `x` is: 
  out[x %% 3 == 0] <- ji("cheese") # divided by 3
  out[x %% 5 == 0] <- ji("car") # divided by 5
  out[out == ""] <- x[out == ""] # and the rest
  out
}

f_subset(x)
#>  [1] "1"  "2"  "πŸ§€" "4"  "πŸš—" "πŸ§€" "7"  "8"  "πŸ§€" "πŸš—"

case_when

f_case_when <- function(x) {
  dplyr::case_when(
    x %% 3 == 0 ~ as.character(ji("cheese")),
    x %% 5 == 0 ~ as.character(ji("car")),
    TRUE ~ as.character(x)
  )
}

f_case_when(x)
#>  [1] "1"  "2"  "πŸ§€" "4"  "πŸš—" "πŸ§€" "7"  "8"  "πŸ§€" "πŸš—"

Benchmark

Let’s measure a performance of 3 approachs of control flow over vector.

mark_ctrflow <- function(x) {
  bench::mark(
    loopif = f_loopif(x), # Loop with if
    ifelse = f_ifelse(x), # ifelse
    subset = f_subset(x),  # subset
    case_when = f_case_when(x) # dplyr::case_when()
  )
}
set.seed(123)

results <- mark_ctrflow(1:10)
results
#> # A tibble: 4 Γ— 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 loopif      184.1Β΅s  241.9Β΅s     3949.   165.8KB     13.9
#> 2 ifelse       89.8Β΅s  101.9Β΅s     7569.    88.1KB     10.4
#> 3 subset       68.9Β΅s   83.9Β΅s    10979.   110.4KB     13.9
#> 4 case_when   198.5Β΅s  247.6Β΅s     3149.    76.2KB     17.2
ggplot2::autoplot(results)
#> Loading required namespace: tidyr

Conclusion

It seems like using vector subsetting is the fastest, followed by ifelse, and then dplyr::case_when() or looping over if approach is about the same.