dplyr::case_when

case_when(...)
Returns: vector · Updated April 5, 2026 · Tidyverse
dplyr r tidyverse data-wrangling conditionals

case_when() is dplyr’s vectorized conditional — it evaluates a series of conditions in order and returns the value from the first matching branch. It’s the tidy equivalent of SQL’s CASE WHEN, and it makes multi-branch logic in mutate() much cleaner than stacking ifelse() calls.

Basic Syntax

Each branch uses the formula syntax condition ~ value:

library(dplyr)

df <- tibble(x = 1:15)

df |>
  mutate(
    label = case_when(
      x %% 35 == 0 ~ "fizz buzz",
      x %% 5 == 0 ~ "fizz",
      x %% 7 == 0 ~ "buzz",
      TRUE ~ as.character(x)
    )
  )

TRUE acts as the catch-all fallback — without it, unmatched rows return NA.

How It Works

case_when() evaluates all conditions from top to bottom, stops at the first match for each row, and returns the corresponding value. It’s lazy in the right direction — rows that match the first condition never evaluate the later conditions.

Order matters: put the most specific conditions first, general ones last. If you put TRUE ~ "other" at the top, everything matches it and you get “other” for every row.

# This works
case_when(
  x %% 15 == 0 ~ "divisible by 15",
  x %% 5 == 0 ~ "divisible by 5",
  TRUE ~ "neither"
)

# This doesn't — every row matches the first condition
case_when(
  TRUE ~ "neither",
  x %% 5 == 0 ~ "divisible by 5"  # never reached
)

Unmatched Rows Return NA

If no branch matches, the result is NA. You can handle this explicitly with TRUE:

case_when(
  x %% 5 == 0 ~ "divisible by 5",
  x %% 3 == 0 ~ "divisible by 3",
  TRUE ~ "neither"
)

Without TRUE, rows matching neither condition get NA.

NA Handling

NA values in the source data don’t automatically get special treatment — you have to handle them explicitly with is.na():

x <- c(1, NA, 3, NA, 5)

case_when(
  x %% 5 == 0 ~ "divisible by 5",
  is.na(x) ~ "missing",
  TRUE ~ "neither"
)
#> "neither", "missing", "neither", "missing", "divisible by 5"

Type Consistency

All right-hand side values must be the same type. This means you need to use typed NA values — plain NA is logical and will throw an error in a character or numeric context:

# Wrong — NA is logical, but RHS is numeric
case_when(
  x %% 5 == 0 ~ 5,
  TRUE ~ NA   # error
)

# Correct — use NA_real_
case_when(
  x %% 5 == 0 ~ 5,
  TRUE ~ NA_real_
)

# For characters
case_when(
  x %% 5 == 0 ~ "divisible",
  TRUE ~ NA_character_
)

Vectorized Operation

case_when() is fully vectorized — it processes every row simultaneously. Each condition and each value is computed for all rows, then the matching value is selected per row:

df <- tibble(a = c(1, -1, 2, -2), b = c(4, 4, 4, 4))

df |>
  mutate(
    result = case_when(
      a > 0 & b > 0 ~ sqrt(a * b),
      TRUE ~ as.numeric(a)
    )
  )
#>    a     b result
#> <dbl> <dbl>  <dbl>
#> 1     1     4   2
#> 2    -1     4  -1
#> 3     2     4   2.83
#> 4    -2     4  -2

Because all RHS expressions are evaluated for all rows, you can get NaN from operations like sqrt() applied to negative numbers even when only some rows match:

y <- c(-2, -1, 0, 1, 2)
case_when(
  y >= 0 ~ sqrt(y),   # sqrt(-2) and sqrt(-1) evaluated too — produce NaN
  TRUE ~ y
)
#> NaN, NaN, 0, 1, 1.414

Use is.nan() or filter rows first if this matters.

Reusable Helper Functions

case_when() is not a tidy eval function — it doesn’t capture tidyverse pronouns. You can extract it into a plain R function and reuse it cleanly:

classify_size <- function(height, mass) {
  case_when(
    height > 200 | mass > 200 ~ "large",
    height > 150 | mass > 100 ~ "medium",
    TRUE ~ "small"
  )
}

starwars |>
  mutate(size = classify_size(height, mass)) |>
  select(name, height, mass, size)

case_when vs ifelse

ifelse() handles exactly two cases — one true branch, one false. case_when() handles any number of branches with arbitrary conditions:

case_when()ifelse()
BranchesUnlimitedTwo only
ConditionsArbitrary expressionsSingle condition
NAs in dataMust handle explicitlyPasses through
Recommended for mutateYesOkay for simple cases

See Also