dplyr::case_when
case_when(...) vector · Updated April 5, 2026 · Tidyverse 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() | |
|---|---|---|
| Branches | Unlimited | Two only |
| Conditions | Arbitrary expressions | Single condition |
| NAs in data | Must handle explicitly | Passes through |
| Recommended for mutate | Yes | Okay for simple cases |
See Also
- dplyr::mutate — creating and transforming columns where
case_when()is most often used - dplyr::filter — filtering rows based on conditions
- base R ifelse — simple two-branch conditionals