flowchart LR
B["Build <br> list(...)"] --> L["A List"]
L --> A["Access <br> [ ], [[ ]], $"]
L --> M["Modify <br> add, replace, delete (NULL)"]
L --> I["Inspect <br> names(), length(), str(), unlist()"]
style B fill:#e3f2fd,stroke:#1976D2
style L fill:#fff3e0,stroke:#F57C00
style A fill:#e8f5e9,stroke:#388E3C
style M fill:#f3e5f5,stroke:#8E24AA
style I fill:#e8f5e9,stroke:#388E3C
12 Lists: Creation, Accessing, and Modification
This chapter introduces R’s second fundamental container: the list. Unlike a vector, a list can hold elements of different types in the same object, numbers next to character strings next to other lists. You will learn how to build a list with list(), how to name its elements, the three styles of accessing list content ([, [[, and $), and the difference between them. You will also see how to replace, add, and remove list elements, how NULL is used to delete an element, and the small helper functions names(), length(), str(), and unlist() that you will reach for repeatedly. By the end of this chapter you will be able to pack any set of heterogeneous values into a list and retrieve them cleanly.
12.1 What Makes a List Different From a Vector?
Every element of an atomic vector has the same type. Every element of a list can be a different type, a number, a string, another vector, a data frame, a function, or another list. That flexibility is why lists are how R returns results from modelling functions, holds the results of a statistical test, and represents any nested configuration.
| Property | Vector | List |
|---|---|---|
| Elements share a type? | Yes. | No. |
| Element can itself be a vector or list? | No. | Yes (nesting is allowed). |
| Built with | c() |
list() |
| Default printing | One line, comma separated. | Multi-line, each element labelled. |
12.2 Creating a List
list() Builds a List; c() Combines Lists
Build a list with the list() function. Arguments can be named or unnamed, and each argument becomes one element.
A named list is self-documenting. Anyone reading the code can see at a glance what each element represents. Unnamed lists are fine for throwaway interactive work, but for anything that lives in a script, use names.
12.3 Inspecting a List
| Function | What It Tells You |
|---|---|
length(x) |
Number of top-level elements. |
names(x) |
Character vector of element names (or NULL). |
str(x) |
Compact structural description of every element. |
class(x) |
"list" for a plain list, or a subclass for objects. |
str() Is Your Best Friend
When you receive an unfamiliar list from a function, a model fit, a configuration object, an API response, str(x) shows you the entire structure compactly. Make it the first function you run on any new list.
12.4 Accessing List Elements: [, [[, and $
R provides three ways to reach into a list. They look similar and beginners regularly confuse them, but they return fundamentally different things.
| Operator | Returns | Use When |
|---|---|---|
x[i] |
A sub-list (still a list). | You want to keep the list container. |
x[[i]] |
The element itself, unwrapped. | You want the value inside. |
x$name |
The element named name, unwrapped (shortcut for x[["name"]]). |
You want one element by its exact name. |
student[1] + 1 Fails, student[[1]] + 1 Works
Because x[1] returns a list (not a number), you cannot use it in arithmetic. Use x[[1]] to pull out the underlying value.
[ is also how you pull multiple elements at once. Pass a vector of names or positions.
12.5 Modifying a List
Use [[<- or $<- to replace an element. The old value is discarded and the new value takes its place.
Assigning to a name that does not yet exist appends a new element to the list.
NULL
Assigning NULL to a list element deletes it. This is a special rule: NULL is not stored inside the list; the whole slot is dropped.
NULL to Mean “Missing”
Setting student$email <- NULL deletes the element; it does not mark it as missing. If you want to keep the slot but record that the value is unknown, use NA.
12.6 Coercing a List to a Vector: unlist()
unlist() collapses a list into a single atomic vector. It works cleanly when every element is atomic and the types can be coerced together. The usual coercion hierarchy applies: if any element is character, the whole result becomes character.
unlist() Is a Coercion, Not a Free Lunch
unlist() is handy for collapsing simple lists, but every time you call it you risk coercion surprises (numbers becoming strings, factors becoming integers). When you care about types, prefer vapply() or purrr::map_*(), which let you assert the expected output type.
12.7 Converting Between List and Vector
as.list() and as.vector()
as.list() wraps each element of a vector into its own list slot. as.vector() strips most attributes from a vector but is rarely interesting on its own.
12.8 A Worked Example: A Student Profile
Every concept from the chapter shows up once: construction with list(), nested lists for contacts, three access styles ($, [[, $ on a named vector inside the list), reassignment, NULL to delete, and appending a new element.
12.9 Summary
| Concept | Key Takeaway |
|---|---|
| Lists are heterogeneous | Any types, any nesting, the universal container. |
Build with list() |
Named arguments become element names. |
[ vs [[ vs $ |
[ keeps the list wrapper; [[ and $ return the element itself. |
| Multi-element selection | x[c("a", "b")] and x[1:2] return sub-lists. |
| Modification | $ or [[ on the left-hand side; assigning NULL deletes. |
NA vs NULL |
NA marks a missing value, NULL removes the element entirely. |
unlist() |
Flatten atomic-only lists into a vector; mind the coercion. |
str() |
The single most useful function for exploring an unknown list. |
Whenever you find yourself wishing a vector could hold a mix of types, a student record, a set of model parameters, the response from a web API, a list is the answer. Get comfortable with the [/[[/$ distinction now; it is the source of more beginner confusion than any other single syntax in R. In the next chapter you will see the operations that run on top of lists: appending, nesting, and the lapply()/sapply() iteration family.