Quick summary
Summarize this blog with AI
Introduction
seq_along(x) produces positions from 1 through length(x), or an empty integer vector when x is empty. It is useful when a loop needs positions. Positions are not business IDs, and for a data frame length counts columns, not rows.
Tested with R 4.5.1. Run the following code blocks in order. All inputs are included.
Generate positions safely for an empty input
The colon operator constructs an inclusive sequence even when its end is zero.
x <- c("apple", "pear", "plum")
stopifnot(identical(seq_along(x), 1:3))
empty <- character(0)
print(1:length(empty)) # 1 0: not an empty sequence
stopifnot(identical(1:length(empty), c(1L, 0L)))
stopifnot(identical(seq_along(empty), integer(0)))
visits <- 0L
for (i in seq_along(empty)) visits <- visits + 1L
stopifnot(visits == 0L)
The safe loop performs zero iterations. seq_len(n) is the corresponding choice when you already have a nonnegative length n.
Preallocate a result and index the original values
Use a separate result with the intended type. seq_along counts NA as an element; it does not filter missing input.
measurements <- c(10, NA_real_, 30)
doubled <- numeric(length(measurements))
for (i in seq_along(measurements)) doubled[i] <- measurements[i] * 2
print(doubled) # 20 NA 60
stopifnot(identical(doubled, c(20, NA_real_, 60)))
items <- list(first = c(1, 2), second = character(0))
sizes <- integer(length(items))
for (i in seq_along(items)) sizes[i] <- length(items[[i]])
stopifnot(identical(sizes, c(2L, 0L)))
For this simple multiplication, measurements * 2 is clearer. Use a loop when its per-element steps help express the real task; prefer lapply or vapply when the result contract fits.
Filter by IDs rather than their positions
Nonconsecutive, reordered IDs expose the difference. Row positions 2 and 4 are unrelated to ID values 2 and 4.
records <- data.frame(id = c(40L, 2L, 99L, 4L), value = c("A", "B", "C", "D"))
ids_to_keep <- c(4L, 40L)
by_id <- records[records$id %in% ids_to_keep, , drop = FALSE]
print(by_id$id) # 40 4: original row order retained
stopifnot(identical(by_id$id, c(40L, 4L)))
by_position <- records[c(2L, 4L), , drop = FALSE]
stopifnot(identical(by_position$id, c(2L, 4L)))
none <- records[records$id %in% integer(0), , drop = FALSE]
stopifnot(nrow(none) == 0, ncol(none) == 2)
Membership filtering preserves input order and repeated matching rows. If you need one result in the order of requested keys, use a lookup with an explicit duplicate and no-match policy instead.
Choose the row, column or cell axis
A data frame is a list of columns. A matrix length counts its cells.
stopifnot(identical(seq_along(records), 1:2)) # two columns
stopifnot(identical(seq_len(nrow(records)), 1:4))
zero_rows <- records[FALSE, , drop = FALSE]
stopifnot(length(seq_len(nrow(zero_rows))) == 0)
m <- matrix(1:6, nrow = 2)
stopifnot(identical(seq_along(m), 1:6))
stopifnot(identical(seq_len(nrow(m)), 1:2))
stopifnot(identical(seq_len(ncol(m)), 1:3))
Decide the axis before writing the loop. A matrix’s linear index traverses columns in R’s storage order; a row loop and a cell loop answer different questions. Keep that distinction explicit when replacing apply-family code.