What is a list?

Hold text and numbers together in one object, read parts of it with [[ ]] and $, and loop over a list of positions to value a book.

A list holds values of different types at once: text, numbers, and other lists side by side. A vector cannot. c() forces everything it is given down to a single type.

In this lesson I describe one position as a list, read parts of it out, then build a list of positions and total the book two ways.

Step 1. A vector flattens the types

Put a ticker and two numbers in one vector and R turns the numbers into text.

mixed <- c("AAA", 10, 185.40)   # one character, two numbers
print(mixed)                     # -> [1] "AAA"   "10"    "185.4"
[1] "AAA"   "10"    "185.4"
print(class(mixed))              # -> [1] "character"
[1] "character"

The quotes around "10" and "185.4" are the giveaway. You can no longer multiply them.

Step 2. A list keeps them apart

list() takes the same three values and leaves each one as it was. Name the parts as you go.

pos <- list(ticker = "AAA", shares = 10, price = 185.40)

print(class(pos))    # -> [1] "list"
[1] "list"
print(length(pos))   # -> [1] 3         <- one element per name
[1] 3
print(names(pos))    # -> [1] "ticker" "shares" "price"
[1] "ticker" "shares" "price" 

length() counts elements. Printing the list shows each element under its name.

print(pos)
$ticker
[1] "AAA"

$shares
[1] 10

$price
[1] 185.4
# -> $ticker
# -> [1] "AAA"
# ->
# -> $shares
# -> [1] 10
# ->
# -> $price
# -> [1] 185.4

str() gives the same thing in three compact lines, with the type of each element.

str(pos)
List of 3
 $ ticker: chr "AAA"
 $ shares: num 10
 $ price : num 185
# -> List of 3
# ->  $ ticker: chr "AAA"
# ->  $ shares: num 10
# ->  $ price : num 185

chr is character and num is numeric, so the types survived. str() rounds 185.4 to 185 for display only; the stored value is unchanged.

Step 3. Double brackets give the value, single brackets give a list

[[ ]] reaches inside and hands you the element. $ does the same with less typing.

print(pos[["shares"]])          # -> [1] 10
[1] 10
print(pos$shares)               # -> [1] 10
[1] 10
print(class(pos$shares))        # -> [1] "numeric"
[1] "numeric"
print(pos$shares * pos$price)   # -> [1] 1854
[1] 1854

[ ] does not reach inside. It selects part of the list and hands back a shorter list.

print(pos["shares"])
$shares
[1] 10
# -> $shares
# -> [1] 10

print(class(pos["shares"]))     # -> [1] "list"
[1] "list"
print(class(pos[["shares"]]))   # -> [1] "numeric"
[1] "numeric"

Same element, two answers: pos["shares"] is a list of length one, pos[["shares"]] is the number 10. Multiplying the first by a price fails, because you cannot multiply a list.

Position works as well as name. pos[[2]] is the second element.

print(pos[[2]])       # -> [1] 10
[1] 10
print(pos$ticker)     # -> [1] "AAA"
[1] "AAA"

Step 4. A list of lists

An element of a list can itself be a list. That is how you hold a book of positions.

positions <- list(
  list(ticker = "AAA", shares = 10, price = 185.40),   # each position is its own list
  list(ticker = "CCC", shares =  4, price = 410.20),
  list(ticker = "DDD", shares = 20, price = 128.75)
)

print(length(positions))          # -> [1] 3          <- three positions
[1] 3
print(class(positions[[1]]))      # -> [1] "list"
[1] "list"
print(positions[[2]]$ticker)      # -> [1] "CCC"     <- second position, its ticker
[1] "CCC"

positions[[2]] is the second position and $ticker reaches into it.

Now loop over the book. Each pass gives you one position list, and p$shares * p$price values it.

book <- 0                                 # running total, starts at zero
for (p in positions) {                    # p is one position list per pass
  value <- p$shares * p$price             # what this position is worth
  book  <- book + value                   # add it to the running total
  cat(p$ticker, "is worth", value, "\n")  # one line per position
}
AAA is worth 1854 
CCC is worth 1640.8 
DDD is worth 2575 
# -> AAA is worth 1854
# -> CCC is worth 1640.8
# -> DDD is worth 2575

print(book)                               # -> [1] 6069.8
[1] 6069.8

Step 5. The same total in one line

sapply() runs a function on every element of a list and returns a vector.

values <- sapply(positions, function(p) p$shares * p$price)  # one value per position

print(values)          # -> [1] 1854.0 1640.8 2575.0
[1] 1854.0 1640.8 2575.0
print(class(values))   # -> [1] "numeric"
[1] "numeric"
print(sum(values))     # -> [1] 6069.8
[1] 6069.8

The result is a plain numeric vector, so sum() works on it directly. The loop printed as it went; sapply() collects instead.

Pull the tickers the same way and hang them on the vector as names.

tickers        <- sapply(positions, function(p) p$ticker)  # one ticker per position
names(values)  <- tickers                                  # label each value

print(values)
   AAA    CCC    DDD 
1854.0 1640.8 2575.0 
# ->    AAA    CCC    DDD
# -> 1854.0 1640.8 2575.0

print(round(values / sum(values), 3))   # each position as a share of the book
  AAA   CCC   DDD 
0.305 0.270 0.424 
# ->   AAA   CCC   DDD
# -> 0.305 0.270 0.424

Your turn

Add a fourth position to positions: 30 shares of BBB at 92.50. Rebuild values with sapply(), name it, and print the weights.

positions[[4]] <- list(ticker = "BBB", shares = 30, price = 92.50)  # append by index
print(length(positions))                                            # -> [1] 4

vals        <- sapply(positions, function(p) p$shares * p$price)    # value each position
names(vals) <- sapply(positions, function(p) p$ticker)              # label with the tickers

print(vals)
# ->    AAA    CCC    DDD    BBB
# -> 1854.0 1640.8 2575.0 2775.0

print(sum(vals))                      # -> [1] 8844.8
print(round(vals / sum(vals), 3))     # the four weights
# ->   AAA   CCC   DDD   BBB
# -> 0.210 0.186 0.291 0.314

A data frame is a list whose elements are columns of equal length. That is Lesson 11.