3 Statsmodels Tricks for Time Series Analysis & Forecasting

Statsmodels Time Series Tricks: Stop Leaving Uncertainty on the Table

A fitted statsmodels model is not a forecast. It is a bundle of estimates, covariance structure and decomposition state, and most forecasting code touches one attribute of it before moving on. Call res.forecast(12) and you get twelve numbers. Call res.get_forecast(12) on the same object and you get the same values plus the uncertainty the model already computed to produce them.

That gap is the subject of a recent walkthrough of three built-in methods on statsmodels results objects, verified against statsmodels 0.15.0. The walkthrough comes from the Real Python tutorial Time Series Forecasting With Statsmodels, which demonstrates all three methods on the same monthly series with the same fitted model, so the only thing that changes between examples is which method gets called. The framing is worth taking seriously: none of these tricks require new forecasting logic. They require inspecting what .fit() already returned. This article covers the three methods and then three things to check before you trust any of them.

Trick 1: get_forecast stops throwing away the intervals

Point forecasts are half the output. When you call res.forecast(12), statsmodels returns a plain array of twelve values. The model did not compute those values in isolation. It computed a predictive distribution, and forecast hands you the mean of it while discarding the rest.

res.get_forecast(12) runs the same underlying calculation and returns a PredictionResults object instead. That object carries predicted_mean, the point forecasts you would have gotten anyway, alongside conf_int, the interval bounds, and summary_frame(), which puts both into a single table. The intervals need no extra computation. They were always part of the calculation. forecast simply drops them on the floor.

For intervals over historical data rather than future periods, the same idea applies through get_prediction, which accepts a range that can include in-sample periods. That is how you get fitted values with uncertainty attached, which matters when you want to check whether a model’s residuals behave the way its assumptions claim.

The practical consequence is that any pipeline reporting a bare point forecast is understating what it knows. A twelve-month projection with no interval is a claim of precision the model never made.

Trick 2: append updates a model without re-estimating it

The second reflex is the expensive one. New monthly observations arrive, so you concatenate them onto the training data and call .fit() again. Every parameter gets re-estimated from scratch, and for a large series with a slow optimizer, that is real time spent. On a few thousand observations with a seasonal ARIMA, the difference is commonly seconds against milliseconds, and it grows with sample size and model order.

append takes a different route. It recreates the results object over the combined data and, with refit=False, keeps the previously estimated parameters. That is the default behavior, so the cheap path is also the one you get without asking. Pass refit=True when enough new data has accumulated that the old estimates are genuinely stale rather than merely incomplete.

The distinction matters because it is a modeling judgment. Reusing parameters assumes the data-generating process has not shifted. If you are appending a month of routine observations, that assumption is usually fine. If you are appending a year, or appending across a regime change, refit=True is the honest choice. The method gives you the option; it does not make the decision for you.

Trick 3: STLForecast packages the seasonal-then-forecast loop

Seasonal decomposition followed by forecasting is a standard pattern, and the manual version has three steps. The walkthrough notes that the third step is where sign errors and index misalignments creep in. Anyone who has subtracted a seasonal component, forecast the remainder, and then added the component back onto a misaligned index knows the failure mode: plausible-looking output with a silent offset.

STLForecast packages the whole loop into one object. The documentation describes it as forecasting “by first subtracting the seasonality estimated using STL, then forecasting the deseasonalized data using a time-series model, for example, ARIMA.” That is the manual procedure, wrapped and tested. What it buys you is the recombination step, which is exactly the step people get wrong, plus a consistent interface for the deseasonalized model’s own intervals.

The API has one trap worth flagging. You pass the ARIMA class itself, not a fitted instance, with its arguments supplied separately in model_kwargs. Passing ARIMA(...) is described as the first mistake most people make with this API, and it is an easy one to make when every other statsmodels workflow trains you to hand over a configured model object.

The package is not always the right call. If you need a decomposition other than STL, such as a moving-average or regression-based seasonal adjustment, or if the seasonal pattern is estimated jointly with the forecasting model rather than subtracted first, the manual route is the one to take. STLForecast is convenience for a specific pipeline, not a general seasonal framework.

The common thread: every trick is a method on an object you already built

Step back and the pattern is almost embarrassing in its simplicity. get_forecast, append and STLForecast are all methods or classes sitting next to the code people already write. Hand-rolled alternatives to each are longer, slower and more often wrong, and the walkthrough attributes their persistence to one habit: not inspecting what .fit() returns.

That habit is not unique to time series. It shows up anywhere a library computes more than the caller asks for. The same instinct that makes someone reimplement seasonal adjustment is the instinct that makes them reimplement language features. 7 Advanced Python Tricks That Use What the Language Already Promises You makes the case that levelling up rarely means learning new syntax, only learning what was already guaranteed. The statsmodels version of that argument is that a results object is a contract, and most code reads one clause of it.

What to verify before you adopt any of this

Each method is a claim about your series. Treat it that way.

On intervals from get_forecast: confirm the interval level you want is the one being returned, and check whether the model’s distributional assumptions hold on your residuals. A prediction interval from a misspecified model is a confident-looking number with no coverage guarantee behind it. Pull the forecast and its bounds out of summary_frame() and plot them against the historical series before you put them in front of anyone.

On append with refit=False: inspect the parameter drift. Fit the full model on the combined data and compare coefficients to the reused ones. If they have moved meaningfully, the cheap path was the wrong one. Also confirm the appended observations share the frequency and index conventions of the original series, since a misaligned index is exactly the kind of error that produces plausible output.

On STLForecast: verify that you are passing the class rather than an instance, and that model_kwargs contains the arguments you actually intend. Then compare the packaged result against your manual loop on a holdout period. If they diverge, the manual version was wrong, and that is useful information about every other series you have run it on.

The broader point is that these are not advanced techniques. They are the documented behavior of objects already in memory. The skill on display is not forecasting. It is reading the return value.

Similar Posts