Lists and Nested Data
A vector is strict: every value in it must be the same type, so the moment you mix a word in with your numbers, R quietly turns them all into text. But real records are not all one type. Think of Maya: her name is text, her age is a number, her hobbies are several words, and her address is a little record of its own. To keep all of that in one place, R gives you the list: a container with named slots that can hold anything, even other lists.
By the end of this lesson you will be able to:
- Build a list that holds text, numbers and other lists side by side
- Pull any piece back out with
$,[[ ]]and[ ], and know why[ ]behaves differently - Reach into a nested list, and see why a data frame is secretly a list too
Prerequisites: you can run a line of R, store a value with <-, and call a function. It helps to know that a vector holds one type. By the end, Maya's whole profile, name, age, hobbies and even her address, will live inside one object you can take apart at will, like the tree below.
A list holds different kinds of things at once
Maya's details are different types: a word, a number, a few words, soon an address. A vector cannot keep them apart, it would coerce them all to text. A list can: you build one with list(), naming each slot as you go, and every value keeps its own type.
Notice the printout: each slot is labelled with $name, $age, $hobbies, and the values underneath are untouched. The text is still text, the number is still a number, and hobbies is a whole vector tucked into one slot. A list is happy to hold a single value in one slot and a vector in the next.
To see a list's shape at a glance, three tools help. length() counts the slots, names() lists their labels, and str() (for structure) prints a compact map of the whole thing:
str() is the single most useful thing to type when you meet an unfamiliar object: it tells you what is inside without dumping the whole contents on your screen.
Vector or list?
You write two things that look similar: c("Maya", 31, TRUE) and list("Maya", 31, TRUE). One coerces, one does not. What is the real difference?
c() builds an atomic vector, which holds a single type, so it coerces everything to text. list() is the container that lets a string, a number and a logical sit side by side, each unchanged.c() returns an atomic vector (one type, so it coerces) while list() returns a list (mixed types preserved). Check with typeof() on each.Pull a slot out by name with $
A list is no use if you cannot get things back out of it. The everyday way is the dollar sign: write the list, a $, then the slot's name, and R hands you back what is in that slot.
Each $ returns the value inside the slot, ready to use: maya$age is the number 31, so you can do arithmetic with it, and maya$hobbies is the character vector, so you can index it further with [:
$ only works with a name you type out, and it does not complain if you get the name wrong. maya$adress (a typo) returns NULL silently, not an error, so a mysterious NULL often means a misspelled slot name.Double brackets open the slot; single brackets keep the wrapper
There are two bracket styles, and the difference catches every beginner. Picture the list as a row of labelled boxes. [[ ]] reaches into one box and hands you what is inside. A single [ ] hands you back a smaller row of boxes, that is, a shorter list with the wrapper still on.
They look almost the same, but they are not. Ask each one what type it is and the difference is plain: [[ ]] gives you the character value, while [ ] gives you back a list:
[[ ]] also takes a position, so maya[[2]] is the second slot's value, the number 31. And maya$name is simply a friendly shorthand for maya[["name"]]. Here are the three ways in, side by side:
[[ ]] and $ extract: they unwrap one slot and give you the value inside. Single [ ] preserves: it gives you back a list (a shorter one). When your next step breaks because it got a list where it expected a value, you almost always meant [[ ]].What does single-bracket give you?
Maya's list has an age slot holding the number 31. You run maya["age"] (single brackets). What comes back?
[ ] always returns the same kind of container you indexed, so indexing a list gives back a (shorter) list. To get the bare number 31, use maya[["age"]] or maya$age.[ ] accepts names too. The difference is the wrapper, [ ] returns a list while [[ ]] returns the value inside.A slot can hold another list
Here is what makes lists so powerful: a slot can itself be a list. Maya's address is really several fields, a city and a postal code, so it is natural to store it as its own little list inside maya. Let us add it:
See how str() indents address and shows its two inner slots: that nesting is the whole idea. To reach a value two levels down, you just chain the accessors, reading left to right, "in maya, go to address, then to city":
That is the same structure as the tree on the cover, now built for real. Each $ (or [[ ]]) steps you one level deeper into the nest.
Dig two levels down
maya now holds a nested address list with a city slot set to "Pune". Replace the blank with an expression that digs down and returns Maya's city. Use $ to step through address, then city.
Show answer
maya$address$city
#> [1] "Pune"Lists are everywhere in R
This is not a niche feature. The most common object in all of R, the data frame, is a list underneath: a list of equal-length columns. That is why the very same $ reaches a column:
The same goes for the results of most functions. Fit a model and what you get back is a list, so you pull pieces out of it by name, just like Maya's profile:
Once you can build a list and reach inside it, you can take apart almost any object R hands you. That is why this small skill opens up so much of the language.
References
A few trustworthy places to take this further, all free:
- Advanced R (2e): Subsetting - the definitive treatment of
[,[[and$, and the preserve-vs-extract rule you just learned. - Advanced R (2e): Vectors - lists as generic vectors, and how they differ from atomic vectors.
- An Introduction to R: Lists and data frames - the canonical first treatment, straight from the R project.
- R for Data Science (2e): Base R - a friendly, example-led tour of
[[and$for everyday data work.
Lesson 1 complete
You met R's most flexible container, the list. You built one with list(), looked at its shape with length(), names() and str(), and reached inside it three ways: $ and [[ ]] to extract the value in a slot, single [ ] to preserve a shorter list. You saw a list hold another list, chained accessors to dig two levels deep, and learned that data frames and fitted models are lists too, so these same tools open them up.
Next, Lesson 2: Data Frames and Tibbles. Now that you know a data frame is really a list of equal-length columns, you will meet it head on, build one, see what makes its columns line up into rows, and learn what a tibble adds on top.