Univariate Time Series Analysis and Forecasting

I still remember the first time a forecasting model embarrassed me in production. A demand signal looked smooth for months, then spiked hard during a regional promotion. My “good” model missed it by a mile because I treated time like just another column. Time series is different: order is information. When you forecast a single variable over time, you’re not just predicting a number—you’re predicting a story with memory. In this guide, I focus on univariate time series analysis and forecasting: using only one variable’s history to make future predictions. I’ll show you how I frame the problem, how I diagnose trends and seasonality, and how I choose models that fit the data’s behavior rather than forcing it into a template. You’ll see end‑to‑end code for a univariate workflow, learn the practical tests I use for stationarity, and get guidance on when simple baselines beat complex neural nets. By the end, you should feel comfortable building, validating, and deploying a univariate forecast that you’d trust in a real system—because it’s honest about uncertainty and grounded in how time series actually behave.

What makes a univariate time series different

A univariate time series is a sequence of observations of a single variable taken at regular intervals—rainfall per month, daily revenue, hourly temperature, weekly sign‑ups. The “uni” part is important: I’m not pulling in external features like price, promotions, or weather. I’m forecasting using the variable’s own history only.

A simple analogy I use: a time series is like a diary written in a single voice. You can infer habits, cycles, and emotional swings, but you cannot see the full cause behind each entry. That means your model has to be great at pattern recognition and humble about causal claims. When a stakeholder asks “why did the model predict that?”, you should be prepared to say: “Because the recent pattern resembles what typically happens before a change.” That’s honest and usually accurate for univariate models.

In practice, the core challenges are:

  • Temporal dependence: today’s value depends on yesterday’s value, often strongly.
  • Non‑stationarity: statistical properties can drift over time.
  • Seasonality and calendar effects: weekly, monthly, quarterly, and yearly cycles show up everywhere.
  • Limited context: without external variables, you’re betting on history repeating or evolving slowly.

If you embrace those constraints, univariate forecasting can be surprisingly powerful and often more reliable than a shaky multivariate model with noisy features.

The mental model: trend, seasonality, noise, and regime shifts

When I open a time series, I immediately look for four components: trend, seasonality, noise, and regime shifts. This decomposition is mental first, then analytical.

  • Trend is the long‑term direction—like a rising subscription base. It helps me decide whether differencing or trend‑aware models are necessary.
  • Seasonality is a repeating pattern. You’ll see it in monthly rainfall, weekday retail volume, or holiday traffic. I never ignore it if it’s visible.
  • Noise is the randomness I shouldn’t overfit. I want to capture structure, not fluctuations that won’t repeat.
  • Regime shifts are abrupt changes in behavior: a product launch, a new billing policy, or a climate event. Univariate models struggle here, so I look for them explicitly.

This mental model maps directly to methods:

  • Trend → differencing or trend‑capable models.
  • Seasonality → seasonal components like SARIMA or STL‑based ETS.
  • Noise → smoothing and robust training.
  • Regime shifts → change‑point detection or retraining cadence.

A strong univariate pipeline respects these components instead of forcing a single model to explain all of them.

Stationarity: the rule you can bend, but shouldn’t ignore

Many traditional models assume stationarity: constant mean and variance over time. Real data rarely complies. But stationarity is still useful because it tells you when a model is likely to be stable.

I check stationarity in three layers:

  • Visual inspection: rolling mean and rolling variance. If they drift, I treat the series as non‑stationary.
  • Statistical tests: Augmented Dickey‑Fuller (ADF) to test for unit roots, and KPSS to test stationarity from the other direction.
  • Model residuals: after fitting, if residuals show trend or seasonality, stationarity assumptions were violated.

I also keep a rule of thumb: if the series is obviously seasonal or trending, I don’t waste time arguing with ADF. I apply transformations (like differencing or seasonal differencing) and re‑test.

Here’s a practical stationarity check I use:

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

from statsmodels.tsa.stattools import adfuller, kpss

def stationarity_report(series: pd.Series, window: int = 12):

s = series.dropna()

roll_mean = s.rolling(window).mean()

roll_std = s.rolling(window).std()

plt.figure(figsize=(12, 5))

plt.plot(s, label="original")

plt.plot(roll_mean, label=f"{window}-period mean")

