Q&A library Python

How do I calculate year-over-year or period-over-period growth in pandas?

Python Advanced growthpct-changetime-series

Short answer

Sort by period, then use pct_change to get the proportional change from the previous period, or pct_change with periods=12 to compare with the same month a year earlier. Group so growth does not cross between series.

The short version

Sort the data into period order, then use pct_change() for the change from the immediately preceding period, or pct_change(periods=12) to compare each month with the same month a year before. Multiply by 100 for a percentage. Sort first, and use groupby so growth is computed within each series rather than across them.

Simple period-over-period growth

import pandas as pd

df = pd.DataFrame({
    "year": [2019, 2020, 2021],
    "revenue": [100, 120, 150],
})

df["growth"] = df["revenue"].pct_change()
print(df["growth"].tolist())   # [nan, 0.2, 0.25]

pct_change divides each value's change by the previous value, so 100 to 120 is 0.2 (a 20 per cent rise). The first row is NaN because it has no earlier period to compare with.

As a readable percentage

df["growth_pct"] = (df["revenue"].pct_change() * 100).round(1)

This gives 20.0 and 25.0, which is usually how growth is reported.

Comparing with the same period last year

For monthly data, period-over-period compares consecutive months, which mixes in seasonal effects. To compare like with like, look back a full cycle with periods.

monthly["yoy"] = monthly["value"].pct_change(periods=12)

periods=12 compares each month with the month twelve rows earlier, which is the same month a year ago, provided the data is monthly and complete. This strips out seasonality, so a December is compared with the previous December rather than with November.

The ordering and completeness traps

pct_change works purely on row order and offset, so two things must hold. The data must be sorted by period, or the comparison is meaningless. And for the periods=12 trick, there must be exactly one row per month with no gaps, or "twelve rows back" will not line up with "one year back". Fill or check for missing periods first.

Keeping growth within each series

With several series in one frame (per city, per product), a plain pct_change would compute the first row of one series against the last of the previous one. Group so each series is handled on its own.

df["growth"] = df.sort_values(["city", "year"]).groupby("city")["revenue"].pct_change()

The first row of each city then correctly comes out as NaN.

A caution on interpreting growth

Percentage growth from a small base can look dramatic, because a rise from 2 to 4 is 100 per cent while 200 to 300 is only 50 per cent. When comparing growth across series, keep the absolute figures in view too, so a large percentage on a tiny base is not mistaken for a bigger change than a modest percentage on a large base.

Enjoying these?

Go from piecing together answers to real fluency.

Our courses take you from looking things up to doing the work with confidence, step by step on real data.

Browse the courses