R (4.6) Module 2 – Vectors, types and missing values
Vector arithmetic, recycling and sequences
Do arithmetic on whole R vectors, predict how recycling pairs vectors of different lengths, and build sequences and totals with seq_len(), rep() and cumsum().
What you will learn
- Apply arithmetic and maths functions element by element to whole vectors
- Predict recycling when vectors of different lengths meet, and when R warns
- Generate sequences with :, seq(), seq_len(), seq_along() and rep()
- Compute running totals, changes and tables with cumsum(), cumprod(), diff() and outer()
Before you start
What you’ll learn · Before you start
What you will learn
- Apply arithmetic and maths functions element by element to whole vectors
- Predict recycling when vectors of different lengths meet, and when R warns
- Generate sequences with :, seq(), seq_len(), seq_along() and rep()
- Compute running totals, changes and tables with cumsum(), cumprod(), diff() and outer()
Before you start
On this page
Converting 1,000 heights from centimetres to metres takes one line in R, heights_cm / 100, and no loop, because
arithmetic works on every element of a vector at once. When two vectors of different lengths meet, R repeats the
shorter one, and it warns about only some mismatches: 1:5 + 1:2 gets a warning, while 12 months paired with 4
quarterly factors gets none.
The runs on this page, from R 4.6.1, show how element-by-element arithmetic works, when recycling helps and when it
hides a mistake, how to count positions in a way that survives an empty vector, how rep() builds the columns of an
experimental design, and how cumsum(), diff() and outer() turn values into running totals, changes and tables.
Arithmetic and maths functions work on every element
heights_cm / 100 divides each of the four heights by 100 and returns four results. An Introduction to R puts the rule
in one line: in an arithmetic expression the operations are performed element by element. Two vectors of the same
length are paired position by position, so c(10, 20, 30) + c(1, 2, 3) is 11 22 33. Arithmetic like this, applied to
a whole vector with no loop written by you, is called vectorised: Applied to every element of a vector at once, with no loop written by you. R's arithmetic, comparisons and most maths functions, such as sqrt() and round(), are vectorised., and so are most maths
functions: sqrt(), log10(), exp() and round() each return one result per element.
from session import run, version
version()
# R reads vectorised.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f vectorised.R
""") Output
# R version 4.6.1 -- "Happy Hop" $ R --vanilla --quiet -f vectorised.R > # One line converts every height, with no loop. > heights_cm <- c(150, 162, 171, 158) > heights_cm / 100 [1] 1.50 1.62 1.71 1.58 > # Two vectors of the same length meet element by element. > c(10, 20, 30) + c(1, 2, 3) [1] 11 22 33 > c(10, 20, 30) * c(1, 2, 3) [1] 10 40 90 > # Maths functions work on every element too. > sqrt(c(4, 9, 16)) [1] 2 3 4 > round(c(1.234, 5.678), 1) [1] 1.2 5.7 > log10(c(1, 10, 100)) [1] 0 1 2 > # The types of the inputs decide the type of the result. > typeof(1:3 * 2L) [1] "integer" > typeof(1:3 * 2) [1] "double" > typeof(6L / 2L) [1] "double" > # %/% divides and rounds down; %% gives the remainder. > c(7, 8, 9) %/% 2 [1] 3 4 4 > c(7, 8, 9) %% 2 [1] 1 0 1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vectorised_session.py
The last lines show that the inputs decide the type of the result. An integer times an integer stays an integer, an
integer times the double 2 becomes a double, and / always gives a double, even for 6L / 2L. %/% divides and
rounds down and %% gives the remainder, so c(7, 8, 9) %/% 2 is 3 4 4 and c(7, 8, 9) %% 2 is 1 0 1.
Write the vector version first
A loop such as for (i in 1:4) m[i] <- heights_cm[i] / 100 gives the same four values, with a counter, an index and
a result vector that can each go wrong. heights_cm / 100 cannot miss an element or stop one short.
Checkpoint
Check: Arithmetic on whole vectors
A shorter vector is recycled, and only an uneven fit warns
1:6 + 1:2 adds two vectors of different lengths and still returns six values. R repeats the shorter vector from its
start until it is as long as the longer one, which is called recycling: R's rule for vectors of different lengths in an element-by-element operation: the shorter one is repeated from its start until it is as long as the longer one. R warns only when the lengths do not divide evenly.. The help page
?Arithmetic gives the rule and its one exception: shorter vectors are recycled as needed, with a warning only when
they are recycled fractionally, and a zero-length vector makes the whole result zero-length. Step through two sums:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Two lengths. 1:6 has six elements and 1:2 has two. Adding them element by element needs a partner for each of the six, and only the first two have one.
- Repeat from the start. R repeats 1:2 from its first element until it is as long as 1:6, so the partners are 1 2 1 2 1 2. Six is a multiple of two, so the copies fit exactly.
- 1:6 + 1:2. Each pair is added, and R prints 2 4 4 6 6 8 with no message at all, because the fit was even.
- 1:5 + 1:2. With five elements the third copy of 1:2 is cut after its first value. R still pairs them, 1 2 1 2 1, and prints 2 4 4 6 6.
- The warning. Five is not a multiple of two, so R adds a warning: longer object length is not a multiple of shorter object length. An even fit never warns, whether or not it was what you meant.
Recycling is how heights_cm / 100 works at all: the single number 100 is a vector of length 1, recycled to every
element. The run below adds a few more cases, then the one that matters most, and its fix:
from session import run, version
version()
# R reads recycling.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f recycling.R
""") Output
# R version 4.6.1 -- "Happy Hop" $ R --vanilla --quiet -f recycling.R > # A single number is recycled to every element. > c(5, 10, 15) - 1 [1] 4 9 14 > # A shorter vector is repeated from its start. > 1:6 + 1:2 [1] 2 4 4 6 6 8 > 1:6 + c(100, 200, 300) [1] 101 202 303 104 205 306 > # An uneven fit still gives an answer, with a warning. > 1:5 + 1:2 [1] 2 4 4 6 6 Warning message: In 1:5 + 1:2 : longer object length is not a multiple of shorter object length > # A zero-length vector gives a zero-length answer. > 1:3 + numeric(0) numeric(0) > # Even recycling is silent, whatever you meant by it. > monthly <- rep(100, 12) > quarterly_factor <- c(0.9, 1.0, 1.1, 1.2) > monthly * quarterly_factor [1] 90 100 110 120 90 100 110 120 90 100 110 120 > # One factor per quarter: repeat each one for its 3 months. > factors <- rep(quarterly_factor, each = 3) > stopifnot(length(monthly) == length(factors)) > monthly * factors [1] 90 90 90 100 100 100 110 110 110 120 120 120
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 recycling_session.py
monthly * quarterly_factor was meant to apply one factor per quarter to 12 monthly figures. Twelve is a multiple
of four, so R recycled the 4 factors three times without a word, and paired January with the first quarter’s factor,
February with the second’s, March with the third’s and April with the fourth’s: 90, 100, 110, 120, then the same
again. The last lines build the 12 factors that were meant, three months per quarter, and check the lengths first.
Taking silence for agreement
No warning only means that the lengths fit evenly, not that the pairing is the one you meant. Make the intended
vector explicitly, factors <- rep(quarterly_factor, each = 3), so both vectors have 12 elements, and check lengths
that must match with stopifnot(length(monthly) == length(factors)), as the run does.
The same silence covers every even fit, 6 values with 3 or 100 with 25, so the check has to come from you.
Key idea
R recycles the shorter vector from its start to the length of the longer one; it warns only when the lengths do not divide evenly, so an even fit with the wrong pairing passes in silence.
Checkpoint
Check: Recycling, and when R warns
Count positions in a way that survives an empty vector
1:5 counts up and 5:1 counts down, which is exactly the trouble with 1:length(x): when x is empty its length is
0, and 1:0 counts down to give the two numbers 1 0. A loop over it runs twice on a vector that has nothing in it.
seq_along(x) gives the positions of x and seq_len(n) the numbers 1 to n, and both give integer(0), no
numbers at all, when there is nothing to count:
| Expression | x holds 3 values |
x is empty |
|---|---|---|
1:length(x) |
1 2 3 |
1 0 |
seq_len(length(x)) |
1 2 3 |
integer(0) |
seq_along(x) |
1 2 3 |
integer(0) |
from session import run, version
version()
# R reads sequences.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f sequences.R
""") Output
# R version 4.6.1 -- "Happy Hop" $ R --vanilla --quiet -f sequences.R > # 1:n counts up, or down when n is below 1. > 1:5 [1] 1 2 3 4 5 > 5:1 [1] 5 4 3 2 1 > # An empty vector has length 0, so 1:length(x) is 1 0. > x <- numeric(0) > length(x) [1] 0 > 1:length(x) [1] 1 0 > # A loop over 1:length(x) runs twice on an empty vector, > # and reads x[1] and x[0], which do not exist. > for (i in 1:length(x)) print(x[i]) [1] NA numeric(0) > # seq_along() and seq_len() give no positions at all. > seq_along(x) integer(0) > seq_len(length(x)) integer(0) > for (i in seq_along(x)) print(x[i]) > # With three values, every form gives 1 2 3. > x <- c(4.2, 3.9, 5.1) > seq_along(x) [1] 1 2 3 > # seq() takes a step, or the length you want. > seq(2, 11, by = 3) [1] 2 5 8 11 > seq(0, 1, length.out = 5) [1] 0.00 0.25 0.50 0.75 1.00
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sequences_session.py
On the empty vector the loop over 1:length(x) ran twice, printing NA for x[1] and numeric(0) for x[0], while
the loop over seq_along(x) printed nothing, which is right. seq() covers the other regular sequences: seq(2, 11, by = 3) steps from 2 to 11 in threes,
and seq(0, 1, length.out = 5) asks for five evenly spaced values and works out the step, 0.25, itself.
Looping over 1:length(x)
for (i in 1:length(x)) works on every vector except an empty one, where it runs for i = 1 and i = 0, reading
x[1] as NA and x[0] as an empty vector. Write for (i in seq_along(x)), which simply does not run.
Checkpoint
Check: Counting positions safely
rep() repeats values to build the columns of a design
An experiment with 3 blocks and 2 treatments has 6 plots, and its data need a block column and a treatment column
that line up row by row. rep() builds both. each repeats every value before moving on, and times repeats the
whole vector. Step through the two calls, then read the rows across:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- each = 2. each = 2 repeats every value twice before moving on, so rep(1:3, each = 2) gives 1 1 2 2 3 3. Rows 1 and 2 belong to block 1.
- times = 3. times = 3 repeats the whole vector three times, so rep(c("ctrl", "trt"), times = 3) gives ctrl trt ctrl trt ctrl trt.
- Read across. Both columns have six values, so row i of one belongs with row i of the other. Every block holds ctrl once and trt once, which makes the design balanced; paste(block, treatment) prints 1 ctrl, 1 trt, 2 ctrl and so on.
- The wrong argument. Made with each = 3 instead, the treatments would be ctrl ctrl ctrl trt trt trt. Block 1 would get ctrl twice and never trt, and R would not warn, because the lengths still match.
The run builds the same columns, then a trial with 4 arms over 7 days, whose 28 rows come from each = 7 for the arm
and times = 4 for the day:
from session import run, version
version()
# R reads design.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f design.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f design.R
> # A balanced design: 3 blocks, each with both treatments.
> block <- rep(1:3, each = 2)
> treatment <- rep(c("ctrl", "trt"), times = 3)
> block
[1] 1 1 2 2 3 3
> treatment
[1] "ctrl" "trt" "ctrl" "trt" "ctrl" "trt"
> paste(block, treatment)
[1] "1 ctrl" "1 trt" "2 ctrl" "2 trt" "3 ctrl" "3 trt"
> # Different counts for each value, or a length to stop at.
> rep(c("ctrl", "trt"), times = c(2, 4))
[1] "ctrl" "ctrl" "trt" "trt" "trt" "trt"
> rep(c("ctrl", "trt"), length.out = 5)
[1] "ctrl" "trt" "ctrl" "trt" "ctrl"
> # A trial with 4 arms over 7 days has 28 rows.
> arm <- rep(c("A", "B", "C", "D"), each = 7)
> day <- rep(seq_len(7), times = 4)
> length(arm)
[1] 28
> head(paste(arm, day), 9)
[1] "A 1" "A 2" "A 3" "A 4" "A 5" "A 6" "A 7" "B 1" "B 2"
> # sequence() joins several counts from 1: 1:3, then 1:2.
> sequence(c(3, 2))
[1] 1 2 3 1 2
> # Two starts with one length: R 4.6 warns by default ...
> sequence(2, from = c(1, 10))
[1] 1 2
Warning message:
In sequence.default(2, from = c(1, 10)) :
length(nvec) 1 < 2 = max(length(from), length(by)) -- future R`s default 'recycle = TRUE' will recycle 'nvec'
> # ... and recycles the length when you ask it to.
> sequence(2, from = c(1, 10), recycle = TRUE)
[1] 1 2 10 11
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 design_session.py
times can also be a vector with one count per value, so rep(c("ctrl", "trt"), times = c(2, 4)) gives two controls
and four treated plots, and length.out stops the repetition at a length, mid-copy if need be. sequence() joins
several counts from 1 into one vector: sequence(c(3, 2)) is 1 2 3 1 2.
New in R 4.6.0: sequence(recycle =)
R 4.6.0 gave sequence() a recycle argument. With the default, FALSE, one length and two starts give only the
first sequence, 1 2, and the run shows the warning that a future R will change this default. With
recycle = TRUE, R recycles the length too and gives 1 2 10 11. Set it explicitly in code that uses more starts
than lengths.
Checkpoint
Check: Repeating values with rep()
cumsum(), diff() and outer() give running totals, changes and tables
A week of rainfall, 12 0 5 30 8 0 21 mm, becomes a running total with cumsum(): 12 12 17 47 55 55 76. Its
relatives work the same way along the vector: cumprod() multiplies as it goes, and cummax() and cummin() keep
the highest and lowest value so far. diff() goes the other way, from values to the change between neighbours:
diff(c(1, 4, 9, 16)) is 3 5 7, and differences = 2 takes the changes of the changes, 2 2. outer() pairs every
value of one vector with every value of another and returns the table of results.
from session import run, version
version()
# R reads running.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f running.R
""") Output
# R version 4.6.1 -- "Happy Hop" $ R --vanilla --quiet -f running.R > # Daily rainfall in mm, and the running total. > rain <- c(12, 0, 5, 30, 8, 0, 21) > cumsum(rain) [1] 12 12 17 47 55 55 76 > # diff() gives the change from one value to the next. > diff(c(1, 4, 9, 16)) [1] 3 5 7 > diff(c(1, 4, 9, 16), differences = 2) [1] 2 2 > diff(cumsum(rain)) [1] 0 5 30 8 0 21 > # cummax() keeps the highest value so far. > cummax(c(3, 1, 4, 1, 5)) [1] 3 3 4 4 5 > # cumprod() multiplies as it goes: 10,000 at 8 % a year. > round(cumprod(rep(1.08, 10)) * 10000) [1] 10800 11664 12597 13605 14693 15869 17138 18509 19990 21589 > # outer() pairs every value of one vector with every value > # of another: daily doses in mg, total after each day. > daily <- c(A = 0, B = 5, C = 10, D = 20) > outer(daily, 1:7) [,1] [,2] [,3] [,4] [,5] [,6] [,7] A 0 0 0 0 0 0 0 B 5 10 15 20 25 30 35 C 10 20 30 40 50 60 70 D 20 40 60 80 100 120 140
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 running_session.py
diff(cumsum(rain)) gives back every day’s rainfall except the first, because each total minus the one before is that
day’s amount. outer(daily, 1:7) multiplies each arm’s daily dose by each day number, so row B reads 5, 10, 15 up to
35 mg after a week.
cumprod(rep(1.08, 10)) * 10000 is a deposit of 10,000 growing by 8 % a year: each year’s balance is the year before
times 1.08. The model below computes the same balance; its table gives 10,800 for the first year and 21,589 for the
tenth, the values the run printed. Change the rate or the years, then check one value with the same line in your R:
10,000 at 8 % a year grows to 21,589 in 10 years; 11,589 of it is interest.
The numbers as a table
| Year | Paid in | Balance |
|---|---|---|
| 1 | 10,000 | 10,800 |
| 2 | 10,000 | 11,664 |
| 3 | 10,000 | 12,597 |
| 4 | 10,000 | 13,605 |
| 5 | 10,000 | 14,693 |
| 6 | 10,000 | 15,869 |
| 7 | 10,000 | 17,138 |
| 8 | 10,000 | 18,509 |
| 9 | 10,000 | 19,990 |
| 10 | 10,000 | 21,589 |
Checkpoint
Check: Running totals, changes and tables
Practise: predict, then check
The lines below use every idea of this page. Write down what you expect each one to print before you look at the run.
Predict first
For x <- c(2, 4, 6, 8), predict x * c(1, -1), x + 1:3 (and whether R warns), seq(1, 10, length.out = 4),
rep(1:2, each = 2, times = 2), cumsum(x) and diff(x). Then type them in your own R and compare.
from session import run, version
version()
# R reads practice.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f practice.R
""") Output
# R version 4.6.1 -- "Happy Hop" $ R --vanilla --quiet -f practice.R > x <- c(2, 4, 6, 8) > x * c(1, -1) [1] 2 -4 6 -8 > x + 1:3 [1] 3 6 9 9 Warning message: In x + 1:3 : longer object length is not a multiple of shorter object length > seq(1, 10, length.out = 4) [1] 1 4 7 10 > rep(1:2, each = 2, times = 2) [1] 1 1 2 2 1 1 2 2 > cumsum(x) [1] 2 6 12 20 > diff(x) [1] 2 2 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py
c(1, -1) was recycled evenly and flipped every second sign; 1:3 did not fit four elements, so R warned; seq()
worked out a step of 3; each and times together repeat each value, then the whole result; and diff(cumsum(x))
would give back 4, 6 and 8.
Last, a round of questions on the five ideas above. The weakest idea comes first, a right answer brings a harder question, and a missed one comes back at the end:
Practice that adapts to you
Practise this lesson
11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what is recycling in R, and when does R warn about it?
When an element-by-element operation such as + or * meets two vectors of different lengths, R repeats the shorter
one from its start until it is as long as the longer one, and then pairs the elements. That is recycling. A single
number is the everyday case: in x * 2 the 2 is recycled to every element of x. R warns only when the longer length
is not a multiple of the shorter one, as in 1:5 + 1:2, and even then it still returns a result. When the lengths
divide evenly, as with 12 values and 4, R recycles in silence, so the absence of a warning never proves that the
pairing was the one you intended. When two vectors must match element for element, check their lengths yourself.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A report function loops with for (i in 1:length(ids)) and passed every test. Called in production with an empty ids vector, it wrote two rows of nonsense. Explain exactly what happened on each pass, and how you would make the function safe for any input.
-
Fresher to mid
Twelve monthly sales figures were multiplied by a vector of seasonal factors, and the yearly total looked plausible. Later someone noticed the factors had 4 values, one per quarter. Why did R not complain, which month got which factor, and how would you stop this kind of mistake?
Key takeaways
- Arithmetic and most maths functions are vectorised: they work element by element on whole vectors, and the input types decide whether the result is an integer or a double.
- When lengths differ, R recycles the shorter vector from its start and warns only when the lengths do not divide evenly, so an even fit with the wrong pairing gives no warning.
1:length(x)is1 0for an emptyx, so loops useseq_along(x)orseq_len(n), which give no positions at all when there is nothing to count;seq()makes other regular sequences by step or by length.rep()repeats values witheach,timesandlength.out, which is how the columns of a design are built, andsequence()joins several counts from 1.cumsum(),cumprod()andcummax()accumulate along a vector,diff()turns values into changes, andouter()builds a table from every pair of values.
Terms in this lesson
- recycling
- R's rule for vectors of different lengths in an element-by-element operation: the shorter one is repeated from its start until it is as long as the longer one. R warns only when the lengths do not divide evenly.
- vectorised
- Applied to every element of a vector at once, with no loop written by you. R's arithmetic, comparisons and most maths functions, such as sqrt() and round(), are vectorised.
References
- An Introduction to R: Vector arithmetic (section 2.2) (The R Core Team)
- An Introduction to R: Generating regular sequences (section 2.3) (The R Core Team)
- Arithmetic Operators (Arithmetic) (The R Core Team)
- Sequence Generation (seq, seq_len, seq_along) (The R Core Team)
- Colon Operator (:) (The R Core Team)
- Replicate Elements of Vectors and Lists (rep) (The R Core Team)
- Create a Vector of Sequences (sequence) (The R Core Team)
- Cumulative Sums, Products, and Extremes (cumsum) (The R Core Team)
- Lagged Differences (diff) (The R Core Team)
- Outer Product of Arrays (outer) (The R Core Team)
- R News: changes in R 4.6.0 (sequence() gains recycle) (The R Core Team)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress