Chapter 9 Lists
In terms of ‘data types’ (objects containing data) we have only been working with atomic vectors and matrices which are both homogenous objects – they can always only contain data of one specific type.
In this chapter we will learn about lists and data frames which allow to store heterogenous data, e.g., integers and characters both in the same object. This chapter does not cover all details of lists, but we will learn all the important aspects we need in the next chapter when learning about data frames as data frames are based on lists (similar to matrices being based on vectors).
Quick reminder: Frequently used data types and how they can be distinguished by their dimensionality and whether they are homogeneous (all elements of the same type) vs. heterogeneous (elements can be of different types).
9.1 List introduction
As (atomic) vectors, lists are also sequences of elements. However, in contrast to (atomic) vectors, lists allow for heterogenity. Technically a list is a generic vector but often only called ‘list’. We will use the same term in this book (atomic vectors: vectors; generic vectors: lists).
Basis: Lists serve as the basis for most complex objects in R.
- Data frames: Lists of variables of the same length, often (but not necessarily) atomic vectors.
- Fitted regression models: Lists of different elements such as the model parameters, covariance matrix, residuals, but also more technical information such as the regression terms or certain matrix decompositions.
Difference: The difference between vectors and lists.
- Vector: All elements must have the same basic type.
- List: Different elements can have different types, including vectors, matrices, lists or more complex objects.
As lists are ‘generic vectors’, empty lists can also be created using the
(compare Creating vectors).
## [] ## NULL ## ## [] ## NULL
Elements of (unnamed) lists are indexed by
[[...]], while we had
[...] for vectors.
The empty list above has two elements where both (first
[] and second
[]) both contain
9.2 Creating lists
We start right away with constructing a few simple lists for illustration.
While vectors can be created using
c(), lists are most often constructed
using the function
A vector: Vector of length 2.
##  3 5
A list: List containing values of the same types/classes.
## [] ##  3 ## ## [] ##  5
This list with two elements (
[]) contains two vectors, each
of length one, indicated by
 (first vector element).
