Skip to content

feat(stats): add Savitzky-Golay smoothing filter - #1986

Open
Shizoqua wants to merge 3 commits into
online-ml:mainfrom
Shizoqua:feat/1424-savitzky-golay
Open

feat(stats): add Savitzky-Golay smoothing filter#1986
Shizoqua wants to merge 3 commits into
online-ml:mainfrom
Shizoqua:feat/1424-savitzky-golay

Conversation

@Shizoqua

Copy link
Copy Markdown

Adds stats.SavitzkyGolay, a rolling Savitzky-Golay smoothing filter as requested in #1424.

Savitzky-Golay fits a polynomial of a given degree to a sliding window of observations by least squares and returns the smoothed value of the most recent point via the precomputed scipy.signal.savgol_coeffs dot product. Because the polynomial is evaluated at the last point of the window, the filter is causal and compatible with online learning.

>>> from river import stats
>>> stat = stats.SavitzkyGolay(window_size=5, polyorder=2)
  • Coefficients computed once in __init__, applied over a fixed-size deque
  • get() returns None until the window has enough observations
  • window_size/polyorder follow scipy's constraint (polyorder < window_size, enforced by scipy)
  • Registered in stats/__init__.py and the docs nav
  • Release-note entry and CodSpeed benchmark included

Verification:

  • pytest tests/stats — 175 passed (incl. doctests)
  • Matches a manual least-squares polynomial fit to the same window; reproduces a degree-≤2 polynomial exactly
  • ruff check / ruff format --check clean

DCO sign-off included.

@codspeed-hq

codspeed-hq Bot commented Aug 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 98 untouched benchmarks
🆕 1 new benchmark
⏩ 16 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 test_savitzky_golay_update N/A 5.1 ms N/A

Comparing Shizoqua:feat/1424-savitzky-golay (0bb9877) with main (b50439f)

Open in CodSpeed

Footnotes

  1. 16 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Shizoqua
Shizoqua force-pushed the feat/1424-savitzky-golay branch from 60dbb8a to b646dd1 Compare August 18, 2026 13:12

@MaxHalford MaxHalford left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, but I would like to see a use case so users can understand when to use this

Comment on lines +71 to +76
self._coeffs = savgol_coeffs(
window_length=window_size,
polyorder=polyorder,
pos=window_size - 1,
use="dot",
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a list or a numpy array? I think it should be the former

@Shizoqua

Copy link
Copy Markdown
Author

Thanks @MaxHalford! I added a use case to the docstring: a noisy temperature probe drifting upward, where smoothing reveals the underlying trend so it can be fed to a model or a threshold. New commit 7121884 includes the example and the doctest output.

@Shizoqua
Shizoqua force-pushed the feat/1424-savitzky-golay branch from 7121884 to 23f2f93 Compare September 1, 2026 15:03
@e10e3

e10e3 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

A commenter in the issue you reference mentioned the article Why and how Savitzky–Golay filters should be replaced, which argues that one should replace the SG filter with other, more stable and less noisy, filters like sinc or Whittaker–Henderson. Is it not better to implement these alternative filters rather than Savitzky–Golay?

Also, did you read the Contributing guide? Your PR cover letter makes me think you did not, but I could be wrong.

Adds stats.SavitzkyGolay, a rolling Savitzky-Golay filter that fits a
polynomial of a given degree to a sliding window of observations by
least squares and returns the smoothed value of the most recent point.

Because the fit is evaluated at the last point of the window, the filter
is causal and can be used online, e.g. to smooth sensor or market
signals before feature extraction. The coefficients are computed once
via scipy.signal.savgol_coeffs and applied with a fixed-size deque;
get() returns None until the window is full.

Signed-off-by: Lanre Shittu <136805224+Shizoqua@users.noreply.github.com>
Signed-off-by: Shizoqua <136805224+Shizoqua@users.noreply.github.com>
Signed-off-by: Shizoqua <136805224+Shizoqua@users.noreply.github.com>
@Shizoqua
Shizoqua force-pushed the feat/1424-savitzky-golay branch from 23f2f93 to 0bb9877 Compare September 7, 2026 14:53
@Shizoqua

Shizoqua commented Sep 7, 2026

Copy link
Copy Markdown
Author

Hi @MaxHalford, I've rebased onto the latest upstream/main and the branch is now clean with all checks green. The use-case example we discussed (noisy temperature probe trend) is still in the docstring. Could you re-review when you have time?

@MaxHalford

Copy link
Copy Markdown
Member

@Shizoqua please answer @e10e3's comment :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants