Skip to content

feat: add EwmaChangeDetector, an EWMA control chart for streams - #7600

Merged
DenizAltunkapan merged 1 commit into
TheAlgorithms:masterfrom
alxkm:feat/ewma-change-detector
Sep 9, 2026
Merged

feat: add EwmaChangeDetector, an EWMA control chart for streams#7600
DenizAltunkapan merged 1 commit into
TheAlgorithms:masterfrom
alxkm:feat/ewma-change-detector

Conversation

@alxkm

@alxkm alxkm commented Sep 9, 2026

Copy link
Copy Markdown
Member

Adds an EWMA control chart, the exponentially weighted counterpart of the CUSUM chart that went in with #7593: it smooths the stream with an average seeded at the expected level and raises an alarm as soon as the smoothed value leaves a band around that level.

z     <- z + alpha * (x - target)
limit  = width * sigma * sqrt( alpha / (2 - alpha) * (1 - (1 - alpha)^(2n)) )

The band is the exact standard deviation of z under the null hypothesis, scaled by the desired width in sigmas, so it starts narrow and widens towards its asymptote width * sigma * sqrt(alpha / (2 - alpha)). Using that asymptote from the first sample onwards -- the usual shortcut -- leaves the chart far too tolerant while the average is still warming up, whereas the exact limit keeps the false alarm rate steady from the very first sample. On an alarm the average is returned to the target, so the detector starts fresh on the next change instead of latching and reporting the same shift forever.

Where a CUSUM chart accumulates evidence without limit and so excels at small persistent shifts, an EWMA chart looks at a decaying window of the recent past: alpha around 0.1 to 0.3 is a good compromise, larger values reacting faster to big jumps and smaller ones being more sensitive to slow drifts. Three sigmas is the customary width, and both are the defaults.

ExponentialMovingAverage carries the recurrence and is the reusable half of this: alpha can be given directly, as a span (2 / (span + 1), matching the responsiveness of a simple moving average of that many samples) or as a half-life (1 - exp(-ln2 / halfLife)), and the class tracks the exponentially weighted variance of the same stream in the same O(1) state. It seeds itself with the first sample rather than with zero, which avoids the warm-up bias a zero seed introduces, or with an explicit resting level, which is what a control chart wants.

The ShiftSignal verdict enum, until now nested inside CusumDetector, becomes a top-level enum of the package so that both detectors report the same type; CusumDetector and its test are updated to it, and nothing else in the repository referred to the nested enum.

Each sample costs O(1) time and neither class allocates anything after construction.

EwmaChangeDetectorTest covers 17 cases and ExponentialMovingAverageTest 31; CusumDetectorTest keeps its 16. Among them: the control limit is checked against its closed form and against its asymptote, upward and downward shifts are found both in a clean step and buried in noise, in-control Gaussian noise rarely trips the band, an alarm returns the statistic to the target, a stream sitting on target never alarms, the mean and variance recurrences are checked against direct computation, alpha == 1 keeps only the latest sample, and on stationary noise the estimates sit close to the true moments.

Checklist

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized it.
  • All filenames are in PascalCase.
  • All functions and variable names follow Java naming conventions.
  • All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.
  • All new algorithms include a corresponding test class that validates their functionality.
  • All new code is formatted with clang-format -i --style=file path/to/your/file.java

Adds an exponentially weighted moving average control chart that flags level shifts in a stream in O(1) time and memory per sample, with an exact variance-based control band so that the warm-up does not trip false alarms.

It builds on a new reusable ExponentialMovingAverage (alpha, span or half-life parameterisation, and the exponentially weighted variance of the same stream) and shares the ShiftSignal verdict enum with CusumDetector, out of which it is extracted to the package level so that both detectors report the same type.

Co-authored-by: Oleksandr Klymenko <19151554+alxkm@users.noreply.github.com>
Signed-off-by: alxkm <19151554+alxkm@users.noreply.github.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.99%. Comparing base (d93d0ea) to head (7266171).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #7600      +/-   ##
============================================
+ Coverage     80.92%   80.99%   +0.07%     
- Complexity     7680     7737      +57     
============================================
  Files           822      825       +3     
  Lines         24490    24594     +104     
  Branches       4800     4810      +10     
============================================
+ Hits          19818    19920     +102     
- Misses         3906     3907       +1     
- Partials        766      767       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@DenizAltunkapan
DenizAltunkapan merged commit 1f08afb into TheAlgorithms:master Sep 9, 2026
7 checks passed
@alxkm
alxkm deleted the feat/ewma-change-detector branch September 9, 2026 20:14
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