Part I: Foundations of Sensory AI
Chapter 3: Signals, Sampling, Time, and Synchronization

Missing samples, jitter, and packet loss

"You promised me 100 samples per second. You delivered 94, at moments of your own choosing, and called it a time series."

A Disappointed AI Agent

The Big Picture

Every model in this book quietly assumes its input arrived on a clean, uniform grid: sample \(n\) at time \(t_0 + n/f_s\), no gaps, no surprises. Real sensor streams break that assumption constantly. A Bluetooth wearable drops radio packets in a crowded room. A CAN bus buffer overflows during a burst. An operating system schedules your acquisition thread late, so the timestamp attached to a sample no longer matches when the physical measurement was actually taken. These are not exotic edge cases; they are the default condition of field data. This section gives you a vocabulary for the three dominant defects (missing samples, timing jitter, and packet loss), the tools to measure them from timestamps alone, and the discipline to mark them explicitly rather than let interpolation launder corruption into confident-looking numbers.

This section assumes you are comfortable with the ideal sampling model and the Nyquist limit from Section 3.1, and with the timestamping and clock concepts from Section 3.3, since a corrupted timestamp is itself a form of missing information. The statistical machinery for reasoning about why data is missing draws on the probability primer in Chapter 4.

A taxonomy of temporal defects

The three failure modes are distinct in cause even when they look similar in a plot. Missing samples means the underlying acquisition produced fewer readings than the nominal rate implies: an ADC conversion was skipped, a sensor entered a fault state, or a firmware watchdog reset the chip mid-stream. The timeline has a hole. Jitter means the samples exist but their spacing is irregular: the intended interval is \(T_s = 1/f_s\), yet the actual interval \(\Delta t_n = t_n - t_{n-1}\) fluctuates. We summarize jitter by the standard deviation of those intervals, \(\sigma_{\Delta t}\), and by the peak-to-peak spread. Packet loss is a transport-layer phenomenon: the sensor measured correctly, but the bytes never reached your process because a radio frame was corrupted, a UDP datagram was dropped, or a ring buffer overwrote unread data. From the consumer's side packet loss looks like missing samples, but it arrives in characteristic bursts and it is often recoverable at the protocol layer, which pure sensor faults are not.

Key Insight

The dangerous move is treating an irregular stream as if it were regular. If you feed jittered samples into an FFT or a fixed-stride convolution assuming uniform \(T_s\), you are lying to the model about time. A sample tagged \(t_n\) but processed as if it landed at \(nT_s\) injects a phase error of \(2\pi f (t_n - nT_s)\) at frequency \(f\). Small timing errors become large spectral errors at high frequencies. Always carry the true timestamps, not just an index.

Why the pattern of missingness matters

Borrowing the standard statistical taxonomy, missing data is missing completely at random (MCAR) when the dropout is independent of everything, missing at random (MAR) when it depends on observed covariates, and missing not at random (MNAR) when it depends on the unobserved value itself. This distinction is not academic. A pulse oximeter that drops readings precisely when the patient moves (motion also being what makes the reading dangerous) is MNAR: the gaps are correlated with the events you care most about. Imputing those gaps with the local mean will systematically erase the very episodes your model is meant to catch, and no amount of downstream accuracy on clean segments will reveal the bias. Characterizing whether your gaps are MCAR, MAR, or MNAR is a prerequisite for choosing any repair strategy, and it directly shapes the leakage-safe splits you will build in Chapter 5.

Practical Example: A wrist wearable in a crowded gym

A fitness band streams a 50 Hz accelerometer over Bluetooth Low Energy to a phone. In a quiet room the phone receives a clean 20 ms cadence. Walk onto a gym floor packed with other 2.4 GHz radios and the picture changes: the BLE link layer retransmits, the connection interval stretches, and whole notification packets (each carrying a batch of samples) are lost. The phone now sees bursts of 40 to 60 missing samples every few seconds, plus jitter of several milliseconds on the packets that do arrive. A naive activity classifier trained on lab data collapses, not because the motion changed, but because its input grid quietly developed holes. The fix is not a better model first; it is measuring the loss, marking it, and making the model gap-aware, in that order.

Measuring defects from timestamps

You cannot repair what you have not measured. Given a vector of arrival timestamps, three numbers tell you almost everything: the distribution of inter-sample intervals \(\Delta t_n\), the count and duration of gaps (intervals exceeding a threshold such as \(1.5\,T_s\)), and the delivery ratio (received samples divided by expected samples over a window). The code below computes all three and, crucially, produces an explicit boolean missingness mask on a uniform reference grid rather than silently filling the holes.

import numpy as np

