Skip to course content
Free data visualization course

Data Visualization and Dashboard Storytelling

Unit 05.01: Trend, seasonality and the eye's mistake

Comparing two halves of a seasonal series measures the season, not the trend.

Trend and seasonality pulling apart

A series with a steady upward trend and a twelve-month seasonal cycle.

The code separates the two and compares half-year means.

import numpy as np

months = np.arange(1, 25)
trend = 100 + months * 2
season = 15 * np.sin(months / 12 * 2 * np.pi)
series = trend + season

print(f"{'month':>6} {'value':>8} {'trend':>8} {'seasonal':>10}")
for m in (1, 4, 7, 10, 13, 16):
    i = m - 1
    print(f"{m:>6} {series[i]:>8.1f} {trend[i]:>8.1f} {season[i]:>+10.1f}")

first_half = series[:6].mean()
second_half = series[6:12].mean()
print(f"\nmonths 1-6 mean:  {first_half:.1f}")
print(f"months 7-12 mean: {second_half:.1f}")
print(f"apparent growth in year 1: {(second_half / first_half - 1):.1%}")
print(f"actual underlying trend:   {(trend[11] / trend[0] - 1):.1%}")

# Comparing two halves of a seasonal series measures the season, not the trend.
# Compare the same period year on year, or remove the seasonal component first.

The apparent first-year growth is much larger than the underlying trend, because the second half of the year sits on the peak of the seasonal cycle. Someone reporting that figure is reporting the season.

The fix is to compare like with like: the same period year on year, or remove the seasonal component first and say that you did.

The mistake this prevents

The mistake is comparing the most recent period with the one before it because that is what a dashboard defaults to. For any seasonal series that comparison is dominated by the season, and every month produces a different and equally misleading headline.

Takeaway

Compare the same period year on year for seasonal data. Consecutive-period comparisons measure the season and change their story every month.