R Lists — Flexible Data Structures
Learning Objectives
By the end of this tutorial, you will be able to:
- Create lists containing elements of different types
- Index lists using
[],[[]], and$notation - Work with nested lists and recursive structures
- Convert between lists and other data structures
- Use lists for function returns, API responses, and structured data
What Is a List?
A list is an ordered collection of elements that can be of any type — numeric, character, logical, even other lists. Unlike vectors (which must be homogeneous), lists are heterogeneous.
# A list with mixed types
my_list <- list(42, "hello", TRUE, 3.14)
print(my_list)
# [[1]]
# [1] 42
#
# [[2]]
# [1] "hello"
#
# [[3]]
# [1] TRUE
#
# [[4]]
# [1] 3.14
str(my_list)
# List of 4
# $ : num 42
# $ : chr "hello"
# $ : logi TRUE
# $ : num 3.14
Creating Lists
Using list()
# Basic list
x <- list(1, 2, 3)
# Named list
person <- list(
name = "Alice",
age = 30,
active = TRUE
)
# Access by name
person$name # [1] "Alice"
person$age # [1] 30
# Nested lists
company <- list(
name = "TechCorp",
employees = list(
list(name = "Alice", role = "Engineer"),
list(name = "Bob", role = "Designer")
)
)
From Vectors
# List from named vector
scores <- list(c(Alice = 95, Bob = 87, Charlie = 92))
# List of individual elements
x <- as.list(1:5)
x
# [[1]]
# [1] 1
# [[2]]
# [1] 2
# ...
Empty Lists
# Empty list
empty <- list()
length(empty) # [1] 0
# Pre-allocated empty list
my_list <- vector("list", 5)
length(my_list) # [1] 5
Indexing Lists
Single Bracket [] — Returns a List
x <- list(a = 1, b = 2, c = 3, d = 4)
# By position
x[1] # Returns a list with first element
x[c(1, 3)] # Returns a list with elements 1 and 3
# By name
x["a"] # Returns a list with element "a"
x[c("a", "c")] # Returns a list with "a" and "c"
# By logical
x[c(TRUE, FALSE, TRUE, FALSE)] # Elements 1 and 3
# Result is always a list
class(x[1]) # [1] "list"
Double Bracket [[]] — Returns the Element
x <- list(a = 1, b = "hello", c = TRUE)
# By position
x[[1]] # [1] 1
class(x[[1]]) # [1] "numeric"
# By name
x[["b"]] # [1] "hello"
# Dollar notation (shorthand for named access)
x$a # [1] 1
x$c # [1] TRUE
# Result is the actual element
class(x$a) # [1] "numeric"
[] vs [[]] Comparison
x <- list(a = 1, b = 2, c = 3)
x[1] # Returns: list(a = 1)
x[[1]] # Returns: 1
x["a"] # Returns: list(a = 1)
x[["a"]] # Returns: 1
# Use [] when you want a sub-list (e.g., for further indexing)
# Use [[]] when you want the actual value
Modifying Lists
x <- list(a = 1, b = 2, c = 3)
# Modify element
x$b <- 20
x[["c"]] <- 30
# Add element
x$d <- 40
x$new_element <- 50
# Remove element
x$new_element <- NULL
x[["d"]] <- NULL
# Replace multiple
x[c("a", "b")] <- list(100, 200)
Working with Lists
Combining Lists
# c() combines lists
list1 <- list(a = 1, b = 2)
list2 <- list(c = 3, d = 4)
combined <- c(list1, list2)
combined
# $a
# [1] 1
#
# $b
# [1] 2
#
# $c
# [1] 3
#
# $d
# [1] 4
Converting Lists to Vectors
# unlist() — flatten list to vector
x <- list(a = 1, b = 2, c = 3)
unlist(x)
# a b c
# 1 2 3
# Mixed types
mixed <- list(1, "two", TRUE)
unlist(mixed)
# [1] "1" "2" "TRUE" (coerced to character)
# preserve types
mixed <- list(a = 1, b = 2, c = "three")
unlist(mixed)
# a b c
# "1" "2" "three"
Converting to Data Frame
# Named list to data frame
scores <- list(
name = c("Alice", "Bob", "Charlie"),
score = c(95, 87, 92)
)
df <- as.data.frame(scores)
df
# name score
# 1 Alice 95
# 2 Bob 87
# 3 Charlie 92
# List of lists to data frame
data <- list(
list(name = "Alice", age = 25),
list(name = "Bob", age = 30),
list(name = "Charlie", age = 35)
)
df <- do.call(rbind, lapply(data, as.data.frame))
df
Recursive Operations
# Recursive function on nested lists
flatten_list <- function(x) {
if (is.list(x)) {
unlist(lapply(x, flatten_list))
} else {
x
}
}
nested <- list(1, list(2, list(3, 4)), 5)
flatten_list(nested)
# [1] 1 2 3 4 5
Practical Examples
Example 1: Function Returning Multiple Values
get_stats <- function(x) {
list(
mean = mean(x, na.rm = TRUE),
median = median(x, na.rm = TRUE),
sd = sd(x, na.rm = TRUE),
n = length(x)
)
}
result <- get_stats(c(10, 20, 30, 40, 50))
result$mean # [1] 30
result$sd # [1] 15.81139
Example 2: JSON-like Data Structure
user <- list(
id = 1,
name = "Alice",
email = "alice@example.com",
roles = c("admin", "editor"),
settings = list(
theme = "dark",
notifications = TRUE
)
)
user$name # [1] "Alice"
user$roles # [1] "admin" "editor"
user$settings$theme # [1] "dark"
Example 3: Storing Model Results
# Multiple model results
models <- list(
linear = lm(mpg ~ wt, data = mtcars),
polynomial = lm(mpg ~ wt + I(wt^2), data = mtcars)
)
# Compare R-squared
sapply(models, function(m) summary(m)$r.squared)
# linear polynomial
# 0.752833 0.8190621
Common Mistakes
1. Using $ with non-standard names
x <- list(`my var` = 1, `another-var` = 2)
# Wrong — won't work
# x$my var # Error
# Right — use [[]]
x[["my var"]] # [1] 1
x[["another-var"]] # [1] 2
2. Forgetting [] vs [[]]
x <- list(a = 1, b = 2)
# You get a list, not a value
class(x[1]) # [1] "list"
# You get the value
class(x[[1]]) # [1] "numeric"
3. Modifying lists inside loops
# Slow — grows list each iteration
result <- list()
for (i in 1:1000) {
result[[i]] <- i^2
}
# Fast — pre-allocate
result <- vector("list", 1000)
for (i in 1:1000) {
result[[i]] <- i^2
}
Practice Exercises
Exercise 1: Student Record
Create a list representing a student with name, age, grades (vector), and whether they passed. Write code to access each element.
Solution
student <- list(
name = "Alice",
age = 20,
grades = c(95, 87, 92, 88),
passed = TRUE
)
student$name # [1] "Alice"
student$age # [1] 20
student$grades # [1] 95 87 92 88
mean(student$grades) # [1] 90.5
Exercise 2: Nested List Access
Given this nested structure, extract "Charlie's email":
company <- list(
employees = list(
list(name = "Alice", email = "alice@co.com"),
list(name = "Bob", email = "bob@co.com"),
list(name = "Charlie", email = "charlie@co.com")
)
)
Solution
company$employees[[3]]$email
# [1] "charlie@co.com"
# Or using lapply
sapply(company$employees, function(e) e$email)
# [1] "alice@co.com" "bob@co.com" "charlie@co.com"
Key Takeaways
- Lists are heterogeneous — can contain any type, including other lists
[]returns a list,[[]]returns the element$is shorthand for[[ ]]with namesunlist()flattens lists to vectors- Lists are ideal for function returns, JSON-like data, and model results
- Pre-allocate lists before filling them in loops
- Use
do.call(rbind, ...)to convert list of lists to data frame
Next: Learn about R Matrices — 2D arrays of homogeneous data.