Lesson 3 - Additive vs Multiplicative Models

Welcome to Additive vs Multiplicative Models

Lesson 1 introduced two ways components combine — y = T + S + R and y = T × S × R — and said the choice depends on whether the seasonal swing is a fixed amount or a fixed percentage of the level. That’s easy to state and easy to get wrong on a real series, because both a decomposition function and your own eyes will happily produce a plausible-looking result either way. This lesson replaces the guess with a direct test: track the seasonal swing relative to the level, year over year, and let the data answer the question.

By the end of this lesson, you will be able to:

  • State precisely what “constant swing” vs “swing scales with level” means and why it’s the deciding question
  • Test a series for additive vs multiplicative structure by tracking swing-to-level ratio across years
  • Apply the test to Cyclepath and to a contrasting series where the swing genuinely grows
  • Use the log-transform trick to convert a multiplicative problem into an additive one

Let’s run the test.


The Deciding Question

Both models describe a series with a rising trend and a repeating seasonal swing. The difference is entirely in how that swing behaves as the trend rises:

  • Additive (y = T + S + R): the peak-to-trough swing within each cycle stays roughly the same absolute size, no matter how high the trend has climbed.
  • Multiplicative (y = T × S × R): the swing scales with the level — it’s a roughly constant percentage of wherever the trend currently is, so the absolute swing grows as the trend grows.

The test, then, is simple: measure the peak-to-trough swing within each year, measure the average level that year, and watch what happens to the ratio of the two over time. A flat ratio says multiplicative; a shrinking or growing ratio (while the raw swing stays flat) says additive.


Testing Cyclepath

import numpy as np, pandas as pd

def cyclepath():
    idx = pd.date_range("2016-01-01", periods=96, freq="MS")
    t = np.arange(96); rng = np.random.default_rng(42)
    trend = 9000 + 90*t; seasonal = 3200*np.sin(2*np.pi*(t-3)/12); noise = rng.normal(0,350,96)
    return pd.Series(np.round(trend+seasonal+noise).astype(int), index=idx, name="trips")

y = cyclepath()

by_year = y.groupby(y.index.year)
swing = by_year.agg(lambda s: s.max() - s.min())
level = by_year.mean()
ratio = (swing / level * 100).round(1)

print(swing.to_dict())
print(level.round(0).to_dict())
print(ratio.to_dict())
swing:  {2016: 6878, 2017: 7224, 2018: 7840, 2019: 6747, 2020: 6537, 2021: 7407, 2022: 7481, 2023: 7592}
level:  {2016: 9446, 2017: 10611, 2018: 11746, 2019: 12769, 2020: 13818, 2021: 14827, 2022: 15961, 2023: 16958}
ratio:  {2016: 72.8, 2017: 68.1, 2018: 66.7, 2019: 52.8, 2020: 47.3, 2021: 50.0, 2022: 46.9, 2023: 44.8}

Two things jump out. First, the raw swing stays in the same rough band (6,537 to 7,840) across all eight years, with no systematic climb — 2023’s swing (7,592) is barely bigger than 2016’s (6,878), even though the trend nearly doubled underneath it. Second, and as a direct consequence, the ratio drops steadily from 72.8% down to 44.8% — the seasonal swing keeps shrinking relative to the ever-rising level. That’s the signature of a constant absolute swing riding on top of a rising trend — in other words, additive. If Cyclepath were multiplicative, the ratio would have stayed roughly flat as the level rose, not fallen by nearly a third.

Two side-by-side line charts. Left chart, titled 'Cyclepath: additive', shows a rising series where the peak-to-trough swing within each yearly cycle stays visually the same height across all eight years even as the whole series climbs — the swing looks like a constant-width ribbon riding up a rising line. Right chart, titled 'toy series: multiplicative', shows a rising series where the peak-to-trough swing visibly widens each year, fanning outward as the level rises, so the later cycles are much taller than the earlier ones.
Additive vs multiplicative, visually: Cyclepath's seasonal swing (left) stays a constant absolute height as the trend rises — a ribbon of fixed width. A genuinely multiplicative series (right) has a swing that widens as the level rises, fanning outward because the swing is a fixed percentage of an ever-larger number.

A Series Where It’s Actually Multiplicative

