0Pricing
R Academy · Lesson

Vectorization for Speed

Replace explicit loops with vectorized operations for major speedups.

Vectorization for Speed is a free R Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Vectorization Matters

R is an interpreted language, so for loops have overhead on every iteration — function call dispatch, bounds checking, type coercion. Vectorized operations push that work into compiled C code that runs orders of magnitude faster.

Vectorization is the single most impactful optimization available in base R.

Loop vs cumsum() Example

Computing a running total with a for loop versus the built-in cumsum() shows the gap clearly. cumsum() calls C-level compiled code and processes the entire vector in one pass.

n <- 500000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  result[1] <- x[1]
  for (i in 2:n) result[i] <- result[i-1] + x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- cumsum(x)
})['elapsed']

cat('Loop  :', t_loop, 's
')
cat('cumsum:', t_vec, 's
')

ifelse() vs for + if

ifelse(condition, yes, no) is a vectorized conditional that evaluates the condition across an entire vector at once. It replaces element-by-element for + if loops with a single C-level pass.

n <- 300000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  for (i in seq_len(n)) result[i] <- if (x[i] > 0) x[i] else -x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- ifelse(x > 0, x, -x)
})['elapsed']

cat('for+if :', t_loop, 's
')
cat('ifelse :', t_vec, 's
')

Pre-allocating Result Vectors

When a loop is unavoidable, pre-allocate the result vector before the loop. Growing a vector with c(result, new_val) inside a loop copies the entire vector on each iteration — O(n^2) total memory operations.

n <- 20000

t_grow <- system.time({
  result <- c()
  for (i in seq_len(n)) result <- c(result, i^2)
})['elapsed']

t_prealloc <- system.time({
  result2 <- numeric(n)
  for (i in seq_len(n)) result2[i] <- i^2
})['elapsed']

cat('Growing vector:', t_grow, 's
')
cat('Pre-allocated :', t_prealloc, 's
')

Correct Pre-allocation Types

Use the typed constructor matching your data to avoid implicit coercion during pre-allocation:

  • numeric(n) — double-precision floats
  • integer(n) — integers
  • character(n) — empty strings
  • logical(n) — FALSE values
  • vector('list', n) — list of NULLs
n <- 5
cat('numeric  :', numeric(n), '
')
cat('integer  :', integer(n), '
')
cat('logical  :', logical(n), '
')
cat('character:', character(n), '
')
cat('list len :', length(vector('list', n)), '
')

colSums() and rowSums() vs apply()

For matrix operations, colSums(m), rowSums(m), colMeans(m), and rowMeans(m) are heavily optimized C routines. They are consistently faster than apply(m, 1, sum) which dispatches the R function sum once per row.

m <- matrix(rnorm(1000 * 2000), nrow = 1000)

t_apply <- system.time(apply(m, 2, sum))['elapsed']
t_colsums <- system.time(colSums(m))['elapsed']

cat('apply(m,2,sum):', t_apply, 's
')
cat('colSums(m)    :', t_colsums, 's
')

Vectorized Arithmetic Is Always Fast

Basic arithmetic on vectors — +, -, *, /, ^, sqrt(), log(), exp() — are all vectorized. They operate element-wise across an entire vector in a single C call. Always prefer them over loops.

x <- 1:1000000

t1 <- system.time(y <- x^2 + 2*x + 1)['elapsed']

t2 <- system.time({
  y2 <- numeric(length(x))
  for (i in seq_along(x)) y2[i] <- x[i]^2 + 2*x[i] + 1
})['elapsed']

cat('Vectorized:', t1, 's
')
cat('Loop      :', t2, 's
')

Logical Subsetting Instead of Loops

Filtering a vector with a logical condition is vectorized. Instead of looping and conditionally appending, create a logical index and subset once — the underlying C code does one pass.

x <- rnorm(500000)

t_loop <- system.time({
  pos <- c()
  for (v in x) if (v > 0) pos <- c(pos, v)
})['elapsed']

t_vec <- system.time({
  pos2 <- x[x > 0]
})['elapsed']

cat('Loop filter:', t_loop, 's
')
cat('Logical idx:', t_vec, 's
')

which() and tabulate() for Index Work

When you need positions of TRUE values, which(condition) is vectorized and fast. tabulate(bin_vector) counts integer occurrences faster than table() for dense integer ranges.

x <- sample(1:10, 100000, replace = TRUE)

t_table    <- system.time(table(x))['elapsed']
t_tabulate <- system.time(tabulate(x, nbins = 10))['elapsed']

cat('table()   :', t_table, 's
')
cat('tabulate():', t_tabulate, 's
')

idx <- which(x == 5)
cat('Positions of 5: first 5 =', head(idx, 5), '
')

When Loops Are Still Acceptable

Not every loop is bad. Loops are acceptable when:

  • Each iteration depends on the previous result (sequential dependency)
  • The number of iterations is small (< 1000)
  • The body of the loop calls a complex function with no vectorized equivalent

In these cases, focus on pre-allocation and avoid growing structures inside the loop.

# Sequential dependency -- loop is correct here
fib <- function(n) {
  result <- integer(n)
  result[1] <- 1L
  if (n >= 2) result[2] <- 1L
  for (i in seq_len(n)[-c(1,2)]) result[i] <- result[i-1] + result[i-2]
  result
}
cat('Fibonacci:', fib(10), '
')

Vectorization Summary

Key vectorization rules for fast R code:

  • Use cumsum/cumprod/diff for sequential accumulation
  • Use ifelse() for element-wise conditionals
  • Pre-allocate with numeric(n) / vector('list',n)
  • Use colSums/rowSums/colMeans/rowMeans over apply()
  • Logical subsetting beats filtering loops

Quick Check: Pre-allocation

Why is growing a vector with result <- c(result, new_val) inside a loop so slow for large n?

Vectorization Recap

Vectorization is R's primary performance lever:

  • Vectorized functions (cumsum, ifelse, arithmetic operators) call compiled C code — they are 10x-100x faster than equivalent R loops
  • Pre-allocate result containers before any unavoidable loop to avoid O(n^2) copying
  • colSums/rowSums beat apply() for matrix aggregations
  • Logical subsetting replaces filtering loops cleanly and quickly

Frequently asked questions

Is the “Vectorization for Speed” lesson free?

Yes — the full text of “Vectorization for Speed” is free to read here on the web, and the R Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the R Academy course, upgrade to CoddyKit PRO.

What will I learn in “Vectorization for Speed”?

Replace explicit loops with vectorized operations for major speedups. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start R Academy?

No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Vectorization for Speed” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this R Academy lesson?

Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. system.time() and proc.time()
  2. Profiling Code with Rprof and profvis
  3. Vectorization for Speed
  4. Benchmarking with microbenchmark
← Back to R Academy