def profile_stream(timestamps, fs_nominal):
    """Diagnose jitter, gaps, and delivery ratio from arrival timestamps."""
    t = np.asarray(timestamps, dtype=float)
    dt = np.diff(t)
    Ts = 1.0 / fs_nominal

    jitter_std = dt.std()                      # timing irregularity, seconds
    gap_mask = dt > 1.5 * Ts                   # intervals that skipped samples
    n_missing = int(np.round((dt[gap_mask] / Ts - 1).sum()))
    expected = int(np.round((t[-1] - t[0]) / Ts)) + 1
    delivery_ratio = len(t) / expected

    # Build a uniform grid and a mask marking which grid slots were observed.
    grid = t[0] + np.arange(expected) * Ts
    idx = np.clip(np.round((t - t[0]) / Ts).astype(int), 0, expected - 1)
    observed = np.zeros(expected, dtype=bool)
    observed[idx] = True                       # True = real sample, False = hole

    return dict(jitter_std=jitter_std, n_missing=n_missing,
                delivery_ratio=delivery_ratio, grid=grid, observed=observed)

# Simulate a 50 Hz stream with jitter and a 1-second dropout burst.
rng = np.random.default_rng(0)
Ts = 1 / 50
clean = np.arange(0, 10, Ts)
jittered = clean + rng.normal(0, 0.002, clean.size)     # 2 ms RMS jitter
kept = jittered[(jittered < 4.0) | (jittered > 5.0)]     # drop 1 s of packets

p = profile_stream(kept, fs_nominal=50)
print(f"jitter RMS   : {p['jitter_std']*1e3:5.1f} ms")
print(f"missing count: {p['n_missing']}")
print(f"delivery     : {p['delivery_ratio']*100:5.1f} %")
Profiling a jittered, lossy sensor stream from timestamps alone. The function returns a boolean observed mask aligned to a uniform grid, so downstream code can distinguish a real zero from an absent sample instead of guessing.

Running it on the simulated stream reports roughly 2 ms of jitter, about 50 missing samples across the one-second dropout, and a delivery ratio near 90 percent. The mask is the deliverable that matters: it travels with the data and lets every later stage decide, explicitly, how to treat the holes.

Library Shortcut

The uniform-grid alignment and mask construction above (roughly 12 lines of index arithmetic) collapses to two lines with pandas. Index your series by a DatetimeIndex, then s.resample("20ms").asfreq() snaps to a regular grid and inserts NaN at every hole; s.resample("20ms").asfreq().isna() is your missingness mask. pandas also handles time-zone-aware timestamps, leap-second edge cases, and the off-by-one grid boundaries that the hand-rolled version glosses over. Reach for the hand-rolled path only on a microcontroller where pandas will not fit.

Repair strategies, and when to trust them

Once holes are marked, you have four broad options, in ascending order of assumption. Leave them: many models, from the Kalman filter to modern attention, can consume a mask and simply skip absent inputs; a Kalman update, covered in Chapter 9, naturally handles a missing measurement by running the prediction step alone and letting uncertainty grow. Interpolate: linear or spline fills are cheap and fine for short gaps in slowly varying signals, but they fabricate high-frequency content and will fool a spectral feature extractor. Filter-based reconstruction: model-aware smoothing, the subject of Chapter 6, respects the signal's bandwidth when bridging gaps. Learned imputation: powerful, but it can hallucinate plausible values and, worse, leak information if fit on data that overlaps your evaluation set. The golden rule: interpolate for visualization freely, but never let an imputed value enter a training label or a safety-critical decision without a flag that says it was invented.

Exercise

Take a 100 Hz IMU recording (or synthesize one). Delete samples under three regimes: MCAR (drop 10 percent uniformly at random), bursty (drop five contiguous 200 ms windows), and MNAR (drop samples whenever \(|a| > 2g\)). For each, compute the RMS error of linear interpolation against the ground truth and the resulting shift in the signal's spectral centroid. Which regime does interpolation handle worst, and why does the MNAR case damage a fall-detection feature far more than its raw RMS error suggests?

Self-Check

  1. A stream has a delivery ratio of 98 percent but a jitter RMS of 8 ms at a nominal 100 Hz. Is this stream safe to feed directly into an FFT? Justify your answer in terms of phase error.
  2. Why is a burst of packet loss on a BLE link often recoverable in a way that a sensor brownout is not?
  3. You impute a gap with the segment mean and your model's accuracy improves. Give one reason this could indicate a problem rather than a success.

What's Next

In Section 3.5, we turn from the timing of samples to their values: how quantization and compression trade bits for fidelity, why an 8-bit ADC and a lossy codec each impose their own noise floor, and how to reason about the distortion budget before it silently caps what any downstream model can perceive.