Another list: Store objects of different types/classes into a list.
list(3, c("five", "six"), matrix(1:6, nrow = 2))
## [] ##  3 ## ## [] ##  "five" "six" ## ## [] ## [,1] [,2] [,3] ## [1,] 1 3 5 ## [2,] 2 4 6
The last example shows a list of length 3 which contains a numeric vector of length \(1\)
[]), a character vector of length \(2\) (second element;
[]), and a
matrix of dimension \(2 \times 3\) (third element;
The two functions
list() work very similarly, except that
b are vectors performs coercion to convert all values into one
(see Vectors: Coercion).
9.3 Recursive structure
A more practical example: We have some information about Peter Falk, a famous actor who played “Lieutenant Columbo” in the long-running TV series Columbo between 1968–2003.
We would like to store all the information in one single object. As we have to deal with both characters (name; month of birth) and numeric values (year and day of birth), we need an object which allows for heterogenity – a list.
list(name = c(given = "Peter", family = "Falk"), (person <-date_of_birth = list(year = 1927, month = "September", day = 16)))
## $name ## given family ## "Peter" "Falk" ## ## $date_of_birth ## $date_of_birth$year ##  1927 ## ## $date_of_birth$month ##  "September" ## ## $date_of_birth$day ##  16
For named lists, the representation (print) changes again compared to unnamed
lists. The elements are now indicated by e.g.,
which we can use to access specific elements. We will come back later when
Our new object
person is a list which contains a named vector (
$name) and a
second element (
$date_of_birth) which itself contains another named list.
This is called a recursive structure, a list can contain lists (can contain
lists (can …)). The figure below shows the structure of the object
with the two lists in green, and the different (integer/character) vectors in
A nice way to get an overview of potentially complex (list-)objects is by using
str() (structure) which we have already seen in the vectors
str() returns us a text-representation similar to the image shown above.
## List of 2 ## $ name : Named chr [1:2] "Peter" "Falk" ## ..- attr(*, "names")= chr [1:2] "given" "family" ## $ date_of_birth:List of 3 ## ..$ year : num 1927 ## ..$ month: chr "September" ## ..$ day : num 16
How to read: the (first-level) list has two elements, one called
name element itself contains a named character of length
\(2\), the second element
date_of_birth is again a list with \(3\) named
day) containing unnamed (plain) vectors of length
\(1\) (numeric, character, numeric). The indent shows the recursive structure,
the more to the right of the
$, the deeper a specific entry in the list.
9.4 List attributes
As all other objects lists always have a specific type and length (object properties). Besides these mandatory properties the default attributes for lists are:
- Class: Objects of class
- Names: Can have names (optional; just like vectors).
Let us investigate the
person object from above:
typeof(person) # Type
##  "list"
length(person) # Number of (first-level) elements
##  2
class(person) # Class is list
##  "list"
names(person) # NAmes of (first-level) elements
##  "name" "date_of_birth"
is.*() function family can be used to check the object.
c("is.list" = is.list(person), "is.vector" = is.vector(person), "is.character" = is.character(person), "is.logical" = is.logical(person), "is.numeric" = is.numeric(person), "is.integer" = is.integer(person))
## is.list is.vector is.character is.logical is.numeric is.integer ## TRUE TRUE FALSE FALSE FALSE FALSE
is.list(): Always returns
is.vector(): As lists are generic vectors they also count as vectors, as long as they only have class, length, type and names (optional).
- A list is never numeric, integer, character, or logical, no matter what the list contains.
9.5 Subsetting lists
Subsetting on lists works slightly different than on vectors and matrices. The basic concepts stay the same, however, due to the more complex structure of the object, additional operators for subsetting become available.
||a list||Select sub-list containing one or more elements. The index vector
||content||Select the content of a single list element if
||content||Select the content of a single list element using the name of the element (without quotes).|
Note that there is a distinct difference between single brackets (
double brackets (
[[...]]). The first always returns a list which is a subset
of the original object to be subsetted, while the latter returns the content of
[i]: The method is similar to vector subsetting except that the result will
not be the content of the elements specified, but a sub-list. Let us call
and see what we get:
## $name ## given family ## "Peter" "Falk"
##  "list"
##  "name"
The result of
person is again a list, but only contains the first entry of the original object
In the same way we can use
person[c(TRUE, FALSE)]. This also works with vectors,
e.g., extracting elements
2:1 (both but reverse order):
## List of 2 ## $ date_of_birth:List of 3 ## ..$ year : num 1927 ## ..$ month: chr "September" ## ..$ day : num 16 ## $ name : Named chr [1:2] "Peter" "Falk" ## ..- attr(*, "names")= chr [1:2] "given" "family"
A negative index (
person[-1]) can be used to get all but the first element
(does not work with characters). The result is again a sub-list (like for positive indices).
[[i]], single value: This subsetting type is most comparable to vector subsetting.
Instead of a sub-list, we will get the content of the element, in this case a named
character vector of length \(2\).
## Named chr [1:2] "Peter" "Falk" ## - attr(*, "names")= chr [1:2] "given" "family"
The same can be achieved using
person[["name"]] on named lists. Note that subsetting
with negative indices (
person[[-1]]) does not work in combination with double brackets.
$ operator: Most commonly used when working with named lists is
$ operator. This operator is called the dollar operator. Instead of
person[["name"]] we can also call
person$name as long as the name
does not contain blanks or special characters. Note that there is no blank
$ operator (not as shown in the output of
## given family ## "Peter" "Falk"
##  "character"
$ operators can also be combined. If we are interested in the month
of birth (
month) which is stored within
date_of_birth we can use:
##  "September"
How to read:
- Right to left: Return
date_of_birthof the object
- Left to right: Inside object
personaccess the element
[[j]] with vectors: Take care, something unexpected happens. As an
example, let us call
person[[c(1, 2)]]. We could think this returns us
person[], but that’s not the case. Instead, nested or recursive
subsetting is performed. The two indices (
c(1, 2)) are used as indices for
different depths of the recursive list.
##  "Falk"
What happened: The first element of the vector (here
1) is used for the top-level list. Our
first entry the vector
name. The second element of the vector (
2) is then used to extract
the second element of whatever
name contains. It is the same as:
##  "Falk"
Note: We will not use this often, but keep it in mind if you run into
interesting results when subsetting lists or data frames. The same happens if
you use a vector, e.g.,
person[[c("name", "last_name")]] or
Exercise 9.1 Practicing subsetting on lists: The following object
demo is a list with information
about two persons, Frank and Petra (simply copy&paste it into your R session).
list( demo <-"Petra" = list(location = "Birmingham", kids = NULL, job = "Programmer"), "Frank" = list(location = "Kufstein", kids = c("Peter", "Paul")) )str(demo)
## List of 2 ## $ Petra:List of 3 ## ..$ location: chr "Birmingham" ## ..$ kids : NULL ## ..$ job : chr "Programmer" ## $ Frank:List of 2 ## ..$ location: chr "Kufstein" ## ..$ kids : chr [1:2] "Peter" "Paul"
- How do we get Franks location?
NULL). Why doesn’t this work?
- How many kids does Frank have (use code to answer)?
- Our friend Petra moves from Birmingham to Vienna. Change her
demo) to Vienna.
Solution. Franks location:
demo is a list with two elements, one called
Thus, we can access the first list element using
demo$Frank. This returns
the content of this list element which itself is, again, a list. In there,
we have the
location we are looking for.
Thus, one option to get Franks location is to use:
##  "Kufstein"
Alternatively, we could use brackets and subsetting by name. Warning: we need double brackets to access the content.
##  "Kufstein"
demo["Frank"]$location: This will not work. The reason is that we only
use single brackets!
demo["Frank"] returns a sub-list of
demo which now only contains
"Petra"). However, we do not get the content or information for Frank.
## $Frank ## $Frank$location ##  "Kufstein" ## ## $Frank$kids ##  "Peter" "Paul"
## $Frank ## $Frank$location ##  "Kufstein" ## ## $Frank$kids ##  "Peter" "Paul"
The last line looks werid but always only just extract
"Frank" from itself but
does not access the list element for
"Frank". Thus, when we try to access
location (which does not exist on this level) we get a
NULL in return.
##  "Frank"
How many kids does Frank have? To answer this question, we need to find out
how long the
kids vector is. Again, we can access this specific element
in different ways, the most easy one:
##  "Peter" "Paul"
##  2
Petra moves to Vienna: You can use any subsetting technique and assign a new value.
I will stick to the
$ operator and do the following:
$Petra$location <- "Vienna" demostr(demo)
## List of 2 ## $ Petra:List of 3 ## ..$ location: chr "Vienna" ## ..$ kids : NULL ## ..$ job : chr "Programmer" ## $ Frank:List of 2 ## ..$ location: chr "Kufstein" ## ..$ kids : chr [1:2] "Peter" "Paul"
9.6 Replacing/deleting elements
Replacement functions are available for all subset types above which can be used to overwrite elements in an existing list or add new elements to a list.
As an example, let us replace the element
$name in the
We use subsetting with the
$ operator and assign (store) a new object.
person$name exists, it will be replaced.
$name <- c(given_name = "Max", middle_name = "Maximilian", family_name = "Mustermann") personstr(person)
## List of 2 ## $ name : Named chr [1:3] "Max" "Maximilian" "Mustermann" ## ..- attr(*, "names")= chr [1:3] "given_name" "middle_name" "family_name" ## $ date_of_birth:List of 3 ## ..$ year : num 1927 ## ..$ month: chr "September" ## ..$ day : num 16
Or replace the month in the date of birth with an integer
9L instead of
$date_of_birth$month <- 9L personstr(person)
## List of 2 ## $ name : Named chr [1:3] "Max" "Maximilian" "Mustermann" ## ..- attr(*, "names")= chr [1:3] "given_name" "middle_name" "family_name" ## $ date_of_birth:List of 3 ## ..$ year : num 1927 ## ..$ month: int 9 ## ..$ day : num 16
The same way, new elements can be added. If the element we assign an object to
does not yet exist, it will be added to the original list object. Let us add a
job element containing
$job <- "Actor" # Adds new element to (first-level) list personstr(person)
## List of 3 ## $ name : Named chr [1:3] "Max" "Maximilian" "Mustermann" ## ..- attr(*, "names")= chr [1:3] "given_name" "middle_name" "family_name" ## $ date_of_birth:List of 3 ## ..$ year : num 1927 ## ..$ month: int 9 ## ..$ day : num 16 ## $ job : chr "Actor"
Delete elements: To delete an element, we simply have to replace it with a
object. An example using a very simple list:
list(a = "first", b = "second", c = NULL))(x <-
## $a ##  "first" ## ## $b ##  "second" ## ## $c ## NULL
$a <- NULL xx
## $b ##  "second" ## ## $c ## NULL
As you can see it is possible that a list element can contain
NULL (see element
but if assigned (
x$a <- NULL) R will remove the element completely (not storing
NULL on it).
Exercise 9.2 Practicing replacement: As in the previous exercise we will use the following list to work with. The list contains information about Petra and Frank.
list( demo <-"Petra" = list(location = "Birmingham", kids = NULL, job = "Programmer"), "Frank" = list(location = "Kufstein", kids = c("Peter", "Paul")) )
We need to update this list and add or change some of the elements.
- Petra moves from Birmingham to Vienna. Update her location.
- Frank just got a newborn baby called
"Malena". Add her name to the
- Add a third person called ‘Regina’, located in ‘Sydney’. She has one child called ‘Lea’ and works as a ‘Teacher’.
Solution. Petra moves to Vienna: You can use any subsetting methods we have just seen.
In this solution we will stick to the
$ operator. All we have to do is to
access Petras location, and assign a new value.
$Petra$location <- "Vienna"demo
Frank got a third child: Here, we could do the same as for Petra and simply assign
a new vector with all three kids to Frank (
demo$Frank$kids <- c("Peter", "Paul", "Malena")).
However, this is not super nice (hard-coded).
Instead we use
c() and combine the vector containing the first two kids with the new born,
and store the vector (combination of subsetting and replacement) as follows:
# assign to get existing new born $Frank$kids <- c(demo$Frank$kids, "Malena")demo
Adding Regina: Regina is not yet in our list, however, we can assign new elements
the same way we replace elements. If the element exists it will be overwritten. If it
does not exist, it will be added. In this case we want to add a new list
to the existing object
$Regina <- list(location = "Sydney", kids = "Lea", job = "Teacher") demostr(demo)
## List of 3 ## $ Petra :List of 3 ## ..$ location: chr "Vienna" ## ..$ kids : NULL ## ..$ job : chr "Programmer" ## $ Frank :List of 2 ## ..$ location: chr "Kufstein" ## ..$ kids : chr [1:3] "Peter" "Paul" "Malena" ## $ Regina:List of 3 ## ..$ location: chr "Sydney" ## ..$ kids : chr "Lea" ## ..$ job : chr "Teacher"
9.7 Combining lists
Multiple lists can be combined using either
c(<list 1>, <list 2>): Creates a new list by combining all elements from the two lists. The result is a list with a length of the number of elements from
list(<list 1>, <list 2>): Creates a new list of length 2, where the first element contains
<list 1>, the second
Example: Using two lists
list2 (both of length 2).
list(3, 4) list1 <- list(100, 200) list2 <-c("length of list 1" = length(list1), "length of list 2" = length(list2))
## length of list 1 length of list 2 ## 2 2
# Use concatenate (c()) c(list1, list2) res1 <-length(res1)
##  4
## List of 4 ## $ : num 3 ## $ : num 4 ## $ : num 100 ## $ : num 200
# Using list() list(list_one = list1, list_two = list2) res2 <-length(res2)
##  2
## List of 2 ## $ list_one:List of 2 ## ..$ : num 3 ## ..$ : num 4 ## $ list_two:List of 2 ## ..$ : num 100 ## ..$ : num 200
The latter results in a recursive list where each element from the list
res2 itself contains a list with two elements. As shown, we can also name the
elements (similar to
rbind() when creating matrices;
Matrices: Combining objects).
Note that the naming of the list-elements has a different effect when using
c(list_one = list1, list_two = list2)).
Just as a brief summary to recap the new content:
- Creating lists: Using the function
- Name attribute: Lists can be named or unnamed.
- Heterogenity: Allows to store objects of different types.
- Replacement: Subsetting can be used to replace existing elements, add new elements, or
delete elemets (assigning
- Recursive lists: Lists can be recursive (lists containing lists containing lists …).
An overview of the different subsetting methods we have learned for different objects (vectors, matrices, and lists).
|Subset||By index||By name||Logical|
Most subsetting methods also work with vectors (vectors of indices or names). You will see that we can re-use most of this when working with data frames, our next topic.