@@ -1600,4 +1600,62 @@ let anovaTests =
16001600 // Expect.floatClose Accuracy.low (Math.Round (twoWayANOVARandom.Total.SumOfSquares,8)) 0.7815 "Total.SumOfSquares deviated from expected value"
16011601 // Expect.floatClose Accuracy.low (Math.Round (twoWayANOVARandom.Total.MeanSquares,8)) 0.04597 "Total.MeanSquares deviated from expected value"
16021602
1603- ]
1603+ ]
1604+
1605+ [<Tests>]
1606+ let testMannWhitneyTest =
1607+ // Reference values cross-checked against R: wilcox.test(..., exact=FALSE, correct=FALSE)
1608+ // and R: wilcox.test(..., exact=FALSE, correct=TRUE)
1609+
1610+ // Test case 1: clear separation (sample1 >> sample2) -- no ties
1611+ // R: wilcox.test(c(6,7,8,9,10), c(1,2,3,4,5), exact=FALSE, correct=FALSE) → W=25, p=0.004516
1612+ // R: wilcox.test(c(6,7,8,9,10), c(1,2,3,4,5), exact=FALSE, correct=TRUE) → W=25, p=0.01220
1613+ let s1a = [| 6. ; 7. ; 8. ; 9. ; 10. |]
1614+ let s2a = [| 1. ; 2. ; 3. ; 4. ; 5. |]
1615+ let resultNoCC = MannWhitneyTest.create s1a s2a false
1616+ let resultCC = MannWhitneyTest.create s1a s2a true
1617+
1618+ // Test case 2: moderate overlap -- no ties
1619+ // sample1=[2;4;6;8;10], sample2=[1;3;5;7;9]
1620+ // U1=15, U2=10; z(no cc)=(15-12.5)/4.787=0.5222; pRight=0.3008; pTwo=0.6016
1621+ let s1b = [| 2. ; 4. ; 6. ; 8. ; 10. |]
1622+ let s2b = [| 1. ; 3. ; 5. ; 7. ; 9. |]
1623+ let resultB = MannWhitneyTest.create s1b s2b false
1624+
1625+ // Test case 3: data with ties
1626+ // sample1=[1;2;3;4], sample2=[2;3;5;6]
1627+ // U1=4, U2=12, mu=8, var=11.714, sigma=3.4225
1628+ // z=-1.1693, pLeft=0.1212, pRight=0.8788, pTwo=0.2423
1629+ let s1c = [| 1. ; 2. ; 3. ; 4. |]
1630+ let s2c = [| 2. ; 3. ; 5. ; 6. |]
1631+ let resultC = MannWhitneyTest.create s1c s2c false
1632+
1633+ testList " Testing.MannWhitneyTest" [
1634+ testCase " U statistics - clear separation" <| fun () ->
1635+ Expect.floatClose Accuracy.medium resultNoCC.U1 25. " U1 should be 25 (sample1 dominates)"
1636+ Expect.floatClose Accuracy.medium resultNoCC.U2 0. " U2 should be 0"
1637+ testCase " U1 + U2 = n1*n2" <| fun () ->
1638+ Expect.floatClose Accuracy.medium ( resultNoCC.U1 + resultNoCC.U2) 25. " U1+U2 must equal n1*n2=25"
1639+ Expect.floatClose Accuracy.medium ( resultB.U1 + resultB.U2) 25. " U1+U2 must equal n1*n2=25"
1640+ Expect.floatClose Accuracy.medium ( resultC.U1 + resultC.U2) 16. " U1+U2 must equal n1*n2=16"
1641+ testCase " p-values - clear separation, no continuity correction" <| fun () ->
1642+ // R: p-value = 0.004516
1643+ Expect.isTrue ( resultNoCC.PValueRight < 0.01 ) " PValueRight should be < 0.01 (sample1 >> sample2)"
1644+ Expect.isTrue ( resultNoCC.PValueLeft > 0.99 ) " PValueLeft should be > 0.99"
1645+ Expect.floatClose Accuracy.low resultNoCC.PValue ( 2. * resultNoCC.PValueRight) " Two-sided = 2 × one-sided min"
1646+ testCase " p-values - clear separation, continuity correction" <| fun () ->
1647+ // R: p-value ≈ 0.01220
1648+ Expect.floatClose Accuracy.low ( Math.Round( resultCC.PValue, 4 )) 0.0122 " PValue with CC should ≈ 0.0122"
1649+ testCase " U statistics - moderate overlap" <| fun () ->
1650+ Expect.floatClose Accuracy.medium resultB.U1 15. " U1 should be 15"
1651+ Expect.floatClose Accuracy.medium resultB.U2 10. " U2 should be 10"
1652+ testCase " p-value not significant - moderate overlap" <| fun () ->
1653+ Expect.isTrue ( resultB.PValue > 0.05 ) " p-value should not be significant for nearly identical distributions"
1654+ testCase " U statistics with ties" <| fun () ->
1655+ Expect.floatClose Accuracy.medium resultC.U1 4. " U1 should be 4 (with ties)"
1656+ Expect.floatClose Accuracy.medium resultC.U2 12. " U2 should be 12"
1657+ testCase " p-values with ties" <| fun () ->
1658+ // z ≈ -1.169, pLeft ≈ 0.121, pTwo ≈ 0.242
1659+ Expect.floatClose Accuracy.low ( Math.Round( resultC.PValueLeft, 3 )) 0.121 " PValueLeft with ties should ≈ 0.121"
1660+ Expect.floatClose Accuracy.low ( Math.Round( resultC.PValue, 3 )) 0.243 " PValueTwoTailed with ties should ≈ 0.243"
1661+ ]
0 commit comments