Нашли ошибку?
продвинутыйpandas 3.0.6

shift, diff, pct_change

Сравнить значение с предыдущим — прирост за день, изменение в процентах, значение за вчера.

Нужно знать:date_range и временной индекс

Содержание статьи

Коротко

  • shift(1) сдвигает значения на строку вниз — в строке оказывается предыдущее значение; shift(-1) — следующее. Освободившиеся места — NaN.
  • diff() — разность с предыдущим (s - s.shift()), pct_change() — относительное изменение (s / s.shift() - 1).
  • periods=k — сравнение со значением k строк назад.
  • shift(1, freq="D") сдвигает даты в индексе, а значения оставляет на месте.
  • Для нескольких рядов в одной таблице — groupby(...)["col"].diff(), иначе разности посчитаются между разными рядами.

Синтаксис

s.shift(periods=1, freq=None, fill_value=None)
s.diff(periods=1)
s.pct_change(periods=1)
df.groupby("key")["col"].shift(), .diff(), .pct_change()

Параметры

ПараметрЧто делаетПо умолчанию
periodsна сколько строк сдвинуть или с каким значением сравнить; отрицательное — вперёд1
freqв shift: сдвинуть индекс-даты на частоту вместо сдвига значенийNone
fill_valueв shift: чем заполнить освободившиеся местаNaN

Примеры

Данные для примеровиспользуются во всех примерах ниже
days = pd.date_range("2024-03-01", periods=6, freq="D")
price = pd.Series([100.0, 102.0, 101.0, 105.0, 105.0, 99.0], index=days, name="price")
trades = pd.DataFrame({                    # сделки по двум тикерам вперемешку
    "ticker": ["A", "B", "A", "B", "A"],
    "price": [10.0, 50.0, 11.0, 48.0, 12.5],
})
Вчера и завтра
pd.DataFrame({"price": price, "yesterday": price.shift(1), "tomorrow": price.shift(-1)})
Вывод
            price  yesterday  tomorrow
2024-03-01  100.0        NaN     102.0
2024-03-02  102.0      100.0     101.0
2024-03-03  101.0      102.0     105.0
2024-03-04  105.0      101.0     105.0
2024-03-05  105.0      105.0      99.0
2024-03-06   99.0      105.0       NaN
Разность с предыдущим
price.diff()                              # price − вчерашняя цена
Вывод
2024-03-01    NaN
2024-03-02    2.0
2024-03-03   -1.0
2024-03-04    4.0
2024-03-05    0.0
2024-03-06   -6.0
Freq: D, Name: price, dtype: float64
Изменение в процентах
(price.pct_change() * 100).round(2)       # изменение в процентах
Вывод
2024-03-01     NaN
2024-03-02    2.00
2024-03-03   -0.98
2024-03-04    3.96
2024-03-05    0.00
2024-03-06   -5.71
Freq: D, Name: price, dtype: float64
Сравнение с k днями назад
price.diff(periods=3)                     # к значению 3 дня назад
Вывод
2024-03-01    NaN
2024-03-02    NaN
2024-03-03    NaN
2024-03-04    5.0
2024-03-05    3.0
2024-03-06   -2.0
Freq: D, Name: price, dtype: float64
Сдвиг дат, а не значений
price.shift(1, freq="D").head(3)          # сдвинулись даты, а не значения
Вывод
2024-03-02    100.0
2024-03-03    102.0
2024-03-04    101.0
Freq: D, Name: price, dtype: float64
Внутри групп
trades.assign(change=trades.groupby("ticker")["price"].diff())   # внутри каждого тикера
Вывод
  ticker  price  change
0      A   10.0     NaN
1      B   50.0     NaN
2      A   11.0     1.0
3      B   48.0    -2.0
4      A   12.5     1.5

Подводные камни

Разности между разными рядами

Если в одной таблице несколько тикеров, магазинов или клиентов, простой diff вычтет значение одного ряда из другого.

diff без groupby
trades.assign(change=trades["price"].diff())   # без группировки — разница между разными тикерами
Вывод
  ticker  price  change
0      A   10.0     NaN
1      B   50.0    40.0
2      A   11.0   -39.0
3      B   48.0    37.0
4      A   12.5   -35.5

pct_change и пропуски

pct_change с пропуском
s = pd.Series([100.0, None, 110.0])
print(s.pct_change())                     # у пропуска и после него — NaN
s.ffill().pct_change()                    # старое поведение — явный ffill перед расчётом
Вывод
0   NaN
1   NaN
2   NaN
dtype: float64
0    NaN
1    0.0
2    0.1
dtype: float64