The apply Family

Reviewed & published by Brayan K

The apply family is a set of R functions that run a function over each element, row, column, or group of your data — replacing explicit loops with concise, readable one-liners.

Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll use sapply() and lapply() over vectors and lists, apply() over rows and columns of a matrix, and tapply() to summarise values by group.

What You'll Learn in This Lesson

1️⃣ sapply() and lapply()

sapply() applies a function to each element and simplifies the result to a vector when possible. lapply() does the same but always returns a list — predictable when results vary in shape.

# sapply() applies a function to each element and SIMPLIFIES to a vector.
# It replaces a loop with one readable line.

nums <- c(1, 4, 9, 16)
sapply(nums, sqrt)         # square root of each -> [1] 1 2 3 4

# It works with your own functions too:
sapply(1:5, function(x) x ^ 2)   # -> [1]  1  4  9 16 25

# lapply() is the same but always returns a LIST:
lapply(1:3, function(x) x * 10)  # -> list of 10, 20, 30

2️⃣ apply() over Rows and Columns

apply(X, MARGIN, FUN) runs a function across a matrix or data frame. MARGIN = 1 works on each row; MARGIN = 2 on each column. You can pass a named function or your own.

# apply() runs a function over the rows or columns of a matrix/data frame.
# MARGIN = 1 means rows, MARGIN = 2 means columns.

m <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, byrow = TRUE)
#      [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    4    5    6

apply(m, 1, sum)     # sum of each ROW    -> [1]  6 15
apply(m, 2, max)     # max of each COLUMN -> [1] 4 5 6
apply(m, 1, function(r) r[1] * r[3])   # custom: col1 * col3 per row -> [1]  3 24

3️⃣ tapply() for Grouped Summaries

tapply(values, groups, FUN) splits a vector by a grouping vector and applies a function to each group — the base-R way to get "average by category". vapply() is a safer sapply() where you declare the result type.

# tapply() splits a vector by a grouping factor and applies a function
# to each group — the classic "average by category" move.

scores <- c(80, 90, 70, 60, 100, 85)
team    <- c("A", "B", "A", "B", "A", "B")

tapply(scores, team, mean)   # mean score per team
#        A        B
# 83.33333 78.33333

# vapply() is like sapply() but you state the result type for safety:
vapply(1:3, function(x) x + 0.5, numeric(1))   # -> [1] 1.5 2.5 3.5

Your turn. Fill in the ___ blank, run it, and compare with the expected output.

# 🎯 YOUR TURN — replace each ___ then run it.

temps_c <- c(0, 20, 37, 100)

# 1) Use sapply() to convert each Celsius value to Fahrenheit (C * 9/5 + 32)
temps_f <- ___(temps_c, function(c) c * 9/5 + 32)   # 👉 replace ___ with sapply
print(temps_f)

# 2) Print the average Fahrenheit, rounded to 1 decimal
cat("Average F:", round(mean(temps_f), 1), "\n")

# ✅ Expected output:
# [1]  32.0  68.0  98.6 212.0
# Average F: 102.7

Mini-Challenge: Grouped Sales

Write it from the outline, run it, and check it against the example output. tapply() with different functions answers different questions about the same groups.

# 🎯 MINI-CHALLENGE: grouped sales summary.
#
# Given:
#   amount <- c(100, 250, 80, 300, 120, 90)
#   region <- c("N", "S", "N", "S", "N", "S")
#
# 1. Use tapply() to get the TOTAL amount per region (function = sum).
# 2. Use tapply() to get the COUNT per region (function = length).
# 3. Print both results.
#
# ✅ Example output:
#      N   S
#    300 640
#      N   S
#      3   3

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — apply Family

FunctionUse
sapply(x, f)each element → simplified vector
lapply(x, f)each element → list
apply(m, 1, f)each row of a matrix
apply(m, 2, f)each column of a matrix
tapply(v, g, f)summary per group
vapply(x, f, t)type-safe sapply

🎉 Lesson Complete!

Practice quiz

What does sapply(c(1, 4, 9), sqrt) return?

  • A list of three values
  • A single number 14
  • A numeric vector c(1, 2, 3)
  • A matrix with one column

