"I am the same fixed length I always was, yet I never sit still. Every step I slide one tick into the future, and the past I carry forward is never quite the past I carried a moment ago."
A Sliding Window With No Fixed Address
A learning algorithm wants a stack of independent input-output pairs, but a time series is a single unbroken thread; windowing is the surgery that turns one into the other. Every supervised temporal model you will ever train, from a humble linear lag regression to a billion-parameter foundation model, is fed by some windowing function that slices the series into a tensor of inputs and a tensor of targets. The choices you make here, how far back to look, how far ahead to predict, how much windows overlap, and how you keep overlapping windows from straddling the train-test boundary, silently set the ceiling on everything downstream. This section gives you the notation, the trade-offs, and the exact reusable code, then hands the leakage-proof evaluation built on top of it to Section 2.6.
The previous section, Section 2.4, built clean feature representations of a series: lags, rolling statistics, calendar encodings, and the rest of the engineered inputs. This section answers the next question: given those features, how do you carve the continuous record into the discrete supervised samples a model actually consumes? The answer is windowing, and it is deceptively simple to get almost right and quietly wrong. We proceed from the framing (series to samples), through the window-type taxonomy and the multi-step forecasting strategies, to the one vectorized function you will copy into every project, and finally to segmentation for classification where overlap and leakage interact in a way that has embarrassed many a published result.
1. From a Series to Supervised Samples Beginner
Let the observed series be $x_1, x_2, \ldots, x_T$, where each $x_t \in \mathbb{R}^{d}$ may be a scalar ($d = 1$) or a vector of $d$ channels. A supervised forecasting sample anchored at time $t$ pairs a lookback window of the most recent $L$ observations with a target horizon of the next $H$ observations. Writing $x_{a:b}$ for the contiguous block $(x_a, x_{a+1}, \ldots, x_b)$, the input and target are
$$X_t = x_{t-L+1:t} \in \mathbb{R}^{L \times d}, \qquad Y_t = x_{t+1:t+H} \in \mathbb{R}^{H \times d}.$$Here $L$ is the lookback (also called the context length or window size), $H$ is the horizon (how many steps ahead you predict), and the anchor $t$ is the boundary between known past and unknown future. The single non-negotiable invariant is that the entire input lies strictly before the entire target: $\max(\text{input index}) = t < t+1 = \min(\text{target index})$. Violate it and you have built look-ahead bias directly into your dataset, the cardinal sin catalogued in Section 1.6 and policed throughout Section 2.6.
To generate many samples you slide the anchor forward by a fixed stride $S$. Starting from the first position where a full lookback exists, the valid anchors are $t \in \{L, L+S, L+2S, \ldots\}$ subject to $t + H \le T$ so that the target also fits. The number of samples produced is therefore
$$N = \left\lfloor \frac{T - L - H}{S} \right\rfloor + 1,$$a formula worth internalizing because it tells you immediately how a longer horizon or a larger stride shrinks your dataset. The symbols $L$, $H$, $S$, $T$, and the index conventions used here are fixed in the unified notation table of Appendix A; we use them consistently for the rest of the book.
A time series is not i.i.d. data, but windowing pretends it is: each window becomes a "sample" and the model treats the collection as if the windows were independent. That pretense is what lets you reuse the entire supervised-learning toolbox (gradient descent, mini-batching, train-test splits) on temporal data. The pretense is also exactly where leakage hides, because consecutive windows overlap and are anything but independent. Hold both halves of this idea at once: windowing buys you the supervised machinery, and the overlap it introduces is the bill you pay later at split time.
2. Window Types: Sliding, Tumbling, and Expanding Beginner
The stride $S$ relative to the lookback $L$ defines three classic window regimes, and the choice governs how many samples you get and how correlated they are. A sliding window uses $S < L$ (very commonly $S = 1$), so consecutive windows overlap heavily and share most of their content. A tumbling window uses $S = L$, so windows abut without overlapping and partition the series into disjoint blocks. An expanding window keeps its left edge pinned at the start and grows its right edge, so the lookback length itself increases with $t$ rather than staying fixed; this is the natural shape for cumulative statistics and for the walk-forward evaluation of Section 2.6. Figure 2.1 shows the shared mechanism: a frame gliding along the series and stamping out fixed-shape samples.
The trade-off is a direct tension between sample count and sample independence. Sliding windows with $S = 1$ maximize the number of training samples, which helps data-hungry deep models, but adjacent samples differ by a single time step and are therefore almost perfectly correlated, so the effective sample size is far smaller than $N$ suggests. Tumbling windows yield far fewer samples but those samples are much closer to independent, which makes them safer for honest variance estimates and statistical tests. The danger that unites both, and that Section 5 below treats in full, is leakage across the train-validation boundary: if a sliding window centered just before the split and another just after share overlapping timestamps, the validation set contains data the training set has already seen.
The most insidious windowing bug is to generate all windows first and split them afterward. With a sliding stride, the window ending at the last training timestamp and the window beginning at the first validation timestamp overlap by $L - S$ steps, so identical raw observations appear on both sides of the wall. The model has literally seen part of its own test set. The fix is to split the raw series by time first and window each segment independently, then optionally discard a purge gap of $L + H - 1$ steps around the boundary so no surviving window straddles it. Never window-then-split; always split-then-window.
3. Direct, Recursive, and Multi-Output Forecasting Intermediate
Once $H > 1$ you must decide how the model produces multiple future steps, and three strategies dominate. The recursive (iterated) strategy trains a single one-step model and rolls it out: predict $\hat{x}_{t+1}$, feed it back as if observed, predict $\hat{x}_{t+2}$, and so on for $H$ steps. The direct strategy trains $H$ separate models, one per horizon, so model $h$ maps the lookback straight to $x_{t+h}$ with no feedback. The multi-output (joint) strategy trains one model whose output layer emits the entire horizon vector $\hat{Y}_t \in \mathbb{R}^{H \times d}$ in a single forward pass; this is what sequence-to-sequence and most modern deep forecasters do.
The strategies differ sharply in error behavior. Recursive forecasting is cheap to train (one model) but compounds error, because each predicted step becomes a noisy input to the next and small mistakes snowball across the horizon. Direct forecasting avoids that feedback loop and so resists error accumulation, but it trains and stores $H$ models and lets each horizon learn an unrelated function, ignoring the smoothness that ties neighboring horizons together. Multi-output forecasting shares representation across the whole horizon and predicts it consistently in one pass, at the cost of a heavier output head and a fixed horizon baked into the architecture. Table 2.5.1 lays the trade-offs side by side.
| Property | Recursive (iterated) | Direct | Multi-output (joint) |
|---|---|---|---|
| Models trained | One (one-step) | $H$ (one per horizon) | One (emits all $H$) |
| Error accumulation | High; mistakes compound step by step | None across steps; each horizon independent | Low; horizon predicted jointly |
| Training cost | Lowest | Highest (scales with $H$) | Moderate |
| Cross-horizon consistency | Smooth by construction | Can be jagged; horizons uncoordinated | Coherent; shared representation |
| Flexible horizon at inference | Yes; roll out to any length | No; fixed to trained horizons | No; fixed at training time |
| Typical home | Classical ARIMA, AR rollouts | Gradient-boosted per-step models | Seq2seq, deep forecasters, foundation models |
No strategy dominates universally. Recursive shines for short horizons on smooth series and when you need a flexible, retrainable horizon; direct wins when horizons behave very differently and compute is cheap relative to accuracy; multi-output is the default for deep learning and is what the architectures of Chapter 14 assume. The classical autoregressive roots of recursion are developed in Chapter 5, and the foundation-model heads that emit a whole horizon at once appear in Chapter 15.
4. Building the Tensors: A Reusable Windowing Function Intermediate
Theory in hand, here is the function you will actually reuse. Code 2.5.1 builds the $(N, L, d)$ input tensor and the $(N, H, d)$ target tensor from a raw array, honoring lookback $L$, horizon $H$, and stride $S$, with an explicit correctness assertion that no target index ever precedes its input. We use NumPy stride tricks (sliding_window_view) so the windowing is a memory view rather than a copy, which matters once $T$ reaches the millions.
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
def make_windows(series, lookback, horizon, stride=1):
"""Turn a (T,) or (T, d) series into supervised windows.
Returns
-------
X : (N, lookback, d) input windows x_{t-L+1 : t}
Y : (N, horizon, d) target windows x_{t+1 : t+H}
"""
x = np.asarray(series, dtype=np.float64)
if x.ndim == 1: # promote scalar series to (T, 1)
x = x[:, None]
T, d = x.shape
span = lookback + horizon # total length each sample covers
if T < span:
raise ValueError(f"series length {T} < lookback+horizon {span}")
# All length-`span` slices, then split each into past (L) and future (H).
frames = sliding_window_view(x, window_shape=span, axis=0) # (T-span+1, d, span)
frames = frames.transpose(0, 2, 1)[::stride] # (N, span, d), apply stride
X = frames[:, :lookback, :] # x_{t-L+1 : t}
Y = frames[:, lookback:, :] # x_{t+1 : t+H}
# Correctness: the last input index must precede the first target index
# for every single sample. This is the leakage invariant, asserted, not hoped.
last_in = np.arange(X.shape[0]) * stride + lookback - 1
first_out = last_in + 1
assert np.all(first_out > last_in), "target precedes input: window is leaky"
return X, Y
make_windows built on sliding_window_view. It accepts scalar or multivariate series, applies the stride by simple slicing, and documents the leakage invariant by construction (every target index strictly follows its input) before returning.The usage example in Code 2.5.2 runs the function on a short synthetic series and prints the resulting tensor shapes alongside the sample-count formula from Section 1, confirming they agree. It also shows the first input window and its target so you can read the temporal boundary directly off the numbers.
# A tiny ramp series 0,1,2,... makes the index alignment readable by eye.
series = np.arange(20.0)
L, H, S = 4, 2, 2
X, Y = make_windows(series, lookback=L, horizon=H, stride=S)
T = len(series)
N_formula = (T - L - H) // S + 1
print("X shape:", X.shape, " Y shape:", Y.shape)
print("N from formula:", N_formula, " N actual:", X.shape[0])
print("first input :", X[0, :, 0]) # x_{1:4} -> [0 1 2 3]
print("first target:", Y[0, :, 0]) # x_{5:6} -> [4 5]
make_windows on a 20-point ramp with $L=4$, $H=2$, $S=2$. The printed shapes match the closed-form $N$, and the first input ends at value 3 while its target begins at value 4, so the temporal boundary holds exactly.X shape: (8, 4, 1) Y shape: (8, 2, 1)
N from formula: 8 N actual: 8
first input : [0. 1. 2. 3.]
first target: [4. 5.]
Once you understand the mechanics, you rarely write the loop again. Keras offers keras.utils.timeseries_dataset_from_array(data, targets, sequence_length=L, sequence_stride=S), which yields a batched, shuffled tf.data pipeline directly; PyTorch users reach for torch.Tensor.unfold(0, L, S) or a tiny Dataset wrapper. Higher up, Darts (TimeSeries plus its forecasting models) and sktime's reduction wrappers, along with Nixtla's mlforecast, perform the windowing, lag construction, and horizon handling internally so you specify $L$ and $H$ as arguments and never touch a stride trick. Reaching for one of these replaces the dozen lines of Code 2.5.1 with a single call and inherits their tested handling of multivariate panels and ragged series.
The most common windowing bug in the wild is a single misplaced index: writing Y = frames[:, lookback-1:, :] instead of lookback:. That one character makes the first target step equal to the last input step, so the model is rewarded for "predicting" a value it was just handed. Accuracy looks superb in development and collapses in production. An entire genre of suspiciously perfect notebooks owes its glory to this typo.
5. Segmentation for Classification and Events Advanced
Forecasting is not the only consumer of windows. In temporal classification, human activity recognition, sleep staging, machine-fault detection, you cut the series into segments and assign each a label rather than a future. Two anchoring schemes compete. Fixed windows tile the series on a regular grid (often with a chosen overlap) and label each by majority vote of the timestamps it covers. Event-anchored windows instead center a window on each event of interest (a detected heartbeat, an alarm, a button press) and label it by that event's class, which keeps the phenomenon centered but produces irregularly spaced, possibly overlapping segments.
Label alignment is the subtle part. When a fixed window spans a transition between two activities, majority vote can mislabel it, so practitioners either shrink the window, require a purity threshold (discard windows that are not at least, say, eighty percent one class), or label by the final timestamp when the task is causal. None of this matters, however, next to the overlap-aware splitting rule, because overlapping classification windows are the single most common source of inflated accuracy in the applied temporal-ML literature.
Who: A wearables team building a human-activity-recognition model from wrist accelerometer data across thirty volunteers.
Situation: They cut each recording into two-second windows with ninety percent overlap to maximize training data, then split all windows randomly eighty-twenty into train and test, reporting 98 percent test accuracy.
Problem: Field accuracy on new wearers hovered near 70 percent, a thirty-point gap that no amount of hyperparameter tuning closed.
Dilemma: Either the field data was somehow harder (a distribution-shift story) or the headline number was contaminated (a splitting story). The first is comfortable; the second voids the result they had already presented.
Decision: They re-examined the split before blaming the field, on the reasoning that 98 percent on noisy accelerometer data is far more often a leak than a triumph.
How: With ninety percent overlap, each window shared 1.8 seconds with its neighbor, so the random split scattered near-duplicate windows into both train and test. They switched to a subject-wise split (no volunteer appears in both sets) combined with non-overlapping test segments, exactly the split-then-window discipline of Section 2.
Result: Honest accuracy fell to 71 percent, matching the field, and a leakage-free splitter became a mandatory gate. The model was no worse than before; only the measurement had been lying.
Lesson: Overlapping windows plus a random split is leakage wearing a lab coat. Split by the natural unit (time block or subject) first, then window, and never let two windows that share raw samples land on opposite sides of the wall.
The safe segmentation recipe is therefore mechanical. First split the raw series (or the cohort of subjects) into disjoint train, validation, and test partitions by time or by identity. Then window each partition independently. Then, at every internal boundary, purge a gap of at least one full window length so no surviving window overlaps across the split. Group-aware cross-validators such as scikit-learn's GroupKFold and the purged, embargoed schemes of Section 2.6 automate exactly this, and the same overlap-leakage trap, viewed from the evaluation side, is introduced in Section 1.6 and developed as the central concern of Section 2.6.
The newest temporal architectures reframe windowing as tokenization. Rather than feeding raw per-step values, patch-based transformers split each series into short contiguous patches and treat every patch as a token, which shortens the attention sequence, sharpens local semantics, and is the design behind PatchTST and the channel-independent patching it popularized. Temporal foundation models carry the idea further: Chronos quantizes values into a discrete vocabulary and tokenizes the series like text, while Moirai, TimesFM, and MOMENT pretrain over enormous corpora on patched or masked windows so a frozen model windows-and-forecasts zero-shot on series it never saw. The throughline of this 2024 to 2026 wave is that the window is no longer just a slicing convenience; it has become the model's vocabulary, and the patch length is now a first-class architectural hyperparameter studied in Chapter 15.
A series has $T = 1000$ observations. Using the formula $N = \lfloor (T - L - H)/S \rfloor + 1$, compute the number of supervised samples for (a) a sliding window with $L = 50$, $H = 10$, $S = 1$; (b) a tumbling window with the same $L$ and $H$ but $S = L$; and (c) the sliding case but with the horizon raised to $H = 200$. Explain in one sentence each why the sample count changes and what it implies about effective (independent) sample size versus raw sample count.
Modify Code 2.5.1 so that an optional argument flatten_target=True returns Y with shape $(N, H \cdot d)$ instead of $(N, H, d)$, the layout most gradient-boosted multi-output regressors expect. Then add a second optional argument return_index=True that also returns the array of anchor times $t$ for each sample, so a caller can map any window back to its position in the original series. Verify on the Code 2.5.2 ramp that the returned anchors satisfy series[anchor+1 : anchor+1+H] equals the corresponding target row, and keep the leakage assertion intact.
Generate a series with a slow regime change at the midpoint (for example, a sine whose frequency doubles after $t = T/2$). Build sliding windows with heavy overlap ($S = 1$, $L$ large), then split the resulting windows randomly into train and test and train any classifier to predict the regime. Record the accuracy. Now redo it with the split-then-window discipline of Section 2 plus a purge gap of $L + H - 1$ around the boundary, and record the accuracy again. Quantify the inflation the random split produced, and write a short paragraph explaining why the gap scales with the window length and the overlap.