plt.plot(roll_std, label=f"{window}-period std")

plt.legend()

plt.title("Rolling Statistics")

plt.show()

adfstat, adfp, *_ = adfuller(s)

kpssstat, kpssp, *_ = kpss(s, nlags="auto")

print(f"ADF p-value: {adf_p:.4f} (lower suggests stationarity)")

print(f"KPSS p-value: {kpss_p:.4f} (higher suggests stationarity)")

I like this combo because ADF and KPSS can disagree. When they do, I lean on domain intuition and residual diagnostics instead of forcing a binary decision.

Core univariate models, from baselines to modern tools

Univariate forecasting spans very simple baselines to neural networks. In 2026, I still start with simple models because they’re transparent, fast, and often competitive.

Baselines I always compute

  • Naive forecast: next value equals last observed value. Great for random walk behavior.
  • Seasonal naive: next value equals last season’s same period. Amazing for weekly or yearly cycles.
  • Moving average: smooths noise and can be a tough baseline to beat.

If my “smart” model can’t beat a seasonal naive baseline on a clean hold‑out, I don’t deploy it.

Traditional models I still use

  • AR (Autoregressive): great when values depend on their own recent history.
  • MA (Moving Average): models errors rather than levels.
  • ARIMA: combines AR and MA with differencing for non‑stationarity.
  • SARIMA: ARIMA with explicit seasonal terms.
  • ETS (Exponential Smoothing): handles trend and seasonality with smoothing weights.

Modern models I reach for when patterns are complex

  • LSTM or GRU: handles long‑range dependencies, but data‑hungry.
  • Temporal CNNs: surprisingly strong for univariate patterns.
  • Transformer‑style models: powerful but can be overkill unless you have lots of data and stable retraining.

Traditional vs modern: when I choose what

Approach

When I recommend it

Why it works

When I avoid it

ARIMA/SARIMA

Strong autocorrelation, clear seasonality, limited data

Interpretable, stable, fast

Frequent regime shifts, irregular sampling

ETS

Smooth trend and seasonality, business reporting

Simple, robust, great baselines

Chaotic signals, sudden shocks

LSTM/GRU

Long sequences, non‑linear dynamics

Learns complex patterns

Sparse data, strict interpretability needs

Transformer‑style

Massive data, multi‑year history

Captures long‑range structure

Tight latency or small datasetsI deliberately keep this table in my team docs because it prevents “model fancy‑ness” from beating good judgment.

A complete univariate workflow: rainfall forecasting example

I’ll walk through a runnable example that forecasts monthly rainfall using only the historical rainfall values. I’ll keep the code concise but complete.

1) Load and inspect data

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

from statsmodels.tsa.seasonal import seasonal_decompose

Load a CSV with columns: date, rainfall

rain = pd.read_csv("rainfall.csv")

rain["date"] = pd.to_datetime(rain["date"])

rain = rain.set_index("date").asfreq("MS") # monthly start frequency

print(rain.head())

plt.figure(figsize=(12, 4))

plt.plot(rain["rainfall"], label="monthly rainfall")

plt.title("Monthly Rainfall")

plt.legend()

plt.show()

I use asfreq("MS") to enforce a regular monthly index. If you don’t standardize frequency, many time series functions will behave unpredictably.

2) Decompose trend and seasonality

result = seasonal_decompose(rain["rainfall"], model="additive", period=12)

result.plot()

plt.show()

This doesn’t “solve” forecasting, but it gives me a concrete sense of trend and seasonality. If the seasonal component is strong and stable, I lean toward SARIMA or ETS.

3) Train‑test split that respects time

split_idx = int(len(rain) * 0.8)

train = rain.iloc[:split_idx]

test = rain.iloc[split_idx:]

print(f"Train: {train.index.min()} → {train.index.max()}")

print(f"Test: {test.index.min()} → {test.index.max()}")

I never shuffle time series data. My test set is always a contiguous block at the end.

4) Baseline: seasonal naive

# Forecast each test month by last year‘s same month

seasonal_naive = test.copy()

seasonal_naive["forecast"] = train["rainfall"].shift(12).reindex(test.index)

plt.figure(figsize=(12, 4))

plt.plot(train["rainfall"], label="train")

plt.plot(test["rainfall"], label="actual")

