Cap outliers by percentile winsorization#99
Merged
Conversation
`test_disabled_matches_zero_percentiles` compared the full metrics DataFrames from two evaluations, including the `timestamp` column that is stamped from `datetime.now()`. When the two calls straddled a one-second boundary (as on the 3.12 CI runner) the timestamps differed and the test failed. Drop the non-deterministic `timestamp` column before comparing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jirisamek
approved these changes
Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds optional percentile winsorization to metrics so that by-unit experiment evaluation stays robust when heavy-tailed metrics (revenue, time spent, actions per user, …) contain extreme outliers. A handful of extreme units can dominate a variant's mean and variance; when those outliers are concentrated in one variant they distort the comparison and cost statistical power.
Approach
Per-unit nominator values are capped (winsorized) to a percentile threshold before aggregation:
Configuration
Two optional parameters on
Metric/SimpleMetric(and the HTTP API metric definition), each a percentage0–50of the tail to cap:outlier_upper_percentile— cap the upper tail.1caps every per-unit value above the pooled 99th percentile down to the 99th-percentile value.outlier_lower_percentile— cap the lower tail (analogous, off by default).Scope & caveats
evaluate_by_unit(it needs the individual per-unit values); the pre-aggregatedevaluate_aggpath is unaffected.Changes
parser.py— pooled winsorization inParser.evaluate_by_unit(no-op when disabled; the original code path is unchanged).metric.py—outlier_upper_percentile/outlier_lower_percentileconfig + validation to[0, 50).server/req.py— exposed both fields on the API metric definition and wired them throughto_metric.tests/epstats/toolkit/test_outlier_winsorization.py— caps preserve count (vs. trimming), pooled threshold applied identically across variants, lower-tail flooring,disabled == 0-percentiles, and validation.docs/stats/outliers.md(+ Statistics nav) — methodology page;Metricdocstrings updated.🤖 Generated with Claude Code