3.5 Lists
Lists are a step up in complexity from atomic vectors: each element can be any type, not just vectors. Technically speaking, each element of a list is actually the same type because, as you saw in Section 2.3.3, each element is really a reference to another object, which can be any type.
3.5.1 Creating
You construct lists with list()
:
list(
l1 <-1:3,
"a",
c(TRUE, FALSE, TRUE),
c(2.3, 5.9)
)
typeof(l1)
#> [1] "list"
str(l1)
#> List of 4
#> $ : int [1:3] 1 2 3
#> $ : chr "a"
#> $ : logi [1:3] TRUE FALSE TRUE
#> $ : num [1:2] 2.3 5.9
Because the elements of a list are references, creating a list does not involve copying the components into the list. For this reason, the total size of a list might be smaller than you might expect.
::obj_size(mtcars)
lobstr#> 7,208 B
list(mtcars, mtcars, mtcars, mtcars)
l2 <-::obj_size(l2)
lobstr#> 7,288 B
Lists can contain complex objects so it’s not possible to pick a single visual style that works for every list. Generally I’ll draw lists like vectors, using colour to remind you of the hierarchy.
Lists are sometimes called recursive vectors because a list can contain other lists. This makes them fundamentally different from atomic vectors.
list(list(list(1)))
l3 <-str(l3)
#> List of 1
#> $ :List of 1
#> ..$ :List of 1
#> .. ..$ : num 1
c()
will combine several lists into one. If given a combination of atomic vectors and lists, c()
will coerce the vectors to lists before combining them. Compare the results of list()
and c()
:
list(list(1, 2), c(3, 4))
l4 <- c(list(1, 2), c(3, 4))
l5 <-str(l4)
#> List of 2
#> $ :List of 2
#> ..$ : num 1
#> ..$ : num 2
#> $ : num [1:2] 3 4
str(l5)
#> List of 4
#> $ : num 1
#> $ : num 2
#> $ : num 3
#> $ : num 4
3.5.2 Testing and coercion
The typeof()
a list is list
. You can test for a list with is.list()
, and coerce to a list with as.list()
.
list(1:3)
#> [[1]]
#> [1] 1 2 3
as.list(1:3)
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 3
You can turn a list into an atomic vector with unlist()
. The rules for the resulting type are complex, not well documented, and not always equivalent to what you’d get with c()
.
3.5.3 Matrices and arrays
With atomic vectors, the dimension attribute is commonly used to create matrices. With lists, the dimension attribute can be used to create list-matrices or list-arrays:
list(1:3, "a", TRUE, 1.0)
l <-dim(l) <- c(2, 2)
l#> [,1] [,2]
#> [1,] Integer,3 TRUE
#> [2,] "a" 1
1, 1]]
l[[#> [1] 1 2 3
These data structures are relatively esoteric but they can be useful if you want to arrange objects in a grid-like structure. For example, if you’re running models on a spatio-temporal grid, it might be more intuitive to store the models in a 3D array that matches the grid structure.
3.5.4 Exercises
List all the ways that a list differs from an atomic vector.
Why do you need to use
unlist()
to convert a list to an atomic vector? Why doesn’tas.vector()
work?Compare and contrast
c()
andunlist()
when combining a date and date-time into a single vector.