Written and maintained by CASRAI Editorial Board
Last updated
In R, ifelse(test, yes, no) returns a vector the same length as test, taking the matching element of yes where the test is TRUE and of no where it is FALSE; a missing value in test gives a missing value in the result. It is the base R function for recoding a column or flagging rows on a condition, and it is also where many silent recoding errors come from: it drops attributes, so Dates and factors do not survive it.
What ifelse() is used for in research
Research data sets need derived variables constantly: a pass/fail flag from a score, an age band from age, a detection flag from a threshold, a recode of a coded value into a label. ifelse() is vectorised, so one call handles a whole column. For a single TRUE/FALSE decision (not a vector), the base R manual says to use if(test) yes else no instead, calling it “much more efficient and often much preferable”. Pair this page with looping in R when the task is iteration rather than recoding, and see types of variables when deciding what a recoded column should be.
Install or access
ifelse() is part of base R, so there is nothing to install. The tidyverse alternatives, if_else() and case_when(), come from the dplyr package, installed once from CRAN with install.packages("dplyr") and loaded each session with library(dplyr).
Step-by-step: recode a column with ifelse()
- Write a condition that returns a logical vector, for example
x > 10. The manual requirestestto be “an object which can be coerced to logical mode”. - Pick the value for TRUE and the value for FALSE:
ifelse(x > 10, "high", "low"). Ifyesornois shorter thantest, its elements are recycled. - Assign the result to a new column so the original is preserved:
df$band <- ifelse(df$score >= 50, "pass", "fail"). - To split into more than two groups, nest a second call in the
noargument:ifelse(x > 15, "high", ifelse(x > 5, "medium", "low")). Each extra level adds one moreifelse(and one more closing bracket, which is why long chains are better written withcase_when()(below). - Check the result against the input before using it, for example with
table(df$band, useNA = "ifany").
Output and how to read it
Take x <- c(5, 12, NA, 20). Then ifelse(x > 10, "high", "low") returns "low" "high" NA "high". Three rules from the manual explain this output:
- The result has the same length and attributes (dimensions and class) as
test, so a matrix test gives a matrix result. - A missing value in
testgives a missing value in the result, which is the third element above. - The result’s mode is coerced from logical only as far as needed to hold the first values taken from
yes, then fromno.
Missing values
The three functions treat an NA in the condition differently, and this is the main source of wrong recodes.
| Function | NA in the condition gives | How to control it |
|---|---|---|
ifelse(test, yes, no) |
NA in the result | Test is.na(x) in an outer ifelse() |
if_else(condition, true, false, missing = NULL) |
NA, unless missing is set, in which case that value |
Set missing = |
case_when() |
Treated like FALSE, so the row falls through to the next case or to .default |
Put an is.na(x) ~ ... case first |
Several categories with case_when()
case_when() evaluates each case in order and uses the first match for each element, so list the most specific condition first. Unmatched rows get .default, which is NA when left as NULL; setting .unmatched = "error" stops the code if any row matches nothing.
With the same x, case_when(is.na(x) ~ "missing", x > 15 ~ "high", x > 5 ~ "medium", .default = "low") returns "low" "medium" "missing" "high". The nested ifelse() form of the same rule is ifelse(is.na(x), "missing", ifelse(x > 15, "high", ifelse(x > 5, "medium", "low"))). For why NA arises in the first place, see MCAR, MAR and MNAR.
What to do when no condition matches
There is no “do nothing” branch. In a nested ifelse() the innermost no value is what every unmatched row receives. In case_when() it is .default. To keep the original value for unmatched rows, pass the original column as that fallback, for example ifelse(x == 99, NA, x).
Troubleshooting
Dates turn into numbers
The manual notes that ifelse() strips attributes and that this matters when working with Dates and factors. A Date returned through ifelse() comes back as a plain number. Restore the class afterwards with as.Date(), or use if_else(), which preserves types.
Factors turn into integer codes
dplyr’s reference states that, for factors, ifelse() returns integer codes while if_else() keeps the factor. If a recoded column shows 1, 2, 3 instead of labels, convert the factor with as.character() first.
The result type depends on the test
The manual warns that the mode of the result can depend on the value of test, and that the class taken from test may be inappropriate for the values chosen from yes and no. Keep yes and no the same type (both text, both numeric) to avoid surprises.
The condition has length one
A single condition returns a single value, which is correct but slower than needed. Use if (cond) a else b there, as the manual recommends.
Free alternatives
| Tool | Use it when | Documented behaviour |
|---|---|---|
ifelse() (base R) |
A two-way recode with no package dependency | Strips attributes; NA in gives NA out |
if_else() (dplyr) |
A two-way recode where the type must be kept (Dates, factors) or NA needs its own value | Preserves types; missing argument; ptype sets output type |
case_when() (dplyr) |
Three or more categories | First match wins; .default; .unmatched = "error" |
if (cond) a else b (base R) |
One TRUE/FALSE decision, not a vector | Recommended by the manual for this case |
FAQ
What is the difference between ifelse and if_else in R?
ifelse() is base R and drops attributes such as Date and factor classes. if_else() is from dplyr, preserves types, and has a missing argument for NA values in the condition.
How do I use ifelse in R with several conditions?
Nest calls in the no argument, or combine tests with & and | inside one condition. For more than three outcomes, case_when() is easier to read and check.
Does ifelse work on a whole data frame column?
Yes. It is vectorised and returns one result per element of the test, so df$new <- ifelse(df$x > 10, "high", "low") fills the whole column. Once a variable has clean category labels it can be plotted directly, as in box plots in R and histograms in R.
Sources
- R Core Team, base R manual, ifelse: Conditional Element Selection (checked 2026-10-29).
- dplyr reference, if_else() (checked 2026-10-29).
- dplyr reference, case_when() (checked 2026-10-29).