plt.plot(seasonal_naive["forecast"], label="seasonal naive")

plt.legend()

plt.show()

This is embarrassingly strong for many climate series. If your fancy model can’t beat this baseline, it’s not ready.

5) SARIMA model

import statsmodels.api as sm

(p,d,q) x (P,D,Q,s) chosen after quick grid search or domain intuition

model = sm.tsa.statespace.SARIMAX(

train["rainfall"],

order=(1, 1, 1),

seasonal_order=(1, 1, 1, 12),

enforce_stationarity=False,

enforce_invertibility=False,

)

fit = model.fit(disp=False)

forecast = fit.get_forecast(steps=len(test))

forecastmean = forecast.predictedmean

forecastci = forecast.confint()

plt.figure(figsize=(12, 4))

plt.plot(train["rainfall"], label="train")

plt.plot(test["rainfall"], label="actual")

plt.plot(forecast_mean, label="SARIMA forecast")

plt.fill_between(

forecast_ci.index,

forecast_ci.iloc[:, 0],

forecast_ci.iloc[:, 1],

color="gray",

alpha=0.2,

label="95% CI",

)

plt.legend()

plt.show()

I keep enforcestationarity and enforceinvertibility off when experimenting. Once I settle on the model, I tighten those constraints to avoid unstable parameter estimates.

6) Evaluate with multiple metrics

from sklearn.metrics import meanabsoluteerror, meansquarederror

mae = meanabsoluteerror(test["rainfall"], forecast_mean)

rmse = meansquarederror(test["rainfall"], forecast_mean, squared=False)

print(f"MAE: {mae:.2f}")

print(f"RMSE: {rmse:.2f}")

I almost always report MAE and RMSE together. MAE is intuitive; RMSE penalizes large misses.

Model selection: practical rules I use in 2026

Model selection can become a rabbit hole. I’ve learned to rely on simple, repeatable rules. Here’s the shortlist I teach new engineers:

  • Start with baselines: naive and seasonal naive. If they’re competitive, treat the problem as simple.
  • Use ACF/PACF for ARIMA hints: these plots still work when interpreted cautiously.
  • Prefer ETS for smooth, stable patterns: it’s faster to implement and often good enough.
  • Use SARIMA for strong seasonality: it’s interpretable and handles yearly cycles well.
  • Consider LSTM or CNN only after baselines fail: deep models need data volume and careful validation.
  • If your series is short, don’t use deep learning: it will overfit.

I also use automated search tools for parameter tuning, but I keep them on a leash. Exhaustive grid search on SARIMA can balloon quickly. I typically do a small grid around reasonable values, then evaluate on a single validation fold.

In 2026, AI‑assisted workflows help with parameter exploration. I’ll often use a notebook agent to propose a small grid based on ACF/PACF and then run it programmatically. The key is to keep the search bounded and driven by domain context, not blind automation.

Common mistakes I see (and how to avoid them)

I’ve reviewed dozens of forecasting pipelines, and the same mistakes keep surfacing. Here are the ones that matter most.

  • Leaking future data: normalization or scaling done on the full dataset instead of only the training set. Fix: fit scalers on train only, then transform test.
  • Shuffling time: breaking temporal order during cross‑validation. Fix: use time series split, never random K‑fold.
  • Ignoring seasonality: fitting ARIMA to seasonal data without seasonal differencing. Fix: test seasonal naive and SARIMA.
  • Over‑differencing: making the series too stationary, losing signal. Fix: check residuals and ACF after differencing.
  • Reporting only one metric: a model can look good on MAE but bad on RMSE. Fix: report at least two.
  • Not logging uncertainty: point forecasts mislead stakeholders. Fix: share prediction intervals.

I also see teams ignore data quality: missing months, irregular intervals, or timestamp misalignment. In time series, small timestamp errors create big modeling errors. Always validate your index before modeling.

When to use univariate forecasting—and when not to

I’m a big fan of univariate models, but they’re not the right choice for every problem.

Use univariate forecasting when

  • You have reliable historical data for one variable, and external features are noisy or unavailable.
  • The signal has strong autocorrelation or seasonality.
  • You need a transparent, interpretable model for business reporting.
  • You need quick iteration cycles and stable deployments.

