How do I use yesterday’s value?

Move a column down one row with lag so the current row can read the previous one, and up one row with lead.

lag(x) shifts a column down one row. Row 2 gets what was in row 1, row 3 gets what was in row 2, and row 1 gets NA because nothing came before it. lead(x) shifts the other way.

In this lesson I put x, lag(x), lag(x, 2) and lead(x) side by side in one small table so you can see the shift, then use lag() on the price file to give every row yesterday’s close.

Step 1. The shift by hand, then the function

Shifting down means dropping the last element and putting NA in front. Shifting up means dropping the first element and putting NA at the end. Both are one line of base R, using the head() and negative indexing from Lesson 5.

library(dplyr)

x <- c(10, 12, 11, 15, 14)   # five values in order

print(c(NA, head(x, -1)))    # -> [1] NA 10 12 11 15
[1] NA 10 12 11 15
print(c(x[-1], NA))          # -> [1] 12 11 15 14 NA
[1] 12 11 15 14 NA

lag() and lead() from dplyr do exactly that, and keep the length the same.

print(lag(x))                # -> [1] NA 10 12 11 15
[1] NA 10 12 11 15
print(lead(x))               # -> [1] 12 11 15 14 NA
[1] 12 11 15 14 NA

Same numbers. From here I use the named function, because lag(Close) says what it means inside a mutate().

Step 2. See the shift in one table

Put all four columns in one data frame and the alignment is on the page.

d <- data.frame(x = x) |>          # one column to start
  mutate(
    lag1  = lag(x),                # x moved down one row
    lag2  = lag(x, 2),             # x moved down two rows
    lead1 = lead(x)                # x moved up one row
  )

print(d)
   x lag1 lag2 lead1
1 10   NA   NA    12
2 12   10   NA    11
3 11   12   10    15
4 15   11   12    14
5 14   15   11    NA
# ->    x lag1 lag2 lead1
# -> 1 10   NA   NA    12
# -> 2 12   10   NA    11
# -> 3 11   12   10    15
# -> 4 15   11   12    14
# -> 5 14   15   11    NA

Read row 3. x is 11, lag1 is 12 which is row 2, lag2 is 10 which is row 1, and lead1 is 15 which is row 4. lag(x, 2) costs two NA rows at the top instead of one, and lead() puts its NA at the bottom.

Step 3. Fill the gap with default

default = says what to put in the row that has no neighbour.

print(lag(x, default = 0))       # -> [1]  0 10 12 11 15
[1]  0 10 12 11 15
print(x - lag(x))                # -> [1] NA  2 -1  4 -1
[1] NA  2 -1  4 -1
print(x - lag(x, default = 0))   # -> [1] 10  2 -1  4 -1
[1] 10  2 -1  4 -1

Without a default, the first difference is NA, because NA propagates through arithmetic. With default = 0 the first row reports 10, which is 10 - 0. For a price change that is wrong: the stock did not rise by its whole price on day one. Leave it as NA unless zero is genuinely the right answer.

Step 4. Yesterday’s close on the price file

Now the same move on real columns. prices.csv holds simulated daily closes for three tickers over 40 business days. read.csv() gives Date as text, so I convert it with as.Date() and sort on it, since a lag is only meaningful once the rows are in date order.

I take one ticker. lag() runs straight down a column and does not know where AAA stops and CCC starts.

prices <- read.csv("prices.csv")     # 120 rows, 3 tickers, simulated
prices$Date <- as.Date(prices$Date)  # text to real dates so sorting works

aapl <- prices |>
  filter(Ticker == "AAA") |>        # one ticker only
  arrange(Date)                      # oldest first

print(nrow(aapl))                    # -> [1] 40
[1] 40
print(head(aapl, 3))
        Date Ticker  Close  Volume
1 2026-01-02    AAA 186.66 2183494
2 2026-01-05    AAA 186.38 8463199
3 2026-01-06    AAA 188.51 5299573
# ->         Date Ticker  Close  Volume
# -> 1 2026-01-02    AAA 186.66 2183494
# -> 2 2026-01-05    AAA 186.38 8463199
# -> 3 2026-01-06    AAA 188.51 5299573

Add yesterday’s close as a column, then subtract.

aapl <- aapl |>
  mutate(
    Close_prev = lag(Close),         # yesterday's close on today's row
    Change     = Close - Close_prev  # today minus yesterday, in dollars
  )

print(head(aapl[, c("Date", "Close", "Close_prev", "Change")], 5))
        Date  Close Close_prev Change
