Time Series with Machine Learning (Part 3)

The lag features from the previous post handed the model raw historical sales values, the exact number sold on a specific day 91 or 364 days ago. That is precise, but precision is not always what you want. A single day’s sales figure is noisy. It might have been a public holiday, an unusual promotion, or just a bad week. If the model learns to rely heavily on one specific past day, it can be thrown off whenever that day happens to be an outlier.

Rolling mean features address this by replacing a single past value with an average over a window of past values. Instead of asking “what did we sell exactly 364 days ago?”, the model is now asking “what was the average daily sales over the past year?”. That question is more stable, more representative, and less sensitive to one-off fluctuations.

What Is a Rolling Mean?

A rolling mean, also called a moving average, slides a window of fixed size along the series and computes the average of the values inside that window at each step. The window size controls how far back you look and, as a consequence, how smooth the result is.

A window of 2 averages the current and the previous observation. A window of 365 averages the past year’s worth of daily values. The larger the window, the more individual spikes are absorbed into the mean and the smoother the resulting curve.

Auto Draft.
Figure 1. Left: the same series smoothed with windows of 7, 14, and 30 days. Larger windows absorb more noise. Right: how NaN fills the early rows while the window accumulates enough observations.

The right panel shows a key behaviour: the first (window − 1) rows will always be NaN. A roll-2 needs at least 2 values, so the very first row has nothing to average. A roll-5 needs 5 values, so the first 4 rows are empty. This is expected and handled, the min_periods parameter discussed later gives control over exactly when the mean starts being computed.

Why Shift Before Rolling?

There is a critical ordering issue that is easy to miss. By default, pandas .rolling() includes the current row in the window. So roll-2 at row t averages y(t) and y(t−1), the current day’s sales and yesterday’s. That means the feature at time t contains the value you are trying to predict. This is data leakage: the model sees the answer while training, which inflates performance on paper and collapses on real data.

The fix is a single shift before rolling. Calling .shift(1) moves the series down by one row, so position t now contains y(t−1). Rolling on that shifted series means the window at row t covers y(t−1), y(t−2), …, entirely in the past. The current value y(t) is never included.

sales_roll_mean_k  =  mean( y(t−1), y(t−2), …, y(t−k) )

Creative writing process for ideas.
Figure 2. Left: rolling without shift — the current row’s sales is included in the average, leaking the target into the feature. Right: shift first, then roll — the window uses only past values.

Notice row t=2 in both tables. Without shifting, roll2 at t=2 is (14+13)/2 = 13.5 — which includes the current sales value of 14. With shifting, roll2 at t=2 is (13+11)/2 = 12.0 — looking only at t=1 and t=0. The difference is subtle in the numbers but fundamental in meaning. One feature is contaminated; the other is clean.

The Triangular Window — Weighting Recent History More

A plain rolling mean treats every value in the window equally. The sale from 364 days ago counts just as much as the sale from 2 days ago. That is often reasonable, but for a seasonality-focused feature it can be improved. Sales from closer in time are usually more informative about what is happening now.

The triangular window (win_type=’triang’) addresses this by assigning linearly increasing weights to observations as they approach the current row. The oldest value in the window gets the smallest weight; the most recent gets the largest. The weights form a ramp, hence the name triangular.

Woman writing in a notebook, exploring ideas and creativity.
Figure 3. Uniform weighting (left) versus triangular weighting (right) across a 7-day window. The triangular scheme puts 25% of the weight on the most recent observation and only 4% on the oldest.

For a 365-day window, the uniform mean would give equal weight to every day of the past year. The triangular mean would give roughly six times more weight to a sale from last week than to a sale from a year ago. In a demand context, that often matches reality better: last quarter’s trend is more predictive of next quarter than the trend from two years ago.

min_periods — Starting Early Without Requiring a Full Window

A 365-day window would normally be NaN for the entire first year of each series. The min_periods=10 parameter relaxes this: the rolling mean is computed as soon as at least 10 observations are available inside the window, even if the full 365 have not accumulated yet. Early estimates are less stable, they are based on fewer values, but they are still informative, and they prevent the model from losing an entire year of training data to NaN.

As more observations accumulate, the window fills up and the estimate converges to the true rolling mean. By the time the series reaches its second year, min_periods has no practical effect, the window is always full.

The Two Windows: 365 and 546 Days

Two rolling mean features are added to the dataset: one with a 365-day window and one with a 546-day window (approximately 1.5 years). Both are computed on the shifted series within each store-item group, and both receive the same small random noise added to the lag features, for the same reason: to prevent the model from memorizing exact feature values.

The 365-day window captures the average sales level over the past full year. This is the most natural reference point for annual seasonality: it tells the model what a typical day looked like across all twelve months. The 546-day window extends that memory by another half-year, giving the model a slightly longer baseline. It is useful for series that have an upward or downward trend, where a single year might not capture the full direction of travel.

Woman writing in a notebook with a laptop, inspiring creative writing.
Figure 4. roll_mean_365 and roll_mean_546 plotted against raw daily sales for Store 1, Item 1. Both curves track the seasonal trend while absorbing day-to-day noise. The 546-day curve is smoother and slower to react.

Looking at the chart, a few things are immediately visible. The raw series is extremely noisy day-to-day. Both rolling curves reveal the underlying seasonal shape, rising through spring and summer, pulling back in winter, that is invisible in the raw data. The 365-day curve responds more quickly to shifts in level; the 546-day curve is slower and more conservative. Together, they give the model two different perspectives on the same trend.

At this point the feature set has grown considerably. Date features encode calendar position. Lag features encode specific past values. Rolling mean features encode smoothed trend levels over longer windows. Each layer adds a different kind of temporal signal, and together they give the model a structured view of time without any explicit time-series modelling.

Leave a Reply

Create a website or blog at WordPress.com

Up ↑

Discover more from Writing my way through ideas.

Subscribe now to keep reading and get access to the full archive.

Continue reading