Skip to main content
v2026.11,858 entries · CC-BY 4.0

ifelse in R: Syntax, NA Handling and Pitfalls

How ifelse() works in R, how it treats NA, why it strips Dates and factors, and when to use if_else() or case_when() instead.

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()

  1. Write a condition that returns a logical vector, for example x > 10. The manual requires test to be “an object which can be coerced to logical mode”.
  2. Pick the value for TRUE and the value for FALSE: ifelse(x > 10, "high", "low"). If yes or no is shorter than test, its elements are recycled.
  3. Assign the result to a new column so the original is preserved: df$band <- ifelse(df$score >= 50, "pass", "fail").
  4. To split into more than two groups, nest a second call in the no argument: ifelse(x > 15, "high", ifelse(x > 5, "medium", "low")). Each extra level adds one more ifelse( and one more closing bracket, which is why long chains are better written with case_when() (below).
  5. 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 test gives 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 from no.

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

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about ifelse in R: Syntax, NA Handling and Pitfalls

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →

Ask CASRAI · Regulatory Radar

Research-admin question? Get an answer that links its sources.

An AI assistant specialized in research administration. Every answer links its sources to check before you act. 2 questions free, no account. $29/month after.

  • Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.
  • Every answer numbers its sources and links each one, so you can check the source yourself.