Skip to content

feat: Benjamini–Hochberg FDR control for the point detector (0.5.0) - #54

Merged
copyleftdev merged 2 commits into
mainfrom
feat/fdr-control-pr
Jun 1, 2026
Merged

copyleftdev merged 2 commits into
mainfrom
feat/fdr-control-pr

Conversation

@copyleftdev

Copy link
Copy Markdown
Owner

What

scan / explain gain --fdr Q — per-column Benjamini–Hochberg false-discovery-rate control for point.modz. Each cell's modified z-score becomes a two-sided p-value, and the fixed point_threshold is replaced by a multiplicity-aware step-up cutoff that bounds the expected proportion of false flags at Q. Opt-in (omitted ⇒ unchanged behavior); the level is in the config_version fingerprint (pfdr=).

New ax_detect::fdr: two_sided_p (normal tail via erfc) + benjamini_hochberg (deterministic sort + step-up).

Why this is #1 toward "full"

It replaces an arbitrary magic cutoff with a principled error-rate guarantee, and makes the discovery set adapt to how many cells were tested — a noise column stops contributing chance flags, and the same outlier can be significant in a small column yet not a large one.

Honest result (dogfooded on the real 127k-row MSHA parquet)

FDR is a correctness control, not a volume knob — and on heavy-tailed data it flags more, not fewer:

mode point.modz findings
default (fixed z>3.5) 32,893
--fdr 0.05 40,079
--fdr 0.01 38,679
--fdr 0.001 36,944

Those extra cells genuinely clear the FDR bar; the old fixed cutoff was stringent in an uncalibrated way (its effective per-cell p was ~2e-7). The takeaway: FDR makes findings defensible; capping their number is a separate lever (column scoping today, severity / top-N output scoping next). They compose: "top-N by score, among the FDR-significant set."

Calibration note

The p-value uses the consistent-σ standardized deviation (x − center)/scale (≈ N(0,1)), not robustz's modified z-score — which folds in the display constant MODZ_K and is therefore not unit-variance. Using the latter would mis-state every p-value.

Gate

proptest + cargo-mutants 0 missed on all four changed files: fdr.rs/point.rs/config.rs (point 26 caught / 1 unviable; BH+config covered) and main.rs (54 caught / 5 unviable). Tests include the BH step-up property, the (x−center) sign, and multiplicity adaptation (same outlier significant at m=22, not at m=2101).

🤖 Generated with Claude Code

`scan`/`explain` gain `--fdr Q`: per-column false-discovery-rate control. Each
cell's modified z-score becomes a two-sided p-value (consistent-σ standardized
deviation, ≈N(0,1) under the null — not robustz's display-scaled modz) and the
fixed point_threshold is replaced by the Benjamini–Hochberg step-up cutoff,
bounding the expected proportion of false flags at Q. Opt-in (None default →
unchanged behavior); the level is part of the config_version fingerprint (pfdr=).

New ax_detect::fdr module: two_sided_p (erfc) + benjamini_hochberg (deterministic
sort + step-up), property/exact tested.

Honest scope: FDR is a CORRECTNESS control, not a volume knob. It replaces an
arbitrary fixed cutoff with a principled, multiplicity-aware error-rate
guarantee. On the real 127k-row MSHA parquet it flags MORE point cells than the
old threshold (40,079 vs 32,893 at q=0.05) — those cells are genuinely
significant; the fixed z>3.5 cutoff was stringent in an uncalibrated way.
Capping volume is a separate concern (column scoping + planned severity/top-N).

Gates: proptest + cargo-mutants 0-missed on fdr.rs/point.rs/config.rs
(point 26 caught/1 unviable) and main.rs (54 caught/5 unviable). Includes a test
pinning the (x-center) sign and one showing the same outlier is significant in a
small column but not a large one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@copyleftdev, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 4 minutes and 46 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c47d0a52-2c2f-474b-a05d-a54c52544147

📥 Commits

Reviewing files that changed from the base of the PR and between a73eceb and 4f15792.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • CHANGELOG.md
  • Cargo.toml
  • crates/anomalyx/src/main.rs
  • crates/ax-detect/src/config.rs
  • crates/ax-detect/src/fdr.rs
  • crates/ax-detect/src/lib.rs
  • crates/ax-detect/src/point.rs
  • docs/src/modes.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fdr-control-pr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@copyleftdev
copyleftdev merged commit e6f231b into main Jun 1, 2026
2 checks passed
@copyleftdev
copyleftdev deleted the feat/fdr-control-pr branch June 1, 2026 13:27
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.

1 participant