To see the contrast, here’s a small toy series built so the swing genuinely is a fixed percentage of the level — 35% above and below trend, every cycle:

idx2 = pd.date_range("2020-01-01", periods=48, freq="MS")
t2 = np.arange(48); rng2 = np.random.default_rng(7)
level2 = 200 + 4*t2
factor2 = 1 + 0.35*np.sin(2*np.pi*(t2-3)/12)
y2 = pd.Series(np.round(level2*factor2 + rng2.normal(0,3,48)).astype(int), index=idx2, name="units")

by_year2 = y2.groupby(y2.index.year)
swing2 = by_year2.agg(lambda s: s.max() - s.min())
level_y2 = by_year2.mean()
ratio2 = (swing2 / level_y2 * 100).round(1)

print(swing2.to_dict())   # {2020: 173, 2021: 199, 2022: 234, 2023: 269}
print(ratio2.to_dict())   # {2020: 77.8, 2021: 74.1, 2022: 73.8, 2023: 73.3}

Here the pattern is the opposite of Cyclepath’s: the raw swing climbs steadily — 173 → 199 → 234 → 269 — right alongside the rising level, while the ratio stays almost flat, hovering around 73–78% the whole time. A roughly constant ratio alongside a growing absolute swing is exactly the multiplicative signature: the swing isn’t fixed, it’s scaling with the level.

The residuals confirm it. Decompose both ways and compare how the additive residual behaves across years:

from statsmodels.tsa.seasonal import seasonal_decompose

add2 = seasonal_decompose(y2, model="additive", period=12)
r = add2.resid.dropna()
print(r.groupby(r.index.year).apply(lambda s: round(s.abs().max(), 1)).to_dict())
# {2020: 14.3, 2021: 15.8, 2022: 17.3, 2023: 18.1}

Forcing an additive decomposition onto this multiplicative series leaves a residual whose largest deviation grows every single year — 14.3 → 15.8 → 17.3 → 18.1 — because a constant seasonal correction can’t keep up with a swing that’s genuinely widening. That’s a patterned residual (Lesson 1’s warning sign), and it’s the tell that additive was the wrong choice here. The multiplicative decomposition doesn’t have this problem — its residual (a ratio around 1.0) stays similarly sized across all four years, because it models a swing that scales, matching the swing that’s actually there.

A growing residual by year is the real diagnostic

You don’t need to eyeball a plot to catch a wrong additive/multiplicative choice — group the residual by year (or any convenient period) and watch whether its spread grows or shrinks. A flat spread across the whole series means the combination rule fits; a residual that visibly widens or narrows over time means the wrong rule is being asked to explain a swing it can’t represent, and it’s leaking into what should be pure noise.


The Log-Transform Trick

There’s a shortcut worth knowing: taking the log of a multiplicative series turns it additive, because logarithms convert multiplication into addition:

log(y) = log(T × S × R) = log(T) + log(S) + log(R)

So instead of decomposing y2 with model="multiplicative", you can decompose log(y2) with model="additive" and get essentially the same seasonal shape back (exponentiate the additive seasonal component to compare it against the multiplicative one directly):

logy2 = np.log(y2)
add_log = seasonal_decompose(logy2, model="additive", period=12)
mul2 = seasonal_decompose(y2, model="multiplicative", period=12)

print(np.exp(add_log.seasonal.iloc[:12]).round(3).tolist())
# [0.676, 0.721, 0.844, 1.036, 1.211, 1.349, 1.388, 1.351, 1.205, 1.03, 0.855, 0.722]
print(mul2.seasonal.iloc[:12].round(3).tolist())
# [0.654, 0.695, 0.812, 0.995, 1.164, 1.301, 1.343, 1.315, 1.176, 1.007, 0.834, 0.702]

The two seasonal shapes closely track each other (both dip to roughly 0.65–0.68 in the trough month and rise to roughly 1.3–1.4 at the peak), though they’re not identical — the two computations aggregate slightly differently (averaging in log-space isn’t quite the same as averaging in linear space). Still, this is why you’ll sometimes see np.log(y) in forecasting code before a model that assumes additive structure (like plain ARIMA, in Module 5): logging first lets an additive tool handle a multiplicative problem.


Practice Exercises

Exercise 1: Read the ratio table

A series has yearly swing-to-level ratios of 40%, 41%, 39%, 40%, 42% across five years. Is this series additive or multiplicative?

