Downsampling
Downsampling means making a dataset smaller, and the word covers two operations that have almost nothing in common beyond that.
- In classification, it means dropping rows from the majority class to reduce imbalance. Which rows go is a modeling choice — at random, by distance, or by cluster structure.
- In time series and signals, it means lowering the sampling rate — one reading per ten seconds where there used to be ten per second. Nothing is imbalanced and no class is involved.
The classification sense is covered in the articles linked above. The signal sense has a failure mode that the classification sense has no analog for, and it is the one worth spending this article on.
Dropping samples can create frequencies
import numpy as np
rate, seconds = 1000, 2.0
t = np.arange(0, seconds, 1 / rate)
signal = np.sin(2 * np.pi * 300 * t)
"""One pure tone at 300 Hz, sampled at 1000 Hz. Keeping every 4th sample gives an
effective rate of 250 Hz, whose Nyquist limit is 125 Hz - below the tone."""
def dominant_hz(x, fs):
spectrum = np.abs(np.fft.rfft(x))
return np.fft.rfftfreq(len(x), 1 / fs)[spectrum.argmax()]
print(f"original rate {rate:4d} Hz dominant frequency {dominant_hz(signal, rate):6.1f} Hz")
print(f"every 4th sample rate {rate // 4:4d} Hz dominant frequency {dominant_hz(signal[::4], rate / 4):6.1f} Hz")
binned = signal[:len(signal) // 4 * 4].reshape(-1, 4).mean(axis=1)
print(f"mean of each 4 rate {rate // 4:4d} Hz dominant frequency {dominant_hz(binned, rate / 4):6.1f} Hz"
f" amplitude kept {np.abs(binned).max() / np.abs(signal).max():.4f}")
# original rate 1000 Hz dominant frequency 300.0 Hz
# every 4th sample rate 250 Hz dominant frequency 50.0 Hz
# mean of each 4 rate 250 Hz dominant frequency 50.0 Hz amplitude kept 0.1910
The signal contains one tone, at 300 Hz. After keeping every fourth sample, the dominant frequency is 50 Hz. Not attenuated, not blurred — relocated. A 50 Hz component now exists that was never in the data, and nothing in the downsampled series marks it as an artifact.
The arithmetic is exact. Keeping every fourth sample of a 1000 Hz series gives 250 Hz, whose Nyquist limit — the highest frequency representable — is 125 Hz. A 300 Hz tone cannot be represented, so it folds: \(|300 - 250| = 50\). Any component above the new Nyquist limit reappears somewhere below it, and once the samples are gone there is no way to tell which is which.
This is why downsampling a signal is not a step you can perform by slicing. Filter first: remove everything above the new Nyquist limit, then drop samples. The filtered series loses the 300 Hz tone, which is honest — it says the tone is not representable at this rate — where the unfiltered one reports a tone that was never there.
Averaging is not the fix
The third line is the one that surprises people. Taking the mean of each group of four — the aggregation that daily-from-hourly and hourly-from-minute pipelines run by default — still reports 50 Hz. What changed is the amplitude: 0.1910 of the original, so the artifact is suppressed but present.
A box average is a low-pass filter, just a poor one. It attenuates rather than removes, and how much it attenuates depends on where the offending frequency sits relative to the window. For a daily mean of hourly sales that is usually fine. For a vibration sensor, an ECG or anything where a periodic component is the signal, “we averaged it to hourly” is not a neutral summary of the data.
Choosing between the three ways
- Take every k-th sample when you already know the signal has no content above the new limit, and only then.
- Aggregate within bins — mean, sum, max — when the quantity is a total or a rate and the bin is what the question asks about. Sum for counts, mean for levels, and say which; a mean of counts and a sum of counts differ by a factor you will have to reconstruct later.
- Filter, then decimate when the series is a measurement of a continuous process and frequencies matter. This is what
scipy.signal.decimatedoes, and it is the default worth reaching for when in doubt.
The other direction, and the other word
Upsampling is the mirror image and carries its own hazard: where downsampling can invent a frequency, upsampling invents values, and interpolation does it using observations from the future.
Subsampling is the third word in this neighborhood and its meaning also splits by field: in machine learning it is a random subset taken for speed, in signal processing it is the every-k-th operation above. When either word appears in someone else’s pipeline, the safe assumption is that you do not yet know which one they meant.
And in the classification sense, dropping majority rows carries a cost that has nothing to do with frequencies: changing the training distribution can alter the probability scale, so check calibration on representative data. Class weighting changes the fitting objective without discarding rows and can also alter calibration. It may achieve similar recall, but that does not make its rankings, selected cases or precision identical to those of a resampled model.
References
- NumPy Reference, Discrete Fourier Transform (numpy.fft)
- SciPy API reference, scipy.signal.decimate
- imbalanced-learn User Guide, Under-sampling
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
