Reproducible Research Course by Eric C. Anderson for (NOAA/SWFSC)


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, or length, or class 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 or hist, etc.) return their results in lists
    • So, you really need to know how to:
      1. Recognize lists when you see them
      2. Access elements from lists
      3. Operate upon the components of lists
    • You will also want to know how to create and use lists yourself.

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 a value or tag=value format.
    • The tags are recorded in the names 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 with c(), just like one can with list():

    # 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:

list-train.png

  • 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 way [ ] sees a list:

So, to the [ ] (“single-chomp” indexing operator) the list Z really looks like this:

list-train-agnostic.png

Hence if do this Z[c(3,1)] you get a list back that looks like:

list-train-agnostic-3-1.png

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:

list-train-3-1.png

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.

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,
    1. We go to boxcar i
    2. 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.
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 NULLs

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 that names 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