Hint

A ratio that stays essentially flat (39–42%, no systematic drift) across years is the multiplicative signature — the swing is consistently about 40% of whatever the level happens to be that year. If it were additive, the ratio would drift as the level changed (rising if the level fell, falling if the level rose), since a fixed absolute swing divided by a moving level can’t stay constant.

Exercise 2: Predict Cyclepath’s future ratio

Cyclepath’s ratio dropped from 72.8% (2016) to 44.8% (2023) while staying additive (constant absolute swing, rising level). If the trend keeps rising at the same rate for five more years, what happens to the ratio — and would you expect Cyclepath to become multiplicative eventually?

Hint

If the absolute swing genuinely stays fixed (additive, as the evidence shows) while the level keeps climbing, the ratio keeps shrinking — seasonality becomes a smaller and smaller fraction of the total, though it never truly becomes multiplicative just by the ratio falling further. A series only becomes multiplicative if the underlying mechanism changes so the swing itself starts scaling with the level — the ratio’s trajectory alone doesn’t cause that, it just reflects whichever mechanism is already in place. In practice, always re-run the swing-vs-ratio test on new data rather than assuming last year’s answer still holds.

Exercise 3: Choosing before modeling

Why does this decision need to happen before you fit ARIMA or any other model, rather than after?

Hint

Getting additive vs. multiplicative wrong leaves a patterned residual — as Exercise 2 of Lesson 1 and this lesson’s toy-series test both showed — which means the “noise” a later model is supposed to explain still secretly contains structure. Any model fit on top of a bad decomposition (or on a series that needed a log transform first and didn’t get one) will systematically mis-forecast in a way that’s hard to diagnose after the fact, because the error looks like ordinary model error rather than a leftover seasonal shape. Settling this question first, with real evidence, is what makes every later diagnostic trustworthy.


Summary

The additive-vs-multiplicative choice comes down to whether the seasonal swing stays a constant absolute size or scales as a constant percentage of the level. The test: group by year, compute peak-to-trough swing and average level, and track the ratio over time. On Cyclepath, the raw swing stayed flat (6,537–7,840) while the level nearly doubled, so the ratio fell from 72.8% to 44.8% — the additive signature. On a toy series built with a genuine 35% seasonal factor, the raw swing climbed (173 → 269) while the ratio stayed flat (73–78%) — the multiplicative signature, confirmed by an additive decomposition’s residual growing worse every year (14.3 → 18.1). Finally, log(y) turns a multiplicative problem additive, since log(T×S×R) = log(T) + log(S) + log(R) — a trick worth remembering whenever a later model assumes additive structure.

Key Concepts

  • Additive signature — constant absolute swing; the swing-to-level ratio drifts as the level changes.
  • Multiplicative signature — swing scales with the level; the swing-to-level ratio stays roughly flat.
  • Swing-to-level ratio test — group by year, compute peak-to-trough swing and mean level, watch the ratio over time.
  • Log transformlog(y) converts a multiplicative series into an additive one, since logs turn products into sums.

Why This Matters

This choice isn’t cosmetic — it determines whether every later component (trend, seasonal indices, residual) is even measuring the right thing. Get it wrong and the residual quietly keeps a piece of the seasonal pattern in it, which then either confuses a model that assumes the residual is pure noise, or gets forecast incorrectly because the seasonal correction was the wrong shape. Cyclepath being additive is not an assumption carried over from Module 1 — you just verified it directly, with a test you can rerun on any new series before trusting either decomposition. Next, you’ll meet STL, a decomposition method that handles messier real-world seasonality — outliers, slowly shifting patterns — better than the classical moving-average approach you just built by hand.


Next Steps

Continue to Lesson 4 - STL Decomposition

A more robust decomposition using LOESS smoothing — handles outliers and slowly changing seasonality better than the classical method.

Back to Module Overview

Return to the Components and Decomposition module overview


Continue Building Your Skills

You now have a real test for additive vs multiplicative, not a guess — and you’ve confirmed Cyclepath is additive with evidence, not assumption. Next you’ll meet STL, a decomposition method built to handle the messier cases classical decomposition struggles with: outliers that shouldn’t drag the trend around, and seasonal patterns that drift slightly instead of repeating identically every single cycle.