7

What's the underlying logic or philosophical foundation to understand the difference between mylist[2] and mylist[[2]] in the following?

What's a simple logical way to understand single square brackets vs. double square brackets?

> mylist <- list(1, list("a","b","c"))

> mylist[2]
# [[1]]
# [[1]][[1]]
# [1] "a"

# [[1]][[2]]
# [1] "b"

# [[1]][[3]]
# [1] "c"

> mylist[[2]]
# [[1]]
# [1] "a"

# [[2]]
# [1] "b"

# [[3]]
# [1] "c"
lmo
  • 37,904
  • 9
  • 56
  • 69
thanks_in_advance
  • 2,603
  • 6
  • 28
  • 44
  • 3
    `mylist[2]` is a sublist of mylist. It will be a list with as many elements are in the numeric or character argument to "[". `mylist[[2]]` is the contents of `mylist[2]`. The "[[" function is required to return only a single element although in this case that is a list containing multiple elements. It can be another list (as in this case) or could have also been a matrix or an undimensioned atomic vector. – IRTFM Apr 21 '16 at 18:28
  • 2
    `mylist[2]` returns a list with one element (which is a list in your example). So you end up with a list of a list of three `character` vectors. `mylist[[2]]` take the second element of `mylist` which is a list of three `character` vector. Try `length(mylist[2])` and `length(mylist[[2]])`. See also the output of `str(mylist[2])` and `str(mylist[[2]])`. – nicola Apr 21 '16 at 18:32
  • 3
    An analogy I have heard is to think of a list as a train. Each car in the train is carrying stuff. If you remove two cars, you have a train with two fewer cars. If you remove all but one car, it is still a train. This is equivalent to `[]`. To look at what stuff a particular car is holding, you have to open the doors, which is equivalent to `[[]]`. – lmo Apr 21 '16 at 18:53
  • 5
    @lmo ...and sometimes when you open the door of a train car there's a whole other train sitting in there! :) – joran Apr 21 '16 at 19:23
  • 1
    Nice remarks till now. One consequence is that you can do `..[i:j]` (giving you a part of the list as list) but you can't do `..[[i:j]]` (because the content of each 'car' may be very different to the content of the others). – jogo Apr 22 '16 at 06:33
  • @lmo I think if you put all comments together it will be a good answer. – jogo Apr 23 '16 at 08:31
  • [This](https://twitter.com/hadleywickham/status/643381054758363136) helped me understand it a little better as well. – tblznbits Apr 23 '16 at 19:46
  • Strongly related: [What's the dffierence between `[` and `[[`?](http://stackoverflow.com/q/1169456/903061) – Gregor Thomas Apr 23 '16 at 23:58

1 Answers1

7

A simple analogy is to think of a list as a train. Each car in the train is carrying stuff. If you remove two cars, you have a train with two fewer cars. If you remove all but one car, it is still a train with a single car.

  • Reducing the size of the train or reorganizing the order of the cars can be achieved though the [] (subsetting) function.
  • To examine the contents of a particular car, you have to open the doors, which is achieved through [[]] (though $ may also be used with a named list). I refer to this as the extraction function, though I'm not sure if this is a widely used term.

In your example, mylist[2] is a sublist of mylist containing one element. You can verify this with length(mylist[2]). Provided that the arguments are valid, the [ function will provide a list with as many elements as are in the numeric or character vector provided as an argument to [. Most often, we are interested in examining the contents of a list item. This is achieved with the [[ function. For example, mylist[[2]] is the contents of mylist[2], which itself is a list containing multiple elements. To see this, try length(mylist[[2]])

Because [ can be thought of as a list subsetting function and [[ as a list element extraction function, mylist[1:2] and mylist[c(1,2)] return a sublist (which is equivalent to mylist in this case), whereas mylist[[1:2]] and mylist[[c(1,2)]] return a "subscript out of bounds" error. It is only possible to extract one list element at a time (ie, per function call).

@richard-scriven alerted me to a link on a Hadley Wickham twitter post providing an additional analogy of a nested list in the form of photographs.

With a fairly simple list structure, str is great way to get an idea of the list contents. In this example, the output of str(mylist[2]) and str(mylist[[2]]) provide additional insight into their differing data structure.

In general, a list is agnostic to its contents, so that a single list may contain other lists, data.frames, matrices, and atomic vectors as separate elements. As @joran, joked in his comment, this where the train analogy gets stretched, maybe a little too much. However, once you are comfortable with the first level of a list, additional nested lists behave in the same way. (maybe the nested lists are boxes carried inside of the train car?)

Side Note:
One of my favorite functions for examining lists and data.frames (which are lists with atomic vectors of a common length), is the str function. I regularly use it after reading in a .csv, .dta, or other file to examine the list structure. A common hurdle with users learning R (as well as experienced users) in debugging code is keeping in mind what data structure they are working with and what data structure is needed as an argument for or what data structure is the output of a function. str together with typeof and class, are an excellent suite of tools in addressing this problem.

This answer benefits from comments from @42, @nicola, @joran, @jogo, and @richard-scriven.

lmo
  • 37,904
  • 9
  • 56
  • 69
  • 2
    Could also point them to this for some nice visualization https://twitter.com/hadleywickham/status/643381054758363136, although I would never put pepper packets inside a pepper shaker. Haha – Rich Scriven Jun 25 '16 at 21:46