diff --git a/src/FSharp.Stats/Distributions/Continuous/ChiSquared.fs b/src/FSharp.Stats/Distributions/Continuous/ChiSquared.fs index 44aa14ac..5fdc11a8 100644 --- a/src/FSharp.Stats/Distributions/Continuous/ChiSquared.fs +++ b/src/FSharp.Stats/Distributions/Continuous/ChiSquared.fs @@ -140,8 +140,11 @@ type ChiSquared = /// The probability value in [0.0, 1.0]. /// The quantile corresponding to the cumulative probability p. static member InvCDF (dof: float) (p: float) : float = + // Chi-squared(dof) = Gamma(alpha=dof/2, scale=2). + // FSharp.Stats Gamma uses the scale parameterisation (Mean = alpha * scale), + // so the correct scale is 2.0, not 1/2. let alpha = dof / 2.0 - let beta = 1. / 2.0 + let beta = 2.0 Gamma.InvCDF alpha beta p /// Returns the support of the exponential distribution: [0, Positive Infinity). diff --git a/tests/FSharp.Stats.Tests/DistributionsContinuous.fs b/tests/FSharp.Stats.Tests/DistributionsContinuous.fs index 6d13c751..c674cad2 100644 --- a/tests/FSharp.Stats.Tests/DistributionsContinuous.fs +++ b/tests/FSharp.Stats.Tests/DistributionsContinuous.fs @@ -618,6 +618,65 @@ let chiSquaredTests = Expect.floatClose Accuracy.veryHigh (testCase.PDF -1.) 0. "Should be equal" Expect.isTrue (Ops.isNan <| testCase.PDF nan) "Should be equal" ] + + // Tests for ChiSquared.InvCDF (the percentile-point function). + // Reference values from standard chi-square tables (and CDF values already tested above). + testList "ChiSquared.InvCDF tests" [ + + test "ChiSquared.InvCDF returns 0.0 for p=0.0" { + let x = Continuous.ChiSquared.InvCDF 5. 0. + Expect.floatClose Accuracy.high x 0. "InvCDF(p=0) should be 0" + } + + test "ChiSquared.InvCDF returns +∞ for p=1.0" { + let x = Continuous.ChiSquared.InvCDF 5. 1. + Expect.isTrue (x = infinity) "InvCDF(p=1) should be +infinity" + } + + // Round-trip tests: CDF(InvCDF(p)) ≈ p + test "ChiSquared.InvCDF round-trip dof=5 p=0.5" { + let dof = 5. + let p = 0.5 + let x = Continuous.ChiSquared.InvCDF dof p + let p2 = Continuous.ChiSquared.CDF dof x + Expect.floatClose Accuracy.high p p2 "CDF(InvCDF(0.5)) ≈ 0.5" + } + + test "ChiSquared.InvCDF round-trip dof=1 p=0.95" { + let dof = 1. + let p = 0.95 + let x = Continuous.ChiSquared.InvCDF dof p + let p2 = Continuous.ChiSquared.CDF dof x + Expect.floatClose Accuracy.high p p2 "CDF(InvCDF(0.95)) ≈ 0.95" + } + + test "ChiSquared.InvCDF round-trip dof=10 p=0.01" { + let dof = 10. + let p = 0.01 + let x = Continuous.ChiSquared.InvCDF dof p + let p2 = Continuous.ChiSquared.CDF dof x + Expect.floatClose Accuracy.high p p2 "CDF(InvCDF(0.01)) ≈ 0.01" + } + + // Known-value tests derived from the CDF cases tested above + // (Williams 1984 table: CDF(20, 12.443) ≈ 0.1 → InvCDF(20, 0.1) ≈ 12.443) + test "ChiSquared.InvCDF known value dof=20 p=0.1" { + let x = Continuous.ChiSquared.InvCDF 20. 0.1 + Expect.floatClose Accuracy.low x 12.443 "InvCDF(20, 0.1) should be ≈ 12.443" + } + + // CDF(3, 1.424) ≈ 0.3 → InvCDF(3, 0.3) ≈ 1.424 + test "ChiSquared.InvCDF known value dof=3 p=0.3" { + let x = Continuous.ChiSquared.InvCDF 3. 0.3 + Expect.floatClose Accuracy.low x 1.424 "InvCDF(3, 0.3) should be ≈ 1.424" + } + + // CDF(100, 129.561) ≈ 0.975 → InvCDF(100, 0.975) ≈ 129.561 + test "ChiSquared.InvCDF known value dof=100 p=0.975" { + let x = Continuous.ChiSquared.InvCDF 100. 0.975 + Expect.floatClose Accuracy.low x 129.561 "InvCDF(100, 0.975) should be ≈ 129.561" + } + ] ] //Test ommitted due to long runtime of CodeCov