Avoid univariate forecasting when

  • External drivers dominate the outcome (price, marketing, policy changes).
  • Regime shifts happen frequently and unpredictably.
  • You need causal explanations, not just predictive patterns.
  • You can access high‑quality exogenous features that you trust.

A practical rule: if domain experts can name a few external variables that explain most of the movement, you should at least consider multivariate modeling. If they can’t, univariate is often the safer bet.

Performance, deployment, and real‑world constraints

In production, forecasting isn’t just about accuracy. It’s about latency, stability, and clarity. Here’s what I track:

  • Latency: classic models like ETS and SARIMA typically score in the 10–50ms range per forecast once trained. Deep models can range from 20–200ms depending on batch size and hardware.
  • Retraining cadence: for monthly data, I retrain monthly or quarterly. For hourly demand, I might retrain daily or weekly.
  • Data drift: I monitor forecast errors over time. If error exceeds a threshold for multiple periods, I retrain early.
  • Forecast horizon: I track accuracy separately for short‑term vs long‑term horizons. Most models degrade as the horizon extends.
  • Explainability: I always provide stakeholders with a baseline comparison and a seasonal plot so they understand what the model is doing.

Even in a simple univariate pipeline, the operational details can make or break success. If you deliver 2% better accuracy but cannot explain it or rerun it reliably, it’s not production‑ready.

ACF and PACF: practical interpretation without the ceremony

I still use ACF (autocorrelation function) and PACF (partial autocorrelation function) as quick diagnostic tools. I don’t treat them as gospel, but they’re good at narrowing the search space.

Here’s my cheat sheet:

  • ACF cuts off sharply after lag q → suggests MA(q).
  • PACF cuts off sharply after lag p → suggests AR(p).
  • Both decay slowly → suggests ARMA or ARIMA with differencing.
  • Seasonal spikes at lag s, 2s, 3s → suggests seasonal terms.

If I’m working with weekly data and I see spikes at lags 7, 14, 21, I’m already thinking about seasonal components. If the ACF decays slowly, I assume non‑stationarity and try differencing first.

In practice, I use ACF/PACF for a small grid search, not to lock myself into a single parameter set. It’s faster and more realistic than trying to “read” the perfect order from the plots.

Transformations that actually help

Transformations are not just math tricks—they’re how I make a series match a model’s assumptions. I don’t apply them blindly. I test, interpret, and keep them reversible.

Common transformations I use

  • Log transform: stabilizes variance when values grow over time. Useful for revenue, counts, and prices.
  • Box‑Cox or Yeo‑Johnson: more flexible than logs, can handle zeros or negatives.
  • Differencing: removes trend. Seasonal differencing removes seasonality.
  • Scaling: only for neural nets or optimization‑heavy models. I avoid it for ARIMA because interpretation matters.

A small transformation workflow

  • Plot the series and check variance over time.
  • Apply log or Box‑Cox if variance increases with the level.
  • Apply differencing if trend is obvious.
  • Recheck stationarity and residuals.
  • Keep track of how to invert the transform for forecasting.

If a transform makes the series “nice” but forecasting accuracy drops, I remove it. It’s better to accept some messiness than to damage the signal.

Edge cases that break univariate models

Most time series tutorials are clean. Real data isn’t. Here are edge cases that have broken my forecasts and how I handle them.

1) Missing time periods

A missing month can create fake seasonality or distort trends. I always reindex to a full range, then decide how to fill gaps.

My default rules:

  • If missing is rare, I fill with interpolation or last known value.
  • If missing is frequent, I either rebuild the series with a different frequency or treat it as irregular (and avoid classic models).

2) Outliers and spikes

One‑off spikes can contaminate model parameters. I inspect outliers manually and decide if they are valid events or data errors. If they’re errors, I correct or remove them. If they’re valid, I keep them but consider robust models or add indicator features (even in univariate, you can add a temporary “event” flag to your training pipeline if you maintain it consistently).

3) Changing seasonality

Some series change their seasonal pattern over time. I use STL decomposition to allow flexible seasonality. If seasonality evolves, ETS or SARIMA with fixed seasonality may struggle. That’s when I consider rolling window training or a model with time‑varying components.

4) Negative or zero values

