21  Conditional Statements: If, If-Else, and Switch

NoteWhat this chapter covers

A program becomes useful the moment it can react to its data. Conditional statements are the way R asks “is this true?” and runs different code depending on the answer. This chapter introduces if, if … else, the chained if … else if … else form, R’s vectorised ifelse(), and the switch() statement for selecting among many named branches. By the end you will choose the right form for the job and avoid the most common pitfalls, especially feeding a vector to if.

21.1 The shape of a decision

Every conditional statement has the same skeleton, a question, then a block of code that runs only if the answer is TRUE.

if (condition) {
  # runs only when condition is TRUE
}

The condition is any expression that evaluates to a single TRUE or FALSE. Three things to remember from day one:

  1. The condition lives in round brackets ().
  2. The body lives in curly braces {}, required for multi-line bodies, optional but recommended for one-liners.
  3. The condition must be a single logical value, not a vector. A vector triggers a warning (and an error in R 4.2+).

21.2 if … else

else runs when the condition is FALSE. The else keyword must sit on the same line as the closing brace of the if block, otherwise R thinks the if is finished and chokes on the dangling else.

WarningWhere the else goes

This works:

if (x > 0) {
  "positive"
} else {
  "non-positive"
}

This breaks at the top level:

if (x > 0) {
  "positive"
}
else {                # error: unexpected 'else'
  "non-positive"
}

Always close the if brace and start else on the same line.

21.3 if … else if … else

To test several conditions in order, chain them with else if. R checks each condition top-to-bottom and runs the first branch that matches. The trailing else is the catch-all.

Order matters, list the most specific condition first. If you tested marks >= 50 before marks >= 90, every passing student would get a “D” because the first matching branch wins.

21.4 if as an expression

Almost everything in R is an expression, if included. That means it returns a value you can assign.

This is a clean alternative to writing the same status <- … line in both branches. Use it when each branch is a single short value; reach for the multi-line form when there is real work to do inside each branch.

21.5 ifelse(), the vectorised version

if works on one value. ifelse() works on a whole vector at once and is the right tool when classifying every row of a data frame.

Three arguments: a logical vector, the value to use where it’s TRUE, the value to use where it’s FALSE. The output has the same length as the input.

Tipif vs ifelse()
  • if chooses a branch of code to run, based on one condition.
  • ifelse() chooses a value for each element, from a vector of conditions.

If you find yourself wrapping a loop around if to classify a vector, switch to ifelse().

21.6 The vector-condition trap

Feeding a vector to if is the single most common beginner mistake. R uses only the first element and warns you (or errors in newer versions).

The fix depends on what you actually meant:

all(), any(), and ifelse() between them cover almost every situation where the wrong instinct would be to put a vector inside if.

21.7 switch(), branching on a value

When you have one variable and many possible values, a long if … else if chain becomes hard to read. switch() matches the value to a label and returns the matching expression.

The first argument is the value to match. Each subsequent named argument is a possible label and the expression to return. The final unnamed argument is the default for unmatched values (similar to the else of an if chain).

A handy shortcut: if a label has no expression, R falls through to the next labelled value. That removes duplication from the day-of-week example:

switch() accepts only character or single-integer matches, and it stops at the first match, there is no need for a break keyword.

21.8 Worked example, student grade and remarks

Bringing it together: classify a single student’s marks into a grade with a chained if, generate a short remark with switch(), and apply the same grade rule to a whole class with ifelse()-style logic.

For a whole class, vectorise:

Nested ifelse() works for short ladders. For longer ones, dplyr::case_when() (Chapter 19’s package) is more readable, but that is a refinement, not a correction. The pattern above is fundamental.

21.9 Summary

Form Use when
if (cond) { … } single condition, side-effect or assignment
if … else … binary choice
if … else if … else small chain of mutually exclusive cases
if (…) a else b use the result as a value (single element)
ifelse(cond, yes, no) classify a whole vector, element-wise
switch(x, a = …, b = …, default) one variable, many labelled cases
all() / any() reduce a logical vector to a single TRUE/FALSE for use inside if

Conditionals are the simplest form of control flow, but they are also where most subtle bugs live. Two habits will protect you: keep else on the same line as the closing brace, and remember that if wants a single TRUE/FALSE, anything vector-shaped belongs inside ifelse(), all(), or any().