ARIMA Forecasting in Python — Core Concepts

What ARIMA actually models

ARIMA stands for AutoRegressive Integrated Moving Average. Each letter group maps to a specific behavior in the data:

  • AR(p) — the autoregressive part. The current value depends linearly on the p most recent values. Think of momentum: if sales have been climbing for three days, AR captures that tendency to keep climbing.
  • I(d) — the integrated part. Before fitting the model, the series is differenced d times to remove trends. First differencing turns a rising series into a flat one by modeling changes instead of levels.
  • MA(q) — the moving average part. The current value depends on the q most recent forecast errors. If the model under-predicted yesterday, MA nudges today’s prediction upward.

Together, the three parameters are written as ARIMA(p, d, q).

Choosing p, d, and q

Step 1: Determine d

Plot the series. If it trends up or down, try d=1. If the first difference still trends, try d=2. Most real-world series need d ≤ 2. You can also run the Augmented Dickey-Fuller test to check stationarity:

from statsmodels.tsa.stattools import adfuller

result = adfuller(series)
if result[1] > 0.05:
    # p-value too high — series is non-stationary, difference it
    series_diff = series.diff().dropna()

Step 2: Determine p and q from ACF/PACF

After differencing, two plots guide parameter selection:

  • ACF (autocorrelation function) — shows correlation at each lag. A sharp cutoff after lag q suggests the MA order.
  • PACF (partial autocorrelation function) — shows the direct correlation at each lag after removing intermediate effects. A sharp cutoff after lag p suggests the AR order.
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

plot_acf(series_diff, lags=40)
plot_pacf(series_diff, lags=40)

Step 3: Use auto_arima as a sanity check

The pmdarima library automates parameter search using information criteria (AIC/BIC):

import pmdarima as pm

model = pm.auto_arima(
    series,
    seasonal=False,
    stepwise=True,
    suppress_warnings=True,
)
print(model.summary())

Fitting and forecasting

from statsmodels.tsa.arima.model import ARIMA

model = ARIMA(train, order=(2, 1, 1))
fitted = model.fit()

forecast = fitted.forecast(steps=30)
conf_int = fitted.get_forecast(30).conf_int()

The forecast gives point predictions. The conf_int gives upper and lower bounds — critical for decision-making because it tells you how uncertain the prediction is.

When ARIMA works well — and when it doesn’t

ScenarioARIMA fit
Monthly airline passengersGreat after log transform
Daily website traffic with weekly cyclesNeeds SARIMA (seasonal extension)
Stock prices (random walk)Poor — no exploitable pattern
Data with sudden regime changesPoor — assumes stable structure

ARIMA shines on single-variable, regularly-spaced data with consistent patterns. It struggles with multiple seasonalities, external factors, or structural breaks.

SARIMA: adding seasonality

When your data has weekly, monthly, or yearly cycles, SARIMA adds a second set of (P, D, Q, m) parameters:

from statsmodels.tsa.statespace.sarimax import SARIMAX

model = SARIMAX(train, order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
fitted = model.fit(disp=False)

Here m=12 means a 12-period seasonal cycle (monthly data with yearly seasonality).

Common misconception

Many beginners think higher p and q values always improve the model. In practice, overfitting kicks in fast. ARIMA(8, 2, 8) will memorize training noise and produce terrible forecasts. Most real-world series work well with p and q between 0 and 3.

The one thing to remember: ARIMA combines three intuitive ideas — use recent values, remove trends, and correct from past errors — but choosing the right parameters matters more than the model itself.

pythontime-seriesarimaforecasting

See Also