Logs break on zero or negative values. I use Yeo‑Johnson or add a small constant if it’s safe. For series that naturally cross zero (like temperature anomalies), I avoid log‑style transforms entirely.

5) Short history

If I only have 24 months of monthly data, a complex model is dangerous. I keep it simple: seasonal naive or ETS. The goal is stability, not a vanity benchmark.

Forecast evaluation that mirrors reality

Most teams do a single train‑test split, then declare victory. I prefer evaluation that resembles the actual deployment scenario.

Rolling‑origin evaluation

I use rolling forecasts to simulate how the model would behave if it were retrained periodically. It’s more work, but it’s honest.

from sklearn.metrics import meanabsoluteerror

horizon = 12

window = 60

errors = []

for start in range(0, len(rain) - window - horizon):

train_slice = rain.iloc[start : start + window]

test_slice = rain.iloc[start + window : start + window + horizon]

model = sm.tsa.statespace.SARIMAX(

train_slice["rainfall"],

order=(1, 1, 1),

seasonal_order=(1, 1, 1, 12),

enforce_stationarity=False,

enforce_invertibility=False,

)

fit = model.fit(disp=False)

preds = fit.getforecast(steps=horizon).predictedmean

errors.append(meanabsoluteerror(test_slice["rainfall"], preds))

print(f"Rolling MAE: {sum(errors)/len(errors):.2f}")

This approach catches models that perform well only on one lucky split. It also tells you how stable the model is across different time windows.

Error metrics I actually trust

  • MAE: I use it for communication with non‑technical stakeholders.
  • RMSE: I use it to penalize large misses.
  • MAPE: I use it only when values are always positive and never near zero.
  • sMAPE: a safer alternative when values can be small.
  • MASE: I like it because it compares against a naive baseline directly.

If I can only report one metric, I pick MAE. If I can report two, I add RMSE. If I can report three, I add MASE.

Prediction intervals: the difference between a forecast and a plan

Point forecasts are easy to misinterpret. The real value comes from showing uncertainty. If a model predicts 120 with a 95% interval of [90, 150], that’s a very different message than “120.”

I encourage teams to treat prediction intervals as a first‑class output. They help with:

  • Inventory planning: buffer stock can be derived from the upper bound.
  • Risk management: worst‑case scenarios are explicit.
  • Stakeholder trust: people trust forecasts that admit uncertainty.

Most classic models (ETS, SARIMA) can produce intervals out of the box. For neural nets, I use techniques like Monte Carlo dropout or quantile regression. I don’t rely on single deterministic predictions in production.

A deeper SARIMA tuning workflow (without overkill)

SARIMA can be powerful, but it’s easy to drown in parameters. My approach is to constrain the search and make it fast.

Step 1: Define reasonable ranges

  • p, q in {0, 1, 2}
  • d in {0, 1}
  • P, Q in {0, 1}
  • D in {0, 1}
  • season length s = known (12 for monthly, 7 for daily weekly seasonality)

Step 2: Use a small validation set

I use the last 10–20% of training data as a validation set. I optimize for MAE and pick the best 3 models.

Step 3: Sanity check residuals

Even if metrics look good, I check residual plots. If residuals show autocorrelation or seasonality, I reject the model.

This workflow avoids the trap of “best metric wins” and focuses on the stability of the model.

Exponential smoothing: the underrated workhorse

ETS models have saved me more times than I can count. They are simple, fast, and often just as accurate as more complex approaches.

When I pick ETS:

  • The series has a clear trend and seasonality.
  • I need forecasts quickly with little parameter tuning.
  • I want robustness and stability rather than fancy accuracy gains.

The big win with ETS is interpretability. You can explain it as “a weighted average of recent history with a trend and seasonal adjustment.” That makes it easy to communicate to business partners.

Neural nets for univariate forecasting: when I actually use them

I’m not anti‑deep learning. I just don’t use it by default for univariate tasks. When I do, it’s usually because:

  • I have long history (thousands of points).
  • The signal is highly non‑linear.
  • Baselines and classic models are clearly underperforming.

I also limit scope. Instead of a giant multi‑layer model, I often use a small LSTM or a 1D CNN. It’s easier to train, easier to debug, and easier to maintain.

A practical trick: I treat deep models as “pattern learners” and still compare them to a seasonal naive baseline. If the gain is small, I don’t ship it.