1 2026-01-02 186.66         NA     NA
2 2026-01-05 186.38     186.66  -0.28
3 2026-01-06 188.51     186.38   2.13
4 2026-01-07 187.24     188.51  -1.27
5 2026-01-08 189.27     187.24   2.03
# ->         Date  Close Close_prev Change
# -> 1 2026-01-02 186.66         NA     NA
# -> 2 2026-01-05 186.38     186.66  -0.28
# -> 3 2026-01-06 188.51     186.38   2.13
# -> 4 2026-01-07 187.24     188.51  -1.27
# -> 5 2026-01-08 189.27     187.24   2.03

Two prices from different rows now sit on the same row, so ordinary column arithmetic can compare them. Row 3 holds both 188.51 and 186.38 and their difference of 2.13.

Step 5. The return falls out of the same shift

Divide instead of subtract and you have the return from Lesson 15.

aapl <- aapl |>
  mutate(Return = Close / lag(Close) - 1)   # today over yesterday, minus one

print(head(aapl[, c("Date", "Close", "Return")], 5))
        Date  Close       Return
1 2026-01-02 186.66           NA
2 2026-01-05 186.38 -0.001500054
3 2026-01-06 188.51  0.011428265
4 2026-01-07 187.24 -0.006737043
5 2026-01-08 189.27  0.010841700
# ->         Date  Close       Return
# -> 1 2026-01-02 186.66           NA
# -> 2 2026-01-05 186.38 -0.001500054
# -> 3 2026-01-06 188.51  0.011428265
# -> 4 2026-01-07 187.24 -0.006737043
# -> 5 2026-01-08 189.27  0.010841700

print(sum(is.na(aapl$Return)))              # -> [1] 1     <- only the first row
[1] 1

Lesson 8 built returns on a bare vector with diff(x) / head(x, -1), which returns 39 numbers from 40 prices. Stick an NA on the front of those 39 and you have the column above.

r_base <- c(NA, diff(aapl$Close) / head(aapl$Close, -1))  # the Lesson 8 rule, padded

print(round(head(r_base, 4), 6))
[1]        NA -0.001500  0.011428 -0.006737
# -> [1]        NA -0.001500  0.011428 -0.006737

print(all.equal(r_base, aapl$Return))   # -> [1] TRUE
[1] TRUE

Same numbers. diff() shortens the vector, so it will not fit next to a 40-row table without padding. lag() keeps the length, which is why it is the version that works in a data frame.

Step 6. Why I filtered to one ticker first

Run the same lag() on all 120 rows sorted by ticker and then date, and look at the seam.

both <- prices |>
  arrange(Ticker, Date) |>           # AAA block, then CCC, then DDD
  mutate(Close_prev = lag(Close))    # lags straight past the ticker boundary

print(both[39:42, c("Date", "Ticker", "Close", "Close_prev")])
         Date Ticker  Close Close_prev
39 2026-02-25    AAA 171.42     172.64
40 2026-02-26    AAA 169.42     171.42
41 2026-01-02    CCC 416.98     169.42
42 2026-01-05    CCC 423.19     416.98
# ->          Date Ticker  Close Close_prev
# -> 39 2026-02-25    AAA 171.42     172.64
# -> 40 2026-02-26    AAA 169.42     171.42
# -> 41 2026-01-02    CCC 416.98     169.42
# -> 42 2026-01-05    CCC 423.19     416.98

Row 41 is the first CCC day, and its “previous close” is 169.42, the last AAA price. lag() counts rows, not tickers. Filter to one ticker, or lag inside group_by(Ticker), which is Lesson 19.

Your turn

Take CCC, sort by date, and add Close_next = lead(Close) and Gap = Close_next - Close. Print the first five rows and the last two. Which end holds the NA?

msft <- prices |>
  filter(Ticker == "CCC") |>            # one ticker
  arrange(Date) |>                       # oldest first
  mutate(
    Close_next = lead(Close),            # tomorrow's close on today's row
    Gap        = Close_next - Close      # tomorrow minus today
  )

print(head(msft[, c("Date", "Close", "Close_next", "Gap")], 5))
# ->         Date  Close Close_next   Gap
# -> 1 2026-01-02 416.98     423.19  6.21
# -> 2 2026-01-05 423.19     423.95  0.76
# -> 3 2026-01-06 423.95     435.34 11.39
# -> 4 2026-01-07 435.34     432.41 -2.93
# -> 5 2026-01-08 432.41     437.84  5.43

print(tail(msft[, c("Date", "Close", "Close_next", "Gap")], 2))
# ->          Date  Close Close_next  Gap
# -> 39 2026-02-25 435.51     435.94 0.43
# -> 40 2026-02-26 435.94         NA   NA

The NA sits at the bottom. The last day has no tomorrow.

lag() is how a value known at the previous row becomes available on the current row. Lesson 18 uses it to run a cumulative product down a column and build an equity curve.