What is a for loop?

Repeat a block of code once per element, fill a vector you made in advance, and total a portfolio the long way and the short way.

A for loop runs the same block of code once for each element of a vector. The loop variable holds one element on each pass.

In this lesson I print prices one at a time, fill a result vector position by position, and total a portfolio in a loop before doing the same thing in one line.

Step 1. One pass per element

for (p in closes) reads as: give p the first element, run the block, then the second, and so on until the vector runs out. The block is everything inside the braces.

closes <- c(185.40, 187.20, 184.90)   # three closing prices

for (p in closes) {                   # p takes each element in turn
  print(p)                            # this line runs three times
}
[1] 185.4
[1] 187.2
[1] 184.9
# -> [1] 185.4
# -> [1] 187.2
# -> [1] 184.9

The body can do anything. Here it values ten shares at each price.

for (p in closes) {                   # same three prices
  print(p * 10)                       # ten shares at that price
}
[1] 1854
[1] 1872
[1] 1849
# -> [1] 1854
# -> [1] 1872
# -> [1] 1849

print(p)                              # -> [1] 184.9    <- p keeps its last value
[1] 184.9

p still exists after the loop finishes, holding whatever it held on the final pass.

Step 2. Fill a vector you made first

Printing throws the numbers away. To keep them, make the answer vector before the loop and write into it. numeric(n) gives you a vector of n zeros.

closes  <- c(185.40, 187.20, 184.90, 188.10)  # four prices this time
doubled <- numeric(length(closes))            # four zeros, ready to be filled

print(doubled)                                # -> [1] 0 0 0 0
[1] 0 0 0 0
for (i in seq_along(closes)) {                # i = 1, 2, 3, 4
  doubled[i] <- closes[i] * 2                 # write the answer into slot i
}

print(doubled)                                # -> [1] 370.8 374.4 369.8 376.2
[1] 370.8 374.4 369.8 376.2

Notice the loop variable is now i, a position rather than a value. closes[i] reads slot i, and doubled[i] <- writes into slot i.

The alternative, starting with an empty vector and pasting one element on the end each pass, makes R copy the whole vector every time. On four prices you will not notice. On four thousand you will.

Step 3. Why seq_along and not 1:length

seq_along(x) and 1:length(x) give the same positions for any vector that has some.

print(seq_along(closes))   # -> [1] 1 2 3 4
[1] 1 2 3 4
print(1:length(closes))    # -> [1] 1 2 3 4
[1] 1 2 3 4

They part company when the vector is empty.

empty <- numeric(0)        # a vector with nothing in it

print(length(empty))       # -> [1] 0
[1] 0
print(seq_along(empty))    # -> integer(0)
integer(0)
print(1:length(empty))     # -> [1] 1 0
[1] 1 0

1:0 counts down from 1 to 0, so it hands the loop two positions that do not exist.

for (i in 1:length(empty)) {            # counts down: 1, then 0
  print(paste("body ran with i =", i))  # runs twice on an empty vector
}
[1] "body ran with i = 1"
[1] "body ran with i = 0"
# -> [1] "body ran with i = 1"
# -> [1] "body ran with i = 0"

for (i in seq_along(empty)) {           # no positions, so no passes
  print(paste("body ran with i =", i))  # nothing prints
}

The second loop printed nothing, which is what an empty vector should give you. Use seq_along.

Step 4. Value a portfolio

Three tickers, three prices, three share counts. The loop fills one position value per holding.

tickers <- c("AAA", "CCC", "DDD")   # three holdings
prices  <- c(185.40, 410.20, 128.75)   # one price each
shares  <- c(10, 4, 20)                # one share count each

values <- numeric(length(prices))      # three zeros to fill

for (i in seq_along(prices)) {         # i = 1, 2, 3
  values[i] <- prices[i] * shares[i]   # price times shares for holding i
}

print(values)                          # -> [1] 1854.0 1640.8 2575.0
[1] 1854.0 1640.8 2575.0

Because i indexes every vector, one loop can read the ticker and the value together.

for (i in seq_along(tickers)) {                   # walk both vectors at once
  print(paste(tickers[i], "is worth", values[i])) # label and amount on one line
}
[1] "AAA is worth 1854"
[1] "CCC is worth 1640.8"
[1] "DDD is worth 2575"
# -> [1] "AAA is worth 1854"
# -> [1] "CCC is worth 1640.8"
# -> [1] "DDD is worth 2575"

Adding the positions up is another loop, this time over values rather than positions. The running total starts at 0 because 0 is neutral for adding.

total <- 0             # the running total
for (v in values) {    # v takes each position value
  total <- total + v   # add it on
}

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

Step 5. The same thing in one line

Lesson 1 showed that arithmetic in R runs element by element, so prices * shares already does what the first loop did. sum() does what the second loop did.

print(sum(prices * shares))   # -> [1] 6069.8    <- both loops in one line
[1] 6069.8
print(total)                  # -> [1] 6069.8
[1] 6069.8

Same number. Reach for the vectorised line when it exists, and for a loop when each pass depends on the one before it or when there is no vectorised form.

Your turn

You hold 3 shares at 50.00, 10 shares at 55.00, and 25 shares at 44.00. Fill a values vector in a loop, total it, and check the total against sum(prices * shares).

prices <- c(50.00, 55.00, 44.00)      # three prices
shares <- c(3, 10, 25)                # the holding for each

values <- numeric(length(prices))     # three zeros to fill
for (i in seq_along(prices)) {        # i = 1, 2, 3
  values[i] <- prices[i] * shares[i]  # write into slot i
}

print(values)                         # -> [1]  150  550 1100
print(sum(values))                    # -> [1] 1800
print(sum(prices * shares))           # -> [1] 1800