Production monitoring: how I know when a model is failing

Monitoring is not optional. Univariate models can drift quietly and deliver wrong forecasts without obvious alarms.

I track:

  • Rolling error: MAE over the last N periods.
  • Bias: average error sign; if it’s consistently positive or negative, the model is biased.
  • Residual autocorrelation: if residuals become correlated, the model is missing structure.
  • Data quality signals: missing values, sudden level shifts, or timestamp gaps.

A simple rule: if error exceeds a threshold for three consecutive periods, I retrain or revert to a baseline until the model stabilizes.

Practical scenarios and model choices

Here are a few real‑world scenarios and how I typically approach them.

Scenario 1: Weekly sign‑ups with strong weekly seasonality

  • Baseline: seasonal naive with period 7.
  • Model: SARIMA with weekly seasonality or ETS with seasonality.
  • Reason: strong repeating cycle, likely stable.

Scenario 2: Monthly revenue with growth trend and holiday spikes

  • Baseline: seasonal naive with period 12.
  • Model: ETS with trend + seasonality or SARIMA with holiday effects handled via outlier adjustments.
  • Reason: trend + seasonal spikes; need stable forecasts.

Scenario 3: Hourly energy demand with multiple seasonalities

  • Baseline: seasonal naive (24 or 168 hours).
  • Model: STL + ARIMA or a simple CNN.
  • Reason: daily and weekly patterns; may require flexible seasonality.

Scenario 4: Sensor data with frequent regime shifts

  • Baseline: naive or short‑window average.
  • Model: rolling window ARIMA or anomaly‑aware forecasts.
  • Reason: non‑stationarity dominates; complex models fail.

Alternative approaches: different ways to solve the same problem

Univariate forecasting isn’t one method; it’s a family of approaches. Here are alternatives I sometimes use when classic models struggle.

1) STL + ARIMA hybrid

I decompose the series with STL, forecast the trend and residual separately, then combine. This works well when seasonality shifts slowly over time.

2) Prophet‑style models

I use additive regression models with trend and seasonality terms when I need quick explainable forecasts and the data is fairly regular. They aren’t perfect, but they’re practical.

3) State‑space models

State‑space models allow time‑varying parameters and can be more robust to changes. They’re more complex, but I use them when the series behavior evolves over time.

4) Simple ensembles

If I’m torn between ETS and SARIMA, I sometimes average their forecasts. Ensembles can reduce error variance and stabilize performance.

The key is to keep complexity proportional to the problem. If a simple model works, I use it. If it doesn’t, I add complexity carefully and measure the gain.

Feature engineering in a univariate world

Even though it’s univariate, I still engineer features—just derived from the series itself.

Examples:

  • Lag features: previous values (t‑1, t‑2, t‑7, t‑12).
  • Rolling statistics: moving averages or rolling standard deviation.
  • Calendar effects: day of week or month of year, if you allow a simple calendar feature (still univariate in the sense that it doesn’t require external data).

These features are often used in tree‑based models or simple regressions when I want a flexible baseline without deep learning. It’s still a univariate forecast because the features come from the target series and time index itself.

Handling multiple seasonalities without going multivariate

Daily data often has both weekly and yearly seasonality. Classic SARIMA struggles with multiple seasonalities. My options:

  • STL decomposition: remove one seasonality, model the residual with another.
  • Fourier terms: include sine and cosine terms for multiple periods in a regression or ARIMA with exogenous regressors.
  • Switch to models that support multiple seasonalities: some modern models handle this out of the box.

I keep it simple: if I can explain the seasonalities with a decomposition, I do. If not, I consider specialized models or a lightweight neural net.

Data prep checklist I run before modeling

Here’s the checklist I apply before I build any model:

  • Confirm index frequency and timezone.
  • Check for duplicates and missing timestamps.
  • Plot raw series and rolling mean/variance.
  • Identify outliers and validate them.
  • Confirm the forecast horizon and business use case.
  • Establish baseline forecasts.
  • Decide on transformations and document how to invert them.

If I can’t pass this checklist, I’m not ready to model. It saves me more time than any fancy algorithm.

Deployment patterns that keep me sane

I’ve deployed univariate forecasts in batch pipelines, APIs, and dashboards. The patterns that survive production are consistent.

