ARIMA
ARIMA forecasts a series from its own past values and its own past errors, after differencing away whatever kept moving.
- 19 min read
- 3 reading levels
- Updated
Read these first
On this page 10
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
The short answer
ARIMA predicts tomorrow using two things: what the series did recently, and how wrong it was recently.
The analogy you have already lived
Think about a tea stall's daily takings.
If yesterday was busy, today will probably be busy too. Customers come in habits, and habits carry over. That is the first half of ARIMA: today looks like the recent past.
Now think about last Tuesday, when the milk delivery failed and takings crashed. That was a surprise, not a habit. Its after-effect lingers for a day or two while regulars drift back. That is the second half: today still carries the echo of recent surprises.
A good stall owner tracks both. She expects today to resemble yesterday, and she also remembers that yesterday was unusually bad for a reason.
What the name means
ARIMA is three ideas glued together, and the name is an initialism of them.
AR — autoregressive. "Auto" means self, "regressive" means predicted from. Today is predicted from its own earlier values. Yesterday, the day before, and so on.
I — integrated. Before any of that works, the series must stop drifting. So you replace values with changes, which is the differencing from stationarity. The "I" is the promise to add those changes back up at the end.
MA — moving average. A confusing name, because it has nothing to do with the moving averages lesson. Here it means: today is partly predicted from how wrong the forecast was on recent days.
Why the surprises matter
This part is genuinely tricky, so take it slowly.
When your forecast for yesterday missed by a lot, that miss is information. Something happened that your model does not have a name for. A power cut, a road closure, a cricket match.
Whatever it was, it probably has not finished. The MA part lets the model carry a fraction of yesterday's miss into today's forecast.
The AR part says "copy the recent level". The MA part says "and correct for the recent surprises". Neither alone is enough.
How it works
raw sales
|
| [ I ] difference until it stops drifting
v
changes in sales
|
| [ AR ] today's change looks like the last few changes
| [ MA ] plus a share of the last few forecast errors
v
forecast of the change
|
| add the changes back onto the last known level
v
forecast of sales, with an honest range around itYou will see the settings written like this:
ARIMA(1, 1, 1)
| | |
| | +-- how many past ERRORS to use (the MA part)
| +----- how many times to difference (the I part)
+-------- how many past VALUES to use (the AR part)Three small whole numbers, usually zero, one or two. That is the entire configuration.
Seasonal ARIMA
Plain ARIMA has no idea that October is a festival month. It only looks a few steps back.
So a seasonal copy of the same three settings was added. It works on a longer step: not yesterday, but the same month last year. That version is called SARIMA, and it is what you use for anything with a repeating pattern.
You give it a second set of three numbers, plus the length of the cycle. Twelve for monthly data with a yearly pattern. Seven for daily data with a weekly one.
Where you have already seen it
- Government inflation and unemployment forecasts are frequently SARIMA models.
- Electricity load planning for the next day uses this family heavily.
- Warehouse restocking across thousands of items.
- Call-centre staffing for tomorrow's expected call volume.
It is not fashionable, and it is still running quietly inside a great deal of infrastructure.
What is honestly hard here
Choosing the three numbers is the hard part, and it is confusing for everyone at first. Read this paragraph twice.
The traditional method involves reading two charts that show how strongly today relates to earlier days. Interpreting those charts is a skill that takes practice, and experienced people disagree about the same chart.
The good news is that automatic search exists and works well. Let a search try many combinations and pick the best by a score. Use the charts to sanity-check the answer, not to find it.
The other honest limit: ARIMA looks only at the series itself. It cannot know that a holiday is coming, that prices changed, or that it rained. Anything outside the series has to be handed to it deliberately.
Remember this
- ARIMA uses past values and past errors, after differencing away the drift.
- The "MA" in ARIMA is not the moving average from the previous lesson.
- Add the seasonal version for anything with a weekly, monthly or yearly rhythm.
What to learn next
- Prophet — a different design that handles holidays and missing data without hand-tuned orders.
- Evaluating a forecast — scoring ARIMA against the baselines it must beat.
- Multivariate forecasting — bringing outside information into the model.
Developer — Code and libraries.
Setup
pip install numpy pandas statsmodelsProve it recovers a process you built
The honest way to trust a fitting routine is to hand it data whose true parameters you know. Here is an AR(2) series built by hand, then fitted.
import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
rng = np.random.default_rng(13)
n = 400
# Build an AR(2) series by hand: today = 0.6*yesterday - 0.3*the day before + noise, around 50.
e = rng.normal(0, 2, n)
y = np.zeros(n) + 50.0
for t in range(2, n):
y[t] = 50 + 0.6 * (y[t-1] - 50) - 0.3 * (y[t-2] - 50) + e[t]
s = pd.Series(y, index=pd.date_range("2024-01-01", periods=n, freq="D")).asfreq("D")
fit = ARIMA(s, order=(2, 0, 0)).fit()
print("true ar1 = 0.600 ar2 = -0.300 mean = 50.0 noise sd = 2.00")
print("found ar1 = %.3f ar2 = %.3f mean = %.1f noise sd = %.2f"
% (fit.params["ar.L1"], fit.params["ar.L2"], fit.params["const"], np.sqrt(fit.params["sigma2"])))true ar1 = 0.600 ar2 = -0.300 mean = 50.0 noise sd = 2.00 found ar1 = 0.617 ar2 = -0.353 mean = 50.0 noise sd = 2.07
Close, not exact. With 400 observations and a noise standard deviation of 2, that gap is ordinary sampling error. The lesson is that estimates come with uncertainty even when the model is exactly right, and your real data is never generated by the model you fit.
Note order=(2, 0, 0): two AR terms, no differencing, no MA terms. The series was built to be stationary, so no differencing was needed.
Reading ACF and PACF, with numbers instead of charts
The two diagnostic functions are usually shown as plots. Printing them as numbers makes the rule concrete.
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import acf, pacf
rng = np.random.default_rng(13)
n = 400
e = rng.normal(0, 2, n)
y = np.zeros(n) + 50.0
for t in range(2, n):
y[t] = 50 + 0.6 * (y[t-1] - 50) - 0.3 * (y[t-2] - 50) + e[t]
s = pd.Series(y)
print("lag ACF PACF")
for lag, (a, p) in enumerate(zip(acf(s, nlags=6), pacf(s, nlags=6))):
if lag:
print(f" {lag} {a:6.3f} {p:6.3f}")lag ACF PACF 1 0.455 0.457 2 -0.072 -0.355 3 -0.222 -0.023 4 -0.084 0.054 5 -0.001 -0.072 6 -0.011 -0.022
ACF is the plain correlation between the series and itself, shifted back by that many steps.
PACF is the same correlation after removing everything the shorter lags already explained. Lag 2's PACF asks: once lag 1 is accounted for, does lag 2 still add anything?
Now the classical rule, and the output above is a textbook example of it:
- PACF cuts off sharply after lag
p, ACF trails off → use AR(p). - ACF cuts off sharply after lag
q, PACF trails off → use MA(q). - Both trail off → you probably need both AR and MA terms.
The PACF here is 0.457, -0.355, then -0.023, 0.054, -0.022. It falls off a cliff after lag 2. With 400 observations the significance threshold is roughly 2/sqrt(400) = 0.1, so lags 1 and 2 clear it and nothing else does. The data came from an AR(2) process, and the diagnostic found it.
Real data is rarely this obliging. Use these functions to form a hypothesis, then confirm it with an information criterion.
Seasonal ARIMA with prediction intervals
This is the shape of a real forecasting job: fit on history, forecast a horizon, and report a band rather than a single line.
import numpy as np
import pandas as pd
from statsmodels.tsa.statespace.sarimax import SARIMAX
rng = np.random.default_rng(8)
months = pd.date_range("2015-01-01", periods=120, freq="MS")
festival = np.tile([-20, -25, -10, 0, 5, -5, 0, 15, 45, 90, 30, 10], 10)
sales = pd.Series(np.linspace(200, 400, 120) + festival + rng.normal(0, 10, 120),
index=months).asfreq("MS")
train, test = sales[:108], sales[108:]
fit = SARIMAX(train, order=(1, 1, 1), seasonal_order=(0, 1, 1, 12),
enforce_stationarity=False).fit(disp=False)
res = fit.get_forecast(steps=12)
out = pd.DataFrame({
"actual": test.round(1),
"forecast": res.predicted_mean.round(1),
"low95": res.conf_int().iloc[:, 0].round(1),
"high95": res.conf_int().iloc[:, 1].round(1),
})
out["inside"] = (out.actual >= out.low95) & (out.actual <= out.high95)
print(out.to_string())
print("\nMAE:", round((out.actual - out.forecast).abs().mean(), 1))
print("actuals inside the 95% band:", int(out.inside.sum()), "of 12")actual forecast low95 high95 inside 2024-01-01 363.3 362.4 340.6 384.2 True 2024-02-01 344.8 358.0 336.2 379.8 True 2024-03-01 366.0 368.1 346.2 390.0 True 2024-04-01 387.5 382.8 360.9 404.8 True 2024-05-01 399.4 394.0 372.0 416.1 True 2024-06-01 371.5 381.6 359.5 403.7 True 2024-07-01 398.7 386.8 364.6 408.9 True 2024-08-01 394.5 398.4 376.2 420.6 True 2024-09-01 432.0 443.8 421.5 466.1 True 2024-10-01 495.2 491.8 469.5 514.2 True 2024-11-01 445.3 427.3 404.9 449.7 True 2024-12-01 409.2 413.4 390.9 435.8 True MAE: 7.5 actuals inside the 95% band: 12 of 12
The MAE of 7.5 is close to the floor. The noise in this series has a standard deviation of 10, so an average absolute error near 8 is roughly what a perfect model would achieve. There is nothing left to win here.
The interval matters more than the point forecast. October's forecast of 491.8 was wrong by 3.4, and November's was wrong by 18.0. A user who saw only the numbers would be surprised by November. A user who saw the band 404.9 to 449.7 would not.
Twelve of twelve inside a 95 percent band is fine, and slightly suspicious. On average you expect about one exceedance in twenty. Twelve of twelve is compatible with correct coverage on a sample this small. Consistently seeing every point inside on a larger sample means your intervals are too wide.
Read seasonal_order=(0, 1, 1, 12) as: no seasonal AR term, one seasonal difference, one seasonal MA term, cycle length twelve. That combination — (0,1,1)(0,1,1,12) — is the "airline model" from Box and Jenkins, and it is a strong default for monthly seasonal data.
Picking the orders by search
Reading charts is a skill. Searching is reproducible. Do both, and let them argue.
import itertools
import numpy as np
import pandas as pd
from statsmodels.tsa.statespace.sarimax import SARIMAX
rng = np.random.default_rng(8)
months = pd.date_range("2015-01-01", periods=120, freq="MS")
festival = np.tile([-20, -25, -10, 0, 5, -5, 0, 15, 45, 90, 30, 10], 10)
sales = pd.Series(np.linspace(200, 400, 120) + festival + rng.normal(0, 10, 120),
index=months).asfreq("MS")[:108]
scores = []
for p, q in itertools.product((0, 1, 2), repeat=2):
fit = SARIMAX(sales, order=(p, 1, q), seasonal_order=(0, 1, 1, 12),
enforce_stationarity=False).fit(disp=False)
scores.append((round(fit.aic, 1), f"({p},1,{q})(0,1,1,12)"))
for aic, name in sorted(scores)[:5]:
print(f"AIC {aic:8.1f} {name}")AIC 628.6 (0,1,2)(0,1,1,12) AIC 629.4 (1,1,2)(0,1,1,12) AIC 632.4 (2,1,2)(0,1,1,12) AIC 635.1 (0,1,1)(0,1,1,12) AIC 635.5 (1,1,1)(0,1,1,12)
AIC is the Akaike information criterion: goodness of fit with a penalty for each extra parameter. Lower is better, and the number itself is meaningless — only differences between models on the same data mean anything.
Two uncomfortable results here, and both are worth more than a tidy one.
The airline model came fourth. The textbook default (0,1,1)(0,1,1,12) lost by 6.5 AIC points to (0,1,2)(0,1,1,12). Defaults are starting points, not answers.
The order used in the forecast script came last of these five, and forecast best. Re-running the twelve-month forecast with each of the top three gives MAE 7.63 for (0,1,2), 7.69 for (0,1,1) and 7.47 for (1,1,1). The AIC winner was not the accuracy winner.
That is not a paradox. AIC estimates one-step-ahead in-sample fit with a complexity penalty; you were scored on a twelve-step-ahead out-of-sample forecast. When two models are within a few AIC points, decide by backtesting on your real horizon, not by the criterion.
Never compare AIC across different differencing orders. Changing d changes the number of rows the likelihood is computed on, so the values are not comparable. Fix d first with a stationarity test, then search p and q.
For real projects, pip install pmdarima gives auto_arima, which does this search with a smarter stepwise algorithm and chooses d and D from tests.
Common mistakes
Differencing by hand and setting d=1 as well. You have now differenced twice. Pass the raw series and let d do the work.
Forecasting a differenced series and forgetting to undo it. If you passed a .diff() series, forecast() returns changes, not levels. statsmodels handles this for you when you use d correctly, which is one more reason to use it.
Feeding a series with a missing freq. Without freq, forecast output loses its date index and seasonal terms can silently misalign. Call .asfreq("D") or .asfreq("MS") and fix gaps before fitting.
Trusting AIC across different data. Adding rows changes AIC. If a colleague quotes an AIC from a different date range, the comparison is void.
Assuming ARIMA beats exponential smoothing. It often does not. Run both, score both on the same rolling origins, and pick by measurement. Exponential smoothing was competitive against ARIMA throughout the M-competitions.
Ignoring the residual diagnostics. After fitting, call fit.plot_diagnostics() or run a Ljung-Box test on the residuals. If leftover autocorrelation remains, the model missed structure and your intervals are too narrow.
Try it yourself
In the SARIMA script, change seasonal_order=(0, 1, 1, 12) to seasonal_order=(0, 0, 0, 0), turning the seasonal part off entirely.
Predict what happens to the October forecast before you run it. Then look at how many actuals fall inside the 95 percent band. The interesting part is not that the error grows — it is watching the model widen its intervals to cover the pattern it can no longer explain.
What to learn next
- Prophet — a different design that handles holidays and missing data without hand-tuned orders.
- Evaluating a forecast — scoring ARIMA against the baselines it must beat.
- Multivariate forecasting — bringing outside information into the model.
Researcher — Mathematics and papers.
The model
Let $L$ denote the lag operator, $L y_t = y_{t-1}$, and $\Delta = 1 - L$. An ARIMA$(p,d,q)$ process satisfies
$$ \phi(L)\,(1-L)^d\, y_t = c + \theta(L)\,\varepsilon_t $$
with
$$ \phi(L) = 1 - \phi_1 L - \dots - \phi_p L^p, \qquad \theta(L) = 1 + \theta_1 L + \dots + \theta_q L^q $$
- $y_t$ — the observation at time $t$.
- $\varepsilon_t$ — white noise, $\mathbb{E}[\varepsilon_t]=0$, $\mathrm{Var}(\varepsilon_t)=\sigma^2$, uncorrelated across $t$.
- $\phi_i$ — autoregressive coefficients; $p$ of them.
- $\theta_j$ — moving-average coefficients; $q$ of them.
- $d$ — the order of ordinary differencing.
- $c$ — a constant; when $d \ge 1$ it induces a deterministic polynomial trend of order $d$ in the levels.
The multiplicative seasonal extension SARIMA$(p,d,q)(P,D,Q)_m$ is
$$ \phi(L)\,\Phi(L^m)\,(1-L)^d (1-L^m)^D\, y_t = c + \theta(L)\,\Theta(L^m)\,\varepsilon_t $$
with $\Phi$ and $\Theta$ polynomials of degree $P$ and $Q$ in $L^m$, and $m$ the seasonal period.
Stationarity, invertibility and why they are enforced
Stationarity of the AR part requires all roots of $\phi(z)=0$ to lie outside the unit circle in the complex plane. Equivalently, the companion matrix has spectral radius below one. For AR(1) this reduces to $|\phi_1|<1$; for AR(2) the admissible region is the triangle $\phi_1+\phi_2<1$, $\phi_2-\phi_1<1$, $|\phi_2|<1$.
Invertibility of the MA part requires all roots of $\theta(z)=0$ to lie outside the unit circle. Without it the process has no convergent AR($\infty$) representation, and — more damaging in practice — the parameters are not identified: for MA(1), $\theta$ and $1/\theta$ generate the same autocovariance function with rescaled $\sigma^2$. Enforcing invertibility picks one of the pair.
enforce_stationarity=False in the SARIMAX call above relaxes the first constraint during optimisation. It is useful when a seasonal difference has already made the series stationary and the optimiser wants to explore a boundary; it is not a licence to ignore a genuinely explosive fit.
Identification by ACF and PACF
The autocorrelation function is $\rho(k) = \gamma(k)/\gamma(0)$. The partial autocorrelation $\alpha(k)$ is the last coefficient in a projection of $y_t$ on $y_{t-1},\dots,y_{t-k}$, computed by the Durbin-Levinson recursion.
The identification rules follow from the theory rather than from convention:
| Process | ACF | PACF |
|---|---|---|
| AR($p$) | decays (geometric or damped sine) | zero for $k>p$ |
| MA($q$) | zero for $k>q$ | decays |
| ARMA($p,q$) | decays after lag $q$ | decays after lag $p$ |
Under the null of white noise, $\hat{\rho}(k)$ is approximately $\mathrm{N}(0, 1/T)$ (Bartlett), giving the familiar $\pm 1.96/\sqrt{T}$ bands. These are pointwise, so with 20 displayed lags you expect one exceedance by chance.
Estimation
The state-space form with the Kalman filter gives the exact Gaussian likelihood, including for series with missing observations. Writing $\mathbf{x}_t$ for the state,
$$ \mathbf{x}t = T\mathbf{x}{t-1} + R\eta_t, \qquad y_t = Z\mathbf{x}_t + \varepsilon_t $$
the prediction error decomposition yields
$$ \log \mathcal{L} = -\frac{1}{2}\sum_{t=1}^{T}\left[\log(2\pi F_t) + \frac{v_t^2}{F_t}\right] $$
with $v_t$ the one-step prediction error and $F_t$ its variance from the filter. Harvey (1989) and Durbin and Koopman (2012) give the construction. Non-stationary components are handled by exact diffuse initialisation (Koopman, 1997), which is what statsmodels uses when $d>0$.
Cost is $O(T r^3)$ for state dimension $r = \max(p+d+mP+mD,\; q+mQ+1)$. For monthly seasonal models $r$ is small; for high-frequency data with $m=336$ the state blows up, which is why SARIMA is impractical for sub-daily multiple seasonality and why Fourier-regressor approaches take over there.
Model selection
$$ \mathrm{AIC} = -2\log\mathcal{L} + 2k, \qquad \mathrm{AICc} = \mathrm{AIC} + \frac{2k(k+1)}{T-k-1}, \qquad \mathrm{BIC} = -2\log\mathcal{L} + k\log T $$
with $k$ the number of estimated parameters including $\sigma^2$. AIC is asymptotically efficient for prediction; BIC is consistent for selecting a true finite-dimensional model. AICc corrects AIC's small-sample bias and should be the default when $T/k < 40$.
The critical restriction: the likelihoods must be computed on the same observations. Different $d$ or $D$ changes the effective sample after differencing, so those values are not comparable. Fix the differencing orders by test, then select $p, q, P, Q$ by criterion.
Hyndman and Khandakar (2008), Automatic time series forecasting: the forecast package for R, specifies the stepwise search that auto.arima and pmdarima implement: choose $D$ by a seasonal-strength or OCSB test, $d$ by KPSS, then a hill-climbing search over the remaining orders under AICc.
Residual diagnostics
A correctly specified model leaves white-noise residuals. The Ljung-Box statistic
$$ Q = T(T+2)\sum_{k=1}^{h}\frac{\hat{\rho}k^2}{T-k} \;\sim\; \chi^2{h-k^*} $$
tests that jointly, where $k^*$ is the number of fitted ARMA parameters (the degrees of freedom must be reduced, a correction routinely omitted). Significant $Q$ means unmodelled structure remains and the prediction intervals are too narrow.
Heteroskedastic residuals with no autocorrelation in levels but strong autocorrelation in squares indicate conditional variance dynamics; ARCH and GARCH (Engle, 1982; Bollerslev, 1986) model the second moment and leave the ARIMA mean equation intact.
Where it sits today
ARIMA remains the reference point rather than the state of the art. In M4, statistical methods including ARIMA formed the backbone of the top hybrid entries but no pure ARIMA entry placed near the top. In M5, gradient-boosted trees on engineered features dominated the hierarchical retail setting, where cross-series information and calendar covariates matter more than a single series' autocorrelation.
ARIMA's enduring advantages are honest ones: closed-form prediction intervals with known coverage properties under its assumptions, few parameters, no tuning infrastructure, and interpretable coefficients. Those matter when you must defend a forecast to a regulator.
Reading
- Box, Jenkins, Reinsel and Ljung, Time Series Analysis: Forecasting and Control, 5th ed., Wiley 2015 — the source of the methodology and of the airline model.
- Hamilton, Time Series Analysis, Princeton 1994, chapters 3 to 5.
- Durbin and Koopman, Time Series Analysis by State Space Methods, 2nd ed., OUP 2012.
- Hyndman and Khandakar (2008), Automatic time series forecasting, Journal of Statistical Software 27(3).
- Ljung and Box (1978), On a measure of lack of fit in time series models, Biometrika 65(2).
What to learn next
- Prophet — a different design that handles holidays and missing data without hand-tuned orders.
- Evaluating a forecast — scoring ARIMA against the baselines it must beat.
- Multivariate forecasting — bringing outside information into the model.