Answer: A numeric vector c(1, 2, 3). sapply() simplifies the per-element results to a vector when it can.

How does lapply() differ from sapply()?

  • lapply() always returns a list
  • lapply() only works on matrices
  • lapply() returns a single number
  • lapply() requires a factor argument

Answer: lapply() always returns a list. lapply() always returns a list; sapply() simplifies when possible.

In apply(m, MARGIN, FUN), what does MARGIN = 1 do?

  • Applies FUN to each column
  • Applies FUN to the whole matrix at once
  • Transposes the matrix first
  • Applies FUN to each row

Answer: Applies FUN to each row. MARGIN = 1 means rows; MARGIN = 2 means columns.

Which call gives the maximum of each COLUMN of matrix m?

  • apply(m, 1, max)
  • apply(m, 2, max)
  • sapply(m, max)
  • tapply(m, max)

Answer: apply(m, 2, max). MARGIN = 2 applies the function down each column.

What is tapply(values, groups, FUN) used for?

  • Applying FUN to each group of values defined by a factor
  • Reading a CSV file
  • Transposing a data frame
  • Sorting a vector

Answer: Applying FUN to each group of values defined by a factor. tapply() splits values by group and applies FUN to each group.

How is vapply() safer than sapply()?

  • It runs in parallel automatically
  • It never returns NA
  • You declare the expected result type and length
  • It ignores the function argument

Answer: You declare the expected result type and length. vapply() checks each result against a template like numeric(1).

What does lapply(1:3, function(x) x * 10) return?

  • The vector c(10, 20, 30)
  • A list containing 10, 20, 30
  • The number 60
  • An error

Answer: A list containing 10, 20, 30. lapply() always returns a list, one element per input.

Extra arguments after FUN in sapply(x, round, digits = 2) are...

  • Ignored by sapply()
  • Used as the result type
  • Treated as a second vector to iterate
  • Forwarded to FUN on each call

Answer: Forwarded to FUN on each call. Anything after FUN is passed through to the function.

Why might sapply() return a list instead of a vector?

  • The input was too long
  • The per-element results had differing lengths
  • It was given a factor
  • round() was used

Answer: The per-element results had differing lengths. If results can't be simplified to equal-length pieces, sapply() returns a list.

For tapply() to align correctly, the values and groups vectors must be...

  • Sorted alphabetically
  • Both factors
  • The same length, matched element by element
  • Of length 1

Answer: The same length, matched element by element. Each value pairs with the group at the same position.

Continue this course

Frequently asked questions

What is the apply family and why use it instead of loops?

The apply family — apply(), sapply(), lapply(), tapply(), vapply(), and mapply() — are functions that run another function repeatedly over the elements of a vector, list, or the rows/columns of a matrix. They express 'do this to each piece' in a single readable line, avoiding the boilerplate of a for loop. They make intent clearer and often run faster. Loops aren't wrong, but for transforming collections, the apply family is the idiomatic R approach.

What's the difference between sapply() and lapply()?

Both apply a function to each element of a vector or list. lapply() always returns a list, which is predictable and safe. sapply() ('simplify apply') tries to simplify the result to a vector or matrix when it can — so sapply(1:3, sqrt) gives a tidy numeric vector rather than a list. Use sapply() for convenience when you expect a simple result, and lapply() when you want a guaranteed list or the results are complex.

How does the MARGIN argument in apply() work?

apply(X, MARGIN, FUN) needs to know whether to work over rows or columns of a matrix or data frame. MARGIN = 1 applies FUN to each row, and MARGIN = 2 applies it to each column. A memory aid: 1 looks like a row divider and comes first, 2 like columns. So apply(m, 2, mean) gives the mean of every column, while apply(m, 1, sum) gives the sum of every row.

When should I reach for tapply()?

tapply() is for grouped summaries: you have a vector of values and a matching vector (or factor) of groups, and you want a summary per group — average sales per region, total clicks per day, count per category. tapply(values, groups, FUN) does exactly that. It's the base-R version of the split-apply-combine idea; later you'll see dplyr's group_by() + summarise() do the same thing with a more readable syntax for data frames.