Batch forecasting

I use batch jobs for daily or monthly forecasts. The job:

  • pulls the latest series,
  • fits or updates the model,
  • generates forecasts and intervals,
  • writes results to a table or cache.

Batch is reliable and easy to monitor. It’s my default for most business use cases.

Real‑time forecasting

I use real‑time only when it’s truly needed. I keep the model simple and maintain a rolling buffer of recent values. The focus is on latency and stability rather than squeezing out a 1% error improvement.

Hybrid

Sometimes I train daily in batch but serve forecasts in real‑time. The model is fixed during the day but updated nightly. This keeps the serving path fast and stable.

Practical performance ranges (and why they matter)

I avoid giving exact speed numbers because hardware and data size vary, but I do track typical ranges:

  • ETS: milliseconds per forecast after training; training itself is usually sub‑second to a few seconds for moderate data sizes.
  • SARIMA: training can range from seconds to minutes depending on parameters; forecasting is fast once fitted.
  • LSTM/GRU: training can be minutes to hours depending on data size; inference is moderate but higher than ETS.

The main performance question is not “how fast can it run,” but “how predictable is the pipeline.” Stable latency and repeatable results matter more than raw speed.

A richer end‑to‑end example: retail demand with multiple baselines

Here’s a slightly more complete workflow that includes baselines, feature engineering, and a simple model comparison.

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import statsmodels.api as sm

from sklearn.metrics import meanabsoluteerror

Load data

sales = pd.readcsv("dailysales.csv")

sales["date"] = pd.to_datetime(sales["date"])

sales = sales.set_index("date").asfreq("D")

Basic cleanup

sales["sales"] = sales["sales"].interpolate(limit_direction="both")

Train/test split

split_idx = int(len(sales) * 0.85)

train = sales.iloc[:split_idx]

test = sales.iloc[split_idx:]

Baseline: naive

naive_pred = test["sales"].copy()

naive_pred[:] = train["sales"].iloc[-1]

Baseline: seasonal naive (weekly)

seasonal_naive = train["sales"].shift(7).reindex(test.index)

SARIMA

model = sm.tsa.statespace.SARIMAX(

train["sales"],

order=(1, 1, 1),

seasonal_order=(1, 1, 1, 7),

enforce_stationarity=False,

enforce_invertibility=False,

)

fit = model.fit(disp=False)

forecast = fit.getforecast(steps=len(test)).predictedmean

Compare

maenaive = meanabsoluteerror(test["sales"], naivepred)

maeseasonal = meanabsoluteerror(test["sales"], seasonalnaive)

maesarima = meanabsolute_error(test["sales"], forecast)

print(f"Naive MAE: {mae_naive:.2f}")

print(f"Seasonal Naive MAE: {mae_seasonal:.2f}")

print(f"SARIMA MAE: {mae_sarima:.2f}")

This is not fancy, but it’s what real univariate forecasting looks like: compare against baselines, use a stable model, and measure with a simple metric. If SARIMA can’t beat seasonal naive, I stop there.

A note on “AI‑assisted” workflows

I use AI tools for boring tasks: generating parameter grids, summarizing residual diagnostics, or building report templates. I don’t use them to “find the best model” without supervision. The human still has to interpret and choose.

A workflow I like:

  • I run baseline models.
  • I ask an assistant to propose a small grid based on ACF/PACF.
  • I run the grid, pick top candidates, then review diagnostics.
  • I choose based on accuracy and stability.

The assistant speeds up iteration. It doesn’t replace the judgment step.

Summary: univariate forecasting done right

Univariate time series analysis is a discipline of humility and rigor. You accept that you only have one variable, then you squeeze as much signal as you can from its history. The goal isn’t to be flashy—it’s to be reliable.

Here’s the core mindset I carry into every project:

  • Start with baselines and respect them.
  • Diagnose trend and seasonality before choosing a model.
  • Treat stationarity as a tool, not a rule.
  • Evaluate in a way that mirrors real usage.
  • Communicate uncertainty, not just point forecasts.
  • Keep the pipeline stable and explainable.

When you do that, univariate forecasting becomes a dependable part of your decision‑making toolkit. It may not explain causality, but it will tell you what is likely to happen next—and that’s often exactly what you need.

Scroll to Top