FPCS is a high-performance time series downsampling algorithm designed to preserve critical visual features (peaks and valleys) while significantly reducing data size. It is particularly well-suited for real-time visualization and streaming data applications.
Implementation based on the Feature-Preserving Compensated Sampling for Time Series Visualization publication.
Unlike simple decimation or even some advanced algorithms like LTTB, FPCS retains the min/max within each sampling window, helping preserve prominent peaks and valleys while allowing smaller local extrema to be dropped.
FPCS maintains the full range of the signal even at high downsampling ratios, whereas LTTB may miss sharp spikes in noisy datasets.
- High Performance: Cython-optimized core is competitive with optimized LTTB (see benchmarks below).
- Streaming data: Supports incremental processing via
FPCSDownsampler. - Feature preserving: Guarantees retention of min/max points within each window.
- Robust: Native handling of
NaNandInfvalues. - Batched processing: Efficiently downsample multiple series at once.
# Install from PyPI
pip install fpcsRequirements
- Python >= 3.11
- NumPy
Optional (for the Cython extension)
- C compiler and Python headers
- Cython (the package falls back to the pure Python backend if Cython isn't available)
# Install from source (local checkout)
pip install .
# For development (includes Cython, pytest, benchmarks)
pip install -e ".[dev]"Processing existing datasets:
import numpy as np
from fpcs import downsample
# Generate 1M points
x = np.arange(1_000_000, dtype=np.float64)
y = np.sin(x * 0.01) + np.random.randn(1_000_000) * 0.5
# Downsample by a factor of 100
x_ds, y_ds = downsample(x, y, ratio=100)
print(f"Original: {len(x)} points | Downsampled: {len(x_ds)} points")Processing data in real-time:
from fpcs import FPCSDownsampler
downsampler = FPCSDownsampler(ratio=10)
# Simulate a stream
for xi, yi in zip(stream_x, stream_y):
# .add() yields retained points immediately
for rx, ry in downsampler.add(xi, yi):
update_plot(rx, ry)
# Flush any remaining points at the end of the stream
for rx, ry in downsampler.flush():
update_plot(rx, ry)The main entry point for batch processing.
- Parameters:
x(Array-like): The x-coordinates (e.g., timestamps).y(Array-like): The y-coordinates.ratio(int): The sampling window size (R). Must be >= 1.
- Returns:
(np.ndarray, np.ndarray): A tuple of(x_downsampled, y_downsampled).
- Notes:
- The first and last input points are always retained in batch mode.
NaNvalues are always retained to preserve gaps.±Infvalues are treated as extrema.- Integer inputs are supported; the Cython backend converts them to float64.
Class for streaming/incremental downsampling.
Initialize the downsampler.
ratio: Sampling window size. Must be >= 1.
Add a single point to the downsampler.
- Yields: Tuples of
(x, y)for any points retained during this step. - Notes:
- Streaming mode does not auto-include the first/last points; if you need them, add them yourself.
NaNvalues are always retained and±Infvalues are treated as extrema.
Flush any remaining points from the internal buffer (e.g., at the end of a stream).
- Yields: Tuples of
(x, y)for remaining points.
Resets the downsampler state to initial conditions.
These functions are available when the Cython extension is successfully
compiled. You can check the active backend with get_backend().
Downsample into pre-allocated output buffers to reduce memory allocation overhead.
- Parameters:
x,y: Input arrays (integer inputs are supported and converted to float64).out_x,out_y: Output buffers (contiguous float64, must be large enough).
- Returns: Number of points written.
- Notes: Recommended output buffer size is `((len(x) + ratio - 1) // ratio)
- 2 + 2`.
Efficiently process a list of time series. Reduces Python overhead compared to a loop.
downsampleandFPCSDownsamplerare always available.downsample_intoanddownsample_batchare only available with the Cython backend (they areNonewhen using pure Python).- Use
get_backend()to check which backend is active ("cython"or"python").
Comparison between FPCS (Python/Cython) and a C implementation of the LTTB algorithm.
Algorithm comparison
| Name (time in ms) | Min | Max | Mean | StdDev | OPS |
|---|---|---|---|---|---|
| Cython FPCS | 1.2370 | 1.3534 | 1.2546 | 0.0162 | 797.0606 |
| LTTB (C impl) | 2.5252 | 2.6372 | 2.5394 | 0.0166 | 393.7865 |
| Pure Python | 393.6482 | 398.6493 | 395.6410 | 2.3321 | 2.5275 |
Ratio scaling (Cython)
| Ratio | Mean Time (ms) | StdDev |
|---|---|---|
| 10 | 4.05 | 0.81 |
| 50 | 3.86 | 0.89 |
| 100 | 4.02 | 0.90 |
| 500 | 4.32 | 0.74 |
This project is licensed under the MIT Licence - see the LICENSE file for details.
