flowchart LR
V["Vectors"] --> A["Arithmetic <br> + - * / ^ %% %/%"]
V --> R["Relational <br> < <= > >= == !="]
V --> L["Logical <br> & | ! xor"]
A --> N["Numeric Vector"]
R --> LV["Logical Vector"]
L --> LV
LV --> S["Summarise <br> sum / mean / any / all / which"]
LV --> F["Filter <br> x[cond], x[!is.na(x) & ...]"]
style V fill:#e3f2fd,stroke:#1976D2
style A fill:#fff3e0,stroke:#F57C00
style R fill:#fff3e0,stroke:#F57C00
style L fill:#fff3e0,stroke:#F57C00
style N fill:#e8f5e9,stroke:#388E3C
style LV fill:#e8f5e9,stroke:#388E3C
style S fill:#f3e5f5,stroke:#8E24AA
style F fill:#f3e5f5,stroke:#8E24AA
11 Arithmetic, Relational, and Logical Operations on Vectors
This chapter shows how the three families of operators you met in Chapter 7 behave when both sides are vectors. You will see that R applies arithmetic operators element-by-element, that relational operators produce logical vectors of the same length as their inputs, and that logical operators combine those results cleanly for filtering and decision making. You will meet the family of summary functions that collapse a vector into a single number (sum(), mean(), min(), max(), prod(), range(), var(), sd()), the helper functions that count or locate matches (any(), all(), which()), and the vectorised decision-maker ifelse(). You will also see two small patterns that recur through the book: centring and scaling, and filter-then-summarise. By the end of this chapter you will be able to read a column of data and answer most first-pass questions about it in a single line.
11.1 Arithmetic on Vectors
Every arithmetic operator acts on each pair of corresponding elements. Adding two length-5 vectors produces a length-5 result.
A single number is a vector of length 1. Combining it with a longer vector recycles it to match, which is the idiom behind shifting or scaling every element at once.
Coming from languages that force you to loop over arrays, it is tempting to write for (i in 1:length(x)) y[i] <- x[i] + 1. R lets you write y <- x + 1. The vectorised form is shorter, clearer, and much faster, because the loop happens in compiled C code instead of interpreted R code. Reach for vectorisation first; reach for a for loop only when you genuinely cannot avoid it.
11.2 Vector Summary Functions
| Function | What It Returns |
|---|---|
sum(x) |
Sum of all elements. |
prod(x) |
Product of all elements. |
mean(x) |
Arithmetic mean. |
median(x) |
Middle value (50th percentile). |
min(x), max(x) |
Smallest, largest element. |
range(x) |
Vector of c(min, max). |
var(x), sd(x) |
Sample variance and standard deviation. |
cumsum(x), cumprod(x) |
Running sum and product (returns a vector the same length as x). |
cummax(x), cummin(x) |
Running maximum and minimum. |
NA
All of the summary functions above take na.rm = TRUE to skip missing values. This is the default way to produce a valid answer when the data contains gaps.
11.3 Relational Operations on Vectors
Every relational operator returns a logical vector of the same length as its input. That vector then drives filtering, counting, and decision-making downstream.
any(), all(), which(): The Logical-Vector Helpers
| Function | What It Answers |
|---|---|
any(cond) |
Is at least one element TRUE? |
all(cond) |
Are all elements TRUE? |
which(cond) |
What are the positions of the TRUE elements? |
sum(cond) |
How many elements are TRUE? (logical coerced to integer) |
mean(cond) |
What fraction of elements are TRUE? |
sum(cond) Is the Fastest Counter
A vectorised comparison returns TRUEs and FALSEs. When you pass those to sum(), R coerces them to 1s and 0s and adds them up. That is a faster, clearer way to count matches than writing a loop or using length(which(cond)).
11.4 Logical Operations on Vectors
&, |, !, and xor() all work element-wise on logical vectors and are the right choice when filtering a whole vector. && and || expect single values and are for single-branch if tests.
&& and &
&& and || stop at the first element and return a single value. Using them to filter a vector is a bug, not a shortcut. From R 4.3 onward, R warns loudly when you do it; before that, it silently gave the wrong answer.
11.5 Filtering with Logical Vectors
x[condition]
The result of a relational or logical operation is a logical vector that can be used as an index. Keeping the elements where the condition is TRUE is the most common operation in all of data analysis.
As Chapter 10 showed, NA leaks through comparisons. When filtering a vector that might contain missing values, guard the condition with !is.na(x) & ....
11.6 ifelse(): A Vectorised if
ifelse(condition, yes, no) takes three vectors of compatible length (recycling as needed) and returns, for each element, either the “yes” value or the “no” value depending on the condition. It is how you build derived columns without writing a loop.
ifelse() Is for Vectors, if Is for Control Flow
if (...) ... else ... returns a single value and decides which code to execute. ifelse(...) returns a vector and is purely a computation. Mixing them up leads to either silent bugs (using if where a vector was expected) or wasted work (using ifelse() inside a function that only needs one result).
11.7 A Worked Example: Two Standard Patterns
Centring (subtracting the mean) and scaling (dividing by the standard deviation) are so common that R ships a function called scale(). Writing the calculation longhand is a good way to see vector arithmetic at work.
Answering a real question is usually a two-step move: filter the vector, then summarise what is left.
11.8 Summary
| Concept | Key Takeaway |
|---|---|
| Arithmetic vectorises | +, * / ^ %% %/% act element by element and recycle a shorter operand. |
| Summary functions | sum, mean, median, min, max, range, sd, var, cumsum, cummax. |
na.rm = TRUE |
The canonical way to skip missing values inside a summary. |
| Relational operators | Produce logical vectors ready for filtering or counting. |
any, all, which, sum, mean |
Five idioms for “at least one / all / which positions / how many / what fraction”. |
| Logical operators | & | for element-wise vector work; && || only for single-value if. |
| Filtering | x[cond] is the bedrock idiom; guard with !is.na(x) & ... when gaps exist. |
ifelse() |
Vectorised branching for derived columns and label construction. |
| Two patterns | Centre-and-scale and filter-then-summarise cover most first-pass analyses. |
Operators turn vectors from “collections of numbers” into “one-line answers”. Whenever you find yourself writing a loop to add, compare, or count, pause and ask whether a vectorised expression would do the same job. In the next chapter you will meet lists, the structure R uses whenever a vector is not enough because the elements do not share a single type.