Visualize how time series data is processed into windows for forecasting and analysis.
Overview
Turning a sequence into a supervised problem
A model needs examples with features and a label. A time series is a single ordered run of values, so you build examples by sliding a fixed-length window along it: the values inside the window are the input, and the value immediately after it is the target.
With the series [10, 12, 15, 13, 18, 20, 19] and a window of 3:
Seven observations become four training examples. In general a series of length n with window W and stride S yields ⌊(n − W) / S⌋ + 1 examples — so the window costs you W observations’ worth of data before you get anything at all.
Generated Windows
0 ITEMS
Enter data to generate windows
Window List ScrollVIEW
Tip: Use the bottom slider to scroll quickly through large lists of windows.
Sliding Window for Time Series: A Practical Guide
Supervised learning needs (input, target) pairs and a time series is just one long sequence. A sliding window manufactures the pairs - and the way you split them afterwards is where most time-series projects go wrong.
Window size and stride
Window size (W) is how much history the model sees per prediction, and it is a real modelling assumption: it asserts that nothing older than W steps matters. Too small and the model cannot see the pattern — a weekly cycle needs at least seven daily steps. Too large and each example carries mostly irrelevant history, the input dimension grows, and the number of examples shrinks.Stride (S) is how far the window jumps between examples. A stride of 1 gives the maximum number of examples, heavily overlapping and therefore highly correlated. A larger stride gives fewer, more independent examples. Stride 1 is the usual choice for training; larger strides are used to cut redundancy on very long series.
Turning a timeline into a table
Machine learning models expect a table: one row per example, one column per feature, and rows that do not depend on each other. A time series is none of those things — it is one long sequence where every value depends on the ones before it.
The sliding window is the conversion. Pick a window length, slide it along the series, and each position becomes one row of an ordinary supervised learning problem.
With daily sales [10, 12, 15, 13, 18, 20, 22] and a window of 3:
Row
Features (past 3 days)
Target (next day)
1
10, 12, 15
13
2
12, 15, 13
18
3
15, 13, 18
20
4
13, 18, 20
22
Seven numbers have become four training examples with three features each. From here, any tabular model will do — linear regression, random forest, gradient boosting — and none of them need to know anything about time.
Two immediate consequences. You lose the first window observations, because there is no complete history for them. And consecutive rows overlap heavily, so they are not independent — which is exactly why the usual random train/test split is invalid here.
Choosing the window and the horizon
Two numbers define the setup, and they answer different questions.
Window size (lookback) — how much history each prediction sees. Too short and the model cannot see the pattern: a window of 3 on daily data cannot represent a weekly cycle. Too long and you have many features, fewer rows, and a lot of ancient history diluting recent signal.
Start from the domain. Daily data with weekly seasonality wants at least 7, often 14. Hourly data with a daily cycle wants at least 24. Then tune around that starting point, and let the data confirm it — an autocorrelation plot shows directly how far back the useful dependence reaches.
Horizon — how far ahead you predict. One step is easiest and most accurate. For multiple steps there are three strategies:
Recursive. Predict one step, feed it back in as an input, predict the next. Simple, and errors compound quickly.
Direct. Train a separate model for each horizon — one for t+1, one for t+2, and so on. More models, no error compounding.
Multi-output. One model predicting the whole horizon vector at once. Efficient, and it can learn the relationships between the steps.
Stride is the third, quieter setting: how far the window moves each time. A stride of 1 extracts every possible window and maximises the number of training rows. Larger strides reduce overlap and training time, at the cost of fewer examples.
Splitting time series without cheating
Shuffling a windowed dataset is the single most common mistake in this topic, and it produces beautiful, worthless results.
The reason is direct: window 1 contains days 1–3 and window 2 contains days 2–4. Put one in training and the other in test, and the test row shares two-thirds of its inputs with a training row. Worse, a shuffled split lets the model train on next month while being tested on this month, which is a capability it will never have in production.
The correct approach is a chronological split — train on the earliest portion, validate on the middle, test on the most recent — and for cross-validation, an expanding or rolling window:
Fold
Train on
Test on
1
Months 1–6
Month 7
2
Months 1–7
Month 8
3
Months 1–8
Month 9
Scikit-learn's TimeSeriesSplit implements exactly this. Add a small gap between the end of training and the start of testing when your windows overlap the boundary, so no test window shares observations with a training window.
The same discipline applies to scaling: fit the scaler on the training period only. Fitting it on the whole series leaks the future's mean and standard deviation into the past.
Interactive Exploration Guide
Watch the window slide. Set Window Size (W) to 3 and Stride (S) to 1, then scroll along the series. Each position produces one training example, and consecutive examples share almost all their values.
Widen the window. Raise Window Size (W) to 10. Each example carries more history, and there are fewer of them — the trade is visible directly in the count.
Increase the stride. Set Stride (S) to 5. The windows stop overlapping heavily and the example count drops sharply, but each one is far more independent of the last.
Change the data. Press Random Series and try a window shorter than the visible cycle, then longer. A window that cannot span one period of the pattern cannot represent it.
Splitting without leaking
This is the part that matters most and is most often got wrong. Random train-test splitting is invalid for time series: it puts future windows in the training set and past windows in the test set, so the model is trained on the future to predict the past.
Scores from that setup look excellent and mean nothing. Split chronologically — train on the earliest portion, validate on the middle, test on the most recent — so the evaluation mirrors how the model will actually be used.
Overlapping windows add a second leak at the boundary: the last training window and the first test window share observations. Leave a gap of at least W steps between the splits.
The same applies to scaling. Fit the scaler on the training period only; computing a mean over the whole series leaks future information into every training example.
Traps worth knowing
Random shuffling before the split. Guarantees leakage and a score that will not survive deployment.
No gap between train and test. Overlapping windows straddle the boundary and share data.
Scaling on the full series. The training data learns statistics that include the test period.
Ignoring non-stationarity. If the mean or variance drifts over time, a model fitted on early data may not transfer. Difference the series or model the trend explicitly.
Predicting one step and reporting it as multi-step. Errors compound when a model’s own predictions are fed back as inputs; evaluate the horizon you actually need.
Key takeaway
A sliding window converts a time series into supervised (input, target) pairs, with the window size encoding how much history you claim is relevant and the stride controlling how much consecutive examples overlap. The modelling choice is straightforward; the discipline is in the split — chronological, with a gap of at least one window between segments, and every scaler fitted on the training period alone.
Features worth adding to the window
The raw lagged values are the starting point, not the finished feature set. Three additions usually earn their place:
Rolling statistics. The mean, standard deviation, minimum and maximum over the window summarise its level and volatility in a few stable numbers. A rolling mean over 7 days is often a stronger feature than seven individual lags.
Calendar features. Day of week, month, hour, and a holiday flag. Most human-generated series have strong calendar structure that lags alone represent inefficiently. Encode cyclical values as sine and cosine pairs so that hour 23 and hour 0 are adjacent rather than 23 units apart.
Differences and ratios. The change from the previous value, or the ratio to the value a week ago, makes trends explicit. For series with a trend this often matters more than anything else, because tree models cannot extrapolate: a forest trained on values between 100 and 200 will never predict 250, but it can predict "a 5% increase" from a differenced target.
import pandas as pd
df = pd.DataFrame({"sales": series})
for lag in range(1, 8):
df[f"lag_{lag}"] = df["sales"].shift(lag)
df["roll_mean_7"] = df["sales"].shift(1).rolling(7).mean()
df["roll_std_7"] = df["sales"].shift(1).rolling(7).std()
df["dow"] = df.index.dayofweek
df["target"] = df["sales"].shift(-1) # next day
df = df.dropna()
The .shift(1) before .rolling() is the important detail. Without it, the rolling mean at time t includes the value at time t — the very thing you are trying to predict. That is a leak, it is easy to write by accident, and it produces suspiciously excellent results.
Where this approach fits
Windowing plus a gradient-boosted model is a strong, unglamorous baseline for most forecasting problems, and it frequently beats deep sequence models on business data with limited history.
It is the right tool for demand and sales forecasting, sensor and IoT prediction, predictive maintenance (windows of vibration or temperature readings), anomaly detection over rolling statistics, and energy load forecasting.
It is the wrong tool when the sequence is very long and the dependencies are distant — a fixed window simply cannot see beyond its own length, which is where recurrent networks and transformers earn their keep. It also handles irregular sampling poorly: windows assume evenly spaced observations, so resample first if your timestamps are ragged.
Turning a series into rows, without cheating
A series is cut into overlapping windows so a supervised model can eat it. Those windows overlap, and that overlap is how a shuffled split quietly hands the model its own answers.
example_01.pyscikit-learn
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import TimeSeriesSplit, KFold, cross_val_score
from sklearn.metrics import mean_absolute_error
rng = np.random.default_rng(0)
n = 900
# a random walk with a cycle on top -- non-stationary, like most real series
series = 50 + np.cumsum(rng.normal(0, 0.8, n)) + 6 * np.sin(np.arange(n) / 11.0)
print("a series of %d points: a random walk with a cycle on top." % n)
print("first eight values:", np.round(series[:8], 2))
print()
def windows(s, lookback, horizon=1):
X = np.array([s[i:i + lookback] for i in range(len(s) - lookback - horizon + 1)])
y = np.array([s[i + lookback + horizon - 1] for i in range(len(X))])
return X, y
X, y = windows(series, lookback=10)
print("with lookback=10, horizon=1:")
print(" row 0 features:", np.round(X[0], 2))
print(" row 0 target :", round(y[0], 2), " (that is series[10])")
print(" row 1 features:", np.round(X[1], 2))
print(" %d points became %d rows of %d columns." % (n, len(X), X.shape[1]))
print(" consecutive rows share 9 of their 10 values. they are near-duplicates,")
print(" and that is where every mistake below comes from.")
print()
split = int(0.8 * len(X))
print("%-34s %12s %12s" % ("", "ridge MAE", "forest MAE"))
def evaluate(label, Xa, ya):
out = []
for mk in (lambda: Ridge(),
lambda: RandomForestRegressor(n_estimators=80, random_state=0)):
m = mk().fit(Xa[:split], ya[:split])
out.append(mean_absolute_error(ya[split:], m.predict(Xa[split:])))
print("%-34s %12.4f %12.4f" % (label, out[0], out[1]))
return out
honest = evaluate("chronological split (correct)", X, y)
idx = rng.permutation(len(X))
cheat = evaluate("shuffled split (cheating)", X[idx], y[idx])
print()
print("the forest went from %.4f to %.4f -- it looks %.1fx better purely from"
% (honest[1], cheat[1], honest[1] / cheat[1]))
print("the shuffle. a shuffled test row shares 9 of its 10 values with training")
print("rows either side of it in time, and a forest is flexible enough to look")
print("that neighbour up rather than learn anything.")
print()
print("note that ridge barely moved (%.4f to %.4f). the leak is real either"
% (honest[0], cheat[0]))
print("way; a linear model just is not able to exploit it. so 'my score did")
print("not change when I shuffled' is not evidence that the split was safe.")
print()
print("the same trap in cross-validation:")
for label, cv in (("KFold(5, shuffled)", KFold(5, shuffle=True, random_state=0)),
("TimeSeriesSplit(5)", TimeSeriesSplit(5))):
s = -cross_val_score(RandomForestRegressor(n_estimators=80, random_state=0),
X, y, cv=cv, scoring="neg_mean_absolute_error").mean()
print(" %-22s forest MAE %.4f" % (label, s))
print(" TimeSeriesSplit only trains on rows that come BEFORE the fold it")
print(" tests on, and the training window grows:")
for i, (a, b) in enumerate(TimeSeriesSplit(5).split(X)):
print(" fold %d: train rows 0-%d, test rows %d-%d" % (i, a[-1], b[0], b[-1]))
print()
print("the baseline you have to beat -- just predict the last value you saw:")
print(" naive MAE %.4f" % mean_absolute_error(y[split:], X[split:, -1]))
print(" ridge, honest MAE %.4f" % honest[0])
print(" forest, honest MAE %.4f" % honest[1])
print(" on a random walk the naive forecast is genuinely hard to beat, which")
print(" is exactly why quoting it is not optional.")
print()
print("lookback is a real hyperparameter. tune it on time-ordered folds:")
print("%10s %14s" % ("lookback", "ridge MAE"))
for lb in (1, 3, 10, 30, 60):
Xl, yl = windows(series, lookback=lb)
print("%10d %14.4f"
% (lb, -cross_val_score(Ridge(), Xl, yl, cv=TimeSeriesSplit(4),
scoring="neg_mean_absolute_error").mean()))
print()
print("and the horizon changes the problem completely:")
for h in (1, 5, 20):
Xh, yh = windows(series, lookback=20, horizon=h)
sp = int(0.8 * len(Xh))
m = Ridge().fit(Xh[:sp], yh[:sp])
print(" %2d step(s) ahead: ridge MAE %.4f naive MAE %.4f"
% (h, mean_absolute_error(yh[sp:], m.predict(Xh[sp:])),
mean_absolute_error(yh[sp:], Xh[sp:, -1])))
print(" a model trained for horizon 1 is not a model for horizon 20.")
print()
print("one more leak with no code to show for it: scaling. fitting a scaler on")
print("the whole series puts the future's mean and standard deviation into")
print("your training rows. fit it on the training rows only, inside a pipeline.")
Output
Questions people ask
How do I choose the window size? Start from the known seasonality, check an autocorrelation plot, then tune it as a hyperparameter with time-series cross-validation.
Can I use a random forest for forecasting? Yes, on windowed features, with one caveat: tree models cannot predict outside the range of their training targets. For a trending series, model the difference rather than the level.
What about missing timestamps? Resample to a regular frequency first and decide explicitly how to fill gaps — forward fill, interpolate, or flag them as missing. Silently skipping them corrupts every window that spans the gap.
Should the target be scaled? Usually not necessary for tree models. For neural networks, yes — and remember to invert the scaling before reporting errors, or your RMSE will be in the wrong units.
How many rows do I need? Enough to cover several full seasonal cycles. Two years of daily data to learn annual seasonality; a few weeks is not enough to learn anything about December.
Do I need a separate model per series? Not necessarily. Training one model across many related series, with an identifier feature, often outperforms hundreds of individual models — the shared model borrows strength from series with more history.
Recap in one screen
A sliding window turns a sequence into a normal feature/target table.
Window size is how far back the model sees; horizon is how far ahead it predicts; stride is how far the window moves.
Never shuffle: split chronologically, use TimeSeriesSplit, and leave a gap when windows overlap.
Add rolling statistics, calendar features and differences — and shift before rolling, or you leak the target.
Fixed windows cannot see beyond their own length; that is where sequence models take over.
Recall check
0 of 3
Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.
Without scrolling back — what is the one-line takeaway from this module?
A sliding window converts a time series into supervised (input, target) pairs, with the window size encoding how much history you claim is relevant and the stride controlling how much consecutive examples overlap. The modelling choice is straightforward; the discipline is in the split — chronological, with a gap of at least one window between segments, and every scaler fitted on the training period alone.
What does this module say about “Turning a sequence into a supervised problem”?
A model needs examples with features and a label. A time series is a single ordered run of values, so you build examples by sliding a fixed-length window along it: the values inside the window are the input, and the value immediately after it is the target.
What does this module say about “Window size and stride”?
Window size (W) is how much history the model sees per prediction, and it is a real modelling assumption: it asserts that nothing older than W steps matters. Too small and the model cannot see the pattern — a weekly cycle needs at least seven daily steps. Too large and each example carries mostly irrelevant history, the input dimension grows, and the number of examples shrinks.
Cheat sheet
Sliding Window for Time Series
A model needs examples with features and a label. A time series is a single ordered run of values, so you build examples by sliding a fixed-length window along it: the values inside the window are the input, and the value immediately after it is the target.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.