R Lists — Flexible Data Structures

R BasicsListsFree Lesson

Advertisement

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 names
  • unlist() 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.

Advertisement

Need Expert R Programming Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement