Lists (aka “recursive vectors”)
Introduction to Lists
- A list in R is a special type of vector.
- Previously we have seen atomic vectors in which each element is a scalar/singleton and all elements must be of the same mode.
- In a list, each component can be any object of any sort! So, a single component of a list can be an atomic vector, or another list, or a function, or an array, or a data frame, etc.
- No restriction that the different components of a list be of the same
mode
, orlength
, orclass
or anything! - This makes the
list
class very powerful (!!) and makes lists a very important type of object in R. - Most high level functions (like
lm
orhist
, etc.) return their results in lists- So, you really need to know how to:
- Recognize lists when you see them
- Access elements from lists
- Operate upon the components of lists
- You will also want to know how to create and use lists yourself.
- So, you really need to know how to:
Creating Lists
You use the list()
function to create a list.
# make L an empty list
L <- list()
L
#> list()
# make a list of two components
M <- list( c(2,4,6), c("a","b") )
M
#> [[1]]
#> [1] 2 4 6
#>
#> [[2]]
#> [1] "a" "b"
# make a list of three components
N <- list( 12, c(3,4), c(T,T,T,F))
N
#> [[1]]
#> [1] 12
#>
#> [[2]]
#> [1] 3 4
#>
#> [[3]]
#> [1] TRUE TRUE TRUE FALSE
How R prints lists
- Notice that the i-th component is printed after a
[[i]]
in R’s output.- Shortly we will see that this explicitly tells us how we can extract these values from the list.
What if a list includes another list as a component?
# make a list that has another list as a component Q <- list(c(1, 2, 3, 4, 5, 6), c("a", "b", "c", "d"), list("squish", list("whizz-bang", c(F, F, T) ) ) ) Q #> [[1]] #> [1] 1 2 3 4 5 6 #> #> [[2]] #> [1] "a" "b" "c" "d" #> #> [[3]] #> [[3]][[1]] #> [1] "squish" #> #> [[3]][[2]] #> [[3]][[2]][[1]] #> [1] "whizz-bang" #> #> [[3]][[2]][[2]] #> [1] FALSE FALSE TRUE
Names for list components
- Just as we saw atomic vectors with a
names
attribute, so too can lists have names. - You can pass names in to the
list()
function:list()
takes arguments that are have avalue
ortag=value
format.The
tag
s are recorded in thenames
attribute of the list.# list of 2 named components and one unnamed one a <- list( foo = c("q","w"), bar = "MINE!", 3+5*1i ) # note that the names of the components are stored in the # list's names attribute. Accessible with the names function names(a) # the names are stored in this attribute #> [1] "foo" "bar" ""
Backtrack: you can do the same with c()
In the last lecture, we may not have noted that
names
can be assigned to the elements of atomic vectors during assignment withc()
, just like one can withlist()
:# example of assigning names and values with c() weights <- c( onefish = 90, twofish = 101, redfish = 112, bluefish = 107 ) weights #> onefish twofish redfish bluefish #> 90 101 112 107 # check out the names: names(weights) #> [1] "onefish" "twofish" "redfish" "bluefish"
How R prints complex lists with names
Let’s recreate our Q list from above, but put a few names on it (and call it Z):
# make a list that has another list as a component
Z <- list(screaming = c(1, 2, 3, 4, 5, 6),
yellow = c("a", "b", "c", "d"),
zonkers = list("squish",
second = list(a.name = "whizz-bang",
c(F, F, T)
)
)
)
Z
#> $screaming
#> [1] 1 2 3 4 5 6
#>
#> $yellow
#> [1] "a" "b" "c" "d"
#>
#> $zonkers
#> $zonkers[[1]]
#> [1] "squish"
#>
#> $zonkers$second
#> $zonkers$second$a.name
#> [1] "whizz-bang"
#>
#> $zonkers$second[[2]]
#> [1] FALSE FALSE TRUE
Accessing Parts of Lists with [ ]
The Boxcar analogy
Someone had a nice way of thinking about lists:
- You can think of a list as a train.
- Each component of a list is a boxcar.
- What that component holds is the contents of the boxcar.
The weird thing about this is that inside a boxcar you can have a whole ’nuther train…
- However, it provides a nice way of thinking about the indexing lists with the different list indexing operators.
List indexing operators
- There are
[ ]
and ’[[ ]]and
$`. - We will talk about
[ ]
first
L[ vec ] — The “standard” indexing operator
- This is what we have been using on atomic vectors.
- When applied to a list the
[ ]
indexing operator- Returns a list, stil!
- Effectively just picks out boxcars from the original list (i.e. original train) and links them up (in the order prescribed by the indexing operator) into a new train.
- I like to call it the “single-chomp” indexing operator—it’s like a pair of jaws that just rips off a chunk of the list but doesn’t “chew” into the boxcars at all.
A diagram might help
- Here is the list
Z
from above. It is a train with three boxcars:
Never mind that the boxcar named “zonkers” has a lot of complicated stuff in it.
- Now, this is crucial: From the perspective of the
[ ]
indexing operator the train is just a bunch of boxcars.- The
[ ]
is agnostic to the contents of the boxcars… - When you index it with
[ ]
, the[ ]
does just what it would do with an atomic vector, it just happens that the elements of a list can be thought of as singleton lists, themselves (instead of logicals or numerics, etc.)
- The
The way [ ] sees a list:
So, to the [ ]
(“single-chomp” indexing operator) the list Z
really looks like this:
Hence if do this Z[c(3,1)]
you get a list back that looks like:
And if you had X-ray eyes and were able to peek into each of the boxcars of the list Z[c(3,1)]
, the list would look like this:
Examples of indexing with [vec] on lists in code
- Consider indexing a list with
[vec]
- As before,
vec
is a vector that is positive numeric, negative numeric, logical, or character (names) (recall the four main ways of indexing a vector…) - So, let’s see what it looks like when we index some of the lists we created up above:
# first play around with the list N
N
#> [[1]]
#> [1] 12
#>
#> [[2]]
#> [1] 3 4
#>
#> [[3]]
#> [1] TRUE TRUE TRUE FALSE
N[c(3,1)]
#> [[1]]
#> [1] TRUE TRUE TRUE FALSE
#>
#> [[2]]
#> [1] 12
N[-1]
#> [[1]]
#> [1] 3 4
#>
#> [[2]]
#> [1] TRUE TRUE TRUE FALSE
# now index the list a
a
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "MINE!"
#>
#> [[3]]
#> [1] 3+5i
a[c(T,T,F)]
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "MINE!"
a[c("foo", "bar")]
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "MINE!"
a[c(3,2)]
#> [[1]]
#> [1] 3+5i
#>
#> $bar
#> [1] "MINE!"
Summary
- When you index a variable
L
with[ ]
, then- If
L
is a list, the result is a list. - If
L
is an atomic vector, the result is an atomic vector.
- If
Accessing Single Components of Lists with [[ ]]
- Indexing with
[ ]
is all fine and dandy if all we ever want to do is shuffle boxcars around. - But, typically, “the goods” are inside the boxcars!
- How can we get at what is in the boxcars?
- Enter the
[[ ]]
indexing operator. - If we do
L[[i]]
, then,- We go to boxcar i
- We open the door of boxcar i, pull out its contents, and return the contents (but not the boxcar “shell”)
Call it a two-chomp extractor…
- I think of
[[ ]]
as the “two-chomp” extractor. (which is what it looks like…if[ ]
is the one-chomp extractor)- The first chomp grabs a boxcar
- The second one chews on the boxcar to “crunch off” its outer shell.
[[ i ]] only works with single indexes
Here is how I think of it:
- Because
[[ ]]
is “chewing” (to crunch into the boxcar) it can’t take too big of a bite - In fact, it can only chew on a single component of a list (single boxcar) at a time
- So
i
in[[i]]
can be only:- a single positive integer, OR
- a single name of a component.
- “Crunching off” its outer shell means it returns the contents of the component, and not a list that contains that component.
- However, if the contents of the boxcar is another list (for example the “zonkers” component in
Z
), then, of course that list gets returned as a list.- i.e., it only chews through the outermost boxcar.
- However, if the contents of the boxcar is another list (for example the “zonkers” component in
N[2] # single chomp
#> [[1]]
#> [1] 3 4
N[[2]] # two chomps (one bite, one chew!)
#> [1] 3 4
a["foo"] # single chomp
#> $foo
#> [1] "q" "w"
a[["foo"]] # two chomps
#> [1] "q" "w"
These would not work
# indexer for [[ ]] must be of length 1
a[[ c("foo", "bar")]]
a[[c(1, 2)]]
# indexer for [[ ]] cannot be negative
a[[-1]]
# indexer for [[ ]] oughtn't be a logical vector
a[[ TRUE ]] # this only works because TRUE
# gets coerced to 1
a[[c(TRUE, TRUE)]] # this returns "q". Weird subtetly.
# not recommended!
a[[c(TRUE, FALSE)]] # fails appropriately
- N.B. The
[[ ]]
can be used with atomic vectors, but seldom is, as it offers nothing over[ ]
in the atomic vector case.
The $ Two-Chomp Extractor
If you have names on your list, you can use the $
to extract the contents of single components like so:
a$foo
#> [1] "q" "w"
a$bar
#> [1] "MINE!"
Which is convenient if you are typing the name since you don’t have to use quotation marks. But it does not work with names that are stored in objects:
i <- "foo"
a$i # NULL! no component named i
#> NULL
a[[i]] # this returns the same as a$foo
#> [1] "q" "w"
Class Activity:
Given our list Z:
Z
#> $screaming
#> [1] 1 2 3 4 5 6
#>
#> $yellow
#> [1] "a" "b" "c" "d"
#>
#> $zonkers
#> $zonkers[[1]]
#> [1] "squish"
#>
#> $zonkers$second
#> $zonkers$second$a.name
#> [1] "whizz-bang"
#>
#> $zonkers$second[[2]]
#> [1] FALSE FALSE TRUE
How can you extract the part whose value is: "whizz-bang"
.
Do that with names, and with integer indices, and also do it with $
and with `[[ ]]
and with a combination of both.
Recall that R prints lists with headers that show how you could access each element.
The $ extractor might need quotes
- If the names of the list don’t satisfy the requirements for names of symbols in the R language, then you need to quote them
b <- list("my bonny" = 1:3, "lies-over" = 4:9, "the.ocean" = 10:14)
# note when you print it you see the single backticks:
b
#> $`my bonny`
#> [1] 1 2 3
#>
#> $`lies-over`
#> [1] 4 5 6 7 8 9
#>
#> $the.ocean
#> [1] 10 11 12 13 14
# so, this will work:
b$`lies-over`
#> [1] 4 5 6 7 8 9
# but it might be preferable to do
b[["lies-over"]]
#> [1] 4 5 6 7 8 9
# this would fail: b$lies-over
# Oddly, this works:
b$my
#> [1] 1 2 3
# Why?
$ and name matching
- If the name given to $ is the unique prefix of name in the list, then it will automagically expand to that
silly <- list( here.is.a.stupid.long.name = c(F, T, T),
here.is.another.stupid.long.name = 4:8
)
silly$here.is.a. # this works for here.is.a.stupid.long.name
#> [1] FALSE TRUE TRUE
silly$here.is.an # this works for here.is.another.stupid.long.name
#> [1] 4 5 6 7 8
# these return NULL
silly$here.is.a
#> NULL
silly$here
#> NULL
silly$something.completely.different
#> NULL
silly[["what.is.going.on.here?"]]
#> NULL
Adding or Changing Components
You can add a component to a list using [[ ]] or $:
length(a) # number of components
#> [1] 3
a[["boing"]] <- 1:24
length(a) # it is now one component longer
#> [1] 4
a$another <- 1:24 > 8 & 1:24 < 13
a[[10]] <- "this is way out there"
Adding an element to a list that is beyond its current length pads the rest with NULL
s
You can also replace a component using [[ ]] or $
a
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "MINE!"
#>
#> [[3]]
#> [1] 3+5i
#>
#> $boing
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24
#>
#> $another
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE
#> [12] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE
#>
#> [[6]]
#> NULL
#>
#> [[7]]
#> NULL
#>
#> [[8]]
#> NULL
#>
#> [[9]]
#> NULL
#>
#> [[10]]
#> [1] "this is way out there"
a$bar <- "YOURS"
a
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "YOURS"
#>
#> [[3]]
#> [1] 3+5i
#>
#> $boing
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24
#>
#> $another
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE
#> [12] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE
#>
#> [[6]]
#> NULL
#>
#> [[7]]
#> NULL
#>
#> [[8]]
#> NULL
#>
#> [[9]]
#> NULL
#>
#> [[10]]
#> [1] "this is way out there"
Various List Essentials
Catenating lists
- You can use the
c()
operator to catenate lists. If any argument is a list, then it creates a list
c(a,N) # stick those in there
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "YOURS"
#>
#> [[3]]
#> [1] 3+5i
#>
#> $boing
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24
#>
#> $another
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE
#> [12] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE
#>
#> [[6]]
#> NULL
#>
#> [[7]]
#> NULL
#>
#> [[8]]
#> NULL
#>
#> [[9]]
#> NULL
#>
#> [[10]]
#> [1] "this is way out there"
#>
#> [[11]]
#> [1] 12
#>
#> [[12]]
#> [1] 3 4
#>
#> [[13]]
#> [1] TRUE TRUE TRUE FALSE
c(a,c("oops", "this", "atomic", "vector", "gets", "listized!"))
#> $foo
#> [1] "q" "w"
#>
#> $bar
#> [1] "YOURS"
#>
#> [[3]]
#> [1] 3+5i
#>
#> $boing
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24
#>
#> $another
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE
#> [12] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE
#>
#> [[6]]
#> NULL
#>
#> [[7]]
#> NULL
#>
#> [[8]]
#> NULL
#>
#> [[9]]
#> NULL
#>
#> [[10]]
#> [1] "this is way out there"
#>
#> [[11]]
#> [1] "oops"
#>
#> [[12]]
#> [1] "this"
#>
#> [[13]]
#> [1] "atomic"
#>
#> [[14]]
#> [1] "vector"
#>
#> [[15]]
#> [1] "gets"
#>
#> [[16]]
#> [1] "listized!"
- Note how different this is than trying to catenate with
list()
z <- list(a,N) # this makes a new list with two components that are lists!
z[[1]]$another; # two two-bites!
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE
#> [12] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE
z[[1]][[5]][11] # two two-bites and a one-bite!
#> [1] TRUE
Coercing To/Away From List
as.list()
to coerce an atomic vector to be a list with each component being a scalar:
w <- c("this", "is", "only", "a", "test")
as.list(w) # make a list of the vector
#> [[1]]
#> [1] "this"
#>
#> [[2]]
#> [1] "is"
#>
#> [[3]]
#> [1] "only"
#>
#> [[4]]
#> [1] "a"
#>
#> [[5]]
#> [1] "test"
names(w) <- c("one", "two", "three", "four", "fish")
as.list(w) # names are preserved
#> $one
#> [1] "this"
#>
#> $two
#> [1] "is"
#>
#> $three
#> [1] "only"
#>
#> $four
#> [1] "a"
#>
#> $fish
#> [1] "test"
unlist()
to “flatten” a list into an atomic vector, coercing mode to the most general. Note thatnames
if present get prepended to indexes, and NULL components are omitted:
unlist(a)
#> foo1 foo2 bar
#> "q" "w" "YOURS"
#> boing1 boing2
#> "3+5i" "1" "2"
#> boing3 boing4 boing5
#> "3" "4" "5"
#> boing6 boing7 boing8
#> "6" "7" "8"
#> boing9 boing10 boing11
#> "9" "10" "11"
#> boing12 boing13 boing14
#> "12" "13" "14"
#> boing15 boing16 boing17
#> "15" "16" "17"
#> boing18 boing19 boing20
#> "18" "19" "20"
#> boing21 boing22 boing23
#> "21" "22" "23"
#> boing24 another1 another2
#> "24" "FALSE" "FALSE"
#> another3 another4 another5
#> "FALSE" "FALSE" "FALSE"
#> another6 another7 another8
#> "FALSE" "FALSE" "FALSE"
#> another9 another10 another11
#> "TRUE" "TRUE" "TRUE"
#> another12 another13 another14
#> "TRUE" "FALSE" "FALSE"
#> another15 another16 another17
#> "FALSE" "FALSE" "FALSE"
#> another18 another19 another20
#> "FALSE" "FALSE" "FALSE"
#> another21 another22 another23
#> "FALSE" "FALSE" "FALSE"
#> another24
#> "FALSE" "this is way out there"
comments powered by Disqus