The if Statement
An if statement in R runs a block of code only when a condition is TRUE. The condition goes in parentheses, the code in braces, and an optional else supplies what happens otherwise:
The condition score >= 60 evaluates to a single TRUE, so the first branch runs. The comparison and logical operators - ==, >=, &&, %in% and friends - are what you build these conditions from.
For more than two outcomes, chain checks with else if. R walks the chain from the first condition down and runs the first branch whose condition is TRUE, skipping the rest:
Order matters: because 85 >= 80 is checked before 85 >= 70, the grade is "B". If you sorted the checks the other way, every score above 70 would get a "C".
The Brace Pitfall: Where else Must Go
This is the error that bites almost everyone's first R script. In a script (or anything run with Rscript), else must appear on the same line as the closing brace of the branch before it. This fails:
if (score >= 60) {
print("You passed")
}
else { # Error: unexpected 'else'
print("Try again")
}
Why: R parses statement by statement. When it reaches the } at the start of a line, the if statement is syntactically complete, and R evaluates it then and there. The else on the next line arrives as the start of a new statement - and a statement can't begin with else. The fix is the pattern used throughout this page:
if (score >= 60) {
print("You passed")
} else { # brace and else on the same line
print("Try again")
}
Inside a function body or any outer set of braces the strict rule relaxes (R keeps reading until the outer brace closes), but don't rely on that - the } else { style works everywhere and is what R style guides prescribe.
The Condition Must Be One TRUE or FALSE
if() demands exactly one logical value. Two things violate that, and both are errors:
if (NA) print("hi")
# Error: missing value where TRUE/FALSE needed
if (c(TRUE, FALSE)) print("hi")
# Error: the condition has length > 1
The NA case matters in practice because comparisons with missing data produce NA: if x is NA, then x > 10 is NA, and an if fed that condition stops your script. The defensive pattern uses short-circuiting &&:
Because !is.na(x) is FALSE, the x > 10 part is never evaluated and no NA reaches the condition. How NA propagates through comparisons is covered in missing values.
The length-greater-than-1 error (enforced since R 4.2; before that R silently used the first element) usually means you meant one of these instead: any(v > 5) if one match is enough, all(v > 5) if every element must pass, or ifelse() if you wanted a per-element answer - which is the next section.
ifelse(): Vectorized If Else
if decides once. ifelse(test, yes, no) decides for every element of a vector and returns a vector of the same length:
Each element of temps is tested independently: "hot" "mild" "mild" "hot" "mild". This is the tool for deriving a new vector or data-frame column from an existing one - no loop needed.
Unlike if, ifelse() handles missing values gracefully: an NA in the test yields an NA in the result rather than an error:
You can nest ifelse() calls for three-way splits, and at that depth it's still readable:
But stop there. Three or more nested ifelse() calls become a bracket-matching puzzle; at that point reach for cut() (for numeric ranges) or dplyr::case_when(), which lays each condition on its own line.
switch() for Multi-Way Choices
When you're matching one value against a list of known names - not testing ranges - a long else if chain is the clumsy tool. switch() says it directly:
Three behaviors to know:
- Matching:
switch()compares the character value against the argument names and returns the matching result. - Fall-through: a name with an empty right side (
sat = ,) falls through to the next case's result - that's how"sat"and"sun"share"weekend"without repeating it. - Default: the final unnamed argument (
"weekday") is returned when nothing matches. Without a default, an unmatched value makesswitch()returnNULLinvisibly - your function silently returns nothing, which is a confusing bug. Always supply a default (or make it an explicitstop()).
switch() shines inside functions that dispatch on a mode or method argument; for anything based on numeric thresholds, stick with else if or cut().
What You Take Away
if (condition) { } else { }- condition in parentheses, one branch runs; chain withelse if, first match wins.- In scripts, write
} else {on one line -elseat the start of a line is a syntax error. if()needs exactly oneTRUE/FALSE:NAis an error, a longer vector is an error (since R 4.2). Guard with!is.na(x) &&, or reduce withany()/all().ifelse(test, yes, no)is the vectorized version - use it to compute new vectors, and don't nest it more than twice.switch()handles multi-way dispatch on names, with fall-through via empty cases - always give it a default.
Next up: doing something more than once - for loops, and when a loop is (and isn't) the right tool.
Frequently Asked Questions
How do you write an if else statement in R?
if (condition) { ... } else { ... } - the condition goes in parentheses and each branch in braces. Chain further checks with else if (condition). In a script, else must sit on the same line as the closing brace of the previous branch (} else {), or R reports unexpected 'else'.
What is the difference between if and ifelse() in R?
if is a control-flow statement that tests exactly one TRUE/FALSE value and runs one branch of code. ifelse(test, yes, no) is a vectorized function: it tests every element of a vector at once and returns a vector of results. Use if to decide what your program does; use ifelse() to compute a new column or vector from an existing one.
Why does R say 'the condition has length > 1'?
You passed a whole vector to if(), which needs a single TRUE or FALSE. Since R 4.2 this is an error rather than a silent use of the first element. Fix it by reducing the vector to one value - any(x > 5), all(x > 5) - or by switching to the vectorized ifelse() if you wanted a per-element result.
How does switch() work in R?
switch(value, name1 = result1, name2 = result2, default) compares a character value against the argument names and returns the matching result. A name with an empty right side falls through to the next result, so several cases can share one outcome. The last unnamed argument acts as the default; without one, an unmatched value returns NULL invisibly.