Move a column down one row with .shift(1) so every row can read the row above it.
.shift(1) moves every value down one row. The index stays where it is, so row 3 now holds the value that used to sit on row 2. A row can then read the value from the row before it, on the same line.
In this lesson I shift a small Series and print it next to the original, rebuild .pct_change() out of a shift, then put a Close_yesterday column on a price table read from a CSV and shift the other way with .shift(-1).
Step 1. What the move looks like
Four prices. s.shift(1) returns a Series of the same length, with each value one row lower and a NaN at the top.
A return divides today’s price by yesterday’s price and subtracts 1. With s.shift(1) giving yesterday’s price on today’s row, that sentence becomes one line of code.
by_hand pct_change
0 NaN NaN
1 0.100000 0.100000
2 -0.100000 -0.100000
3 0.090909 0.090909
print(by_hand.equals(built_in)) # -> True
True
.pct_change() from Lesson 17 is this shift with the arithmetic written for you.
Step 3. Yesterday’s close on a price table
prices.csv holds 40 business days of daily closes for three tickers, simulated rather than real market history.
Shifting mixes tickers together if you shift the whole file at once, so take one ticker first. reset_index(drop=True) renumbers the rows from 0 after the filter.
Date Close Close_yesterday Change
0 2026-01-02 186.66 NaN NaN
1 2026-01-05 186.38 186.66 -0.28
2 2026-01-06 188.51 186.38 2.13
3 2026-01-07 187.24 188.51 -1.27
4 2026-01-08 189.27 187.24 2.03
5 2026-01-09 185.32 189.27 -3.95
Read row 2 across: the close on 6 January was 188.51, the close on 5 January was 186.38, and the price rose 2.13. Both numbers sit on one row, so the subtraction is a column minus a column.
The first row is the only gap.
print(aapl["Change"].isna().sum()) # -> 1
1
Step 4. .shift(-1) moves the other way
A negative number moves values up. shift(-1) puts tomorrow’s close on today’s row.