"A sensor stream has no rows. You invent them, and then you spend the rest of the project living with the ones you chose."
A Pragmatic AI Agent
The Big Picture
Almost every model you will build in this book expects fixed-shape inputs: a tensor of \(L\) timesteps by \(C\) channels. But real sensors deliver an unending stream of samples with no natural row boundaries. Windowing is the act of cutting that stream into training examples, and labeling is the act of deciding what each cut means. These two choices are quietly the most consequential decisions in a sensor project. They set your effective sample size, your class balance, your latency budget, and (as the next section will show) whether your test accuracy is real or an illusion. Get windowing wrong and no amount of model tuning recovers it.
This section assumes you are comfortable with sampling rate, the sampling interval \(\Delta t = 1/f_s\), and timestamp alignment across channels, all covered in Chapter 3. Here we take a synchronized, uniformly sampled multichannel stream as the input and turn it into a labeled dataset that a model can consume. We stay deliberately out of the way of Section 5.3 (leakage) and Section 5.4 (splits); windowing decisions cause most leakage, so we flag the trap here and treat the cure there.
Why we cut streams into windows
What. A window is a contiguous slice of the stream, \(x_{t:t+L}\), of fixed length \(L\) samples, treated as one example. Why. Three forces demand it. Models with fixed input tensors (CNNs, most transformers, classical feature extractors) need a fixed \(L\). Labels usually apply to spans of time, not to individual samples: "walking" is a property of a two-second stretch, not of one accelerometer reading. And decisions are made on a cadence: a fall detector must emit a verdict every fraction of a second, so it must consume the stream in bites of roughly that size.
How. You choose three numbers. The window length \(L\) (how much context each example holds), the stride or hop \(S\) (how far you advance between consecutive windows), and the windowing policy (fixed-rate sliding, non-overlapping tumbling, or event-triggered). The overlap fraction follows as \(1 - S/L\). When \(S = L\) the windows tile the stream with no overlap (tumbling); when \(S < L\) they overlap; when \(S > L\) you drop samples between windows.
Key Insight
Overlap is a data-augmentation knob and a leakage hazard wearing the same coat. Setting \(S \ll L\) multiplies your window count and smooths class boundaries, which flatters training. But two overlapping windows share raw samples, so if one lands in train and its neighbor in test, the model has effectively seen the answer. The rule that saves you: choose overlap for the training signal you want, then split along time or entity boundaries so overlapping windows never straddle the split. The augmentation is legitimate; the leakage is not, and the two are separable.
Choosing window length and stride
What drives \(L\). The window must be long enough to contain the phenomenon and short enough to stay homogeneous. Match it to the physical timescale of the signal: at least one full cycle of the slowest relevant component, and ideally a few. A human gait cycle is roughly 1 second, so 2 to 3 second windows are standard for activity recognition. A cardiac cycle is under a second; ECG beat classifiers often window a single beat plus margin. A bearing-fault signature repeats at the shaft rotation frequency, so the window must span several revolutions to resolve it (a theme picked up in Chapter 36).
When to go short vs long. Short windows react faster (lower detection latency) and stay purer (one label per window), but starve the model of context and inflate variance. Long windows are information-rich and stable but slow to react and prone to mixing two activities in one example. There is a genuine tradeoff between latency and context, and it is a design decision, not a default. What drives \(S\). Stride sets your deployment cadence and your dataset size. In production the model re-runs every \(S\) samples, so \(S\) is a latency and compute budget. In training you may use a smaller stride to harvest more (correlated) windows.
Practical Example: a wrist wearable that keeps missing the first few seconds
A team building a smartwatch activity classifier picked 10-second windows because longer context lifted validation F1. In the field, users complained the watch was slow to notice they had started running. The cause was structural: with a 10-second tumbling window, the model could not commit to "running" until a full 10 seconds of running had accumulated and closed a window, so the median detection delay approached 10 seconds. The fix was not a better model but a windowing change: keep the 10-second context but slide it with a 1-second stride, so a fresh verdict lands every second on a mostly-running window. Same \(L\), smaller \(S\), latency dropped roughly tenfold. The lesson: \(L\) buys accuracy, \(S\) buys responsiveness, and you tune them separately.
Assigning labels to windows
Once a window is cut, what is its label? This is trivial only when the entire stream carries one class. In practice a window can span a label boundary, and you need an explicit policy. The common ones:
- Majority / mode. Assign the label held by the plurality of samples in the window. Simple, robust, but blurs transitions and can invent a label the user never actually held for the full window.
- Center (or last) sample. Take the label at the window's midpoint or trailing edge. The trailing edge matches causal deployment, where you only know the past; the center is better for offline analysis.
- Purity threshold. Keep the window only if one label covers at least a fraction \(\tau\) (say 80%) of it; otherwise discard it as a transition window. This trades sample count for label cleanliness.
- Dense / per-timestep. Do not collapse at all. Keep a label per sample and train a sequence-labeling model (segmentation). Richest, but demands per-sample annotations, which are expensive (see Section 5.6).
Formally, majority labeling of the window starting at \(t\) is
$$ y_t = \arg\max_{c}\ \sum_{i=t}^{t+L-1} \mathbb{1}[\,\ell_i = c\,], $$where \(\ell_i\) is the per-sample annotation. Transition windows, where no class dominates, are exactly the ones this rule labels least reliably, which is why a purity threshold is often layered on top.
Watch Out: label delay and edge effects
Two subtle biases live at window edges. First, if annotations were recorded with a lag (a rater pressed a button a beat after the event began), your label boundaries are shifted, and short windows near transitions inherit the wrong class. Second, causal (trailing-edge) labeling means the window is labeled by its end, so an event only becomes visible once it has filled enough of the window to survive your policy. Budget for this delay explicitly rather than discovering it in the field, as the wearable team above did.
From stream to array: the mechanics
Concretely, windowing a synchronized array is a strided view plus a label reduction. The code below turns a \((T, C)\) stream and a length-\(T\) label vector into \((N, L, C)\) windows with majority labels, using NumPy's zero-copy sliding-window view so you are not materializing \(N\) copies of overlapping data.
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
from scipy import stats
def windowize(x, labels, L, S):
"""x: (T, C) stream; labels: (T,) per-sample labels.
Returns windows (N, L, C) and majority label per window (N,)."""
# sliding_window_view gives (T-L+1, C, L) views with stride 1...
views = sliding_window_view(x, window_shape=L, axis=0) # (T-L+1, C, L)
views = views.transpose(0, 2, 1) # (T-L+1, L, C)
starts = np.arange(0, len(views), S) # apply the hop
windows = views[starts] # (N, L, C)
lbl_views = sliding_window_view(labels, L)[starts] # (N, L)
y = stats.mode(lbl_views, axis=1, keepdims=False).mode # majority vote
return windows, y
# 3 s windows, 1 s hop on a 50 Hz stream: L=150, S=50
X = np.random.randn(10_000, 6) # 200 s of 6-axis IMU at 50 Hz
lab = np.repeat([0, 1, 2, 1], 2500) # 4 activity blocks
W, y = windowize(X, lab, L=150, S=50)
print(W.shape, y.shape) # (197, 150, 6) (197,)
sliding_window_view avoids copying overlapping samples until you index starts, so memory stays flat even at heavy overlap; the stats.mode reduction implements the majority policy from the previous section.The snippet above is deliberately explicit so the mechanics are visible: strided view, hop selection, label reduction. In a real pipeline you rarely write it by hand.
Right Tool: let a windowing library carry the bookkeeping
Purity thresholds, per-window timestamps, dropping partial trailing windows, and grouping by recording so windows never cross session boundaries add up to roughly 40 to 60 lines of fiddly, bug-prone code. Libraries such as tsfresh's roll utilities, sktime's sliding-window transformers, or a few lines of pandas DataFrame.rolling plus groupby collapse that to about 3 to 5 lines and, critically, keep windows inside their group so you do not accidentally splice two devices together. You still choose \(L\), \(S\), and the label policy; the library just stops the off-by-one and cross-boundary bugs.
Exercise
Take a labeled 50 Hz IMU recording with four activity blocks. (a) Window it at \(L = 150\) with strides \(S \in \{150, 75, 15\}\) and report how many windows each produces and what fraction are "transition" windows (no label covering 80%). (b) Add a purity filter at \(\tau = 0.8\) and recount. (c) Explain in two sentences why the \(S = 15\) dataset will make a random train/test split look far more accurate than it truly is, and how you would split instead. Verify your reasoning against Section 5.3.
Self-Check
- You need a fall detector to respond within 0.5 s but a fall signature spans 2 s. Which knob do you change, \(L\) or \(S\), and why can you not simply shrink \(L\) to 0.5 s?
- Your dataset has 90% overlap between adjacent windows. Name one training benefit and one evaluation danger of that choice.
- Under trailing-edge (causal) labeling, why does a freshly started activity take a while to appear in the labels even with a small stride?
Windowing and labeling turn a formless stream into rows you can model, evaluate, and split. Because those rows are the atoms of everything downstream, from feature extraction to human activity recognition to leakage-safe benchmarking in Chapter 65, the choices here propagate through the whole project. Decide them on purpose, write them down, and version them with the pipeline.
What's Next
In Section 5.3, we confront the field's most common and most expensive error: leakage. We will see exactly how overlapping windows, shared subjects, and normalization computed on the full dataset let information from the test set bleed into training, why the resulting accuracy numbers are fiction, and the concrete disciplines that keep your evaluation honest.