R: R Lists
Last updated: 2026-08-26
In the previous three lessons, we learned about vectors and matrices—both of which require that all elements be of the same type. However, in reality, data is often heterogeneous: student information includes student ID (numbers), name (characters), and grades (vectors). R uses lists to solve the problem of storing heterogeneous data.
Lists are R’s true “Swiss Army knife”—they can hold any type (vectors, matrices, data frames, functions, or even other lists). Data frames data.frame are essentially “lists of vectors of equal length” at their core. This lesson is key to Phase 1.
1. What You'll Learn
- list() creates a container of any type
- List of Names (Access by Name)
- 4 access modes (
[[ ]][ ]$[[ "name" ]]) - Modifying, adding, and deleting items in a list
- lapply() for batch processing lists
- Nested lists
- list2env() converts a list to environment variables
2. A Story About Student Information Management
(1) Pain Point: Heterogeneous Data Storage
Ivan works in HR and needs to store the complete information for one employee:
Employee ID:2024001(Numbers)
Name:Alice(Character)
Department:Data Analysis Department(Character)
Salary:15000(Numbers)
Skills:c("R", "Python", "SQL")(Character vector)
If you use a vector, all elements must be of the same type—it won't work. If you use a data frame, each column must have the same number of columns—it won't work either.
(2) Solution Using Lists
# One list Fits all types
employee <- list(
id = 2024001,
name = "Alice",
department = "Data Analysis Department",
salary = 15000,
skills = c("R", "Python", "SQL"),
is_active = TRUE
)
# Access any field
employee$name # [1] "Alice"
employee$skills[1] # [1] "R"
employee$salary * 12 # [1] 180000 (Annual Salary)
A single list containing 6 fields of different types. That’s the power of R lists.
3. list(): Create a list
(1) Basic Syntax
list(key1 = value1, key2 = value2, ...)
(2) 5 Ways to Create
# 1. List of Names(Most Commonly Used)
person <- list(name = "Alice", age = 25, scores = c(85, 90, 92))
person
# $name
# [1] "Alice"
#
# $age
# [1] 25
#
# $scores
# [1] 85 90 92
# 2. Anonymous List(Browse by Location)
unnamed <- list("a", 1, TRUE)
# [[1]] "a"
# [[2]] 1
# [[3]] TRUE
# 3. Nested Lists
company <- list(
hr = list(name = "HR", count = 5),
it = list(name = "IT", count = 20)
)
# 4. Empty list
empty <- list()
length(empty) # [1] 0
# 5. Use list() to convert vector (Maintain Heterogeneity)
as.list(c(1, 2, 3))
# [[1]] 1
# [[2]] 2
# [[3]] 3
(3) Comparison of Lists and Vectors
graph TB
A[R Data Structures] --> B[Vector<br/>Homogeneous<br/>One-dimensional]
A --> C[Matrix<br/>Homogeneous<br/>Two-dimensional]
A --> D[List<br/>Heterogeneous<br/>Any type]
A --> E[Data Frame<br/>List of vectors of equal length<br/>Two-dimensional]
style A fill:#fff3cd
style B fill:#cce5ff
style C fill:#d4edda
style D fill:#f8d7da
style E fill:#e1d4ff
| Property | Vector c(1, 2, 3) |
List list(1, "a", TRUE) |
|---|---|---|
| Element Type | Must Be the Same | Can Be Different |
| Element Type | 6 basic types | Any R object (including functions and data frames) |
| Visit | v[1] |
l[[1]] |
| Variable length | ❌ | ✅ (Can be added to or removed as desired) |
| Similar to Python | tuple |
dict / list |
4. 4 Access Methods (Key Points)
(1) 4 Types of Lookup Tables
| Method | Syntax | Return | Purpose |
|---|---|---|---|
[[i]] |
The i-th element | The element itself | Retrieve a single element |
[i] |
The i-th element | Sublist | Take a subset |
$name |
By name | The element itself | Get the named element |
[["name"]] |
By name | The element itself | Equivalent $name |
(2) [[ ]] vs [ ]: Key Differences
person <- list(name = "Alice", age = 25, scores = c(85, 90, 92))
# [[ ]] Return the element itself
person[[1]]
# [1] "Alice" ← Character
# [ ] Return to Sublist
person[1]
# $name
# [1] "Alice" ← Or a list
# Key Differences: Using [ ] after extraction, attempting to access sub-elements may cause an error
person[1]$name # ✅ $name Still accessible(Syntax sugar)
person[1][1] # ❌ This is the 1 element,But there is only one child list $name
# Practical Application:Calculate the average score
mean(person[[3]]) # [1] 89 ← [[3]] Extract the vector
mean(person$scores) # [1] 89 ← $scores Extract the vector
mean(person[3]) # Error: need numeric data
[[ ]] or $ to access elements, and [ ] to access subsets. This is the most confusing aspect of accessing lists in R.
(3) $ vs [["name"]]
# $ is syntactic sugar for [[ "name" ]]
person$name # Equivalent person[["name"]]
person[["name"]] # Equivalent person$name
# $ Supports partial matches(Not recommended)
person$na # Automatically matched to "name"
# [1] "Alice"
# [[ ]] Partial matches are not supported(Safer)
person[["na"]] # Error
[[ "name" ]] for production code—it’s clear and unambiguous, and doesn’t rely on partial matches.
(4) Accessing Nested Lists
company <- list(
hr = list(name = "HR", count = 5, head = "Alice"),
it = list(name = "IT", count = 20, head = "Bob")
)
# Accessing Nested Elements
company$hr$head # [1] "Alice"
company[["it"]][["head"]] # [1] "Bob"
company$it$count # [1] 20
5. Modifying, Adding, and Deleting Items in a List
(1) Modify an element
person <- list(name = "Alice", age = 25)
# Edit ([[ ]] or $ both can be changed)
person$age <- 26
person[["age"]]
# [1] 26
# Batch Edit
person$age <- person$age + 1
person$age # [1] 27
(2) Add an element
# Methods 1: Use $ to add
person$email <- "alice@example.com"
# Methods 2: Use [[ ]] to add
person[["phone"]] <- "13800000000"
# Methods 3: Use c() to merge
person <- c(person, address = "Haidian District, Beijing")
(3) Deleting Elements
# Assign NULL to delete
person$phone <- NULL
# Or
person[["address"]] <- NULL
(4) View the list structure
# View Structure(Important Debugging Functions)
str(person)
# List of 4
# $ name : chr "Alice"
# $ age : num 27
# $ email : chr "alice@example.com"
# $ address : chr "Haidian District, Beijing"
# View All Names
names(person)
# [1] "name" "age" "email" "address"
6. lapply(): Batch Processing of Lists
(1) Why use lapply?
The element types in a list can vary—you cannot simply use sum() mean(). The lapply function allows us to apply a function to each element:
mixed_list <- list(
numbers = 1:5,
chars = c("a", "b", "c"),
logical = c(TRUE, FALSE, TRUE)
)
# Calculate the length of each element
lapply(mixed_list, length)
# $numbers
# [1] 5
#
# $chars
# [1] 3
#
# $logical
# [1] 3
(2) lapply + Custom Function
# CalculateFor each element,"Abstract"
summary_list <- lapply(mixed_list, function(x) {
if (is.numeric(x)) {
return(list(mean = mean(x), sum = sum(x)))
} else {
return(list(length = length(x), class = class(x)))
}
})
summary_list$numbers
# $mean
# [1] 3
#
# $sum
# [1] 15
(3) Simplifying the results with sapply()
# lapply Back to List,sapply Simplify to a vector
sapply(mixed_list, length)
# numbers chars logical
# 5 3 3
7. Merging Lists: c() and unlist()
(1) c() Merge Lists
# Merge Multiple Lists
list1 <- list(a = 1, b = 2)
list2 <- list(c = 3, d = 4)
merged <- c(list1, list2)
merged
# $a 1
# $b 2
# $c 3
# $d 4
(2) unlist() flattens to a vector
# Flatten the list into a vector(Only for"Atom"Element Valid)
numbers_list <- list(a = 1, b = 2, c = 3)
unlist(numbers_list)
# a b c
# 1 2 3
# Note:The type will be coerced after flattening.
mixed <- list(1, "a", TRUE)
unlist(mixed)
# [1] "1" "a" "TRUE" ← Convert All to Strings
8. list2env(): Convert a list to an environment
Turn each element in the list into a global variable:
config <- list(
host = "localhost",
port = 3306,
user = "root",
password = "secret"
)
# Convert list elements to environment variables
list2env(config, envir = .GlobalEnv)
# You can use it right away
host # [1] "localhost"
port # [1] 3306
list2env() can pollute the global environment, so use with caution. It is most commonly used in scenarios where configurations are loaded from YAML or JSON.
9. Comparison of Common Operations
(1) c() vs list() vs unlist()
| Function | Purpose | Input → Output |
|---|---|---|
c() |
Combining Atomic Elements | c(1, 2, 3) → Vectors |
list() |
Create a heterogeneous container | list(1, "a") → List |
unlist() |
Flatten the list into a vector | unlist(list(1,2,3)) → Vector |
(2) Converting a List to a DataFrame
# A list of vectors of equal length can be converted data.frame
df <- as.data.frame(list(
name = c("A", "B", "C"),
age = c(20, 25, 30),
score = c(85, 90, 78)
))
print(df)
# name age score
# 1 A 20 85
# 2 B 25 90
# 3 C 30 78
10. Complete Example: Employee Record Management
Below is an example of a complete workflow that ties together all the list features covered in this lesson.
▶ Example: Employee File Management
# ============================================
# Employee File Management
# Features:Manage with Lists 3 Complete information about each employee
# ============================================
# 1. Create 3 List of employees
employees <- list(
alice = list(
id = 2024001,
name = "Alice",
department = "Data Analysis Department",
salary = 15000,
skills = c("R", "Python", "SQL"),
projects = c("User Profiles", "Sales Forecast")
),
bob = list(
id = 2024002,
name = "Bob",
department = "Engineering Department",
salary = 18000,
skills = c("Java", "Go", "Docker"),
projects = c("API Refactoring", "Microservice Migration")
),
charlie = list(
id = 2024003,
name = "Charlie",
department = "Product Department",
salary = 20000,
skills = c("Figma", "Axure"),
projects = c("V2.0 Design", "User Research")
)
)
# 2. View a specific employee's full profile
cat("=== Alice Full Information ===\n")
str(employees$alice)
# List of 6
# $ id : num 2024001
# $ name : chr "Alice"
# $ department : chr "Data Analysis Department"
# $ salary : num 15000
# $ skills : chr [1:3] "R" "Python" "SQL"
# $ projects : chr [1:2] "User Profiles" "Sales Forecast"
# 3. Use sapply for batch processing
cat("\n=== Annual Salaries of All Employees ===\n")
annual_salaries <- sapply(employees, function(emp) emp$salary * 12)
print(annual_salaries)
# alice bob charlie
# 180000 216000 240000
# 4. Use sapply to count skills
cat("\n=== Number of skills per employee ===\n")
skill_counts <- sapply(employees, function(emp) length(emp$skills))
print(skill_counts)
# alice bob charlie
# 3 3 2
# 5. Use split for grouping by department
cat("\n=== Grouped by Department ===\n")
dept_groups <- split(
sapply(employees, function(emp) emp$name),
sapply(employees, function(emp) emp$department)
)
print(dept_groups)
# $Data Analysis Department
# [1] "Alice"
#
# $Engineering Department
# [1] "Bob"
#
# $Product Department
# [1] "Charlie"
# 6. Add a New Employee
employees$diana <- list(
id = 2024004,
name = "Diana",
department = "Data Analysis Department",
salary = 16000,
skills = c("R", "Tableau"),
projects = c("Data Visualization")
)
cat("\n=== Add Diana after ===\n")
cat("Total Number of Employees:", length(employees), "\n")
cat("Diana Department:", employees$diana$department, "\n")
# 7. Identify the employee with the highest salary
top_salary <- which.max(sapply(employees, function(emp) emp$salary))
cat("\nHighest Salary:", names(employees)[top_salary], "(", max(sapply(employees, function(emp) emp$salary)), "USD)\n")
# 8. Retrieve the skills of all employees(Merge all skill vectors)
all_skills <- unlist(lapply(employees, function(emp) emp$skills))
cat("\n=== Company-wide Skills Summary ===\n")
print(table(all_skills))
# all_skills
# Axure Docker Figma Go Java Python R SQL Tableau
# 1 1 1 1 1 1 2 1 1
Expected Output (Excerpt):
=== Alice Full Information ===
List of 6
$ id : num 2024001
$ name : chr "Alice"
...
=== Annual Salaries of All Employees ===
alice bob charlie
180000 216000 240000
=== Company-wide Skills Summary ===
all_skills
Axure Docker Figma Go Java Python R SQL Tableau
1 1 1 1 1 1 2 1 1
❓ FAQ
[[ ]] and [ ]?[[ ]] returns the element itself (which can be further manipulated), while [ ] returns a sublist (whose elements cannot be further manipulated). Use [[ ]] or $ to retrieve elements, and [ ] to retrieve subsets. This is one of the most confusing aspects of R syntax.$ and [[ "name" ]] the same?$ supports partial matching ($na automatically matches "name"), while [[ ]] does not. It is safer to use [[ "name" ]] in production code.data.frame At the core, a list is a "list of vectors of equal length"—understanding lists means understanding data frames.lapply() and sapply()?lapply() returns a list (preserving the structure), while sapply() simplifies the result to a vector or matrix (making it easier to read). Use sapply for everyday tasks, and lapply for functional programming pipelines.unlist() When should it be used?📖 Summary
- Lists are R's "universal containers": they can hold any type (vectors, matrices, functions, or other lists)
- 4 access methods:
[[i]]$name[[ "name" ]](retrieve elements) vs[i](retrieve a subset) $nameSupports partial matching (not recommended),[[ "name" ]]Exact matching (recommended) Add/Delete/Modify:l$new <- x(Add)/l$old <- NULL(Delete)/l$old <- new_x(Modify)lapply()Applying a function to each element returns a list;sapply()Simplified to a vectorunlist()Flatten a list into a vector (heterogeneous elements will be coerced)- The data frame
data.frameis fundamentally a "list of vectors of equal length"—understanding lists is a prerequisite for understanding data frames.
📝 Exercises
-
Basic Exercise: Create a list named
studentcontaining four fields:name(character),age(number),scores(vector of values c(85, 90, 92)), andpassed(boolean). Print the list structure (str()), and accessnameandscores[2]. -
Basic Problem: Create a nested list
familycontaining three sublistsfather,mother, andchild(each of which containsnameandage). Accesschild$ageand print it. -
Basic Problem: Use
lapply()to batch-calculate the "annual salary" (salary * 12) for a list of three employees (which you construct yourself), and usesapply()to simplify the results into a vector output. -
Advanced Problem: Write a function
create_employee(name, dept, salary)that returns a named list (containing id, name, dept, salary, skills=empty vector, is_active=TRUE). Call it three times to create three employees and store them in a large list. Usesapply()to extract the "annual salary" for all employees. -
Challenge: Write a script to simulate "library" management: ① Create a list
librarycontaining 3 books (each book has 4 fields:title,author,year, andavailable); ② Uselapplyto set theavailablevalue for all books to FALSE (checked out); ③ Usesapplyto find all books available for checkout (available == TRUE); ④ Add a new book to the list.