Manipulating Lists

Like vectors and matrices, we can access and modify the slots of a list. Unlike those simpler data objects, however, we have several different ways to select list elements (not just the [] operator).

Accessing List Slots

R provides several mechanisms for accessing the contents of a list, each serving a distinct purpose. The three main operators used for this task are the dollar sign ($), single brackets ([]), and double brackets ([[]]).

Accessing Single Slots

The $ operator accesses a single list slot by name and returns the contents of that list slot.

l1 <- list(
  name = "bob",
  alive = TRUE,
  age = 33,
  relationshipStatus = 42 + 3i
)

l1$name
[1] "bob"
class(l1$name)
[1] "character"

The [[]] operator works like the $ operator but also accepts numeric indices.

l1[["age"]]
[1] 33
l1[[2]]
[1] TRUE
class(l1[[2]])
[1] "logical"

Multi-Slot Access

The [] operator is notably different: it extracts any number of list slots and returns the result as a list. Even if we only select one element, the [] operator still returns a list. This behavior is useful when we want to retain the list structure for further processing.

l1["name"]
$name
[1] "bob"
l1[2]
$alive
[1] TRUE
class(l1[2])
[1] "list"
l1[c("name", "alive")]
$name
[1] "bob"

$alive
[1] TRUE
l1[1:2]
$name
[1] "bob"

$alive
[1] TRUE
class(l1[1:2])
[1] "list"

If we want to select multiple list elements, the [] operator is our only option. You cannot use the $ or [[]] operators to select multiple list slots.

l1[[1:2]]
Error in `l1[[1:2]]`:
! subscript out of bounds
l1$c("name", "alive")
Error:
! attempt to apply non-function
Moving Day: A Mental Model for $, [[]], and []

To gain some intuition for the different flavors of list subsetting, you can think of a list as a stack of moving boxes.

Let’s consider a three-slot list as our example. We’ll conceptualize this list as a stack of three boxes (box = list slot). Let’s say the first box contains clothes (pile of clothes = some kind of vector), the second box contains books (stack of books = different kind of vector), and the third box contains four smaller boxes (set of smaller boxes = nested list) that hold various types of kitchen supplies.

  • The $ and [[]] operators work like reaching inside a single box and retrieving its contents.

    In our analogy, these two operators unpack a single box. For example, we might remove the books from the second box and place them on a bookshelf. We’re not interested in the box; we’re directly interacting with the books.

  • The [] operator works like choosing one or more unopened boxes from our stack and doing something with those boxes.

    In our analogy, this operator picks up some boxes but doesn’t open them. For example, we might pick up the first two boxes and move them to our bedroom. This time, we’re only interacting with the boxes; we’re not concerned with anything inside the boxes.

If you’re ever surprised that a selection “is still a list” when you expected the raw value from the list slot, check whether you used [] (which always keeps the list wrapper) instead of $ or [[]] (which unwrap the selected value).

Predict: What type of object will the following code print?

class(someList$c)
[1] "list"

The command someList$c returns a list because the c slot in someList holds another list. Nothing special happens here: using the single element extraction procedure returns the contents of the c slot, as it always does. In this case, those contents just happen to be another list. In terms of our “Moving Day” analogy, running someList$c is like unpacking the third box. After unpacking, we’re left with more boxes, since the third box only contained a set of smaller boxes.

This example is meant as a warning against overgeneralizing simple rules. Don’t fall into the trap of thinking that $ and [[]] never return a list. Both of these operators will return a list, when the selected slot holds a list.

Modifying List Elements

As with vectors and matrices, you shouldn’t think about the list selection operators ($, [], [[]]) as “subsetting operators”. It’s better to think about the selection operators as designating some part of a list for further processing. In the above examples, the further processing was simply printing the selected elements to the R console, but we can also use the selection operators to modify the designated list elements.

# View the original list
l1
$name
[1] "bob"

$alive
[1] TRUE

$age
[1] 33

$relationshipStatus
[1] 42+3i
# Modify some list elements
l1$age <- 57
l1[[1]] <- "suzy"
l1[c(2, 4)] <- c("foo", "bar")

# View the modified list
l1
$name
[1] "suzy"

$alive
[1] "foo"

$age
[1] 57

$relationshipStatus
[1] "bar"

Lists in R do not require a fixed length or predefined structure. New elements can be added at any point, either by position or by name. This flexibility makes lists well suited for incremental construction.

# Create an empty list
(l2 <- list())
list()
# Add new list slots
l2$grass <- "green"
l2$logical <- FALSE
l2[[3]] <- 1:4

l2
$grass
[1] "green"

$logical
[1] FALSE

[[3]]
[1] 1 2 3 4
Practice

Use the list that you created in the last practice problem for this exercise.

Replace the character vector that you assigned to the hair slot with a two-slot list comprising two length-one character vectors.

  • Use the first vector to describe your hair color.
    • This vector should be the same as the one previously stored in the hair slot.
  • Use the second vector to describe your hair type (e.g., wavy, straight, curly).

Recall my solution for the last practice problem.

myInfo
$name
[1] "bob"

$eyes
[1] "blue"

$hair
[1] "black"

$color
[1] "green"

I would modify my list as follows.

myInfo$hair <- list(
  color = "black",
  type = "wavy"
)

myInfo
$name
[1] "bob"

$eyes
[1] "blue"

$hair
$hair$color
[1] "black"

$hair$type
[1] "wavy"


$color
[1] "green"

Using List Elements

Importantly, the types of the objects you store in a list are maintained.

x <- 1:5
y <- list()
z <- letters[1:10]
l3 <- list(a = x, b = y, c = z)

class(x)
[1] "integer"
class(l3$a)
[1] "integer"
class(y)
[1] "list"
class(l3$b)
[1] "list"
class(z)
[1] "character"
class(l3$c)
[1] "character"

Consequently, objects stored in a list behave just as they would outside of the list. For example, if we store a function as an element of a list, we can call that function directly from within the list.

# Store the Base R 'mean' function in the 'l3' list
l3$myMean <- mean

# Call the normal 'mean' function
mean(1:5)
[1] 3
# Call the function from our list
l3$myMean(1:5)
[1] 3
Practice

Use the list that you created in the last practice problem for this exercise.

Using a single command, test if your eye color OR your hair color is also your favorite color.

Recall the basic logical operators:

  • ==, !=: Equal, Not Equal
  • >, <, >=, <=: Greater Than, Less Than
  • &, |: Logical AND, Logical OR (not exclusive)

Recall my solution for the last practice problem.

myInfo
$name
[1] "bob"

$eyes
[1] "blue"

$hair
$hair$color
[1] "black"

$hair$type
[1] "wavy"


$color
[1] "green"

So, I would set up the necessary logical test as follows.

myInfo$eyes == myInfo$color | myInfo$hair$color == myInfo$color
[1] FALSE
Knowledge Check
  • If l$x returns an error but l[["x"]] works, what does that tell you about l?
  • Assuming the name slot in the list l does not hold another list, why would l["name"] return a list, while l[["name"]] returns some other type of object?
  • Which operator would you need if you wanted to select two named list slots at once?
Ready to Continue?

You’re ready to move on to Data Frames if you can:

  • Explain what a list is and describe at least one situation where a list is the right tool.
  • Create a list with named elements.
  • Explain the difference between $, [], and [[]], and choose the right one for a given task.

If any of these still feel shaky, revisit the relevant page above, or try the Knowledge Quiz to pinpoint exactly where to focus.

Back to top