Return None from Empirical::variance for a single sample - #468
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe empirical distribution now returns undefined variance for fewer than two samples. Tests verify that both variance and standard deviation return ChangesEmpirical variance handling
Estimated code review effort: 2 (Simple) | ~5 minutes Merge Risk: ⚪ Minimal · up to Empirical variance and standard deviation now report undefined results for single-sample distributions rather than NaN. The boundary behavior is covered without affecting multi-sample calculations, and no current merge-blocking risk remains. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Thanks for this! I consider it a fix. I expect people will be okay with that. @youdie006 would you be able to write the commit according to Conventional Commit with a |
The guard checked emptiness while the denominator is n - 1, so one sample gave 0.0 / 0.0. OnlineMoments::variance and Hypergeometric::variance both guard the denominator instead.
20481a3 to
773927f
Compare
|
Done - amended to |
Empirical::variance()returnsSome(NaN)for a single sample.src/distribution/empirical.rs:245guards emptiness while the denominator isn - 1:At
n == 1the denominator is zero andself.varis zero too, so it is0.0 / 0.0.std_dev()isthe default trait method
variance().map(f64::sqrt), so the NaN reaches a second public method.Optionis already this crate's channel for "undefined here" - it is whatn == 0returns.The siblings guard the denominator, not emptiness
src/statistics/online.rs:52,OnlineMoments<2>::variance-if self.count < 2 { None }, and itsdoc says so outright: "or
Noneif fewer than two observations have been pushed"src/statistics/online.rs:95,OnlineMoments<3>::variance- the samesrc/distribution/hypergeometric.rs:332-if self.population <= 1 { None }Empiricalis the one that checks the wrong thing.The change
self.data.is_empty()becomesself.sum < 2. The struct's own invariant atempirical.rs:62-"Must be 0 iff data.is_empty()" - means the new predicate subsumes the old one, so
n == 0isunchanged.
Observable change, plainly:
Empirical::from_iter([x]).variance()goes fromSome(NaN)toNone, and.std_dev()with it. Nothing else moves -n == 0andn >= 2are identical, and thetest below pins that from both sides.
Tests
Three lines added to the existing
test_var, which coveredvec![]andvec![4.0; 100]but nevern == 1. Reverting only the guard:I also mutation-checked the guard itself, because my first draft was not tight enough:
self.sum < 1(the old empty-only guard, restated) ->test_varFAILSself.sum < 3(over-guardingn == 2) ->test_varFAILSThe second only started failing after I added
test_var_for_samples(2.0, vec![1.0, 3.0])to pinn == 2from the other side.2.0is exact in binary floating point, so that assertion is notrounding-direction dependent.
CI gates:
cargo fmt -- --checkclean,cargo clippy --all-targets -- -D warningsclean,cargo testgives833 passed; 0 failed; 2 ignoredand195 passed; 0 failed.Related
#460 (
Triangular::pdfreturning NaN when mode equals min) fixed the same0/0-reaching-the-callerclass in this crate last week.
Disclosure: found and prepared with AI assistance (Claude). Every figure above is from a run on this
branch, and the sibling comparison and mutation checks are mine, not a summary of a tool's output.
Summary by CodeRabbit