1 /**Hypothesis testing beyond simple CDFs. All functions work with input 2 * ranges with elements implicitly convertible to double unless otherwise noted. 3 * 4 * Author: David Simcha*/ 5 /* 6 * License: 7 * Boost Software License - Version 1.0 - August 17th, 2003 8 * 9 * Permission is hereby granted, free of charge, to any person or organization 10 * obtaining a copy of the software and accompanying documentation covered by 11 * this license (the "Software") to use, reproduce, display, distribute, 12 * execute, and transmit the Software, and to prepare derivative works of the 13 * Software, and to permit third-parties to whom the Software is furnished to 14 * do so, all subject to the following: 15 * 16 * The copyright notices in the Software and this entire statement, including 17 * the above license grant, this restriction and the following disclaimer, 18 * must be included in all copies of the Software, in whole or in part, and 19 * all derivative works of the Software, unless such copies or derivative 20 * works are solely in the form of machine-executable object code generated by 21 * a source language processor. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 26 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 27 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 28 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 29 * DEALINGS IN THE SOFTWARE. 30 */ 31 module dstats.tests; 32 33 import std.functional, std.range, std.conv, std.math, std.traits, 34 std.exception, std.typetuple; 35 36 import std.algorithm : reverse, copy, max, min, swap, map, reduce; 37 38 import dstats.base, dstats.distrib, dstats.alloc, dstats.summary, dstats.sort; 39 40 static import dstats.cor; 41 42 private static import dstats.infotheory; 43 44 version(unittest) { 45 import std.stdio, dstats.random; 46 } 47 48 /**Alternative hypotheses. Exact meaning varies with test used.*/ 49 enum Alt { 50 /// f(input1) != X 51 twoSided, 52 53 /// f(input1) < X 54 less, 55 56 /// f(input1) > X 57 greater, 58 59 /** 60 Skip P-value computation (and confidence intervals if applicable) 61 and just return the test statistic. 62 */ 63 none 64 } 65 66 /** 67 A plain old data struct for returning the results of hypothesis tests. 68 */ 69 struct TestRes { 70 /// The test statistic. What exactly this is is specific to the test. 71 double testStat; 72 73 /**The P-value against the provided alternative. This struct can 74 * be implicitly converted to just the P-value via alias this.*/ 75 double p; 76 77 /// Allow implicit conversion to the P-value. 78 alias p this; 79 80 /// 81 string toString() { 82 return text("Test Statistic = ", testStat, "\nP = ", p); 83 } 84 } 85 86 /** 87 A plain old data struct for returning the results of hypothesis tests 88 that also produce confidence intervals. Contains, can implicitly convert 89 to, a TestRes. 90 */ 91 struct ConfInt { 92 /// This is alias this'd. 93 TestRes testRes; 94 95 /// Lower bound of the confidence interval at the level specified. 96 double lowerBound; 97 98 /// Upper bound of the confidence interval at the level specified. 99 double upperBound; 100 101 alias testRes this; 102 103 /// 104 string toString() { 105 return text("Test Statistic = ", testRes.testStat, "\nP = ", testRes.p, 106 "\nLower Confidence Bound = ", lowerBound, 107 "\nUpper Confidence Bound = ", upperBound); 108 } 109 } 110 111 /** 112 Tests whether a struct/class has the necessary information for calculating 113 a T-test. It must have a property .mean (mean), .stdev (stdandard deviation), 114 .var (variance), and .N (sample size). 115 */ 116 template isSummary(T) { 117 enum bool isSummary = is(typeof(T.init.mean)) && is(typeof(T.init.stdev)) && 118 is(typeof(T.init.var)) && is(typeof(T.init.N)); 119 } 120 121 /** 122 One-sample Student's T-test for difference between mean of data and 123 a fixed value. Alternatives are Alt.less, meaning mean(data) < testMean, 124 Alt.greater, meaning mean(data) > testMean, and Alt.twoSided, meaning 125 mean(data)!= testMean. 126 127 data may be either an iterable with elements implicitly convertible to 128 double or a summary struct (see isSummary). 129 * 130 Examples: 131 --- 132 uint[] data = [1,2,3,4,5]; 133 134 // Test the null hypothesis that the mean of data is >= 1 against the 135 // alternative that the mean of data is < 1. Calculate confidence 136 // intervals at 90%. 137 auto result1 = studentsTTest(data, 1, Alt.less, 0.9); 138 139 // Do the same thing, only this time we've already calculated the summary 140 // statistics explicitly before passing them to studensTTest. 141 auto summary = meanStdev(data); 142 writeln(summary.stdev); 143 result2 = studentsTTest(summary, 1, Alt.less, 0.9); // Same as result1. 144 assert(result1 == result2); 145 --- 146 147 Returns: A ConfInt containing T, the P-value and the boundaries of 148 the confidence interval for mean(data) at the level specified. 149 150 References: http://en.wikipedia.org/wiki/Student%27s_t-test 151 */ 152 ConfInt studentsTTest(T)( 153 T data, 154 double testMean = 0, 155 Alt alt = Alt.twoSided, 156 double confLevel = 0.95 157 ) if( (isSummary!T || doubleIterable!T)) { 158 enforceConfidence(confLevel); 159 dstatsEnforce(isFinite(testMean), "testMean must not be infinite or NaN."); 160 161 static if(isSummary!T) { 162 return pairedTTest(data, testMean, alt, confLevel); 163 } else static if(doubleIterable!T) { 164 return pairedTTest(meanStdev(data), testMean, alt, confLevel); 165 } 166 } 167 168 unittest { 169 auto t1 = studentsTTest([1, 2, 3, 4, 5].dup, 2); 170 assert(approxEqual(t1.testStat, 1.4142)); 171 assert(approxEqual(t1.p, 0.2302)); 172 assert(approxEqual(t1.lowerBound, 1.036757)); 173 assert(approxEqual(t1.upperBound, 4.963243)); 174 assert(t1 == studentsTTest( meanStdev([1,2,3,4,5].dup), 2)); 175 176 auto t2 = studentsTTest([1, 2, 3, 4, 5].dup, 2, Alt.less); 177 assert(approxEqual(t2.p, .8849)); 178 assert(approxEqual(t2.testStat, 1.4142)); 179 assert(t2.lowerBound == -double.infinity); 180 assert(approxEqual(t2.upperBound, 4.507443)); 181 182 auto t3 = studentsTTest( summary([1, 2, 3, 4, 5].dup), 2, Alt.greater); 183 assert(approxEqual(t3.p, .1151)); 184 assert(approxEqual(t3.testStat, 1.4142)); 185 assert(approxEqual(t3.lowerBound, 1.492557)); 186 assert(t3.upperBound == double.infinity); 187 } 188 189 /** 190 Two-sample T test for a difference in means, 191 assumes variances of samples are equal. Alteratives are Alt.less, meaning 192 mean(sample1) - mean(sample2) < testMean, Alt.greater, meaning 193 mean(sample1) - mean(sample2) > testMean, and Alt.twoSided, meaning 194 mean(sample1) - mean(sample2) != testMean. 195 196 sample1 and sample2 may be either iterables with elements implicitly 197 convertible to double or summary structs (see isSummary). 198 199 Returns: A ConfInt containing the T statistic, the P-value, and the 200 boundaries of the confidence interval for the difference between means 201 of sample1 and sample2 at the specified level. 202 203 References: http://en.wikipedia.org/wiki/Student%27s_t-test 204 */ 205 ConfInt studentsTTest(T, U)( 206 T sample1, 207 U sample2, 208 double testMean = 0, 209 Alt alt = Alt.twoSided, 210 double confLevel = 0.95 211 ) if( (doubleIterable!T || isSummary!T) && (doubleIterable!U || isSummary!U)) { 212 enforceConfidence(confLevel); 213 dstatsEnforce(isFinite(testMean), "testMean must not be infinite or nan."); 214 215 static if(isSummary!T) { 216 alias sample1 s1summ; 217 } else { 218 immutable s1summ = meanStdev(sample1); 219 } 220 221 static if(isSummary!U) { 222 alias sample2 s2summ; 223 } else { 224 immutable s2summ = meanStdev(sample2); 225 } 226 227 immutable n1 = s1summ.N, n2 = s2summ.N; 228 229 immutable sx1x2 = sqrt((n1 * s1summ.mse + n2 * s2summ.mse) / 230 (n1 + n2 - 2)); 231 immutable normSd = (sx1x2 * sqrt((1.0 / n1) + (1.0 / n2))); 232 immutable meanDiff = s1summ.mean - s2summ.mean; 233 ConfInt ret; 234 ret.testStat = (meanDiff - testMean) / normSd; 235 if(alt == Alt.none) { 236 return ret; 237 } else if(alt == Alt.less) { 238 ret.p = studentsTCDF(ret.testStat, n1 + n2 - 2); 239 240 ret.lowerBound = -double.infinity; 241 if(confLevel > 0) { 242 immutable delta = invStudentsTCDF(1 - confLevel, n1 + n2 - 2) 243 * normSd; 244 ret.upperBound = meanDiff - delta; 245 } else { 246 ret.upperBound = meanDiff; 247 } 248 249 } else if(alt == Alt.greater) { 250 ret.p = studentsTCDF(-ret.testStat, n1 + n2 - 2); 251 252 ret.upperBound = double.infinity; 253 if(confLevel > 0) { 254 immutable delta = invStudentsTCDF(1 - confLevel, n1 + n2 - 2) 255 * normSd; 256 ret.lowerBound = meanDiff + delta; 257 } else { 258 ret.lowerBound = meanDiff; 259 } 260 261 } else { 262 immutable t = ret.testStat; 263 ret.p = 2 * ((t < 0) ? 264 studentsTCDF(t, n1 + n2 - 2) : 265 studentsTCDFR(t, n1 + n2 - 2)); 266 267 if(confLevel > 0) { 268 immutable delta = invStudentsTCDF( 269 0.5 * (1 - confLevel), n1 + n2 - 2) * normSd; 270 ret.lowerBound = meanDiff + delta; 271 ret.upperBound = meanDiff - delta; 272 } else { 273 ret.lowerBound = meanDiff; 274 ret.upperBound = meanDiff; 275 } 276 } 277 return ret; 278 } 279 280 unittest { 281 // Values from R. 282 auto t1 = studentsTTest([1,2,3,4,5], [1,3,4,5,7,9]); 283 assert(approxEqual(t1.p, 0.2346)); 284 assert(approxEqual(t1.testStat, -1.274)); 285 assert(approxEqual(t1.lowerBound, -5.088787)); 286 assert(approxEqual(t1.upperBound, 1.422120)); 287 288 289 assert(approxEqual(studentsTTest([1,2,3,4,5], [1,3,4,5,7,9], 0, Alt.less), 290 0.1173)); 291 assert(approxEqual(studentsTTest([1,2,3,4,5], [1,3,4,5,7,9], 0, Alt.greater), 292 0.8827)); 293 auto t2 = studentsTTest([1,3,5,7,9,11], [2,2,1,3,4], 5); 294 assert(approxEqual(t2.p, 0.44444)); 295 assert(approxEqual(t2.testStat, -0.7998)); 296 assert(approxEqual(t2.lowerBound, -0.3595529)); 297 assert(approxEqual(t2.upperBound, 7.5595529)); 298 299 300 auto t5 = studentsTTest([1,3,5,7,9,11], [2,2,1,3,4], 0, Alt.less); 301 assert(approxEqual(t5.p, 0.965)); 302 assert(approxEqual(t5.testStat, 2.0567)); 303 assert(approxEqual(t5.upperBound, 6.80857)); 304 assert(t5.lowerBound == -double.infinity); 305 306 auto t6 = studentsTTest([1,3,5,7,9,11], [2,2,1,3,4], 0, Alt.greater); 307 assert(approxEqual(t6.p, 0.03492)); 308 assert(approxEqual(t6.testStat, 2.0567)); 309 assert(approxEqual(t6.lowerBound, 0.391422)); 310 assert(t6.upperBound == double.infinity); 311 312 auto t7 = studentsTTest([1, 2, 4], [3]); 313 assert(approxEqual(t7.p, 0.7418)); 314 assert(approxEqual(t7.testStat, 0.-.378)); 315 assert(approxEqual(t7.lowerBound, -8.255833)); 316 assert(approxEqual(t7.upperBound, 6.922499)); 317 318 } 319 320 /** 321 Two-sample T-test for difference in means. Does not assume variances are equal. 322 Alteratives are Alt.less, meaning mean(sample1) - mean(sample2) < testMean, 323 Alt.greater, meaning mean(sample1) - mean(sample2) > testMean, and 324 Alt.twoSided, meaning mean(sample1) - mean(sample2) != testMean. 325 326 sample1 and sample2 may be either iterables with elements implicitly 327 convertible to double or summary structs (see isSummary). 328 329 Returns: A ConfInt containing the T statistic, the P-value, and the 330 boundaries of the confidence interval for the difference between means 331 of sample1 and sample2 at the specified level. 332 333 References: http://en.wikipedia.org/wiki/Student%27s_t-test 334 */ 335 ConfInt welchTTest(T, U)(T sample1, U sample2, double testMean = 0, 336 Alt alt = Alt.twoSided, double confLevel = 0.95) 337 if( (isSummary!T || doubleIterable!T) && (isSummary!U || doubleIterable!U)) { 338 enforceConfidence(confLevel); 339 dstatsEnforce(isFinite(testMean), "testMean cannot be infinite or NaN."); 340 341 static if(isSummary!T) { 342 alias sample1 s1summ; 343 } else { 344 auto s1summ = meanStdev(sample1); 345 } 346 347 static if(isSummary!U) { 348 alias sample2 s2summ; 349 } else { 350 auto s2summ = meanStdev(sample2); 351 } 352 353 immutable double n1 = s1summ.N, 354 n2 = s2summ.N; 355 356 immutable v1 = s1summ.var, v2 = s2summ.var; 357 immutable double sx1x2 = sqrt(v1 / n1 + v2 / n2); 358 immutable double meanDiff = s1summ.mean - s2summ.mean - testMean; 359 immutable double t = meanDiff / sx1x2; 360 double numerator = v1 / n1 + v2 / n2; 361 numerator *= numerator; 362 double denom1 = v1 / n1; 363 denom1 = denom1 * denom1 / (n1 - 1); 364 double denom2 = v2 / n2; 365 denom2 = denom2 * denom2 / (n2 - 1); 366 immutable double df = numerator / (denom1 + denom2); 367 368 ConfInt ret; 369 ret.testStat = t; 370 if(alt == Alt.none) { 371 return ret; 372 } else if(alt == Alt.less) { 373 ret.p = studentsTCDF(t, df); 374 ret.lowerBound = -double.infinity; 375 376 if(confLevel > 0) { 377 ret.upperBound = meanDiff + 378 testMean - invStudentsTCDF(1 - confLevel, df) * sx1x2; 379 } else { 380 ret.upperBound = meanDiff + testMean; 381 } 382 383 } else if(alt == Alt.greater) { 384 ret.p = studentsTCDF(-t, df); 385 ret.upperBound = double.infinity; 386 387 if(confLevel > 0) { 388 ret.lowerBound = meanDiff + 389 testMean + invStudentsTCDF(1 - confLevel, df) * sx1x2; 390 } else { 391 ret.lowerBound = meanDiff + testMean; 392 } 393 394 } else { 395 ret.p = 2 * ((t < 0) ? 396 studentsTCDF(t, df) : 397 studentsTCDF(-t, df)); 398 399 if(confLevel > 0) { 400 double delta = invStudentsTCDF(0.5 * (1 - confLevel), df) * sx1x2; 401 ret.upperBound = meanDiff + testMean - delta; 402 ret.lowerBound = meanDiff + testMean + delta; 403 } else { 404 ret.upperBound = meanDiff + testMean; 405 ret.lowerBound = meanDiff + testMean; 406 } 407 } 408 return ret; 409 } 410 411 unittest { 412 // Values from R. 413 auto t1 = welchTTest( meanStdev([1,2,3,4,5]), [1,3,4,5,7,9], 2); 414 assert(approxEqual(t1.p, 0.02285)); 415 assert(approxEqual(t1.testStat, -2.8099)); 416 assert(approxEqual(t1.lowerBound, -4.979316)); 417 assert(approxEqual(t1.upperBound, 1.312649)); 418 419 auto t2 = welchTTest([1,2,3,4,5], summary([1,3,4,5,7,9]), -1, Alt.less); 420 assert(approxEqual(t2.p, 0.2791)); 421 assert(approxEqual(t2.testStat, -0.6108)); 422 assert(t2.lowerBound == -double.infinity); 423 assert(approxEqual(t2.upperBound, 0.7035534)); 424 425 auto t3 = welchTTest([1,2,3,4,5], [1,3,4,5,7,9], 0.5, Alt.greater); 426 assert(approxEqual(t3.p, 0.9372)); 427 assert(approxEqual(t3.testStat, -1.7104)); 428 assert(approxEqual(t3.lowerBound, -4.37022)); 429 assert(t3.upperBound == double.infinity); 430 431 assert(approxEqual(welchTTest([1,3,5,7,9,11], [2,2,1,3,4]).p, 0.06616)); 432 assert(approxEqual(welchTTest([1,3,5,7,9,11], [2,2,1,3,4], 0, 433 Alt.less).p, 0.967)); 434 assert(approxEqual(welchTTest([1,3,5,7,9,11], [2,2,1,3,4], 0, 435 Alt.greater).p, 0.03308)); 436 } 437 438 /** 439 Paired T test. Tests the hypothesis that the mean difference between 440 corresponding elements of before and after is testMean. Alternatives are 441 Alt.less, meaning the that the true mean difference (before[i] - after[i]) 442 is less than testMean, Alt.greater, meaning the true mean difference is 443 greater than testMean, and Alt.twoSided, meaning the true mean difference is not 444 equal to testMean. 445 446 before and after must be input ranges with elements implicitly convertible 447 to double and must have the same length. 448 449 Returns: A ConfInt containing the T statistic, the P-value, and the 450 boundaries of the confidence interval for the mean difference between 451 corresponding elements of sample1 and sample2 at the specified level. 452 453 References: http://en.wikipedia.org/wiki/Student%27s_t-test 454 */ 455 ConfInt pairedTTest(T, U)( 456 T before, 457 U after, 458 double testMean = 0, 459 Alt alt = Alt.twoSided, 460 double confLevel = 0.95 461 ) if(doubleInput!(T) && doubleInput!(U) && isInputRange!T && isInputRange!U) { 462 enforceConfidence(confLevel); 463 dstatsEnforce(isFinite(testMean), "testMean cannot be infinite or nan."); 464 465 MeanSD msd; 466 while(!before.empty && !after.empty) { 467 immutable diff = cast(double) before.front - cast(double) after.front; 468 before.popFront(); 469 after.popFront(); 470 msd.put(diff); 471 } 472 473 dstatsEnforce(before.empty && after.empty, 474 "before and after have different lengths in pairedTTest."); 475 476 return pairedTTest(msd, testMean, alt, confLevel); 477 } 478 479 /** 480 Compute a paired T test directly from summary statistics of the differences 481 between corresponding samples. 482 483 Examples: 484 --- 485 float[] data1 = [8, 6, 7, 5, 3, 0, 9]; 486 float[] data2 = [3, 6, 2, 4, 3, 6, 8]; 487 488 // Calculate summary statistics on difference explicitly. 489 MeanSD summary; 490 foreach(i; 0..data1.length) { 491 summary.put(data1[i] - data2[i]); 492 } 493 494 // Test the null hypothesis that the mean difference between corresponding 495 // elements (data1[i] - data2[i]) is greater than 5 against the null that it 496 // is <= 5. Calculate confidence intervals at 99%. 497 auto result = pairedTTest(summary, 5, Alt.twoSided, 0.99); 498 499 // This is equivalent to: 500 auto result2 = pairedTTest(data1, data2, 5, Alt.twoSided, 0.99); 501 --- 502 503 References: http://en.wikipedia.org/wiki/Student%27s_t-test 504 */ 505 ConfInt pairedTTest(T)( 506 T diffSummary, 507 double testMean = 0, 508 Alt alt = Alt.twoSided, 509 double confLevel = 0.95 510 ) if(isSummary!T) { 511 enforceConfidence(confLevel); 512 dstatsEnforce(isFinite(testMean), "testMean cannot be infinite or nan."); 513 514 if(diffSummary.N < 2) { 515 return ConfInt.init; 516 } 517 518 // Save typing. 519 alias diffSummary msd; 520 521 ConfInt ret; 522 ret.testStat = (msd.mean - testMean) / msd.stdev * sqrt(msd.N); 523 auto sampleMean = msd.mean; 524 auto sampleSd = msd.stdev; 525 double normSd = sampleSd / sqrt(msd.N); 526 ret.testStat = (sampleMean - testMean) / normSd; 527 528 if(alt == Alt.none) { 529 return ret; 530 } else if(alt == Alt.less) { 531 ret.p = studentsTCDF(ret.testStat, msd.N - 1); 532 ret.lowerBound = -double.infinity; 533 534 if(confLevel > 0) { 535 double delta = invStudentsTCDF(1 - confLevel, msd.N - 1) * normSd; 536 ret.upperBound = sampleMean - delta; 537 } else { 538 ret.upperBound = sampleMean; 539 } 540 541 } else if(alt == Alt.greater) { 542 ret.p = studentsTCDF(-ret.testStat, msd.N - 1); 543 ret.upperBound = double.infinity; 544 545 if(confLevel > 0) { 546 double delta = invStudentsTCDF(1 - confLevel, msd.N - 1) * normSd; 547 ret.lowerBound = sampleMean + delta; 548 } else { 549 ret.lowerBound = sampleMean; 550 } 551 552 } else { 553 immutable double t = ret.testStat; 554 ret.p = 2 * ((t < 0) ? 555 studentsTCDF(t, msd.N - 1) : 556 studentsTCDF(-t, msd.N - 1)); 557 558 if(confLevel > 0) { 559 double delta = invStudentsTCDF(0.5 * (1 - confLevel), msd.N - 1) * normSd; 560 ret.lowerBound = sampleMean + delta; 561 ret.upperBound = sampleMean - delta; 562 } else { 563 ret.lowerBound = ret.upperBound = sampleMean; 564 } 565 566 } 567 return ret; 568 } 569 570 unittest { 571 // Values from R. 572 auto t1 = pairedTTest([3,2,3,4,5], [2,3,5,5,6], 1); 573 assert(approxEqual(t1.p, 0.02131)); 574 assert(approxEqual(t1.testStat, -3.6742)); 575 assert(approxEqual(t1.lowerBound, -2.1601748)); 576 assert(approxEqual(t1.upperBound, 0.561748)); 577 578 assert(approxEqual(pairedTTest([3,2,3,4,5], [2,3,5,5,6], 0, Alt.less).p, 0.0889)); 579 assert(approxEqual(pairedTTest([3,2,3,4,5], [2,3,5,5,6], 0, Alt.greater).p, 0.9111)); 580 assert(approxEqual(pairedTTest([3,2,3,4,5], [2,3,5,5,6], 0, Alt.twoSided).p, 0.1778)); 581 assert(approxEqual(pairedTTest([3,2,3,4,5], [2,3,5,5,6], 1, Alt.less).p, 0.01066)); 582 assert(approxEqual(pairedTTest([3,2,3,4,5], [2,3,5,5,6], 1, Alt.greater).p, 0.9893)); 583 } 584 585 /** 586 Tests the null hypothesis that the variances of all groups are equal against 587 the alternative that heteroscedasticity exists. data must be either a 588 tuple of ranges or a range of ranges. central is an alias for the measure 589 of central tendency to be used. This can be any function that maps a 590 forward range of numeric types to a numeric type. The commonly used ones 591 are median (default) and mean (less robust). Trimmed mean is sometimes 592 useful, but is currently not implemented in dstats.summary. 593 594 References: 595 Levene, Howard (1960). "Robust tests for equality of variances". in Ingram 596 Olkin, Harold Hotelling et al. Contributions to Probability and Statistics: 597 Essays in Honor of Harold Hotelling. Stanford University Press. pp. 278-292. 598 599 Examples: 600 --- 601 int[] sample1 = [1,2,3,4,5]; 602 int[] sample2 = [100,200,300,400,500]; 603 auto result = levenesTest(sample1, sample2); 604 605 // Clearly the variances are different between these two samples. 606 assert( approxEqual(result.testStat, 10.08)); 607 assert( approxEqual(result.p, 0.01310)); 608 --- 609 */ 610 TestRes levenesTest(alias central = median, T...)(T data) { 611 return anovaLevene!(true, false, central, T)(data); 612 } 613 614 unittest { 615 // Values from R's car package, which uses the median definition 616 // exclusively. 617 auto res1 = levenesTest([1,2,3,4,5][], [2,4,8,16,32][]); 618 assert(approxEqual(res1.testStat, 3.0316)); 619 assert(approxEqual(res1.p, 0.1198), res1.toString()); 620 621 auto res2 = levenesTest([[1,2,3,4,5][], [100,200,300,400,500,600][]][]); 622 assert(approxEqual(res2.testStat, 13.586)); 623 assert(approxEqual(res2.p, 0.005029)); 624 625 auto res3 = levenesTest([8,6,7,5,3,0,9][], [3,6,2,4,3,6][]); 626 assert(approxEqual(res3.testStat, 1.1406)); 627 assert(approxEqual(res3.p, 0.3084)); 628 } 629 630 /** 631 The F-test is a one-way ANOVA extension of the T-test to >2 groups. 632 It's useful when you have 3 or more groups with equal variance and want 633 to test whether their means are equal. Data can be input as either a 634 tuple or a range. This may contain any combination of ranges of numeric 635 types, MeanSD structs and Summary structs. 636 637 Note: This test makes the assumption that all groups have equal variances, 638 also known as homoskedasticity. For a similar test that does not make these 639 assumptions, see welchAnova. 640 641 Examples: 642 --- 643 uint[] thing1 = [3,1,4,1], 644 thing2 = [5,9,2,6,5,3], 645 thing3 = [5,8,9,7,9,3]; 646 auto result = fTest(thing1, meanStdev(thing2), summary(thing3)); 647 assert(approxEqual(result.testStat, 4.9968)); 648 assert(approxEqual(result.p, 0.02456)); 649 --- 650 651 References: http://en.wikipedia.org/wiki/F-test 652 653 Returns: 654 655 A TestRes containing the F statistic and the P-value for the alternative 656 that the means of the groups are different against the null that they 657 are identical. 658 */ 659 TestRes fTest(T...)(T data) { 660 return anovaLevene!(false, false, "dummy", T)(data); 661 } 662 663 /** 664 Same as fTest, except that this test does not require the assumption of 665 equal variances. In exchange it's slightly less powerful. 666 667 References: 668 669 B.L. Welch. On the Comparison of Several Mean Values: An Alternative Approach 670 Biometrika, Vol. 38, No. 3/4 (Dec., 1951), pp. 330-336. 671 */ 672 TestRes welchAnova(T...)(T data) { 673 return anovaLevene!(false, true, "dummy", T)(data); 674 } 675 676 unittest { 677 // Values from R. 678 uint[] thing1 = [3,1,4,1], 679 thing2 = [5,9,2,6,5,3], 680 thing3 = [5,8,9,7,9,3]; 681 auto result = fTest(thing1, meanStdev(thing2), summary(thing3)); 682 assert(approxEqual(result.testStat, 4.9968)); 683 assert(approxEqual(result.p, 0.02456)); 684 685 auto welchRes1 = welchAnova(thing1, thing2, thing3); 686 assert( approxEqual(welchRes1.testStat, 6.7813)); 687 assert( approxEqual(welchRes1.p, 0.01706)); 688 689 // Test array case. 690 auto res2 = fTest([thing1, thing2, thing3].dup); 691 assert(approxEqual(result.testStat, res2.testStat)); 692 assert(approxEqual(result.p, res2.p)); 693 694 thing1 = [2,7,1,8,2]; 695 thing2 = [8,1,8]; 696 thing3 = [2,8,4,5,9]; 697 auto res3 = fTest(thing1, thing2, thing3); 698 assert(approxEqual(res3.testStat, 0.377)); 699 assert(approxEqual(res3.p, 0.6953)); 700 701 auto res4 = fTest([summary(thing1), summary(thing2), summary(thing3)][]); 702 assert(approxEqual(res4.testStat, res3.testStat)); 703 assert(approxEqual(res4.testStat, res3.testStat)); 704 705 auto welchRes2 = welchAnova(summary(thing1), thing2, thing3); 706 assert( approxEqual(welchRes2.testStat, 0.342)); 707 assert( approxEqual(welchRes2.p, 0.7257)); 708 709 auto res5 = fTest([1, 2, 4], [3]); 710 assert(approxEqual(res5.testStat, 0.1429)); 711 assert(approxEqual(res5.p, 0.7418)); 712 } 713 714 // Levene's Test, Welch ANOVA and F test have massive overlap at the 715 // implementation level but less at the conceptual level, so I've combined 716 // the implementations into one horribly complicated but well-encapsulated 717 // templated function but left the interfaces as three unrelated functions. 718 private TestRes anovaLevene(bool levene, bool welch, alias central, T...) 719 (T dataIn) { 720 static if(dataIn.length == 1) { 721 auto alloc = newRegionAllocator(); 722 auto data = alloc.array(dataIn[0]); 723 auto withins = alloc.uninitializedArray!(MeanSD[])(data.length); 724 withins[] = MeanSD.init; 725 } else { 726 enum len = dataIn.length; 727 alias dataIn data; 728 MeanSD[len] withins; 729 } 730 731 static if(levene) { 732 static if(dataIn.length == 1) { 733 auto centers = alloc.uninitializedArray!(double[])(data.length); 734 } else { 735 double[len] centers; 736 } 737 738 foreach(i, category; data) { 739 static assert( isForwardRange!(typeof(category)) && 740 is(Unqual!(ElementType!(typeof(category))) : double), 741 "Can only perform Levene's test on input ranges of elements " ~ 742 "implicitly convertible to doubles."); 743 744 // The cast is to force conversions to double on alias this'd stuff 745 // like the Mean struct. 746 centers[i] = cast(double) central(category.save); 747 } 748 749 double preprocess(double dataPoint, size_t category) { 750 return abs(dataPoint - centers[category]); 751 } 752 } else { 753 static double preprocess(double dataPoint, size_t category) { 754 return dataPoint; 755 } 756 } 757 758 759 auto DFGroups = data.length - 1; 760 ulong N = 0; 761 762 foreach(category, range; data) { 763 static if(isInputRange!(typeof(range)) && 764 is(Unqual!(ElementType!(typeof(range))) : double)) { 765 foreach(elem; range) { 766 double preprocessed = preprocess(elem, category); 767 withins[category].put(preprocessed); 768 N++; 769 } 770 } else static if(isSummary!(typeof(range))) { 771 withins[category] = range.toMeanSD(); 772 N += roundTo!long(range.N); 773 } else { 774 static assert(0, "Can only perform ANOVA on input ranges of " ~ 775 "numeric types, MeanSD structs and Summary structs, not a " ~ 776 typeof(range).stringof ~ "."); 777 } 778 } 779 780 static if(!welch) { 781 immutable ulong DFDataPoints = N - data.length; 782 double mu = 0; 783 foreach(summary; withins) { 784 mu += summary.mean * (summary.N / N); 785 } 786 787 double totalWithin = 0; 788 double totalBetween = 0; 789 foreach(group; withins) { 790 totalWithin += group.mse * (group.N / DFDataPoints); 791 immutable diffSq = (group.mean - mu) * (group.mean - mu); 792 totalBetween += diffSq * (group.N / DFGroups); 793 } 794 795 immutable F = totalBetween / totalWithin; 796 if(isNaN(F)) { 797 return TestRes.init; 798 } 799 800 return TestRes(F, fisherCDFR(F, DFGroups, DFDataPoints)); 801 } else { 802 immutable double k = data.length; 803 double sumW = 0; 804 foreach(summary; withins) { 805 sumW += summary.N / summary.var; 806 } 807 808 double sumFt = 0; 809 foreach(summary; withins) { 810 sumFt += ((1 - summary.N / summary.var / sumW) ^^ 2) / (summary.N - 1); 811 } 812 813 immutable kSqM1 = (k * k - 1.0); 814 immutable df2 = 1.0 / (3.0 / kSqM1 * sumFt); 815 immutable denom = 1 + 2 * (k - 2.0) / kSqM1 * sumFt; 816 817 double yHat = 0; 818 foreach(i, summary; withins) { 819 yHat += summary.mean * (summary.N / summary.var); 820 } 821 yHat /= sumW; 822 823 double numerator = 0; 824 foreach(i, summary; withins) { 825 numerator += summary.N / summary.var * ((summary.mean - yHat) ^^ 2); 826 } 827 numerator /= (k - 1); 828 829 immutable F = numerator / denom; 830 if(isNaN(F)) { 831 return TestRes.init; 832 } 833 834 return TestRes(F, fisherCDFR(F, DFGroups, df2)); 835 } 836 } 837 838 /**Performs a correlated sample (within-subjects) ANOVA. This is a 839 * generalization of the paired T-test to 3 or more treatments. This 840 * function accepts data as either a tuple of ranges (1 for each treatment, 841 * such that a given index represents the same subject in each range) or 842 * similarly as a range of ranges. 843 * 844 * Returns: A TestRes with the F-statistic and P-value for the null that 845 * the the variable being measured did not vary across treatments against the 846 * alternative that it did. 847 * 848 * Examples: 849 * --- 850 * // Test the hypothesis that alcohol, loud music, caffeine and sleep 851 * // deprivation all have equivalent effects on programming ability. 852 * 853 * uint[] alcohol = [8,6,7,5,3,0,9]; 854 * uint[] caffeine = [3,6,2,4,3,6,8]; 855 * uint[] noSleep = [3,1,4,1,5,9,2]; 856 * uint[] loudMusic = [2,7,1,8,2,8,1]; 857 * // Subject 0 had ability of 8 under alcohol, 3 under caffeine, 3 under 858 * // no sleep, 2 under loud music. Subject 1 had ability of 6 under alcohol, 859 * // 6 under caffeine, 1 under no sleep, and 7 under loud music, etc. 860 * auto result = correlatedAnova(alcohol, caffeine, noSleep, loudMusic); 861 * --- 862 * 863 * References: "Concepts and Applications of Inferrential Statistics". 864 * Richard Lowry. Vassar College. version. 865 * http://faculty.vassar.edu/lowry/webtext.html 866 */ 867 TestRes correlatedAnova(T...)(T dataIn) 868 if(allSatisfy!(isInputRange, T)) { 869 static if(dataIn.length == 1 && isInputRange!(typeof(dataIn[0].front))) { 870 auto alloc = newRegionAllocator(); 871 auto data = alloc.array(dataIn[0]); 872 auto withins = alloc.newArray!(MeanSD[])(data.length); 873 } else { 874 enum len = dataIn.length; 875 alias dataIn data; 876 MeanSD[len] withins; 877 } 878 MeanSD overallSumm; 879 double nGroupNeg1 = 1.0 / data.length; 880 881 bool someEmpty() { 882 foreach(elem; data) { 883 if(elem.empty) { 884 return true; 885 } 886 } 887 return false; 888 } 889 890 uint nSubjects = 0; 891 double subjSum = 0; 892 while(!someEmpty) { 893 double subjSumInner = 0; 894 foreach(i, elem; data) { 895 auto dataPoint = elem.front; 896 subjSumInner += dataPoint; 897 overallSumm.put(dataPoint); 898 withins[i].put(dataPoint); 899 data[i].popFront; 900 } 901 nSubjects++; 902 subjSum += subjSumInner * subjSumInner * nGroupNeg1; 903 } 904 double groupSum = 0; 905 foreach(elem; withins) { 906 groupSum += elem.mean * elem.N; 907 } 908 909 groupSum /= sqrt(cast(double) nSubjects * data.length); 910 groupSum *= groupSum; 911 immutable subjErr = subjSum - groupSum; 912 913 double betweenDev = 0; 914 immutable mu = overallSumm.mean; 915 foreach(group; withins) { 916 double diff = (group.mean - mu); 917 diff *= diff; 918 betweenDev += diff * (group.N / (data.length - 1)); 919 } 920 921 size_t errDf = data.length * nSubjects - data.length - nSubjects + 1; 922 double randError = -subjErr / errDf; 923 foreach(group; withins) { 924 randError += group.mse * (group.N / errDf); 925 } 926 927 immutable F = betweenDev / randError; 928 if(!(F >= 0)) { 929 return TestRes(double.nan, double.nan); 930 } 931 932 return TestRes(F, fisherCDFR(F, data.length - 1, errDf)); 933 } 934 935 unittest { 936 // Values from VassarStats utility at 937 // http://faculty.vassar.edu/lowry/VassarStats.html, but they like to 938 // round a lot, so the approxEqual tolerances are fairly wide. I 939 // think it's adequate to demonstrate the correctness of this function, 940 // though. 941 uint[] alcohol = [8,6,7,5,3,0,9]; 942 uint[] caffeine = [3,6,2,4,3,6,8]; 943 uint[] noSleep = [3,1,4,1,5,9,2]; 944 uint[] loudMusic = [2,7,1,8,2,8,1]; 945 auto result = correlatedAnova(alcohol, caffeine, noSleep, loudMusic); 946 assert(approxEqual(result.testStat, 0.43, 0.0, 0.01)); 947 assert(approxEqual(result.p, 0.734, 0.0, 0.01)); 948 949 uint[] stuff1 = [3,4,2,6]; 950 uint[] stuff2 = [4,1,9,8]; 951 auto result2 = correlatedAnova([stuff1, stuff2].dup); 952 assert(approxEqual(result2.testStat, 0.72, 0.0, 0.01)); 953 assert(approxEqual(result2.p, 0.4584, 0.0, 0.01)); 954 } 955 956 /**The Kruskal-Wallis rank sum test. Tests the null hypothesis that data in 957 * each group is not stochastically ordered with respect to data in each other 958 * groups. This is a one-way non-parametric ANOVA and can be thought of 959 * as either a generalization of the Wilcoxon rank sum test to >2 groups or 960 * a non-parametric equivalent to the F-test. Data can be input as either a 961 * tuple of ranges (one range for each group) or a range of ranges 962 * (one element for each group). 963 * 964 * Bugs: Asymptotic approximation of P-value only, not exact. In this case, 965 * I'm not sure a practical way to compute the exact P-value even exists. 966 * 967 * Returns: A TestRes with the K statistic and the P-value for the null that 968 * no group is stochastically larger than any other against the alternative that 969 * groups are stochastically ordered. 970 * 971 * References: "Concepts and Applications of Inferrential Statistics". 972 * Richard Lowry. Vassar College. version. 973 * http://faculty.vassar.edu/lowry/webtext.html 974 */ 975 TestRes kruskalWallis(T...)(T dataIn) 976 if(doubleInput!(typeof(dataIn[0].front)) || allSatisfy!(doubleInput, T)) { 977 auto alloc = newRegionAllocator(); 978 size_t N = 0; 979 980 static if(dataIn.length == 1 && isInputRange!(typeof(dataIn[0].front))) { 981 auto data = alloc.array(dataIn[0]); 982 alias ElementType!(typeof(data[0])) C; 983 static if(hasLength!(typeof(data[0]))) { 984 enum bool useLength = true; 985 } else { 986 enum bool useLength = false; 987 } 988 } else { 989 enum len = dataIn.length; 990 alias dataIn data; 991 alias staticMap!(ElementType, T) Es; 992 alias CommonType!(Es) C; 993 static if(allSatisfy!(hasLength, T)) { 994 enum bool useLength = true; 995 } else { 996 enum bool useLength = false; 997 } 998 } 999 1000 size_t[] lengths = alloc.uninitializedArray!(size_t[])(data.length); 1001 static if(useLength) { 1002 foreach(i, rng; data) { 1003 auto rngLen = rng.length; 1004 lengths[i] = rngLen; 1005 N += rngLen; 1006 } 1007 auto dataArray = alloc.uninitializedArray!(Unqual!(C)[])(N); 1008 size_t pos = 0; 1009 foreach(rng; data) { 1010 foreach(elem; rng) { 1011 dataArray[pos++] = elem; 1012 } 1013 } 1014 } else { 1015 auto app = appender!(Unqual!(C)[])(); 1016 foreach(i, rng; data) { 1017 size_t oldLen = dataArray.length; 1018 app.put(rng); 1019 lengths[i] = dataArray.length - oldLen; 1020 N += lengths[i]; 1021 } 1022 auto dataArray = app.data; 1023 } 1024 1025 double[] ranks = alloc.uninitializedArray!(double[])(dataArray.length); 1026 try { 1027 rankSort(dataArray, ranks); 1028 } catch(SortException) { 1029 return TestRes.init; 1030 } 1031 1032 size_t index = 0; 1033 double denom = 0, numer = 0; 1034 double rBar = 0.5 * (N + 1); 1035 foreach(meanI, l; lengths) { 1036 Mean groupStats; 1037 foreach(i; index..index + l) { 1038 groupStats.put( ranks[i]); 1039 double diff = ranks[i] - rBar; 1040 diff *= diff; 1041 denom += diff; 1042 } 1043 index += l; 1044 double nDiff = groupStats.mean - rBar; 1045 nDiff *= nDiff; 1046 numer += l * nDiff; 1047 } 1048 double K = (N - 1) * (numer / denom); 1049 1050 // Tie correction. 1051 double tieSum = 0; 1052 uint nTies = 1; 1053 foreach(i; 1..dataArray.length) { 1054 if(dataArray[i] == dataArray[i - 1]) { 1055 nTies++; 1056 } else if(nTies > 1) { 1057 double partialSum = nTies; 1058 partialSum = (partialSum * partialSum * partialSum) - partialSum; 1059 tieSum += partialSum; 1060 nTies = 1; 1061 } 1062 } 1063 if(nTies > 1) { 1064 double partialSum = nTies; 1065 partialSum = (partialSum * partialSum * partialSum) - partialSum; 1066 tieSum += partialSum; 1067 } 1068 double tieDenom = N; 1069 tieDenom = (tieDenom * tieDenom * tieDenom) - tieDenom; 1070 tieSum = 1 - (tieSum / tieDenom); 1071 K *= tieSum; 1072 1073 if(isNaN(K)) { 1074 return TestRes(double.nan, double.nan); 1075 } 1076 1077 return TestRes(K, chiSquareCDFR(K, data.length - 1)); 1078 } 1079 1080 unittest { 1081 // These values are from the VassarStat web tool at 1082 // http://faculty.vassar.edu/lowry/VassarStats.html . 1083 // R is actually wrong here because it apparently doesn't use a correction 1084 // for ties. 1085 auto res1 = kruskalWallis([3,1,4,1].idup, [5,9,2,6].dup, [5,3,5].dup); 1086 assert(approxEqual(res1.testStat, 4.15)); 1087 assert(approxEqual(res1.p, 0.1256)); 1088 1089 // Test for other input types. 1090 auto res2 = kruskalWallis([[3,1,4,1].idup, [5,9,2,6].idup, [5,3,5].idup].dup); 1091 assert(res2 == res1); 1092 auto res3 = kruskalWallis(map!"a"([3,1,4,1].dup), [5,9,2,6].dup, [5,3,5].dup); 1093 assert(res3 == res1); 1094 auto res4 = kruskalWallis([map!"a"([3,1,4,1].dup), 1095 map!"a"([5,9,2,6].dup), 1096 map!"a"([5,3,5].dup)].dup); 1097 assert(res4 == res1); 1098 1099 // Test w/ one more case, just with one input type. 1100 auto res5 = kruskalWallis([2,7,1,8,2].dup, [8,1,8,2].dup, [8,4,5,9,2].dup, 1101 [7,1,8,2,8,1,8].dup); 1102 assert(approxEqual(res5.testStat, 1.06)); 1103 assert(approxEqual(res5.p, 0.7867)); 1104 } 1105 1106 /**The Friedman test is a non-parametric within-subject ANOVA. It's useful 1107 * when parametric assumptions cannot be made. Usage is identical to 1108 * correlatedAnova(). 1109 * 1110 * References: "Concepts and Applications of Inferrential Statistics". 1111 * Richard Lowry. Vassar College. version. 1112 * http://faculty.vassar.edu/lowry/webtext.html 1113 * 1114 * Bugs: No exact P-value calculation. Asymptotic approx. only. 1115 */ 1116 TestRes friedmanTest(T...)(T dataIn) 1117 if(doubleInput!(typeof(dataIn[0].front)) || allSatisfy!(doubleInput, T)) { 1118 static if(dataIn.length == 1 && isInputRange!(typeof(dataIn[0].front))) { 1119 auto alloc = newRegionAllocator(); 1120 auto data = alloc.array(dataIn[0]); 1121 auto ranks = alloc.uninitializedArray!(double[])(data.length); 1122 auto dataPoints = alloc.uninitializedArray!(double[])(data.length); 1123 auto colMeans = alloc.newArray!(Mean[])(data.length); 1124 } else { 1125 enum len = dataIn.length; 1126 alias dataIn data; 1127 double[len] ranks; 1128 double[len] dataPoints; 1129 Mean[len] colMeans; 1130 } 1131 double rBar = cast(double) data.length * (data.length + 1.0) / 2.0; 1132 MeanSD overallSumm; 1133 1134 bool someEmpty() { 1135 foreach(elem; data) { 1136 if(elem.empty) { 1137 return true; 1138 } 1139 } 1140 return false; 1141 } 1142 1143 uint N = 0; 1144 while(!someEmpty) { 1145 foreach(i, range; data) { 1146 dataPoints[i] = data[i].front; 1147 data[i].popFront; 1148 } 1149 1150 try { 1151 rank(dataPoints[], ranks[]); 1152 } catch(SortException) { 1153 return TestRes.init; 1154 } 1155 1156 foreach(i, rank; ranks) { 1157 colMeans[i].put(rank); 1158 overallSumm.put(rank); 1159 } 1160 N++; 1161 } 1162 1163 double between = 0; 1164 double mu = overallSumm.mean; 1165 foreach(mean; colMeans) { 1166 double diff = mean.mean - overallSumm.mean; 1167 between += diff * diff; 1168 } 1169 between *= N; 1170 double within = overallSumm.mse * (overallSumm.N / (overallSumm.N - N)); 1171 double chiSq = between / within; 1172 double df = data.length - 1; 1173 return TestRes(chiSq, chiSquareCDFR(chiSq, df)); 1174 } 1175 1176 unittest { 1177 // Values from R 1178 uint[] alcohol = [8,6,7,5,3,0,9]; 1179 uint[] caffeine = [3,6,2,4,3,6,8]; 1180 uint[] noSleep = [3,1,4,1,5,9,2]; 1181 uint[] loudMusic = [2,7,1,8,2,8,1]; 1182 auto result = friedmanTest(alcohol, caffeine, noSleep, loudMusic); 1183 assert(approxEqual(result.testStat, 1.7463)); 1184 assert(approxEqual(result.p, 0.6267)); 1185 1186 uint[] stuff1 = [3,4,2,6]; 1187 uint[] stuff2 = [4,1,9,8]; 1188 auto result2 = friedmanTest([stuff1, stuff2].dup); 1189 assert(approxEqual(result2.testStat, 1)); 1190 assert(approxEqual(result2.p, 0.3173)); 1191 } 1192 1193 /**Computes Wilcoxon rank sum test statistic and P-value for 1194 * a set of observations against another set, using the given alternative. 1195 * Alt.less means that sample1 is stochastically less than sample2. 1196 * Alt.greater means sample1 is stochastically greater than sample2. 1197 * Alt.twoSided means sample1 is stochastically less than or greater than 1198 * sample2. 1199 * 1200 * exactThresh is the threshold value of (n1 + n2) at which this function 1201 * switches from exact to approximate computation of the p-value. Do not set 1202 * exactThresh to more than 200, as the exact 1203 * calculation is both very slow and not numerically stable past this point, 1204 * and the asymptotic calculation is very good for N this large. To disable 1205 * exact calculation entirely, set exactThresh to 0. 1206 * 1207 * Notes: Exact p-value computation is never used when ties are present in the 1208 * data, because it is not computationally feasible. 1209 * 1210 * Input ranges for this function must define a length. 1211 * 1212 * This test is also known as the Mann-Whitney U test. 1213 * 1214 * Returns: A TestRes containing the W test statistic and the P-value against 1215 * the given alternative. 1216 * 1217 * References: http://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U 1218 * 1219 * StackOverflow Question 376003 http://stackoverflow.com/questions/376003 1220 * 1221 * Loughborough University MLSC Statistics 2.3 The Mann-Whitney U Test 1222 * http://mlsc.lboro.ac.uk/resources/statistics/Mannwhitney.pdf 1223 */ 1224 TestRes wilcoxonRankSum(T, U) 1225 (T sample1, U sample2, Alt alt = Alt.twoSided, uint exactThresh = 50) 1226 if(isInputRange!T && isInputRange!U && 1227 is(typeof(sample1.front < sample2.front) == bool) && 1228 is(CommonType!(ElementType!T, ElementType!U))) { 1229 1230 auto alloc = newRegionAllocator(); 1231 alias Unqual!(CommonType!(ElementType!(T), ElementType!(U))) C; 1232 1233 static if(hasLength!T && hasLength!U) { 1234 auto n1 = sample1.length, n2 = sample2.length, N = n1 + n2; 1235 auto combined = alloc.uninitializedArray!(C[])(N); 1236 copy(sample1, combined[0..n1]); 1237 copy(sample2, combined[n1..$]); 1238 } else { 1239 auto app = appender!(C[])(); 1240 1241 foreach(elem; sample1) { 1242 app.put(elem); 1243 } 1244 1245 uint n1 = app.data.length; 1246 foreach(elem; sample2) { 1247 app.put(elem); 1248 } 1249 1250 auto combined = app.data; 1251 uint N = combined.length; 1252 uint n2 = N - n1; 1253 } 1254 1255 double[] ranks = alloc.uninitializedArray!(double[])(N); 1256 try { 1257 rankSort(combined, ranks); 1258 } catch(SortException) { 1259 return TestRes.init; 1260 } 1261 double w = reduce!("a + b") 1262 (0.0, ranks[0..n1]) - cast(ulong) n1 * (n1 + 1) / 2UL; 1263 1264 if(alt == Alt.none) { 1265 return TestRes(w); 1266 } 1267 1268 double tieSum = 0; 1269 // combined is sorted by rankSort. Can use it to figure out how many 1270 // ties we have w/o another allocation or sorting. 1271 enum oneOverTwelve = 1.0 / 12.0; 1272 tieSum = 0; 1273 ulong nties = 1; 1274 foreach(i; 1..N) { 1275 if(combined[i] == combined[i - 1]) { 1276 nties++; 1277 } else { 1278 if(nties == 1) 1279 continue; 1280 tieSum += ((nties * nties * nties) - nties) * oneOverTwelve; 1281 nties = 1; 1282 } 1283 } 1284 // Handle last run. 1285 if(nties > 1) { 1286 tieSum += ((nties * nties * nties) - nties) * oneOverTwelve; 1287 } 1288 1289 immutable p = wilcoxonRankSumPval(w, n1, n2, alt, tieSum, exactThresh); 1290 return TestRes(w, p); 1291 } 1292 1293 unittest { 1294 // Values from R. 1295 1296 assert(wilcoxonRankSum([1, 2, 3, 4, 5].dup, [2, 4, 6, 8, 10].dup).testStat == 5); 1297 assert(wilcoxonRankSum([2, 4, 6, 8, 10].dup, [1, 2, 3, 4, 5].dup).testStat == 20); 1298 assert(wilcoxonRankSum([3, 7, 21, 5, 9].dup, [2, 4, 6, 8, 10].dup).testStat == 15); 1299 1300 // Simple stuff (no ties) first. Testing approximate 1301 // calculation first. 1302 assert(approxEqual(wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup, 1303 Alt.twoSided, 0), 0.9273)); 1304 assert(approxEqual(wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup, 1305 Alt.less, 0), 0.6079)); 1306 assert(approxEqual(wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup, 1307 Alt.greater, 0).p, 0.4636)); 1308 assert(approxEqual(wilcoxonRankSum([1,2,6,10,12].dup, [3,5,7,8,13,15].dup, 1309 Alt.twoSided, 0).p, 0.4113)); 1310 assert(approxEqual(wilcoxonRankSum([1,2,6,10,12].dup, [3,5,7,8,13,15].dup, 1311 Alt.less, 0).p, 0.2057)); 1312 assert(approxEqual(wilcoxonRankSum([1,2,6,10,12].dup, 1313 map!"a"([3,5,7,8,13,15].dup), Alt.greater, 0).p, 0.8423)); 1314 assert(approxEqual(wilcoxonRankSum([1,3,5,7,9].dup, [2,4,6,8,10].dup, 1315 Alt.twoSided, 0), .6745)); 1316 assert(approxEqual(wilcoxonRankSum([1,3,5,7,9].dup, [2,4,6,8,10].dup, 1317 Alt.less, 0), .3372)); 1318 assert(approxEqual(wilcoxonRankSum([1,3,5,7,9].dup, [2,4,6,8,10].dup, 1319 Alt.greater, 0), .7346)); 1320 1321 // Now, lots of ties. 1322 assert(approxEqual(wilcoxonRankSum([1,2,3,4,5].dup, [2,3,4,5,6].dup, 1323 Alt.twoSided, 0), 0.3976)); 1324 assert(approxEqual(wilcoxonRankSum([1,2,3,4,5].dup, [2,3,4,5,6].dup, 1325 Alt.less, 0), 0.1988)); 1326 assert(approxEqual(wilcoxonRankSum([1,2,3,4,5].dup, [2,3,4,5,6].dup, 1327 Alt.greater, 0), 0.8548)); 1328 assert(approxEqual(wilcoxonRankSum([1,2,1,1,2].dup, [1,2,3,1,1].dup, 1329 Alt.twoSided, 0), 0.9049)); 1330 assert(approxEqual(wilcoxonRankSum([1,2,1,1,2].dup, [1,2,3,1,1].dup, 1331 Alt.less, 0), 0.4524)); 1332 assert(approxEqual(wilcoxonRankSum([1,2,1,1,2].dup, [1,2,3,1,1].dup, 1333 Alt.greater, 0), 0.64)); 1334 1335 // Now, testing the exact calculation on the same data. 1336 assert(approxEqual(wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup, 1337 Alt.twoSided), 0.9307)); 1338 assert(approxEqual(wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup, 1339 Alt.less), 0.6039)); 1340 assert(approxEqual(wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup, 1341 Alt.greater), 0.4654)); 1342 assert(approxEqual(wilcoxonRankSum([1,2,6,10,12].dup, [3,5,7,8,13,15].dup, 1343 Alt.twoSided), 0.4286)); 1344 assert(approxEqual(wilcoxonRankSum([1,2,6,10,12].dup, [3,5,7,8,13,15].dup, 1345 Alt.less), 0.2143)); 1346 assert(approxEqual(wilcoxonRankSum([1,2,6,10,12].dup, [3,5,7,8,13,15].dup, 1347 Alt.greater), 0.8355)); 1348 assert(approxEqual(wilcoxonRankSum([1,3,5,7,9].dup, [2,4,6,8,10].dup, 1349 Alt.twoSided), .6905)); 1350 assert(approxEqual(wilcoxonRankSum([1,3,5,7,9].dup, [2,4,6,8,10].dup, 1351 Alt.less), .3452)); 1352 assert(approxEqual(wilcoxonRankSum([1,3,5,7,9].dup, [2,4,6,8,10].dup, 1353 Alt.greater), .7262)); 1354 } 1355 1356 private 1357 double wilcoxonRankSumPval(double w, ulong n1, ulong n2, Alt alt = Alt.twoSided, 1358 double tieSum = 0, uint exactThresh = 50) { 1359 if(alt == Alt.none) { 1360 return double.nan; 1361 } 1362 1363 immutable double N = n1 + n2; 1364 1365 if(N < exactThresh && tieSum == 0) { 1366 return wilcoxRSPExact(roundTo!uint(w), cast(uint) n1, cast(uint) n2, alt); 1367 } 1368 1369 immutable sd = sqrt(cast(double) (n1 * n2) / (N * (N - 1)) * 1370 ((N * N * N - N) / 12 - tieSum)); 1371 1372 // Can happen if all samples are tied. 1373 if(!(sd > 0)) { 1374 return double.nan; 1375 } 1376 1377 immutable mean = (n1 * n2) / 2.0; 1378 1379 if(alt == Alt.twoSided) { 1380 if(abs(w - mean) < 0.5) { 1381 return 1; 1382 } else if(w < mean) { 1383 return 2 * normalCDF(w + 0.5, mean, sd); 1384 } else { 1385 assert(w > mean); 1386 return 2 * normalCDFR(w - 0.5, mean, sd); 1387 } 1388 } else if(alt == Alt.less) { 1389 return normalCDF(w + 0.5, mean, sd); 1390 } else if(alt == Alt.greater) { 1391 return normalCDFR(w - 0.5, mean, sd); 1392 } 1393 1394 assert(0); 1395 } 1396 1397 unittest { 1398 /* Values from R. I could only get good values for Alt.less directly. 1399 * Using W-values to test Alt.twoSided, Alt.greater indirectly.*/ 1400 assert(approxEqual(wilcoxonRankSumPval(1200, 50, 50, Alt.less), .3670)); 1401 assert(approxEqual(wilcoxonRankSumPval(1500, 50, 50, Alt.less), .957903)); 1402 assert(approxEqual(wilcoxonRankSumPval(8500, 100, 200, Alt.less), .01704)); 1403 auto w = wilcoxonRankSum([2,4,6,8,12].dup, [1,3,5,7,11,9].dup).testStat; 1404 assert(approxEqual(wilcoxonRankSumPval(w, 5, 6), 0.9273)); 1405 assert(approxEqual(wilcoxonRankSumPval(w, 5, 6, Alt.greater), 0.4636)); 1406 assert(approxEqual(wilcoxonRankSumPval(w, 5, 6, Alt.less), 0.6079)); 1407 1408 // Monte carlo unit testing: Make sure that the exact and asymptotic 1409 // versions agree within a small epsilon; 1410 double maxEpsilon = 0; 1411 foreach(i; 0..1_000) { 1412 uint n1 = uniform(5U, 25U); 1413 uint n2 = uniform(5U, 25U); 1414 uint testStat = uniform!"[]"(0, (n1 * n2)); 1415 1416 foreach(alt; [Alt.less, Alt.greater, Alt.twoSided]) { 1417 double approxP = wilcoxonRankSumPval(testStat, n1, n2, alt, 0, 0); 1418 double exactP = wilcoxonRankSumPval(testStat, n1, n2, alt, 0, 50); 1419 double epsilon = abs(approxP - exactP); 1420 assert(epsilon < 0.02); 1421 maxEpsilon = max(maxEpsilon, epsilon); 1422 } 1423 } 1424 } 1425 1426 /* Used internally by wilcoxonRankSum. This function uses dynamic 1427 * programming to count the number of combinations of numbers [1..N] that sum 1428 * of length n1 that sum to <= W in O(N * W * n1) time. 1429 * Algorithm obtained from StackOverflow Question 376003 1430 * (http://stackoverflow.com/questions/376003).*/ 1431 private double wilcoxRSPExact(uint W, uint n1, uint n2, Alt alt = Alt.twoSided) { 1432 uint N = n1 + n2; 1433 immutable maxPossible = n1 * n2; 1434 1435 switch(alt) { 1436 case Alt.less: 1437 if(W >= maxPossible) { // Value impossibly large 1438 return 1; 1439 } else if(W * 2 <= maxPossible) { 1440 break; 1441 } else { 1442 return 1 - wilcoxRSPExact(maxPossible - W - 1, n1, n2, Alt.less); 1443 } 1444 assert(0); 1445 case Alt.greater: 1446 if(W > maxPossible) { // Value impossibly large 1447 return 0; 1448 } else if(W * 2 >= maxPossible) { 1449 return wilcoxRSPExact(maxPossible - W, n1, n2, Alt.less); 1450 } else if(W <= 0) { 1451 return 1; 1452 } else { 1453 return 1 - wilcoxRSPExact(W - 1, n1, n2, Alt.less); 1454 } 1455 assert(0); 1456 case Alt.twoSided: 1457 if(W * 2 <= maxPossible) { 1458 return min(1, wilcoxRSPExact(W, n1, n2, Alt.less) + 1459 wilcoxRSPExact(maxPossible - W, n1, n2, Alt.greater)); 1460 } else { 1461 return min(1, wilcoxRSPExact(W, n1, n2, Alt.greater) + 1462 wilcoxRSPExact(maxPossible - W, n1, n2, Alt.less)); 1463 } 1464 assert(0); 1465 default: 1466 assert(0); 1467 } 1468 1469 W += n1 * (n1 + 1) / 2UL; 1470 1471 auto alloc = newRegionAllocator(); 1472 float* cache = (alloc.uninitializedArray!(float[])((n1 + 1) * (W + 1))).ptr; 1473 float* cachePrev = (alloc.uninitializedArray!(float[])((n1 + 1) * (W + 1))).ptr; 1474 cache[0..(n1 + 1) * (W + 1)] = 0; 1475 cachePrev[0..(n1 + 1) * (W + 1)] = 0; 1476 1477 /* Using doubles for the intermediate steps is too slow, but I didn't want to 1478 * lose too much precision. Since my sums must be between 0 and 1, I am 1479 * using the entire bit space of a float to hold numbers between zero and 1480 * one. This is precise to at least 1e-7. This is good enough for a few 1481 * reasons: 1482 * 1483 * 1. This is a p-value, and therefore will likely not be used in 1484 * further calculations where rounding error would accumulate. 1485 * 2. If this is too slow, the alternative is to use the asymptotic 1486 * approximation. This is can have relative errors of several orders 1487 * of magnitude in the tails of the distribution, and is therefore 1488 * clearly worse. 1489 * 3. For very large N, where this function could give completely wrong 1490 * answers, it would be so slow that any reasonable person would use the 1491 * asymptotic approximation anyhow.*/ 1492 1493 1494 // Algorithm based on StackOverflow question 376003. 1495 double comb = exp(-logNcomb(N, n1)); 1496 double floatMax = cast(double) float.max; 1497 cache[0] = cast(float) (comb * floatMax); 1498 cachePrev[0] = cast(float) (comb * floatMax); 1499 1500 foreach(i; 1..N + 1) { 1501 swap(cache, cachePrev); 1502 foreach(k; 1..min(i + 1, n1 + 1)) { 1503 1504 uint minW = k * (k + 1) / 2; 1505 float* curK = cache + k * (W + 1); 1506 float* prevK = cachePrev + k * (W + 1); 1507 float* prevKm1 = cachePrev + (k - 1) * (W + 1); 1508 1509 foreach(w; minW..W + 1) { 1510 curK[w] = prevK[w] + ((i <= w) ? prevKm1[w - i] : 0); 1511 } 1512 } 1513 } 1514 1515 double sum = 0; 1516 float* lastLine = cache + n1 * (W + 1); 1517 foreach(w; 1..W + 1) { 1518 sum += (cast(double) lastLine[w] / floatMax); 1519 } 1520 1521 return sum; 1522 } 1523 1524 unittest { 1525 // Values from R. 1526 assert(approxEqual(wilcoxRSPExact(14, 5, 6), 0.9307)); 1527 assert(approxEqual(wilcoxRSPExact(14, 5, 6, Alt.less), 0.4654)); 1528 assert(approxEqual(wilcoxRSPExact(14, 5, 6, Alt.greater), 0.6039)); 1529 assert(approxEqual(wilcoxRSPExact(16, 6, 5), 0.9307)); 1530 assert(approxEqual(wilcoxRSPExact(16, 6, 5, Alt.less), 0.6039)); 1531 assert(approxEqual(wilcoxRSPExact(16, 6, 5, Alt.greater), 0.4654)); 1532 assert(approxEqual(wilcoxRSPExact(66, 10, 35, Alt.less), 0.001053)); 1533 assert(approxEqual(wilcoxRSPExact(78, 13, 6, Alt.less), 1)); 1534 1535 // Mostly to make sure that underflow doesn't happen until 1536 // the N's are truly unreasonable: 1537 //assert(approxEqual(wilcoxRSPExact(6_000, 120, 120, Alt.less), 0.01276508)); 1538 } 1539 1540 /**Computes a test statistic and P-value for a Wilcoxon signed rank test against 1541 * the given alternative. Alt.less means that elements of before are stochastically 1542 * less than corresponding elements of after. Alt.greater means elements of 1543 * before are stochastically greater than corresponding elements of after. 1544 * Alt.twoSided means there is a significant difference in either direction. 1545 * 1546 * exactThresh is the threshold value of before.length at which this function 1547 * switches from exact to approximate computation of the p-value. Do not set 1548 * exactThresh to more than 200, as the exact calculation is both very slow and 1549 * not numerically stable past this point, and the asymptotic calculation is 1550 * very good for N this large. To disable exact calculation entirely, set 1551 * exactThresh to 0. 1552 * 1553 * Notes: Exact p-value computation is never used when ties are present, 1554 * because it is not computationally feasible. 1555 * 1556 * The input ranges for this function must define a length and must be 1557 * forward ranges. 1558 * 1559 * Returns: A TestRes of the W statistic and the p-value against the given 1560 * alternative. 1561 * 1562 * References: http://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test 1563 * 1564 * StackOverflow Question 376003 http://stackoverflow.com/questions/376003 1565 * 1566 * Handbook of Parametric and nonparametric statistical procedures. David Sheskin. 1567 * Third Edition. (2004) CRC Press. Pg. 616. 1568 */ 1569 TestRes wilcoxonSignedRank(T, U)(T before, U after, Alt alt = Alt.twoSided, uint exactThresh = 50) 1570 if(doubleInput!(T) && doubleInput!(U) && 1571 is(typeof(before.front - after.front) : double)) { 1572 uint nZero = 0; 1573 byte sign(double input) { 1574 if(input < 0) 1575 return -1; 1576 if(input > 0) 1577 return 1; 1578 nZero++; 1579 return 0; 1580 } 1581 1582 auto alloc = newRegionAllocator(); 1583 1584 static if(hasLength!T && hasLength!U) { 1585 dstatsEnforce(before.length == after.length, 1586 "Ranges must have same lengths for wilcoxonSignedRank."); 1587 1588 double[] diffRanks = alloc.uninitializedArray!(double[])(before.length); 1589 byte[] signs = alloc.uninitializedArray!(byte[])(before.length); 1590 double[] diffs = alloc.uninitializedArray!(double[])(before.length); 1591 1592 size_t ii = 0; 1593 while(!before.empty && !after.empty) { 1594 double diff = cast(double) before.front - cast(double) after.front; 1595 signs[ii] = sign(diff); 1596 diffs[ii] = abs(diff); 1597 ii++; 1598 before.popFront; 1599 after.popFront; 1600 } 1601 } else { 1602 double[] diffRanks; 1603 auto diffApp = appender!(double[])(); 1604 auto signApp = appender!(byte[])(); 1605 1606 while(!before.empty && !after.empty) { 1607 double diff = cast(double) before.front - cast(double) after.front; 1608 signApp.put(sign(diff)); 1609 diffApp.put(abs(diff)); 1610 before.popFront; 1611 after.popFront; 1612 } 1613 1614 auto diffs = diffApp.data; 1615 auto signs = signApp.data; 1616 diffRanks = alloc.uninitializedArray!(double[])(diffs.length); 1617 } 1618 try { 1619 rankSort(diffs, diffRanks); 1620 } catch(SortException) { 1621 return TestRes.init; 1622 } 1623 1624 ulong N = diffs.length - nZero; 1625 1626 double W = 0; 1627 foreach(i, dr; diffRanks) { 1628 if(signs[i] == 1) { 1629 W += dr - nZero; 1630 } 1631 } 1632 1633 // Just a sanity check. Should be mathematically impossible for this 1634 // assert to fail. The 1e-5 is for round-off error. 1635 assert(W > -1e-5 && W <= (N * (N + 1) / 2) + 1e-5); 1636 1637 if(alt == Alt.none) { 1638 return TestRes(W); 1639 } 1640 1641 // Handle ties. 1642 double tieSum = 0; 1643 1644 // combined is sorted by rankSort. Can use it to figure out how many 1645 // ties we have w/o another allocation or sorting. 1646 enum denom = 1.0 / 48.0; 1647 ulong nties = 1; 1648 foreach(i; 1..diffs.length) { 1649 if(diffs[i] == diffs[i - 1] && diffs[i] != 0) { 1650 nties++; 1651 } else { 1652 if(nties == 1) 1653 continue; 1654 tieSum += ((nties * nties * nties) - nties) * denom; 1655 nties = 1; 1656 } 1657 } 1658 // Handle last run. 1659 if(nties > 1) { 1660 tieSum += ((nties * nties * nties) - nties) * denom; 1661 } 1662 if(nZero > 0 && tieSum == 0) { 1663 tieSum = double.nan; // Signal that there were zeros and exact p-val can't be computed. 1664 } 1665 1666 return TestRes(W, wilcoxonSignedRankPval(W, N, alt, tieSum, exactThresh)); 1667 } 1668 1669 unittest { 1670 // Values from R. 1671 alias approxEqual ae; 1672 assert(wilcoxonSignedRank([1,2,3,4,5].dup, [2,1,4,5,3].dup).testStat == 7.5); 1673 assert(wilcoxonSignedRank([3,1,4,1,5].dup, [2,7,1,8,2].dup).testStat == 6); 1674 assert(wilcoxonSignedRank([8,6,7,5,3].dup, [0,9,8,6,7].dup).testStat == 5); 1675 1676 // With ties, normal approx. 1677 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,1,4,5,3].dup), 1)); 1678 assert(ae(wilcoxonSignedRank([3,1,4,1,5].dup, map!"a"([2,7,1,8,2].dup)), 0.7865)); 1679 assert(ae(wilcoxonSignedRank([8,6,7,5,3].dup, [0,9,8,6,7].dup), 0.5879)); 1680 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,1,4,5,3].dup, Alt.less), 0.5562)); 1681 assert(ae(wilcoxonSignedRank([3,1,4,1,5].dup, [2,7,1,8,2].dup, Alt.less), 0.3932)); 1682 assert(ae(wilcoxonSignedRank([8,6,7,5,3].dup, [0,9,8,6,7].dup, Alt.less), 0.2940)); 1683 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,1,4,5,3].dup, Alt.greater), 0.5562)); 1684 assert(ae(wilcoxonSignedRank([3,1,4,1,5].dup, [2,7,1,8,2].dup, Alt.greater), 0.706)); 1685 assert(ae(wilcoxonSignedRank([8,6,7,5,3].dup, [0,9,8,6,7].dup, Alt.greater), 0.7918)); 1686 assert(ae(wilcoxonSignedRank(cast(int[]) [1,16,2,4,8], cast(int[]) [1,5,2,3,4]).testStat, 6)); 1687 assert(ae(wilcoxonSignedRank(cast(int[]) [1,16,2,4,8], cast(int[]) [1,5,2,3,4]), 0.1814)); 1688 1689 // Exact. 1690 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,-4,-8,16,32].dup), 0.625)); 1691 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,-4,-8,16,32].dup, Alt.less), 0.3125)); 1692 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,-4,-8,16,32].dup, Alt.greater), 0.7812)); 1693 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,-4,-8,-16,32].dup), 0.8125)); 1694 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,-4,-8,-16,32].dup, Alt.less), 0.6875)); 1695 assert(ae(wilcoxonSignedRank([1,2,3,4,5].dup, [2,-4,-8,-16,32].dup, Alt.greater), 0.4062)); 1696 1697 // Monte carlo unit testing. Make sure exact, approx are really, 1698 // really close to each other. 1699 double maxEpsilon = 0; 1700 foreach(i; 0..1_000) { 1701 uint N = uniform(10U, 50U); 1702 uint testStat = uniform!"[]"(0, N * (N + 1) / 2); 1703 1704 foreach(alt; [Alt.less, Alt.greater, Alt.twoSided]) { 1705 double approxP = wilcoxonSignedRankPval(testStat, N, alt, 0, 0); 1706 double exactP = wilcoxonSignedRankPval(testStat, N, alt, 0, 50); 1707 double epsilon = abs(approxP - exactP); 1708 assert(epsilon < 0.02); 1709 maxEpsilon = max(maxEpsilon, epsilon); 1710 } 1711 } 1712 } 1713 1714 /**Same as the overload, but allows testing whether a range is stochastically 1715 * less than or greater than a fixed value mu rather than paired elements of 1716 * a second range.*/ 1717 TestRes wilcoxonSignedRank(T)(T data, double mu, Alt alt = Alt.twoSided, uint exactThresh = 50) 1718 if(doubleInput!(T) && is(typeof(data.front - mu) : double)) { 1719 return wilcoxonSignedRank(data, repeat(mu, data.length), alt, exactThresh); 1720 } 1721 1722 unittest { 1723 auto res = wilcoxonSignedRank([-8,-6,2,4,7].dup, 0); 1724 assert(approxEqual(res.testStat, 7)); 1725 assert(approxEqual(res.p, 1)); 1726 } 1727 1728 private double wilcoxonSignedRankPval(double W, ulong N, Alt alt = Alt.twoSided, 1729 double tieSum = 0, uint exactThresh = 50) 1730 in { 1731 assert(N > 0); 1732 assert(tieSum >= 0 || isNaN(tieSum)); 1733 } do { 1734 if(alt == Alt.none) { 1735 return double.nan; 1736 } 1737 1738 if(tieSum == 0 && !isNaN(tieSum) && N <= exactThresh) { 1739 return wilcoxSRPExact(roundTo!uint(W), to!uint(N), alt); 1740 } 1741 1742 if(isNaN(tieSum)) { 1743 tieSum = 0; 1744 } 1745 1746 immutable expected = N * (N + 1) * 0.25; 1747 immutable sd = sqrt(N * (N + 1) * (2 * N + 1) / 24.0 - tieSum); 1748 1749 if(alt == Alt.less) { 1750 return normalCDF(W + 0.5, expected, sd); 1751 } else if(alt == Alt.greater) { 1752 return normalCDFR(W - 0.5, expected, sd); 1753 } else { 1754 assert(alt == Alt.twoSided); 1755 if(abs(W - expected) <= 0.5) { 1756 return 1; 1757 } else if(W < expected) { 1758 return 2 * normalCDF(W + 0.5, expected, sd); 1759 } else { 1760 assert(W > expected); 1761 return 2 * normalCDFR(W - 0.5, expected, sd); 1762 } 1763 } 1764 } 1765 // Tested indirectly through other overload. 1766 1767 /* Yes, a little cut and paste coding was involved here from wilcoxRSPExact, 1768 * but this function and wilcoxRSPExact are just different enough that 1769 * it would be more trouble than it's worth to write one generalized 1770 * function. 1771 * 1772 * Algorithm adapted from StackOverflow question 376003 1773 * (http://stackoverflow.com/questions/376003). 1774 */ 1775 private double wilcoxSRPExact(uint W, uint N, Alt alt = Alt.twoSided) { 1776 immutable maxPossible = N * (N + 1) / 2; 1777 1778 switch(alt) { 1779 case Alt.less: 1780 if(W >= maxPossible) { // Value impossibly large 1781 return 1; 1782 } else if(W * 2 <= maxPossible) { 1783 break; 1784 } else { 1785 return 1 - wilcoxSRPExact(maxPossible - W - 1, N, Alt.less); 1786 } 1787 case Alt.greater: 1788 if(W > maxPossible) { // Value impossibly large 1789 return 0; 1790 } else if(W == 0) { 1791 return 1; 1792 } else if(W * 2 >= maxPossible) { 1793 return wilcoxSRPExact(maxPossible - W, N, Alt.less); 1794 } else { 1795 return 1 - wilcoxSRPExact(W - 1, N, Alt.less); 1796 } 1797 case Alt.twoSided: 1798 if(W * 2 <= maxPossible) { 1799 return min(1, wilcoxSRPExact(W, N, Alt.less) + 1800 wilcoxSRPExact(maxPossible - W, N, Alt.greater)); 1801 } else { 1802 return min(1, wilcoxSRPExact(W, N, Alt.greater) + 1803 wilcoxSRPExact(maxPossible - W, N, Alt.less)); 1804 } 1805 default: 1806 assert(0); 1807 } 1808 1809 auto alloc = newRegionAllocator(); 1810 float* cache = (alloc.uninitializedArray!(float[])((N + 1) * (W + 1))).ptr; 1811 float* cachePrev = (alloc.uninitializedArray!(float[])((N + 1) * (W + 1))).ptr; 1812 cache[0..(N + 1) * (W + 1)] = 0; 1813 cachePrev[0..(N + 1) * (W + 1)] = 0; 1814 1815 double comb = pow(2.0, -(cast(double) N)); 1816 double floatMax = cast(double) float.max; 1817 cache[0] = cast(float) (comb * floatMax); 1818 cachePrev[0] = cast(float) (comb * floatMax); 1819 1820 foreach(i; 1..N + 1) { 1821 swap(cache, cachePrev); 1822 foreach(k; 1..i + 1) { 1823 1824 uint minW = k * (k + 1) / 2; 1825 float* curK = cache + k * (W + 1); 1826 float* prevK = cachePrev + k * (W + 1); 1827 float* prevKm1 = cachePrev + (k - 1) * (W + 1); 1828 1829 foreach(w; minW..W + 1) { 1830 curK[w] = prevK[w] + ((i <= w) ? prevKm1[w - i] : 0); 1831 } 1832 } 1833 } 1834 1835 double sum = 0; 1836 foreach(elem; cache[0..(N + 1) * (W + 1)]) { 1837 sum += cast(double) elem / (cast(double) float.max); 1838 } 1839 1840 return sum; 1841 } 1842 1843 unittest { 1844 // Values from R. 1845 assert(approxEqual(wilcoxSRPExact(25, 10, Alt.less), 0.4229)); 1846 assert(approxEqual(wilcoxSRPExact(25, 10, Alt.greater), 0.6152)); 1847 assert(approxEqual(wilcoxSRPExact(25, 10, Alt.twoSided), 0.8457)); 1848 assert(approxEqual(wilcoxSRPExact(31, 10, Alt.less), 0.6523)); 1849 assert(approxEqual(wilcoxSRPExact(31, 10, Alt.greater), 0.3848)); 1850 assert(approxEqual(wilcoxSRPExact(31, 10, Alt.twoSided), 0.7695)); 1851 } 1852 1853 /**Sign test for differences between paired values. This is a very robust 1854 * but very low power test. Alternatives are Alt.less, meaning elements 1855 * of before are typically less than corresponding elements of after, 1856 * Alt.greater, meaning elements of before are typically greater than 1857 * elements of after, and Alt.twoSided, meaning that there is a significant 1858 * difference in either direction. 1859 * 1860 * Returns: A TestRes with the proportion of elements of before that were 1861 * greater than the corresponding element of after, and the P-value against 1862 * the given alternative. 1863 */ 1864 TestRes signTest(T, U)(T before, U after, Alt alt = Alt.twoSided) 1865 if(doubleInput!(T) && doubleInput!(U) && 1866 is(typeof(before.front < after.front) == bool)) { 1867 ulong greater, less; 1868 while(!before.empty && !after.empty) { 1869 if(before.front < after.front) { 1870 less++; 1871 } else if(after.front < before.front) { 1872 greater++; 1873 } 1874 1875 // Ignore equals. 1876 before.popFront; 1877 after.popFront; 1878 } 1879 1880 double propGreater = to!double(greater) / (greater + less); 1881 1882 final switch(alt) { 1883 case Alt.none: 1884 return TestRes(propGreater); 1885 case Alt.less: 1886 return TestRes(propGreater, 1887 binomialCDF(greater, less + greater, 0.5)); 1888 case Alt.greater: 1889 return TestRes(propGreater, 1890 binomialCDF(less, less + greater, 0.5)); 1891 case Alt.twoSided: 1892 if(less > greater) { 1893 return TestRes(propGreater, 1894 2 * binomialCDF(greater, less + greater, 0.5)); 1895 } else if(greater > less) { 1896 return TestRes(propGreater, 1897 2 * binomialCDF(less, less + greater, 0.5)); 1898 } else { 1899 return TestRes(propGreater, 1); 1900 } 1901 } 1902 } 1903 1904 unittest { 1905 alias approxEqual ae; 1906 assert(ae(signTest([1,3,4,2,5].dup, [1,2,4,8,16].dup), 1)); 1907 assert(ae(signTest([1,3,4,2,5].dup, [1,2,4,8,16].dup, Alt.less), 0.5)); 1908 assert(ae(signTest([1,3,4,2,5].dup, [1,2,4,8,16].dup, Alt.greater), 0.875)); 1909 assert(ae(signTest([5,3,4,6,8].dup, [1,2,3,4,5].dup, Alt.greater), 0.03125)); 1910 assert(ae(signTest([5,3,4,6,8].dup, [1,2,3,4,5].dup, Alt.less), 1)); 1911 assert(ae(signTest([5,3,4,6,8].dup, [1,2,3,4,5].dup), 0.0625)); 1912 1913 assert(approxEqual(signTest([1,2,6,7,9].dup, 2), 0.625)); 1914 assert(ae(signTest([1,2,6,7,9].dup, 2).testStat, 0.75)); 1915 } 1916 1917 /**Similar to the overload, but allows testing for a difference between a 1918 * range and a fixed value mu.*/ 1919 TestRes signTest(T)(T data, double mu, Alt alt = Alt.twoSided) 1920 if(doubleInput!(T) && is(typeof(data.front < mu) == bool)) { 1921 return signTest(data, repeat(mu), alt); 1922 } 1923 1924 /** 1925 Two-sided binomial test for whether P(success) == p. The one-sided 1926 alternatives are covered by dstats.distrib.binomialCDF and binomialCDFR. 1927 k is the number of successes observed, n is the number of trials, p 1928 is the probability of success under the null. 1929 1930 Returns: The P-value for the alternative that P(success) != p against 1931 the null that P(success) == p. 1932 1933 Notes: This test can also be performed using multinomialTest, but this 1934 implementation is much faster and easier to use. 1935 */ 1936 double binomialTest(ulong k, ulong n, double p) { 1937 dstatsEnforce(k <= n, "k must be <= n for binomial test."); 1938 dstatsEnforce(p >= 0 && p <= 1, "p must be between 0, 1 for binomial test."); 1939 1940 enum epsilon = 1 - 1e-6; // Small but arbitrary constant to deal w/ rounding error. 1941 1942 immutable mode = cast(long) ((n + 1) * p); 1943 if(k == mode || 1944 approxEqual(binomialPMF(k, n, p), binomialPMF(mode, n, p), 1 - epsilon)) { 1945 return 1; 1946 } else if(k > mode) { 1947 immutable double upperPart = binomialCDFR(k, n, p); 1948 immutable pExact = binomialPMF(k, n, p); 1949 ulong ulim = mode, llim = 0, guess; 1950 while(ulim - llim > 1) { 1951 guess = (ulim + llim) / 2; 1952 immutable double pGuess = binomialPMF(guess, n, p); 1953 1954 if(pGuess == pExact) { 1955 ulim = guess + 1; 1956 llim = guess; 1957 break; 1958 } else if(pGuess < pExact) { 1959 llim = guess; 1960 } else { 1961 ulim = guess; 1962 } 1963 } 1964 1965 guess = ulim; 1966 while(binomialPMF(guess, n, p) < pExact * epsilon) { 1967 guess++; 1968 } 1969 while(guess > 0 && binomialPMF(guess, n, p) > pExact / epsilon) { 1970 guess--; 1971 } 1972 if(guess == 0 && binomialPMF(0, n, p) > pExact / epsilon) { 1973 return upperPart; 1974 } 1975 return upperPart + binomialCDF(guess, n, p); 1976 } else { 1977 static double myPMF(ulong k, ulong n, double p) { 1978 return k > n ? 0 : binomialPMF(k, n, p); 1979 } 1980 1981 immutable lowerPart = binomialCDF(k, n, p); 1982 immutable pExact = binomialPMF(k, n, p); 1983 ulong ulim = n + 1, llim = mode, guess; 1984 while(ulim - llim > 1) { 1985 guess = (ulim + llim) / 2; 1986 immutable double pGuess = myPMF(guess, n, p); 1987 if(pGuess == pExact) { 1988 ulim = guess; 1989 llim = guess; 1990 break; 1991 } else if(pGuess < pExact) { 1992 ulim = guess; 1993 } else { 1994 llim = guess; 1995 } 1996 } 1997 1998 // All this stuff is necessary to deal with round-off error properly. 1999 guess = llim; 2000 while(myPMF(guess, n, p) < pExact * epsilon && guess > 0) { 2001 guess--; 2002 } 2003 while(myPMF(guess, n, p) > pExact / epsilon) { 2004 guess++; 2005 } 2006 2007 return lowerPart + ((guess > n) ? 0 : binomialCDFR(guess, n, p)); 2008 } 2009 } 2010 2011 unittest { 2012 // Values from R. 2013 assert(approxEqual(binomialTest(46, 96, 0.5), 0.759649)); 2014 assert(approxEqual(binomialTest(44, 56, 0.5), 2.088e-5)); 2015 assert(approxEqual(binomialTest(12, 56, 0.5), 2.088e-5)); 2016 assert(approxEqual(binomialTest(0, 40, 0.25), 2.236e-5)); 2017 assert(approxEqual(binomialTest(5, 16, 0.5), 0.2101)); 2018 assert(approxEqual(binomialTest(0, 20, 0.4), 4.16e-5)); 2019 assert(approxEqual(binomialTest(20, 20, 0.6), 4.16e-5)); 2020 assert(approxEqual(binomialTest(6, 88, 0.1), 0.3784)); 2021 assert(approxEqual(binomialTest(3, 4, 0.5), 0.625)); 2022 assert(approxEqual(binomialTest(4, 7, 0.8), 0.1480)); 2023 assert(approxEqual(binomialTest(3, 9, 0.8), 0.003066)); 2024 assert(approxEqual(binomialTest(9, 9, 0.7), 0.06565)); 2025 assert(approxEqual(binomialTest(2, 11, 0.1), 0.3026)); 2026 assert(approxEqual(binomialTest(1, 11, 0.1), 1)); 2027 assert(approxEqual(binomialTest(5, 11, 0.1), 0.002751)); 2028 assert(approxEqual(binomialTest(5, 12, 0.5), 0.7744)); 2029 assert(approxEqual(binomialTest(12, 12, 0.5), 0.0004883)); 2030 assert(approxEqual(binomialTest(12, 13, 0.6), 0.02042)); 2031 assert(approxEqual(binomialTest(0, 9, 0.1), 1)); 2032 } 2033 2034 ///For chiSquareFit and gTestFit, is expected value range counts or proportions? 2035 enum Expected { 2036 /// 2037 count, 2038 2039 /// 2040 proportion 2041 } 2042 2043 /**Performs a one-way Pearson's chi-square goodness of fit test between a range 2044 of observed and a range of expected values. This is a useful statistical 2045 test for testing whether a set of observations fits a discrete distribution. 2046 2047 Returns: A TestRes of the chi-square statistic and the P-value for the 2048 alternative hypothesis that observed is not a sample from expected against 2049 the null that observed is a sample from expected. 2050 2051 Notes: By default, expected is assumed to be a range of expected proportions. 2052 These proportions are automatically normalized, and can sum to any number. 2053 By passing Expected.count in as the last parameter, calculating expected 2054 counts will be skipped, and expected will assume to already be properly 2055 normalized. This is slightly faster, but more importantly 2056 allows input ranges to be used. 2057 2058 The chi-square test relies on asymptotic statistical properties 2059 and is therefore not considered valid, as a rule of thumb, when expected 2060 counts are below 5. However, this rule is likely to be unnecessarily 2061 stringent in most cases. 2062 2063 Examples: 2064 --- 2065 // Test to see whether a set of categorical observations differs 2066 // statistically from a discrete uniform distribution. 2067 2068 uint[] observed = [980, 1028, 1001, 964, 1102]; 2069 auto expected = repeat(1.0); 2070 auto res2 = chiSquareFit(observed, expected); 2071 assert(approxEqual(res2, 0.0207)); 2072 assert(approxEqual(res2.testStat, 11.59)); 2073 --- 2074 * 2075 References: http://en.wikipedia.org/wiki/Pearson%27s_chi-square_test 2076 */ 2077 TestRes chiSquareFit(T, U)( 2078 T observed, 2079 U expected, 2080 Expected countProp = Expected.proportion 2081 ) if(doubleInput!(T) && doubleInput!(U)) { 2082 return goodnessFit!(pearsonChiSqElem, T, U)(observed, expected, countProp); 2083 } 2084 2085 unittest { 2086 // Test to see whether a set of categorical observations differs 2087 // statistically from a discrete uniform distribution. 2088 uint[] observed = [980, 1028, 1001, 964, 1102]; 2089 auto expected = repeat(cast(double) sum(observed) / observed.length); 2090 auto res = chiSquareFit(observed, expected, Expected.count); 2091 assert(approxEqual(res, 0.0207)); 2092 assert(approxEqual(res.testStat, 11.59)); 2093 2094 auto expected2 = [5.0, 5, 5, 5, 5, 0]; 2095 observed ~= 0; 2096 auto res2 = chiSquareFit(observed, expected2); 2097 assert(approxEqual(res2, 0.0207)); 2098 assert(approxEqual(res2.testStat, 11.59)); 2099 } 2100 2101 // Alias for old name, for backwards compatibility. Don't document it 2102 // because it will be deprecated eventually. 2103 alias chiSquareFit chiSqrFit; 2104 2105 /**The G or likelihood ratio chi-square test for goodness of fit. Roughly 2106 * the same as Pearson's chi-square test (chiSquareFit), but may be more 2107 * accurate in certain situations and less accurate in others. However, it is 2108 * still based on asymptotic distributions, and is not exact. Usage is is 2109 * identical to chiSquareFit. 2110 * 2111 * References: http://en.wikipedia.org/wiki/G_test 2112 * 2113 */ 2114 TestRes gTestFit(T, U)(T observed, U expected, Expected countProp = Expected.proportion) 2115 if(doubleInput!(T) && doubleInput!(U)) { 2116 return goodnessFit!(gTestElem, T, U)(observed, expected, countProp); 2117 } 2118 // No unittest because I can't find anything to test this against. However, 2119 // it's hard to imagine how it could be wrong, given that goodnessFit() and 2120 // gTestElem() both work, and, as expected, this function produces roughly 2121 // the same results as chiSquareFit. 2122 2123 private TestRes goodnessFit(alias elemFun, T, U)(T observed, U expected, Expected countProp) 2124 if(doubleInput!(T) && doubleInput!(U)) { 2125 if(countProp == Expected.proportion) { 2126 dstatsEnforce(isForwardRange!(U), 2127 "Can't use expected proportions instead of counts with input ranges."); 2128 } 2129 2130 uint len = 0; 2131 double chiSq = 0; 2132 double multiplier = 1; 2133 2134 // Normalize proportions to add up to the sum of the data. 2135 if(countProp == Expected.proportion) { 2136 double expectSum = 0; 2137 multiplier = 0; 2138 auto obsCopy = observed.save; 2139 auto expCopy = expected.save; 2140 while(!obsCopy.empty && !expCopy.empty) { 2141 multiplier += obsCopy.front; 2142 expectSum += expCopy.front; 2143 obsCopy.popFront; 2144 expCopy.popFront; 2145 } 2146 multiplier /= expectSum; 2147 } 2148 2149 while(!observed.empty && !expected.empty) { 2150 scope(exit) { 2151 observed.popFront(); 2152 expected.popFront(); 2153 } 2154 double e = expected.front * multiplier; 2155 2156 // If e is zero, then we should just treat the cell as if it didn't 2157 // exist. 2158 if(e == 0) { 2159 dstatsEnforce(observed.front == 0, 2160 "Can't have non-zero observed value w/ zero expected value."); 2161 continue; 2162 } 2163 2164 chiSq += elemFun(observed.front, e); 2165 len++; 2166 } 2167 2168 if(isNaN(chiSq)) { 2169 return TestRes(double.nan, double.nan); 2170 } 2171 2172 return TestRes(chiSq, chiSquareCDFR(chiSq, len - 1)); 2173 } 2174 2175 /** 2176 The exact multinomial goodness of fit test for whether a set of counts 2177 fits a hypothetical distribution. counts is an input range of counts. 2178 proportions is an input range of expected proportions. These are normalized 2179 automatically, so they can sum to any value. 2180 2181 Returns: The P-value for the null that counts is a sample from proportions 2182 against the alternative that it isn't. 2183 2184 Notes: This test is EXTREMELY slow for anything but very small samples and 2185 degrees of freedom. The Pearson's chi-square (chiSquareFit()) or likelihood 2186 ratio chi-square (gTestFit()) are good enough approximations unless sample 2187 size is very small. 2188 */ 2189 double multinomialTest(U, F)(U countsIn, F proportions) 2190 if(isInputRange!U && isInputRange!F && 2191 isIntegral!(ElementType!U) && isFloatingPoint!(ElementType!(F))) { 2192 auto alloc = newRegionAllocator(); 2193 2194 static if(isRandomAccessRange!U && hasLength!U) { 2195 alias countsIn counts; 2196 } else { 2197 auto counts = alloc.array(countsIn); 2198 } 2199 2200 uint N = sum(counts); 2201 2202 double[] logPs; 2203 static if(std.range.hasLength!F) { 2204 logPs = alloc.uninitializedArray!(double[])(proportions.length); 2205 size_t pIndex; 2206 foreach(p; proportions) { 2207 logPs[pIndex++] = p; 2208 } 2209 } else { 2210 auto app = appender(logPs); 2211 foreach(p; proportions) { 2212 app.put(p); 2213 } 2214 logPs = app.data; 2215 } 2216 2217 logPs[] /= reduce!"a + b"(0.0, logPs); 2218 foreach(ref elem; logPs) { 2219 elem = log(elem); 2220 } 2221 2222 2223 double[] logs = alloc.uninitializedArray!(double[])(N + 1); 2224 logs[0] = 0; 2225 foreach(i; 1..logs.length) { 2226 logs[i] = log(cast(double)i); 2227 } 2228 2229 double nFact = logFactorial(N); 2230 double pVal = 0; 2231 uint nLeft = N; 2232 double pSoFar = nFact; 2233 2234 double pActual = nFact; 2235 foreach(i, count; counts) { 2236 pActual += logPs[i] * count - logFactorial(count); 2237 } 2238 pActual -= pActual * 1e-6; // Epsilon to handle numerical inaccuracy. 2239 2240 void doIt(uint pos) { 2241 if(pos == counts.length - 1) { 2242 immutable pOld = pSoFar; 2243 pSoFar += logPs[$ - 1] * nLeft - logFactorial(nLeft); 2244 2245 if(pSoFar <= pActual) { 2246 pVal += exp(pSoFar); 2247 } 2248 pSoFar = pOld; 2249 return; 2250 } 2251 2252 uint nLeftOld = nLeft; 2253 immutable pOld = pSoFar; 2254 double pAdd = 0; 2255 2256 foreach(i; 0..nLeft + 1) { 2257 if(i > 0) { 2258 pAdd += logPs[pos] - logs[i]; 2259 } 2260 pSoFar = pOld + pAdd; 2261 doIt(pos + 1); 2262 nLeft--; 2263 } 2264 nLeft = nLeftOld; 2265 pSoFar = pOld; 2266 } 2267 doIt(0); 2268 return pVal; 2269 } 2270 2271 unittest { 2272 // Nothing to test this against for more than 1 df, but it matches 2273 // chi-square roughly and should take the same paths for 2 vs. N degrees 2274 // of freedom. 2275 for(uint n = 4; n <= 100; n += 4) { 2276 foreach(k; 0..n + 1) { 2277 for(double p = 0.05; p <= 0.95; p += 0.05) { 2278 double bino = binomialTest(k, n, p); 2279 double[] ps = [p, 1 - p]; 2280 uint[] counts = [k, n - k]; 2281 double multino = multinomialTest(counts, ps); 2282 //writeln(k, "\t", n, "\t", p, "\t", bino, "\t", multino); 2283 assert(approxEqual(bino, multino), 2284 text(bino, '\t', multino, '\t', k, '\t', n, '\t', p)); 2285 } 2286 } 2287 } 2288 } 2289 2290 /** 2291 Performs a Pearson's chi-square test on a contingency table of arbitrary 2292 dimensions. When the chi-square test is mentioned, this is usually the one 2293 being referred to. Takes a set of finite forward ranges, one for each column 2294 in the contingency table. These can be expressed either as a tuple of ranges 2295 or a range of ranges. Returns a P-value for the alternative hypothesis that 2296 frequencies in each row of the contingency table depend on the column against 2297 the null that they don't. 2298 2299 Notes: The chi-square test relies on asymptotic statistical properties 2300 and is therefore not exact. The typical rule of thumb is that each cell 2301 should have an expected value of at least 5. However, this is likely to 2302 be unnecessarily stringent. 2303 2304 Yates's continuity correction is never used in this implementation. If 2305 you want something that's guaranteed to be conservative, use fisherExact(). 2306 2307 This is, for all practical purposes, an inherently non-directional test. 2308 Therefore, the one-sided verses two-sided option is not provided. 2309 2310 For 2x2 contingency tables, fisherExact is a more conservative test, in that 2311 the type I error rate is guaranteed to never be above the nominal P-value. 2312 However, even for small sample sizes this test may produce results closer 2313 to the true P-value, at the risk of possibly being non-conservative. 2314 2315 Examples: 2316 --- 2317 // Test to see whether the relative frequency of outcome 0, 1, and 2 2318 // depends on the treatment (drug1, drug2 or placebo) in some hypothetical 2319 // experiment. For example, 1500 people had outcome 2 if they were treated 2320 // with drug1 and 1100 had outcome 1 if treated with placebo. 2321 uint[] drug1 = [1000, 2000, 1500]; 2322 uint[] drug2 = [1500, 3000, 2300]; 2323 uint[] placebo = [500, 1100, 750]; 2324 auto result1 = chiSquareContingency(drug1, drug2, placebo); 2325 2326 // The following uses a range of ranges instead of an array of ranges, 2327 // and will produce equivalent results. 2328 auto rangeOfRanges = [drug1, drug2, placebo]; 2329 auto result2 = chiSquareContingency(rangeOfRanges); 2330 --- 2331 2332 References: http://en.wikipedia.org/wiki/Pearson%27s_chi-square_test 2333 * 2334 */ 2335 TestRes chiSquareContingency(T...)(T inputData) { 2336 return testContingency!(pearsonChiSqElem, T)(inputData); 2337 } 2338 2339 unittest { 2340 // Test array version. Using VassarStat's chi-square calculator. 2341 uint[][] table1 = [[60, 80, 70], 2342 [20, 50, 40], 2343 [10, 15, 11]]; 2344 uint[][] table2 = [[60, 20, 10], 2345 [80, 50, 15], 2346 [70, 40, 11]]; 2347 assert(approxEqual(chiSquareContingency(table1), 0.3449)); 2348 assert(approxEqual(chiSquareContingency(table2), 0.3449)); 2349 assert(approxEqual(chiSquareContingency(table1).testStat, 4.48)); 2350 2351 // Test tuple version. 2352 auto p1 = chiSquareContingency(cast(uint[]) [31, 41, 59], 2353 cast(uint[]) [26, 53, 58], 2354 cast(uint[]) [97, 93, 93]); 2355 assert(approxEqual(p1, 0.0059)); 2356 2357 auto p2 = chiSquareContingency(cast(uint[]) [31, 26, 97], 2358 cast(uint[]) [41, 53, 93], 2359 cast(uint[]) [59, 58, 93]); 2360 assert(approxEqual(p2, 0.0059)); 2361 2362 uint[] drug1 = [1000, 2000, 1500]; 2363 uint[] drug2 = [1500, 3000, 2300]; 2364 uint[] placebo = [500, 1100, 750]; 2365 assert(approxEqual(chiSquareContingency(drug1, drug2, placebo), 0.2397)); 2366 } 2367 2368 // Alias for old name, for backwards compatibility. Don't document it 2369 // because it is deprecated and has been scheduled for deprecation for 2370 // ages. 2371 deprecated alias chiSquareContingency chiSqrContingency; 2372 2373 /** 2374 This struct is a subtype of TestRes and is used to return the results of 2375 gTestContingency and gTestObs. Due to the information theoretic interpretation 2376 of the G test, it contains an extra field to return the mutual information 2377 in bits. 2378 */ 2379 struct GTestRes { 2380 /// 2381 TestRes testRes; 2382 2383 /// 2384 alias testRes this; 2385 2386 /** 2387 The mutual info of the two random variables in the joint distribution 2388 represented by the contingency table, in bits (base 2). 2389 */ 2390 double mutualInfo; 2391 } 2392 2393 /** 2394 The G or likelihood ratio chi-square test for contingency tables. Roughly 2395 the same as Pearson's chi-square test (chiSquareContingency), but may be more 2396 accurate in certain situations and less accurate in others. 2397 2398 Like Pearson's Chi-square, the G-test is based on asymptotic distributions, 2399 and is not exact. Usage is is identical to chiSquareContingency. 2400 2401 Note: This test can be thought of as a test for nonzero mutual information 2402 between the random variables represented by the rows and the columns, 2403 since the test statistic and P-value are strictly increasing 2404 and strictly decreasing, respectively, in mutual information. Therefore, this 2405 function returns a GTestRes, which is a subtype of TestRes and also gives 2406 the mutual information for use in information theoretic settings. 2407 2408 References: http://en.wikipedia.org/wiki/G_test, last retrieved 1/22/2011 2409 */ 2410 GTestRes gTestContingency(T...)(T inputData) { 2411 return testContingency!(gTestElem, T)(inputData); 2412 } 2413 2414 unittest { 2415 // Values from example at http://udel.edu/~mcdonald/statgtestind.html 2416 // Handbook of Biological Statistics. 2417 uint[] withoutCHD = [268, 199, 42]; 2418 uint[] withCHD = [807, 759, 184]; 2419 auto res = gTestContingency(withoutCHD, withCHD); 2420 assert(approxEqual(res.testStat, 7.3)); 2421 assert(approxEqual(res.p, 0.026)); 2422 assert(approxEqual(res.mutualInfo, 0.0023313)); 2423 2424 2425 uint[] moringa = [127, 99, 264]; 2426 uint[] vicinus = [116, 67, 161]; 2427 auto res2 = gTestContingency(moringa, vicinus); 2428 assert(approxEqual(res2.testStat, 6.23)); 2429 assert(approxEqual(res2.p, 0.044)); 2430 assert(approxEqual(res2.mutualInfo, 0.00538613)); 2431 } 2432 2433 // Pearson and likelihood ratio code are pretty much the same. Factor out 2434 // the one difference into a function that's a template parameter. However, 2435 // for API simplicity, this is hidden and they look like two separate functions. 2436 private GTestRes testContingency(alias elemFun, T...)(T rangesIn) { 2437 auto alloc = newRegionAllocator(); 2438 static if(isInputRange!(T[0]) && T.length == 1 && 2439 isForwardRange!(typeof(rangesIn[0].front()))) { 2440 auto ranges = alloc.array(rangesIn[0]); 2441 2442 foreach(ref range; ranges) { 2443 range = range.save; 2444 } 2445 2446 } else static if(allSatisfy!(isForwardRange, typeof(rangesIn))) { 2447 auto saved = saveAll(rangesIn); 2448 auto ranges = saved.expand; 2449 } else { 2450 static assert(0, "Can only perform contingency table test" ~ 2451 " on a tuple of ranges or a range of ranges."); 2452 } 2453 2454 double[] colSums = alloc.uninitializedArray!(double[])(ranges.length); 2455 colSums[] = 0; 2456 size_t nCols = 0; 2457 immutable size_t nRows = ranges.length; 2458 foreach(ri, range; ranges) { 2459 size_t curLen = 0; 2460 foreach(elem; range.save) { 2461 colSums[ri] += cast(double) elem; 2462 curLen++; 2463 } 2464 if(ri == 0) { 2465 nCols = curLen; 2466 } else { 2467 assert(curLen == nCols); 2468 } 2469 } 2470 2471 bool noneEmpty() { 2472 foreach(range; ranges) { 2473 if(range.empty) { 2474 return false; 2475 } 2476 } 2477 return true; 2478 } 2479 2480 void popAll() { 2481 foreach(i, range; ranges) { 2482 ranges[i].popFront; 2483 } 2484 } 2485 2486 double sumRow() { 2487 double rowSum = 0; 2488 foreach(range; ranges) { 2489 rowSum += cast(double) range.front; 2490 } 2491 return rowSum; 2492 } 2493 2494 double chiSq = 0; 2495 immutable double NNeg1 = 1.0 / sum(colSums); 2496 while(noneEmpty) { 2497 auto rowSum = sumRow(); 2498 foreach(ri, range; ranges) { 2499 double expected = NNeg1 * rowSum * colSums[ri]; 2500 chiSq += elemFun(range.front, expected); 2501 } 2502 popAll(); 2503 } 2504 2505 if(isNaN(chiSq)) { 2506 return GTestRes(TestRes(double.nan, double.nan), double.nan); 2507 } 2508 2509 // This can happen in some cases due to numerical fuzz. 2510 if(chiSq > 1e-5 && chiSq <= 0) { 2511 return GTestRes(TestRes(0, 1), 0); 2512 } 2513 2514 immutable pVal = (chiSq >= 0) ? 2515 chiSquareCDFR(chiSq, (nRows - 1) * (nCols - 1)) : double.nan; 2516 2517 // 1 / (2 * LN2), for converting chiSq to mutualInfo. 2518 enum chiToMi = 1 / (2 * LN2); 2519 2520 // This is the mutual information between the two random variables 2521 // represented by the contingency table, only if we're doing a G test. 2522 // If we're doing a Pearson's test, it's a completely meaningless quantity, 2523 // but never gets returned by any public function. 2524 immutable mutualInfo = chiSq * NNeg1 * chiToMi; 2525 2526 return GTestRes(TestRes(chiSq, pVal), mutualInfo); 2527 } 2528 2529 private double pearsonChiSqElem(double observed, double expected) pure nothrow { 2530 immutable diff = observed - expected; 2531 return diff * diff / expected; 2532 } 2533 2534 private double gTestElem(double observed, double expected) pure nothrow { 2535 return (observed == 0 && expected > 0) ? 0 : 2536 (observed * log(observed / expected) * 2); 2537 } 2538 2539 /** 2540 Given two vectors of observations of jointly distributed variables x, y, tests 2541 the null hypothesis that values in x are independent of the corresponding 2542 values in y. This is done using Pearson's Chi-Square Test. For a similar test 2543 that assumes the data has already been tabulated into a contingency table, see 2544 chiSquareContingency. 2545 2546 x and y must both be input ranges. If they are not the same length, a 2547 DstatsArgumentException is thrown. 2548 2549 Examples: 2550 --- 2551 // Test whether the appearance of "foo" vs. "bar" is independent of the 2552 // appearance of "baz" vs. "xxx". 2553 auto x = ["foo", "bar", "bar", "foo", "foo"]; 2554 auto y = ["xxx", "baz", "baz", "xxx", "baz"]; 2555 auto result = chiSquareObs(x, y); 2556 2557 // This is equivalent to: 2558 auto contingency = new uint[][](2, 2); 2559 foreach(i; 0..x.length) { 2560 immutable index1 = (x[i] == "foo"); 2561 immutable index2 = (y[i] == "xxx"); 2562 contingency[index1][index2]++; 2563 } 2564 2565 auto result2 = chiSquareContingency(contingency); 2566 --- 2567 */ 2568 TestRes chiSquareObs(T, U)(T x, U y) 2569 if(isInputRange!T && isInputRange!U) { 2570 uint xFreedom, yFreedom, n; 2571 typeof(return) ret; 2572 2573 static if(!hasLength!T && !hasLength!U) { 2574 ret.testStat = toContingencyScore!(T, U, uint) 2575 (x, y, &pearsonChiSqElem, xFreedom, yFreedom, n); 2576 } else { 2577 immutable minLen = min(x.length, y.length); 2578 if(minLen <= ubyte.max) { 2579 ret.testStat = toContingencyScore!(T, U, ubyte) 2580 (x, y, &pearsonChiSqElem, xFreedom, yFreedom, n); 2581 } else if(minLen <= ushort.max) { 2582 ret.testStat = toContingencyScore!(T, U, ushort) 2583 (x, y, &pearsonChiSqElem, xFreedom, yFreedom, n); 2584 } else { 2585 ret.testStat = toContingencyScore!(T, U, uint) 2586 (x, y, &pearsonChiSqElem, xFreedom, yFreedom, n); 2587 } 2588 } 2589 2590 ret.p = chiSquareCDFR(ret.testStat, xFreedom * yFreedom); 2591 return ret; 2592 } 2593 2594 unittest { 2595 // We know the chi-square contingency works, so test that the automatic 2596 // binning works, too. 2597 ubyte[] obs1 = [1, 2, 3, 1, 2, 3, 1, 2, 3]; 2598 ubyte[] obs2 = [1, 3, 2, 1, 3, 2, 1, 3, 2]; 2599 2600 uint[][] cTable = [[3, 0, 0], 2601 [0, 0, 3], 2602 [0, 3, 0]]; 2603 auto gRes = chiSquareContingency(cTable); 2604 auto miRes = chiSquareObs(obs1, obs2); 2605 foreach(ti, elem; miRes.tupleof) { 2606 assert(approxEqual(elem, gRes.tupleof[ti])); 2607 } 2608 2609 auto x = ["foo", "bar", "bar", "foo", "foo"]; 2610 auto y = ["xxx", "baz", "baz", "xxx", "baz"]; 2611 auto result = chiSquareObs(x, y); 2612 assert(approxEqual(result.testStat, 2.22222222)); 2613 assert(approxEqual(result.p, 0.136037)); 2614 2615 auto contingency = new uint[][](2, 2); 2616 foreach(i; 0..x.length) { 2617 immutable index1 = (x[i] == "foo"); 2618 immutable index2 = (y[i] == "xxx"); 2619 contingency[index1][index2]++; 2620 } 2621 2622 auto result2 = chiSquareContingency(contingency); 2623 assert(approxEqual(result.testStat, result2.testStat), 2624 text(result.testStat, ' ', result2.testStat)); 2625 assert(approxEqual(result.p, result2.p)); 2626 } 2627 2628 /** 2629 Given two ranges of observations of jointly distributed variables x, y, tests 2630 the null hypothesis that values in x are independent of the corresponding 2631 values in y. This is done using the Likelihood Ratio G test. Usage is similar 2632 to chiSquareObs. For an otherwise identical test that assumes the data has 2633 already been tabulated into a contingency table, see gTestContingency. 2634 2635 Note: This test can be thought of as a test for nonzero mutual information 2636 between x and y, since the test statistic and P-value are strictly increasing 2637 and strictly decreasing, respectively, in mutual information. Therefore, this 2638 function returns a GTestRes, which is a subtype of TestRes and also gives 2639 the mutual information for use in information theoretic settings. 2640 */ 2641 GTestRes gTestObs(T, U)(T x, U y) 2642 if(isInputRange!T && isInputRange!U) { 2643 uint xFreedom, yFreedom, n; 2644 typeof(return) ret; 2645 2646 static if(!hasLength!T && !hasLength!U) { 2647 ret.testStat = toContingencyScore!(T, U, uint) 2648 (x, y, &gTestElem, xFreedom, yFreedom, n); 2649 } else { 2650 immutable minLen = min(x.length, y.length); 2651 if(minLen <= ubyte.max) { 2652 ret.testStat = toContingencyScore!(T, U, ubyte) 2653 (x, y, &gTestElem, xFreedom, yFreedom, n); 2654 } else if(minLen <= ushort.max) { 2655 ret.testStat = toContingencyScore!(T, U, ushort) 2656 (x, y, &gTestElem, xFreedom, yFreedom, n); 2657 } else { 2658 ret.testStat = toContingencyScore!(T, U, uint) 2659 (x, y, &gTestElem, xFreedom, yFreedom, n); 2660 } 2661 } 2662 2663 ret.p = chiSquareCDFR(ret.testStat, xFreedom * yFreedom); 2664 ret.mutualInfo = ret.testStat / (2 * LN2 * n); 2665 return ret; 2666 } 2667 2668 unittest { 2669 // We know the g test contingency works, so test that the automatic binning 2670 // works, too. 2671 ubyte[] obs1 = [1, 2, 3, 1, 2, 3, 1, 2, 3]; 2672 ubyte[] obs2 = [1, 3, 2, 1, 3, 2, 1, 3, 2]; 2673 2674 uint[][] cTable = [[3, 0, 0], 2675 [0, 0, 3], 2676 [0, 3, 0]]; 2677 auto gRes = gTestContingency(cTable); 2678 auto miRes = gTestObs(obs1, obs2); 2679 foreach(ti, elem; miRes.tupleof) { 2680 assert(approxEqual(elem, gRes.tupleof[ti])); 2681 } 2682 2683 auto x = ["foo", "bar", "bar", "foo", "foo"]; 2684 auto y = ["xxx", "baz", "baz", "xxx", "baz"]; 2685 auto result = gTestObs(x, y); 2686 assert(approxEqual(result.testStat, 2.91103)); 2687 assert(approxEqual(result.p, 0.0879755)); 2688 assert(approxEqual(result.mutualInfo, 0.419973)); 2689 } 2690 2691 package double toContingencyScore(T, U, Uint) 2692 (T x, U y, double function(double, double) elemFun, 2693 out uint xFreedom, out uint yFreedom, out uint nPtr) { 2694 2695 import dstats.infotheory : NeedsHeap; 2696 enum needsHeap = NeedsHeap!T || 2697 NeedsHeap!U; 2698 alias dstats.infotheory.ObsEnt!(ElementType!T, ElementType!U) ObsType; 2699 2700 static if(needsHeap) { 2701 Uint[ObsType] jointCounts; 2702 Uint[ElementType!T] xCounts; 2703 Uint[ElementType!U] yCounts; 2704 } else { 2705 auto alloc = newRegionAllocator(); 2706 dstatsEnforce(x.length == y.length, 2707 "Can't calculate mutual info with different length vectors."); 2708 immutable len = x.length; 2709 auto jointCounts = StackHash!(ObsType, Uint)(max(20, len / 20), alloc); 2710 auto xCounts = StackHash!(ElementType!T, Uint)(max(10, len / 40), alloc); 2711 auto yCounts = StackHash!(ElementType!U, Uint)(max(10, len / 40), alloc); 2712 } 2713 2714 uint n = 0; 2715 while(!x.empty && !y.empty) { 2716 n++; 2717 auto a = x.front; 2718 auto b = y.front; 2719 jointCounts[ObsType(a, b)]++; 2720 xCounts[a]++; 2721 yCounts[b]++; 2722 2723 x.popFront(); 2724 y.popFront(); 2725 } 2726 2727 dstatsEnforce(x.empty && y.empty, 2728 "Can't calculate mutual info with different length vectors."); 2729 2730 xFreedom = cast(uint) xCounts.length - 1; 2731 yFreedom = cast(uint) yCounts.length - 1; 2732 nPtr = n; 2733 2734 double ret = 0; 2735 immutable double nNeg1 = 1.0 / n; 2736 foreach(key1, marg1; xCounts) foreach(key2, marg2; yCounts) { 2737 immutable observed = jointCounts.get( 2738 ObsType(key1, key2), 0 2739 ); 2740 immutable expected = marg1 * nNeg1 * marg2; 2741 ret += elemFun(observed, expected); 2742 } 2743 2744 return ret; 2745 } 2746 2747 /** 2748 Fisher's Exact test for difference in odds between rows/columns 2749 in a 2x2 contingency table. Specifically, this function tests the odds 2750 ratio, which is defined, for a contingency table c, as (c[0][0] * c[1][1]) 2751 / (c[1][0] * c[0][1]). Alternatives are Alt.less, meaning true odds ratio 2752 < 1, Alt.greater, meaning true odds ratio > 1, and Alt.twoSided, meaning 2753 true odds ratio != 1. 2754 2755 Accepts a 2x2 contingency table as an array of arrays of uints. 2756 For now, only does 2x2 contingency tables. 2757 2758 Notes: Although this test is "exact" in that it does not rely on asymptotic 2759 approximations, it is very statistically conservative when the marginals 2760 are not truly fixed in the experimental design in question. If a 2761 closer but possibly non-conservative approximation of the true P-value is 2762 desired, Pearson's chi-square test (chiSquareContingency) may perform better, 2763 even for small samples. 2764 2765 Returns: A TestRes of the odds ratio and the P-value against the given 2766 alternative. 2767 2768 Examples: 2769 --- 2770 double res = fisherExact([[2u, 7], [8, 2]], Alt.less); 2771 assert(approxEqual(res.p, 0.01852)); // Odds ratio is very small in this case. 2772 assert(approxEqual(res.testStat, 4.0 / 56.0)); 2773 --- 2774 2775 References: http://en.wikipedia.org/wiki/Fisher%27s_Exact_Test 2776 */ 2777 TestRes fisherExact(T)(const T[2][2] contingencyTable, Alt alt = Alt.twoSided) 2778 if(isIntegral!(T)) { 2779 foreach(range; contingencyTable) { 2780 foreach(elem; range) { 2781 dstatsEnforce(elem >= 0, 2782 "Cannot have negative elements in a contingency table."); 2783 } 2784 } 2785 2786 static double fisherLower(const T[2][2] contingencyTable) { 2787 alias contingencyTable c; 2788 return hypergeometricCDF(c[0][0], c[0][0] + c[0][1], c[1][0] + c[1][1], 2789 c[0][0] + c[1][0]); 2790 } 2791 2792 static double fisherUpper(const T[2][2] contingencyTable) { 2793 alias contingencyTable c; 2794 return hypergeometricCDFR(c[0][0], c[0][0] + c[0][1], c[1][0] + c[1][1], 2795 c[0][0] + c[1][0]); 2796 } 2797 2798 2799 alias contingencyTable c; // Save typing. 2800 immutable oddsRatio = cast(double) c[0][0] * c[1][1] / c[0][1] / c[1][0]; 2801 if(alt == Alt.none) { 2802 return TestRes(oddsRatio); 2803 } else if(alt == Alt.less) { 2804 return TestRes(oddsRatio, fisherLower(contingencyTable)); 2805 } else if(alt == Alt.greater) { 2806 return TestRes(oddsRatio, fisherUpper(contingencyTable)); 2807 } 2808 2809 2810 immutable uint n1 = c[0][0] + c[0][1], 2811 n2 = c[1][0] + c[1][1], 2812 n = c[0][0] + c[1][0]; 2813 2814 immutable uint mode = 2815 cast(uint) ((cast(double) (n + 1) * (n1 + 1)) / (n1 + n2 + 2)); 2816 immutable double pExact = hypergeometricPMF(c[0][0], n1, n2, n); 2817 immutable double pMode = hypergeometricPMF(mode, n1, n2, n); 2818 2819 enum epsilon = 1 - 1e-5; 2820 if(approxEqual(pExact, pMode, 1 - epsilon)) { 2821 return TestRes(oddsRatio, 1); 2822 } else if(c[0][0] < mode) { 2823 immutable double pLower = hypergeometricCDF(c[0][0], n1, n2, n); 2824 2825 if(hypergeometricPMF(n, n1, n2, n) > pExact / epsilon) { 2826 return TestRes(oddsRatio, pLower); 2827 } 2828 2829 // Binary search for where to begin upper half. 2830 uint min = mode, max = n, guess = uint.max; 2831 while(max - min > 1) { 2832 guess = cast(uint) ( 2833 (max == min + 1 && guess == min) ? max : 2834 (cast(ulong) max + cast(ulong) min) / 2UL); 2835 2836 immutable double pGuess = hypergeometricPMF(guess, n1, n2, n); 2837 if(pGuess <= pExact && 2838 hypergeometricPMF(guess - 1, n1, n2, n) > pExact) { 2839 break; 2840 } else if(pGuess < pExact) { 2841 max = guess; 2842 } else min = guess; 2843 } 2844 2845 if(guess == uint.max) { 2846 guess = min; 2847 } 2848 2849 while(guess > 0 && hypergeometricPMF(guess, n1, n2, n) < pExact * epsilon) { 2850 guess--; 2851 } 2852 2853 while(hypergeometricPMF(guess, n1, n2, n) > pExact / epsilon) { 2854 guess++; 2855 } 2856 2857 static import std.algorithm; 2858 double p = std.algorithm.min(pLower + 2859 hypergeometricCDFR(guess, n1, n2, n), 1.0); 2860 return TestRes(oddsRatio, p); 2861 } else { 2862 immutable double pUpper = hypergeometricCDFR(c[0][0], n1, n2, n); 2863 2864 if(hypergeometricPMF(0, n1, n2, n) > pExact / epsilon) { 2865 return TestRes(oddsRatio, pUpper); 2866 } 2867 2868 // Binary search for where to begin lower half. 2869 uint min = 0, max = mode, guess = uint.max; 2870 while(max - min > 1) { 2871 guess = cast(uint) ( 2872 (max == min + 1 && guess == min) ? max : 2873 (cast(ulong) max + cast(ulong) min) / 2UL); 2874 immutable double pGuess = hypergeometricPMF(guess, n1, n2, n); 2875 2876 if(pGuess <= pExact && 2877 hypergeometricPMF(guess + 1, n1, n2, n) > pExact) { 2878 break; 2879 } else if(pGuess <= pExact) { 2880 min = guess; 2881 } else max = guess; 2882 } 2883 2884 if(guess == uint.max) { 2885 guess = min; 2886 } 2887 2888 while(hypergeometricPMF(guess, n1, n2, n) < pExact * epsilon) { 2889 guess++; 2890 } 2891 2892 while(guess > 0 && 2893 hypergeometricPMF(guess, n1, n2, n) > pExact / epsilon) { 2894 guess--; 2895 } 2896 2897 static import std.algorithm; 2898 double p = std.algorithm.min(pUpper + 2899 hypergeometricCDF(guess, n1, n2, n), 1.0); 2900 return TestRes(oddsRatio, p); 2901 } 2902 } 2903 2904 /** 2905 Convenience function. Converts a dynamic array to a static one, then 2906 calls the overload. 2907 */ 2908 TestRes fisherExact(T)(const T[][] contingencyTable, Alt alt = Alt.twoSided) 2909 if(isIntegral!(T)) { 2910 dstatsEnforce(contingencyTable.length == 2 && 2911 contingencyTable[0].length == 2 && 2912 contingencyTable[1].length == 2, 2913 "Fisher exact only supports 2x2 tables."); 2914 2915 T[2][2] newTable; 2916 newTable[0][0] = contingencyTable[0][0]; 2917 newTable[0][1] = contingencyTable[0][1]; 2918 newTable[1][1] = contingencyTable[1][1]; 2919 newTable[1][0] = contingencyTable[1][0]; 2920 return fisherExact(newTable, alt); 2921 } 2922 2923 unittest { 2924 // Simple, naive impl. of two-sided to test against. 2925 static double naive(const uint[][] c) { 2926 immutable uint n1 = c[0][0] + c[0][1], 2927 n2 = c[1][0] + c[1][1], 2928 n = c[0][0] + c[1][0]; 2929 immutable uint mode = 2930 cast(uint) ((cast(double) (n + 1) * (n1 + 1)) / (n1 + n2 + 2)); 2931 immutable double pExact = hypergeometricPMF(c[0][0], n1, n2, n); 2932 immutable double pMode = hypergeometricPMF(mode, n1, n2, n); 2933 if(approxEqual(pExact, pMode, 1e-7)) 2934 return 1; 2935 double sum = 0; 2936 foreach(i; 0..n + 1) { 2937 double pCur = hypergeometricPMF(i, n1, n2, n); 2938 if(pCur <= pExact / (1 - 1e-5)) 2939 sum += pCur; 2940 } 2941 return sum; 2942 } 2943 2944 uint[][] c = new uint[][](2, 2); 2945 2946 foreach(i; 0..100_000) { 2947 c[0][0] = uniform(0U, 51U); 2948 c[0][1] = uniform(0U, 51U); 2949 c[1][0] = uniform(0U, 51U); 2950 c[1][1] = uniform(0U, 51U); 2951 double naiveAns = naive(c); 2952 double fastAns = fisherExact(c); 2953 assert(approxEqual(naiveAns, fastAns), text(c, " - ", naiveAns, " - ", fastAns)); 2954 } 2955 2956 auto res = fisherExact([[19000, 80000], [20000, 90000]]); 2957 assert(approxEqual(res.testStat, 1.068731)); 2958 assert(approxEqual(res, 3.319e-9)); 2959 res = fisherExact([[18000, 80000], [20000, 90000]]); 2960 assert(approxEqual(res, 0.2751)); 2961 res = fisherExact([[14500, 20000], [30000, 40000]]); 2962 assert(approxEqual(res, 0.01106)); 2963 res = fisherExact([[100, 2], [1000, 5]]); 2964 assert(approxEqual(res, 0.1301)); 2965 res = fisherExact([[2, 7], [8, 2]]); 2966 assert(approxEqual(res, 0.0230141)); 2967 res = fisherExact([[5, 1], [10, 10]]); 2968 assert(approxEqual(res, 0.1973244)); 2969 res = fisherExact([[5, 15], [20, 20]]); 2970 assert(approxEqual(res, 0.0958044)); 2971 res = fisherExact([[5, 16], [20, 25]]); 2972 assert(approxEqual(res, 0.1725862)); 2973 res = fisherExact([[10, 5], [10, 1]]); 2974 assert(approxEqual(res, 0.1973244)); 2975 res = fisherExact([[5, 0], [1, 4]]); 2976 assert(approxEqual(res.p, 0.04761904)); 2977 res = fisherExact([[0, 1], [3, 2]]); 2978 assert(approxEqual(res.p, 1.0)); 2979 res = fisherExact([[0, 2], [6, 4]]); 2980 assert(approxEqual(res.p, 0.4545454545)); 2981 res = fisherExact([[2, 7], [8, 2]], Alt.less); 2982 assert(approxEqual(res, 0.01852)); 2983 res = fisherExact([[5, 1], [10, 10]], Alt.less); 2984 assert(approxEqual(res, 0.9783)); 2985 res = fisherExact([[5, 15], [20, 20]], Alt.less); 2986 assert(approxEqual(res, 0.05626)); 2987 res = fisherExact([[5, 16], [20, 25]], Alt.less); 2988 assert(approxEqual(res, 0.08914)); 2989 res = fisherExact([[2, 7], [8, 2]], Alt.greater); 2990 assert(approxEqual(res, 0.999)); 2991 res = fisherExact([[5, 1], [10, 10]], Alt.greater); 2992 assert(approxEqual(res, 0.1652)); 2993 res = fisherExact([[5, 15], [20, 20]], Alt.greater); 2994 assert(approxEqual(res, 0.985)); 2995 res = fisherExact([[5, 16], [20, 25]], Alt.greater); 2996 assert(approxEqual(res, 0.9723)); 2997 } 2998 2999 /** 3000 Performs a Kolmogorov-Smirnov (K-S) 2-sample test. The K-S test is a 3001 non-parametric test for a difference between two empirical distributions or 3002 between an empirical distribution and a reference distribution. 3003 3004 Returns: A TestRes with the K-S D value and a P value for the null that 3005 FPrime is distributed identically to F against the alternative that it isn't. 3006 This implementation uses a signed D value to indicate the direction of the 3007 difference between distributions. To get the D value used in standard 3008 notation, simply take the absolute value of this D value. 3009 3010 Bugs: Exact calculation not implemented. Uses asymptotic approximation. 3011 3012 References: http://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test 3013 */ 3014 TestRes ksTest(T, U)(T F, U Fprime) 3015 if(doubleInput!(T) && doubleInput!(U)) { 3016 double D = ksTestD(F, Fprime); 3017 return TestRes(D, ksPval(F.length, Fprime.length, D)); 3018 } 3019 3020 unittest { 3021 assert(approxEqual(ksTest([1,2,3,4,5], [1,2,3,4,5]).testStat, 0)); 3022 assert(approxEqual(ksTestDestructive([1,2,3,4,5], [1,2,2,3,5]).testStat, -.2)); 3023 assert(approxEqual(ksTest([-1,0,2,8, 6], [1,2,2,3,5]).testStat, .4)); 3024 assert(approxEqual(ksTest([1,2,3,4,5], [1,2,2,3,5,7,8]).testStat, .2857)); 3025 assert(approxEqual(ksTestDestructive([1, 2, 3, 4, 4, 4, 5], 3026 [1, 2, 3, 4, 5, 5, 5]).testStat, .2857)); 3027 3028 assert(approxEqual(ksTest([1, 2, 3, 4, 4, 4, 5], [1, 2, 3, 4, 5, 5, 5]), 3029 .9375)); 3030 assert(approxEqual(ksTestDestructive([1, 2, 3, 4, 4, 4, 5], 3031 [1, 2, 3, 4, 5, 5, 5]), .9375)); 3032 } 3033 3034 template isArrayLike(T) { 3035 enum bool isArrayLike = hasSwappableElements!(T) && hasAssignableElements!(T) 3036 && hasLength!(T) && isRandomAccessRange!(T); 3037 } 3038 3039 /** 3040 One-sample Kolmogorov-Smirnov test against a reference distribution. 3041 Takes a callable object for the CDF of refernce distribution. 3042 3043 Returns: A TestRes with the Kolmogorov-Smirnov D value and a P value for the 3044 null that Femp is a sample from F against the alternative that it isn't. This 3045 implementation uses a signed D value to indicate the direction of the 3046 difference between distributions. To get the D value used in standard 3047 notation, simply take the absolute value of this D value. 3048 3049 Bugs: Exact calculation not implemented. Uses asymptotic approximation. 3050 3051 Examples: 3052 --- 3053 auto stdNormal = parametrize!(normalCDF)(0.0, 1.0); 3054 auto empirical = [1, 2, 3, 4, 5]; 3055 auto res = ksTest(empirical, stdNormal); 3056 --- 3057 3058 References: http://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test 3059 */ 3060 TestRes ksTest(T, Func)(T Femp, Func F) 3061 if(doubleInput!(T) && is(ReturnType!(Func) : double)) { 3062 double D = ksTestD(Femp, F); 3063 return TestRes(D, ksPval(Femp.length, D)); 3064 } 3065 3066 unittest { 3067 auto stdNormal = paramFunctor!(normalCDF)(0.0, 1.0); 3068 assert(approxEqual(ksTest([1,2,3,4,5].dup, stdNormal).testStat, -.8413)); 3069 assert(approxEqual(ksTestDestructive([-1,0,2,8, 6].dup, stdNormal).testStat, -.5772)); 3070 auto lotsOfTies = [5,1,2,2,2,2,2,2,3,4].dup; 3071 assert(approxEqual(ksTest(lotsOfTies, stdNormal).testStat, -0.8772)); 3072 3073 assert(approxEqual(ksTest([0,1,2,3,4].dup, stdNormal), .03271)); 3074 3075 auto uniform01 = parametrize!(uniformCDF)(0, 1); 3076 assert(approxEqual(ksTestDestructive([0.1, 0.3, 0.5, 0.9, 1].dup, uniform01), 0.7591)); 3077 3078 } 3079 3080 /**Same as ksTest, except sorts in place, avoiding memory allocations.*/ 3081 TestRes ksTestDestructive(T, U)(T F, U Fprime) 3082 if(isArrayLike!(T) && isArrayLike!(U)) { 3083 double D = ksTestDDestructive(F, Fprime); 3084 return TestRes(D, ksPval(F.length, Fprime.length, D)); 3085 } 3086 3087 ///Ditto. 3088 TestRes ksTestDestructive(T, Func)(T Femp, Func F) 3089 if(isArrayLike!(T) && is(ReturnType!Func : double)) { 3090 double D = ksTestDDestructive(Femp, F); 3091 return TestRes(D, ksPval(Femp.length, D)); 3092 } 3093 3094 private double ksTestD(T, U)(T F, U Fprime) 3095 if(isInputRange!(T) && isInputRange!(U)) { 3096 auto alloc = newRegionAllocator(); 3097 return ksTestDDestructive(alloc.array(F), alloc.array(Fprime)); 3098 } 3099 3100 private double ksTestDDestructive(T, U)(T F, U Fprime) 3101 if(isArrayLike!(T) && isArrayLike!(U)) { 3102 qsort(F); 3103 qsort(Fprime); 3104 double D = 0; 3105 size_t FprimePos = 0; 3106 foreach(i; 0..2) { //Test both w/ Fprime x vals, F x vals. 3107 double diffMult = (i == 0) ? 1 : -1; 3108 foreach(FPos, Xi; F) { 3109 if(FPos < F.length - 1 && F[FPos + 1] == Xi) 3110 continue; //Handle ties. 3111 while(FprimePos < Fprime.length && Fprime[FprimePos] <= Xi) { 3112 FprimePos++; 3113 } 3114 double diff = diffMult * (cast(double) (FPos + 1) / F.length - 3115 cast(double) FprimePos / Fprime.length); 3116 if(abs(diff) > abs(D)) 3117 D = diff; 3118 } 3119 swap(F, Fprime); 3120 FprimePos = 0; 3121 } 3122 return D; 3123 } 3124 3125 private double ksTestD(T, Func)(T Femp, Func F) 3126 if(doubleInput!(T) && is(ReturnType!Func : double)) { 3127 auto alloc = newRegionAllocator(); 3128 return ksTestDDestructive(alloc.array(Femp), F); 3129 } 3130 3131 private double ksTestDDestructive(T, Func)(T Femp, Func F) 3132 if(isArrayLike!(T) && is(ReturnType!Func : double)) { 3133 qsort(Femp); 3134 double D = 0; 3135 3136 foreach(FPos, Xi; Femp) { 3137 double diff = cast(double) FPos / Femp.length - F(Xi); 3138 if(abs(diff) > abs(D)) 3139 D = diff; 3140 } 3141 3142 return D; 3143 } 3144 3145 private double ksPval(ulong N, ulong Nprime, double D) 3146 in { 3147 assert(D >= -1); 3148 assert(D <= 1); 3149 } do { 3150 return 1 - kolmogorovDistrib(sqrt(cast(double) (N * Nprime) / (N + Nprime)) * abs(D)); 3151 } 3152 3153 private double ksPval(ulong N, double D) 3154 in { 3155 assert(D >= -1); 3156 assert(D <= 1); 3157 } do { 3158 return 1 - kolmogorovDistrib(abs(D) * sqrt(cast(double) N)); 3159 } 3160 3161 /** 3162 Wald-wolfowitz or runs test for randomness of the distribution of 3163 elements for which positive() evaluates to true. For example, given 3164 a sequence of coin flips [H,H,H,H,H,T,T,T,T,T] and a positive() function of 3165 "a == 'H'", this test would determine that the heads are non-randomly 3166 distributed, since they are all at the beginning of obs. This is done 3167 by counting the number of runs of consecutive elements for which 3168 positive() evaluates to true, and the number of consecutive runs for which 3169 it evaluates to false. In the example above, we have 2 runs. These are the 3170 block of 5 consecutive heads at the beginning and the 5 consecutive tails 3171 at the end. 3172 3173 Alternatives are Alt.less, meaning that less runs than expected have been 3174 observed and data for which positive() is true tends to cluster, 3175 Alt.greater, which means that more runs than expected have been observed 3176 and data for which positive() is true tends to not cluster even moreso than 3177 expected by chance, and Alt.twoSided, meaning that elements for which 3178 positive() is true cluster as much as expected by chance. 3179 3180 Bugs: No exact calculation of the P-value. Asymptotic approximation only. 3181 3182 References: http://en.wikipedia.org/wiki/Runs_test 3183 */ 3184 double runsTest(alias positive = "a > 0", T)(T obs, Alt alt = Alt.twoSided) 3185 if(isIterable!(T)) { 3186 RunsTest!(positive, ForeachType!(T)) r; 3187 foreach(elem; obs) { 3188 r.put(elem); 3189 } 3190 return r.p(alt); 3191 } 3192 3193 unittest { 3194 // Values from R lawstat package, for which "a < median(data)" is 3195 // hard-coded as the equivalent to positive(). The median of this data 3196 // is 0.5, so everything works. 3197 immutable int[] data = [1,0,0,0,1,1,0,0,1,0,1,0,1,0,1,1,1,0,0,1].idup; 3198 assert(approxEqual(runsTest(data), 0.3581)); 3199 assert(approxEqual(runsTest(data, Alt.less), 0.821)); 3200 assert(approxEqual(runsTest(data, Alt.greater), 0.1791)); 3201 } 3202 3203 /** 3204 Runs test as in runsTest(), except calculates online instead of from stored 3205 array elements. 3206 */ 3207 struct RunsTest(alias positive = "a > 0", T) { 3208 private: 3209 uint nPos; 3210 uint nNeg; 3211 uint nRun; 3212 bool lastPos; 3213 3214 alias unaryFun!(positive) pos; 3215 3216 public: 3217 3218 /// 3219 void put(T elem) { 3220 bool curPos = pos(elem); 3221 if(nRun == 0) { 3222 nRun = 1; 3223 if(curPos) { 3224 nPos++; 3225 } else { 3226 nNeg++; 3227 } 3228 } else if(pos(elem)) { 3229 nPos++; 3230 if(!lastPos) { 3231 nRun++; 3232 } 3233 } else { 3234 nNeg++; 3235 if(lastPos) { 3236 nRun++; 3237 } 3238 } 3239 lastPos = curPos; 3240 } 3241 3242 /// 3243 uint nRuns() { 3244 return nRun; 3245 } 3246 3247 /// 3248 double p(Alt alt = Alt.twoSided) { 3249 uint N = nPos + nNeg; 3250 double expected = 2.0 * nPos * nNeg / N + 1; 3251 double sd = sqrt((expected - 1) * (expected - 2) / (N - 1)); 3252 if(alt == Alt.less) { 3253 return normalCDF(nRun, expected, sd); 3254 } else if(alt == Alt.greater) { 3255 return normalCDFR(nRun, expected, sd); 3256 } else { 3257 return 2 * ((nRun < expected) ? 3258 normalCDF(nRun, expected, sd) : 3259 normalCDFR(nRun, expected, sd)); 3260 } 3261 } 3262 } 3263 3264 deprecated { 3265 // Aliases for old names for correlation tests. 3266 alias pearsonCorTest pcorTest; 3267 alias spearmanCorTest scorTest; 3268 alias kendallCorTest kcorTest; 3269 } 3270 3271 /** 3272 Tests the hypothesis that the Pearson correlation between two ranges is 3273 different from some 0. Alternatives are Alt.less 3274 (pearsonCor(range1, range2) < 0), Alt.greater (pearsonCor(range1, range2) 3275 0) and Alt.twoSided (pearsonCor(range1, range2) != 0). 3276 3277 Returns: A ConfInt of the estimated Pearson correlation of the two ranges, 3278 the P-value against the given alternative, and the confidence interval of 3279 the correlation at the level specified by confLevel. 3280 3281 References: http://en.wikipedia.org/wiki/Pearson_correlation 3282 */ 3283 ConfInt pearsonCorTest(T, U)( 3284 T range1, 3285 U range2, 3286 Alt alt = Alt.twoSided, 3287 double confLevel = 0.95 3288 ) if(doubleInput!(T) && doubleInput!(U)) { 3289 enforceConfidence(confLevel); 3290 3291 auto pearsonRes = dstats.cor.pearsonCor(range1, range2); 3292 if(isNaN(pearsonRes.cor)) { 3293 return ConfInt.init; 3294 } 3295 3296 return pearsonCorTest(pearsonRes.cor, pearsonRes.N, alt, confLevel); 3297 } 3298 3299 /** 3300 Same as overload, but uses pre-computed correlation coefficient and sample 3301 size instead of computing them. 3302 3303 Note: This is a template only because of DMD Bug 2972. 3304 */ 3305 ConfInt pearsonCorTest()( 3306 double cor, 3307 double N, 3308 Alt alt = Alt.twoSided, 3309 double confLevel = 0.95 3310 ) { 3311 dstatsEnforce(N >= 0, "N must be >= 0 for pearsonCorTest."); 3312 dstatsEnforce(cor > -1.0 || approxEqual(cor, -1.0), 3313 "Correlation must be between 0, 1."); 3314 dstatsEnforce(cor < 1.0 || approxEqual(cor, 1.0), 3315 "Correlation must be between 0, 1."); 3316 enforceConfidence(confLevel); 3317 3318 immutable double denom = sqrt((1 - cor * cor) / (N - 2)); 3319 immutable double t = cor / denom; 3320 ConfInt ret; 3321 ret.testStat = cor; 3322 3323 double sqN, z; 3324 if(confLevel > 0) { 3325 sqN = sqrt(N - 3.0); 3326 z = sqN * atanh(cor); 3327 } 3328 3329 final switch(alt) { 3330 case Alt.none : 3331 return ret; 3332 case Alt.twoSided: 3333 ret.p = (abs(cor) >= 1) ? 0 : 3334 2 * ((t < 0) ? studentsTCDF(t, N - 2) : studentsTCDFR(t, N - 2)); 3335 3336 if(confLevel > 0) { 3337 double deltaZ = invNormalCDF(0.5 * (1 - confLevel)); 3338 ret.lowerBound = tanh((z + deltaZ) / sqN); 3339 ret.upperBound = tanh((z - deltaZ) / sqN); 3340 } else { 3341 ret.lowerBound = cor; 3342 ret.upperBound = cor; 3343 } 3344 3345 break; 3346 case Alt.less: 3347 if(cor >= 1) { 3348 ret.p = 1; 3349 } else if(cor <= -1) { 3350 ret.p = 0; 3351 } else { 3352 ret.p = studentsTCDF(t, N - 2); 3353 } 3354 3355 if(confLevel > 0) { 3356 double deltaZ = invNormalCDF(1 - confLevel); 3357 ret.lowerBound = -1; 3358 ret.upperBound = tanh((z - deltaZ) / sqN); 3359 } else { 3360 ret.lowerBound = -1; 3361 ret.upperBound = cor; 3362 } 3363 3364 break; 3365 case Alt.greater: 3366 if(cor >= 1) { 3367 ret.p = 0; 3368 } else if(cor <= -1) { 3369 ret.p = 1; 3370 } else { 3371 ret.p = studentsTCDFR(t, N - 2); 3372 } 3373 3374 if(confLevel > 0) { 3375 double deltaZ = invNormalCDF(1 - confLevel); 3376 ret.lowerBound = tanh((z + deltaZ) / sqN); 3377 ret.upperBound = 1; 3378 } else { 3379 ret.lowerBound = cor; 3380 ret.upperBound = 1; 3381 } 3382 3383 break; 3384 } 3385 return ret; 3386 } 3387 3388 unittest { 3389 // Values from R. 3390 auto t1 = pearsonCorTest([1,2,3,4,5].dup, [2,1,4,3,5].dup, Alt.twoSided); 3391 auto t2 = pearsonCorTest([1,2,3,4,5].dup, [2,1,4,3,5].dup, Alt.less); 3392 auto t3 = pearsonCorTest([1,2,3,4,5].dup, [2,1,4,3,5].dup, Alt.greater); 3393 3394 assert(approxEqual(t1.testStat, 0.8)); 3395 assert(approxEqual(t2.testStat, 0.8)); 3396 assert(approxEqual(t3.testStat, 0.8)); 3397 3398 assert(approxEqual(t1.p, 0.1041)); 3399 assert(approxEqual(t2.p, 0.948)); 3400 assert(approxEqual(t3.p, 0.05204)); 3401 3402 assert(approxEqual(t1.lowerBound, -0.2796400)); 3403 assert(approxEqual(t3.lowerBound, -0.06438567)); 3404 assert(approxEqual(t2.lowerBound, -1)); 3405 3406 assert(approxEqual(t1.upperBound, 0.9861962)); 3407 assert(approxEqual(t2.upperBound, 0.9785289)); 3408 assert(approxEqual(t3.upperBound, 1)); 3409 3410 // Test special case hack for cor = +- 1. 3411 uint[] myArr = [1,2,3,4,5]; 3412 uint[] myArrReverse = myArr.dup; 3413 reverse(myArrReverse); 3414 3415 auto t4 = pearsonCorTest(myArr, myArr, Alt.twoSided); 3416 auto t5 = pearsonCorTest(myArr, myArr, Alt.less); 3417 auto t6 = pearsonCorTest(myArr, myArr, Alt.greater); 3418 assert(approxEqual(t4.testStat, 1)); 3419 assert(approxEqual(t4.p, 0)); 3420 assert(approxEqual(t5.p, 1)); 3421 assert(approxEqual(t6.p, 0)); 3422 3423 auto t7 = pearsonCorTest(myArr, myArrReverse, Alt.twoSided); 3424 auto t8 = pearsonCorTest(myArr, myArrReverse, Alt.less); 3425 auto t9 = pearsonCorTest(myArr, myArrReverse, Alt.greater); 3426 assert(approxEqual(t7.testStat, -1)); 3427 assert(approxEqual(t7.p, 0)); 3428 assert(approxEqual(t8.p, 0)); 3429 assert(approxEqual(t9.p, 1)); 3430 } 3431 3432 /** 3433 Tests the hypothesis that the Spearman correlation between two ranges is 3434 different from some 0. Alternatives are 3435 Alt.less (spearmanCor(range1, range2) < 0), Alt.greater (spearmanCor(range1, range2) 3436 > 0) and Alt.twoSided (spearmanCor(range1, range2) != 0). 3437 3438 Returns: A TestRes containing the Spearman correlation coefficient and 3439 the P-value for the given alternative. 3440 3441 Bugs: Exact P-value computation not yet implemented. Uses asymptotic 3442 approximation only. This is good enough for most practical purposes given 3443 reasonably large N, but is not perfectly accurate. Not valid for data with 3444 very large amounts of ties. 3445 */ 3446 TestRes spearmanCorTest(T, U)(T range1, U range2, Alt alt = Alt.twoSided) 3447 if(isInputRange!(T) && isInputRange!(U) && 3448 is(typeof(range1.front < range1.front) == bool) && 3449 is(typeof(range2.front < range2.front) == bool)) { 3450 3451 static if(!hasLength!T) { 3452 auto alloc = newRegionAllocator(); 3453 auto r1 = alloc.array(range1); 3454 } else { 3455 alias range1 r1; 3456 } 3457 immutable double N = r1.length; 3458 3459 return pearsonCorTest(dstats.cor.spearmanCor(range1, range2), N, alt, 0); 3460 } 3461 3462 unittest { 3463 // Values from R. 3464 int[] arr1 = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; 3465 int[] arr2 = [8,6,7,5,3,0,9,8,6,7,5,3,0,9,3,6,2,4,3,6,8]; 3466 auto t1 = spearmanCorTest(arr1, arr2, Alt.twoSided); 3467 auto t2 = spearmanCorTest(arr1, arr2, Alt.less); 3468 auto t3 = spearmanCorTest(arr1, arr2, Alt.greater); 3469 3470 assert(approxEqual(t1.testStat, -0.1769406)); 3471 assert(approxEqual(t2.testStat, -0.1769406)); 3472 assert(approxEqual(t3.testStat, -0.1769406)); 3473 3474 assert(approxEqual(t1.p, 0.4429)); 3475 assert(approxEqual(t3.p, 0.7785)); 3476 assert(approxEqual(t2.p, 0.2215)); 3477 } 3478 3479 /** 3480 Tests the hypothesis that the Kendall Tau-b between two ranges is 3481 different from 0. Alternatives are Alt.less (kendallCor(range1, range2) < 0), 3482 Alt.greater (kendallCor(range1, range2) > 0) and Alt.twoSided 3483 (kendallCor(range1, range2) != 0). 3484 3485 3486 exactThresh controls the maximum length of the range for which exact P-value 3487 computation is used. The default is 50. Exact calculation is never used 3488 when ties are present because it is not computationally feasible. 3489 Do not set this higher than 100, as it will be very slow 3490 and the asymptotic approximation is pretty good at even a fraction of this 3491 size. 3492 3493 Note: 3494 3495 As an optimization, when a range is a SortedRange with predicate "a < b", 3496 it is assumed already sorted and not sorted a second time by this function. 3497 This is useful when applying this function multiple times with one of the 3498 arguments the same every time. 3499 3500 Returns: A TestRes containing the Kendall correlation coefficient and 3501 the P-value for the given alternative. 3502 3503 References: StackOverflow Question 948341 (http://stackoverflow.com/questions/948341) 3504 3505 The Variance of Tau When Both Rankings Contain Ties. M.G. Kendall. 3506 Biometrika, Vol 34, No. 3/4 (Dec., 1947), pp. 297-298 3507 */ 3508 TestRes kendallCorTest(T, U)( 3509 T range1, 3510 U range2, 3511 Alt alt = Alt.twoSided, 3512 uint exactThresh = 50 3513 ) if(isInputRange!(T) && isInputRange!(U)) { 3514 auto alloc = newRegionAllocator(); 3515 3516 static if(dstats.cor.isDefaultSorted!T) { 3517 alias range1 i1d; 3518 } else { 3519 auto i1d = alloc.array(range1); 3520 } 3521 3522 static if(dstats.cor.isDefaultSorted!U) { 3523 alias range2 i2d; 3524 } else { 3525 auto i2d = alloc.array(range2); 3526 } 3527 3528 immutable res = dstats.cor.kendallCorDestructiveLowLevel(i1d, i2d, true); 3529 immutable double n = i1d.length; 3530 3531 immutable double var = 3532 (2.0 / 9) * n * (n - 1) * (2 * n + 5) 3533 - (2.0 / 9) * res.tieCorrectT1 3534 - (2.0 / 9) * res.tieCorrectU1 3535 + (4 / (9 * n * (n - 1) * (n - 2))) * res.tieCorrectT2 * res.tieCorrectU2 3536 + 2 / (n * (n - 1)) * res.tieCorrectT3 * res.tieCorrectU3; 3537 3538 // Need the / 2 to change C, as used in Kendall's paper to S, as used here. 3539 immutable double sd = sqrt(var) / 2; 3540 3541 enum double cc = 1; 3542 auto tau = res.tau; 3543 auto s = res.s; 3544 3545 immutable bool noTies = res.tieCorrectT1 == 0 && res.tieCorrectU1 == 0; 3546 3547 if(noTies && n <= exactThresh) { 3548 // More than uint.max data points for exact calculation is 3549 // not plausible. 3550 assert(i1d.length < uint.max); 3551 immutable N = cast(uint) i1d.length; 3552 immutable nSwaps = (N * (N - 1) / 2 - cast(uint) s) / 2; 3553 return TestRes(tau, kendallCorExactP(N, nSwaps, alt)); 3554 } 3555 3556 final switch(alt) { 3557 case Alt.none : 3558 return TestRes(tau); 3559 case Alt.twoSided: 3560 if(abs(s) <= cc) { 3561 return TestRes(tau, 1); 3562 } else if(s < 0) { 3563 return TestRes(tau, 2 * normalCDF(s + cc, 0, sd)); 3564 } else { 3565 assert(s > 0); 3566 return TestRes(tau, 2 * normalCDFR(s - cc, 0, sd)); 3567 } 3568 assert(0); 3569 3570 case Alt.less: 3571 return TestRes(tau, normalCDF(s + cc, 0, sd)); 3572 case Alt.greater: 3573 return TestRes(tau, normalCDFR(s - cc, 0, sd)); 3574 } 3575 } 3576 3577 // Dynamic programming algorithm for computing exact Kendall tau P-values. 3578 // Thanks to ShreevatsaR from StackOverflow. 3579 private double kendallCorExactP(uint N, uint swaps, Alt alt) { 3580 uint maxSwaps = N * (N - 1) / 2; 3581 assert(swaps <= maxSwaps); 3582 immutable expectedSwaps = cast(ulong) N * (N - 1) * 0.25; 3583 if(alt == Alt.greater) { 3584 if(swaps > expectedSwaps) { 3585 if(swaps == maxSwaps) { 3586 return 1; 3587 } 3588 return 1.0 - kendallCorExactP(N, maxSwaps - swaps - 1, Alt.greater); 3589 } 3590 } else if(alt == Alt.less) { 3591 if(swaps == 0) { 3592 return 1; 3593 } 3594 return kendallCorExactP(N, maxSwaps - swaps + 0, Alt.greater); 3595 } else if(alt == Alt.twoSided) { 3596 if(swaps < expectedSwaps) { 3597 return min(1, 2 * kendallCorExactP(N, swaps, Alt.greater)); 3598 } else if(swaps > expectedSwaps) { 3599 return min(1, 2 * kendallCorExactP(N, swaps, Alt.less)); 3600 } else { 3601 return 1; 3602 } 3603 } else { // Alt.none 3604 return double.nan; 3605 } 3606 3607 /* This algorithm was obtained from Question 948341 on stackoverflow.com 3608 * and works as follows: 3609 * 3610 * swaps is the number of swaps that would be necessary in a bubble sort 3611 * to sort one list in the same order as the other. N is the sample size. 3612 * We want to find the number of ways that we could get a bubble sort 3613 * distance of at least swaps, and divide it by the total number of 3614 * permutations, pElem. 3615 * 3616 * The number of swaps necessary to sort a list is equivalent to the 3617 * number of inversions in the list, i.e. where i > j, but 3618 * list[i] < list[j]. This is a bottom-up dynamic programming algorithm 3619 * based on this principle. 3620 * 3621 * Assume c(N, k) is the number of permutations that require <= swaps 3622 * inversions. 3623 * We use the recurrence relation: 3624 * When k ≤ N - 1, c(N,k) = c(N,k-1) + c(N-1,k) 3625 * When k ≥ N, c(N,k) = c(N,k-1) + c(N-1,k) - c(N-1,k-N) 3626 * 3627 * We also divide every value by the constant N! to turn this count into a 3628 * probability. 3629 */ 3630 3631 immutable double pElem = exp(-logFactorial(N)); 3632 auto alloc = newRegionAllocator(); 3633 double[] cur = alloc.uninitializedArray!(double[])(swaps + 1); 3634 double[] prev = alloc.uninitializedArray!(double[])(swaps + 1); 3635 3636 prev[] = pElem; 3637 cur[0] = pElem; 3638 foreach(k; 1..N + 1) { 3639 immutable uint nSwapsPossible = k * (k - 1) / 2; 3640 immutable uint upTo = min(swaps, nSwapsPossible) + 1; 3641 foreach(j; 1..upTo) { 3642 if(j < k) { 3643 cur[j] = prev[j] + cur[j - 1]; 3644 } else { 3645 cur[j] = prev[j] - prev[j - k] + cur[j - 1]; 3646 } 3647 } 3648 cur[upTo..$] = cur[upTo - 1]; 3649 swap(cur, prev); 3650 } 3651 3652 return prev[$ - 1]; 3653 } 3654 3655 unittest { 3656 /* Values from R, with continuity correction enabled. Note that large 3657 * one-sided inexact P-values are commented out because R seems to have a 3658 * slightly different interpretation of the proper continuity correction 3659 * than this library. This library corrects the z-score in the direction 3660 * that would make the test more conservative. R corrects towards zero. 3661 * I can't find a reference to support either one, but empirically it seems 3662 * like correcting towards more conservative results more accurately mirrors 3663 * the results of the exact test. This isn't too big a deal anyhow since: 3664 * 3665 * 1. The difference is small. 3666 * 2. It only occurs on results that are very far from significance 3667 * (P > 0.5). 3668 */ 3669 int[] arr1 = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; 3670 int[] arr2 = [8,6,7,5,3,0,9,8,6,7,5,3,0,9,3,6,2,4,3,6,8]; 3671 auto t1 = kendallCorTest(arr1, arr2, Alt.twoSided); 3672 auto t2 = kendallCorTest(arr1, arr2, Alt.less); 3673 auto t3 = kendallCorTest(arr1, arr2, Alt.greater); 3674 3675 assert(approxEqual(t1.testStat, -.1448010)); 3676 assert(approxEqual(t2.testStat, -.1448010)); 3677 assert(approxEqual(t3.testStat, -.1448010)); 3678 3679 assert(approxEqual(t1.p, 0.3923745)); 3680 //assert(approxEqual(t3.p, 0.8038127)); 3681 assert(approxEqual(t2.p, 0.1961873)); 3682 3683 // Now, test the case of ties in both arrays. 3684 arr1 = [1,1,1,2,2,3,4,5,5,6]; 3685 arr2 = [1,1,2,3,4,5,5,5,5,6]; 3686 assert(approxEqual(kendallCorTest(arr1, arr2, Alt.twoSided).p, 0.001216776)); 3687 //assert(approxEqual(kendallCorTest(arr1, arr2, Alt.less).p, 0.9993916)); 3688 assert(approxEqual(kendallCorTest(arr1, arr2, Alt.greater).p, 0.0006083881)); 3689 3690 arr1 = [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]; 3691 arr2 = [1,1,1,3,3,3,2,2,2,5,5,5,4,4,4]; 3692 assert(approxEqual(kendallCorTest(arr1, arr2).p, 0.006123)); 3693 assert(approxEqual(kendallCorTest(assumeSorted(arr1), arr2).p, 0.006123)); 3694 3695 // Test the exact stuff. Still using values from R. 3696 uint[] foo = [1,2,3,4,5]; 3697 uint[] bar = [1,2,3,5,4]; 3698 uint[] baz = [5,3,1,2,4]; 3699 3700 assert(approxEqual(kendallCorTest(foo, foo).p, 0.01666666)); 3701 assert(approxEqual(kendallCorTest(foo, foo, Alt.greater).p, 0.008333333)); 3702 assert(approxEqual(kendallCorTest(foo, foo, Alt.less).p, 1)); 3703 3704 assert(approxEqual(kendallCorTest(foo, bar).p, 0.083333333)); 3705 assert(approxEqual(kendallCorTest(foo, bar, Alt.greater).p, 0.041666667)); 3706 assert(approxEqual(kendallCorTest(foo, bar, Alt.less).p, 0.9917)); 3707 3708 assert(approxEqual(kendallCorTest(foo, baz).p, 0.8167)); 3709 assert(approxEqual(kendallCorTest(foo, baz, Alt.greater).p, 0.7583)); 3710 assert(approxEqual(kendallCorTest(foo, baz, Alt.less).p, .4083)); 3711 3712 assert(approxEqual(kendallCorTest(bar, baz).p, 0.4833)); 3713 assert(approxEqual(kendallCorTest(bar, baz, Alt.greater).p, 0.8833)); 3714 assert(approxEqual(kendallCorTest(bar, baz, Alt.less).p, 0.2417)); 3715 3716 // A little monte carlo unittesting. For large ranges, the deviation 3717 // between the exact and approximate version should be extremely small. 3718 foreach(i; 0..100) { 3719 uint nToTake = uniform(15, 65); 3720 auto lhs = array(take(randRange!rNormal(0, 1), nToTake)); 3721 auto rhs = array(take(randRange!rNormal(0, 1), nToTake)); 3722 if(i & 1) { 3723 lhs[] += rhs[] * 0.2; // Make sure there's some correlation. 3724 } else { 3725 lhs[] -= rhs[] * 0.2; 3726 } 3727 double exact = kendallCorTest(lhs, rhs).p; 3728 double approx = kendallCorTest(lhs, rhs, Alt.twoSided, 0).p; 3729 assert(abs(exact - approx) < 0.01); 3730 3731 exact = kendallCorTest(lhs, rhs, Alt.greater).p; 3732 approx = kendallCorTest(lhs, rhs, Alt.greater, 0).p; 3733 assert(abs(exact - approx) < 0.01); 3734 3735 exact = kendallCorTest(lhs, rhs, Alt.less).p; 3736 approx = kendallCorTest(lhs, rhs, Alt.less, 0).p; 3737 assert(abs(exact - approx) < 0.01); 3738 } 3739 } 3740 3741 /**A test for normality of the distribution of a range of values. Based on 3742 * the assumption that normally distributed values will have a sample skewness 3743 * and sample kurtosis very close to zero. 3744 * 3745 * Returns: A TestRes with the K statistic, which is Chi-Square distributed 3746 * with 2 degrees of freedom under the null, and the P-value for the alternative 3747 * that the data has skewness and kurtosis not equal to zero against the null 3748 * that skewness and kurtosis are near zero. A normal distribution always has 3749 * skewness and kurtosis that converge to zero as sample size goes to infinity. 3750 * 3751 * Notes: Contrary to popular belief, tests for normality should usually 3752 * not be used to deterimine whether T-tests are valid. If the sample size is 3753 * large, T-tests are valid regardless of the distribution due to the central 3754 * limit theorem. If the sample size is small, a test for normality will 3755 * likely not be very powerful, and a priori knowledge or simple inspection 3756 * of the data is often a better idea. 3757 * 3758 * References: 3759 * D'Agostino, Ralph B., Albert Belanger, and Ralph B. D'Agostino, Jr. 3760 * "A Suggestion for Using Powerful and Informative Tests of Normality", 3761 * The American Statistician, Vol. 44, No. 4. (Nov., 1990), pp. 316-321. 3762 */ 3763 TestRes dAgostinoK(T)(T range) 3764 if(doubleIterable!(T)) { 3765 // You are not meant to understand this. I sure don't. I just implemented 3766 // these formulas off of Wikipedia, which got them from: 3767 3768 // D'Agostino, Ralph B., Albert Belanger, and Ralph B. D'Agostino, Jr. 3769 // "A Suggestion for Using Powerful and Informative Tests of Normality", 3770 // The American Statistician, Vol. 44, No. 4. (Nov., 1990), pp. 316-321. 3771 3772 // Amazing. I didn't even realize things this complicated were possible 3773 // in 1990, before widespread computer algebra systems. 3774 3775 // Notation from Wikipedia. Keeping same notation for simplicity. 3776 double sqrtb1 = void, b2 = void, n = void; 3777 { 3778 auto summ = summary(range); 3779 sqrtb1 = summ.skewness; 3780 b2 = summ.kurtosis + 3; 3781 n = summ.N; 3782 } 3783 3784 // Calculate transformed skewness. 3785 double Y = sqrtb1 * sqrt((n + 1) * (n + 3) / (6 * (n - 2))); 3786 double beta2b1Numer = 3 * (n * n + 27 * n - 70) * (n + 1) * (n + 3); 3787 double beta2b1Denom = (n - 2) * (n + 5) * (n + 7) * (n + 9); 3788 double beta2b1 = beta2b1Numer / beta2b1Denom; 3789 double Wsq = -1 + sqrt(2 * (beta2b1 - 1)); 3790 double delta = 1.0 / sqrt(log(sqrt(Wsq))); 3791 double alpha = sqrt( 2.0 / (Wsq - 1)); 3792 double Zb1 = delta * log(Y / alpha + sqrt(pow(Y / alpha, 2) + 1)); 3793 3794 // Calculate transformed kurtosis. 3795 double Eb2 = 3 * (n - 1) / (n + 1); 3796 double sigma2b2 = (24 * n * (n - 2) * (n - 3)) / ( 3797 (n + 1) * (n + 1) * (n + 3) * (n + 5)); 3798 double x = (b2 - Eb2) / sqrt(sigma2b2); 3799 3800 double sqBeta1b2 = 6 * (n * n - 5 * n + 2) / ((n + 7) * (n + 9)) * 3801 sqrt((6 * (n + 3) * (n + 5)) / (n * (n - 2) * (n - 3))); 3802 double A = 6 + 8 / sqBeta1b2 * (2 / sqBeta1b2 + sqrt(1 + 4 / (sqBeta1b2 * sqBeta1b2))); 3803 double Zb2 = ((1 - 2 / (9 * A)) - 3804 cbrt((1 - 2 / A) / (1 + x * sqrt(2 / (A - 4)))) ) * 3805 sqrt(9 * A / 2); 3806 3807 double K2 = Zb1 * Zb1 + Zb2 * Zb2; 3808 3809 if(isNaN(K2)) { 3810 return TestRes(double.nan, double.nan); 3811 } 3812 3813 return TestRes(K2, chiSquareCDFR(K2, 2)); 3814 } 3815 3816 unittest { 3817 // Values from R's fBasics package. 3818 int[] arr1 = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; 3819 int[] arr2 = [8,6,7,5,3,0,9,8,6,7,5,3,0,9,3,6,2,4,3,6,8]; 3820 3821 auto r1 = dAgostinoK(arr1); 3822 auto r2 = dAgostinoK(arr2); 3823 3824 assert(approxEqual(r1.testStat, 3.1368)); 3825 assert(approxEqual(r1.p, 0.2084)); 3826 3827 assert(approxEqual(r2.testStat, 1.1816)); 3828 assert(approxEqual(r2.p, 0.5539)); 3829 } 3830 3831 /**Fisher's method of meta-analyzing a set of P-values to determine whether 3832 * there are more significant results than would be expected by chance. 3833 * Based on a chi-square statistic for the sum of the logs of the P-values. 3834 * 3835 * Returns: A TestRes containing the chi-square statistic and a P-value for 3836 * the alternative hypothesis that more small P-values than would be expected 3837 * by chance are present against the alternative that the distribution of 3838 * P-values is uniform or enriched for large P-values. 3839 * 3840 * References: Fisher, R. A. (1948) "Combining independent tests of 3841 * significance", American Statistician, vol. 2, issue 5, page 30. 3842 * (In response to Question 14) 3843 */ 3844 TestRes fishersMethod(R)(R pVals) 3845 if(doubleInput!R) { 3846 double chiSq = 0; 3847 uint df = 0; 3848 foreach(pVal; pVals) { 3849 dstatsEnforce(pVal >= 0 && pVal <= 1, 3850 "P-values must be between 0, 1 for Fisher's Method."); 3851 chiSq += log( cast(double) pVal); 3852 df += 2; 3853 } 3854 chiSq *= -2; 3855 return TestRes(chiSq, chiSquareCDFR(chiSq, df)); 3856 } 3857 3858 unittest { 3859 // First, basic sanity check. Make sure w/ one P-value, we get back that 3860 // P-value. 3861 for(double p = 0.01; p < 1; p += 0.01) { 3862 assert(approxEqual(fishersMethod([p].dup).p, p)); 3863 } 3864 float[] ps = [0.739, 0.0717, 0.01932, 0.03809]; 3865 auto res = fishersMethod(ps); 3866 assert(approxEqual(res.testStat, 20.31)); 3867 assert(res.p < 0.01); 3868 } 3869 3870 /// For falseDiscoveryRate. 3871 enum Dependency : bool { 3872 /** 3873 Assume that dependency among hypotheses may exist. (More conservative, 3874 Benjamini-Yekutieli procedure.) 3875 */ 3876 yes = true, 3877 3878 /** 3879 Assume hypotheses are independent. (Less conservative, Benjamine- 3880 Hochberg procedure.) 3881 */ 3882 no = false 3883 } 3884 3885 /** 3886 Computes the false discovery rate statistic given a list of 3887 p-values, according to Benjamini and Hochberg (1995) (independent) or 3888 Benjamini and Yekutieli (2001) (dependent). The Dependency parameter 3889 controls whether hypotheses are assumed to be independent, or whether 3890 the more conservative assumption that they are correlated must be made. 3891 3892 Returns: 3893 An array of adjusted P-values with indices corresponding to the order of 3894 the P-values in the input data. 3895 3896 References: 3897 Benjamini, Y., and Hochberg, Y. (1995). Controlling the false discovery rate: 3898 a practical and powerful approach to multiple testing. Journal of the Royal 3899 Statistical Society Series B, 57, 289-200 3900 3901 Benjamini, Y., and Yekutieli, D. (2001). The control of the false discovery 3902 rate in multiple testing under dependency. Annals of Statistics 29, 1165-1188. 3903 */ 3904 float[] falseDiscoveryRate(T)(T pVals, Dependency dep = Dependency.no) 3905 if(doubleInput!(T)) { 3906 auto qVals = array(map!(to!float)(pVals)); 3907 3908 double C = 1; 3909 if(dep == Dependency.yes) { 3910 foreach(i; 2..qVals.length + 1) { 3911 C += 1.0 / i; 3912 } 3913 } 3914 3915 auto alloc = newRegionAllocator(); 3916 auto perm = alloc.uninitializedArray!(size_t[])(qVals.length); 3917 foreach(i, ref elem; perm) { 3918 elem = i; 3919 } 3920 3921 qsort(qVals, perm); 3922 3923 foreach(i, ref q; qVals) { 3924 q = min(1.0f, q * C * cast(double) qVals.length / (cast(double) i + 1)); 3925 } 3926 3927 float smallestSeen = float.max; 3928 foreach(ref q; retro(qVals)) { 3929 if(q < smallestSeen) { 3930 smallestSeen = q; 3931 } else { 3932 q = smallestSeen; 3933 } 3934 } 3935 3936 qsort(perm, qVals); //Makes order of qVals correspond to input. 3937 return qVals; 3938 } 3939 3940 unittest { 3941 // Comparing results to R. 3942 auto pVals = [.90, .01, .03, .03, .70, .60, .01].dup; 3943 auto qVals = falseDiscoveryRate(pVals); 3944 alias approxEqual ae; 3945 assert(ae(qVals[0], .9)); 3946 assert(ae(qVals[1], .035)); 3947 assert(ae(qVals[2], .052)); 3948 assert(ae(qVals[3], .052)); 3949 assert(ae(qVals[4], .816666666667)); 3950 assert(ae(qVals[5], .816666666667)); 3951 assert(ae(qVals[6], .035)); 3952 3953 auto p2 = [.1, .02, .6, .43, .001].dup; 3954 auto q2 = falseDiscoveryRate(p2); 3955 assert(ae(q2[0], .16666666)); 3956 assert(ae(q2[1], .05)); 3957 assert(ae(q2[2], .6)); 3958 assert(ae(q2[3], .5375)); 3959 assert(ae(q2[4], .005)); 3960 3961 // Dependent case. 3962 qVals = falseDiscoveryRate(pVals, Dependency.yes); 3963 assert(ae(qVals[0], 1)); 3964 assert(ae(qVals[1], .09075)); 3965 assert(ae(qVals[2], .136125)); 3966 assert(ae(qVals[3], .136125)); 3967 assert(ae(qVals[4], 1)); 3968 assert(ae(qVals[5], 1)); 3969 assert(ae(qVals[6], .09075)); 3970 3971 q2 = falseDiscoveryRate(p2, Dependency.yes); 3972 assert(ae(q2[0], .38055555)); 3973 assert(ae(q2[1], .1141667)); 3974 assert(ae(q2[2], 1)); 3975 assert(ae(q2[3], 1)); 3976 assert(ae(q2[4], .01141667)); 3977 } 3978 3979 /**Uses the Hochberg procedure to control the familywise error rate assuming 3980 * that hypothesis tests are independent. This is more powerful than 3981 * Holm-Bonferroni correction, but requires the independence assumption. 3982 * 3983 * Returns: 3984 * An array of adjusted P-values with indices corresponding to the order of 3985 * the P-values in the input data. 3986 * 3987 * References: 3988 * Hochberg, Y. (1988). A sharper Bonferroni procedure for multiple tests of 3989 * significance. Biometrika, 75, 800-803. 3990 */ 3991 float[] hochberg(T)(T pVals) 3992 if(doubleInput!(T)) { 3993 auto qVals = array(map!(to!float)(pVals)); 3994 3995 auto alloc = newRegionAllocator(); 3996 auto perm = alloc.uninitializedArray!(size_t[])(qVals.length); 3997 foreach(i, ref elem; perm) 3998 elem = i; 3999 4000 qsort(qVals, perm); 4001 4002 foreach(i, ref q; qVals) { 4003 dstatsEnforce(q >= 0 && q <= 1, 4004 "P-values must be between 0, 1 for hochberg."); 4005 q = min(1.0f, q * (cast(double) qVals.length - i)); 4006 } 4007 4008 float smallestSeen = float.max; 4009 foreach(ref q; retro(qVals)) { 4010 if(q < smallestSeen) { 4011 smallestSeen = q; 4012 } else { 4013 q = smallestSeen; 4014 } 4015 } 4016 4017 qsort(perm, qVals); //Makes order of qVals correspond to input. 4018 return qVals; 4019 } 4020 4021 unittest { 4022 alias approxEqual ae; 4023 auto q = hochberg([0.01, 0.02, 0.025, 0.9].dup); 4024 assert(ae(q[0], 0.04)); 4025 assert(ae(q[1], 0.05)); 4026 assert(ae(q[2], 0.05)); 4027 assert(ae(q[3], 0.9)); 4028 4029 auto p2 = [.1, .02, .6, .43, .001].dup; 4030 auto q2 = hochberg(p2); 4031 assert(ae(q2[0], .3)); 4032 assert(ae(q2[1], .08)); 4033 assert(ae(q2[2], .6)); 4034 assert(ae(q2[3], .6)); 4035 assert(ae(q2[4], .005)); 4036 } 4037 4038 /**Uses the Holm-Bonferroni method to adjust a set of P-values in a way that 4039 * controls the familywise error rate (The probability of making at least one 4040 * Type I error). This is basically a less conservative version of 4041 * Bonferroni correction that is still valid for arbitrary assumptions and 4042 * controls the familywise error rate. Therefore, there aren't too many good 4043 * reasons to use regular Bonferroni correction instead. 4044 * 4045 * Returns: 4046 * An array of adjusted P-values with indices corresponding to the order of 4047 * the P-values in the input data. 4048 * 4049 * References: 4050 * Holm, S. (1979). A simple sequentially rejective multiple test procedure. 4051 * Scandinavian Journal of Statistics, 6, 65-70. 4052 */ 4053 float[] holmBonferroni(T)(T pVals) 4054 if(doubleInput!(T)) { 4055 auto alloc = newRegionAllocator(); 4056 4057 auto qVals = array(map!(to!float)(pVals)); 4058 auto perm = alloc.uninitializedArray!(size_t[])(qVals.length); 4059 4060 foreach(i, ref elem; perm) { 4061 elem = i; 4062 } 4063 4064 qsort(qVals, perm); 4065 4066 foreach(i, ref q; qVals) { 4067 dstatsEnforce(q >= 0 && q <= 1, 4068 "P-values must be between 0, 1 for holmBonferroni."); 4069 q = min(1.0, q * (cast(double) qVals.length - i)); 4070 } 4071 4072 foreach(i; 1..qVals.length) { 4073 if(qVals[i] < qVals[i - 1]) { 4074 qVals[i] = qVals[i - 1]; 4075 } 4076 } 4077 4078 qsort(perm, qVals); //Makes order of qVals correspond to input. 4079 return qVals; 4080 } 4081 4082 unittest { 4083 // Values from R. 4084 auto ps = holmBonferroni([0.001, 0.2, 0.3, 0.4, 0.7].dup); 4085 alias approxEqual ae; 4086 assert(ae(ps[0], 0.005)); 4087 assert(ae(ps[1], 0.8)); 4088 assert(ae(ps[2], 0.9)); 4089 assert(ae(ps[3], 0.9)); 4090 assert(ae(ps[4], 0.9)); 4091 4092 ps = holmBonferroni([0.3, 0.1, 0.4, 0.1, 0.5, 0.9].dup); 4093 assert(ps == [1f, 0.6f, 1f, 0.6f, 1f, 1f]); 4094 } 4095 4096 // old unconstrained approxEqual to work around https://issues.dlang.org/show_bug.cgi?id=18287 4097 static if (__VERSION__ == 2078) private 4098 { 4099 /** 4100 Computes whether two values are approximately equal, admitting a maximum 4101 relative difference, and a maximum absolute difference. 4102 Params: 4103 lhs = First item to compare. 4104 rhs = Second item to compare. 4105 maxRelDiff = Maximum allowable difference relative to `rhs`. 4106 maxAbsDiff = Maximum absolute difference. 4107 Returns: 4108 `true` if the two items are approximately equal under either criterium. 4109 If one item is a range, and the other is a single value, then the result 4110 is the logical and-ing of calling `approxEqual` on each element of the 4111 ranged item against the single item. If both items are ranges, then 4112 `approxEqual` returns `true` if and only if the ranges have the same 4113 number of elements and if `approxEqual` evaluates to `true` for each 4114 pair of elements. 4115 */ 4116 bool approxEqual(T, U, V)(T lhs, U rhs, V maxRelDiff, V maxAbsDiff = 1e-5) 4117 { 4118 import std.range.primitives : empty, front, isInputRange, popFront; 4119 static if (isInputRange!T) 4120 { 4121 static if (isInputRange!U) 4122 { 4123 // Two ranges 4124 for (;; lhs.popFront(), rhs.popFront()) 4125 { 4126 if (lhs.empty) return rhs.empty; 4127 if (rhs.empty) return lhs.empty; 4128 if (!approxEqual(lhs.front, rhs.front, maxRelDiff, maxAbsDiff)) 4129 return false; 4130 } 4131 } 4132 else static if (isIntegral!U) 4133 { 4134 // convert rhs to real 4135 return approxEqual(lhs, real(rhs), maxRelDiff, maxAbsDiff); 4136 } 4137 else 4138 { 4139 // lhs is range, rhs is number 4140 for (; !lhs.empty; lhs.popFront()) 4141 { 4142 if (!approxEqual(lhs.front, rhs, maxRelDiff, maxAbsDiff)) 4143 return false; 4144 } 4145 return true; 4146 } 4147 } 4148 else 4149 { 4150 static if (isInputRange!U) 4151 { 4152 // lhs is number, rhs is range 4153 for (; !rhs.empty; rhs.popFront()) 4154 { 4155 if (!approxEqual(lhs, rhs.front, maxRelDiff, maxAbsDiff)) 4156 return false; 4157 } 4158 return true; 4159 } 4160 else static if (isIntegral!T || isIntegral!U) 4161 { 4162 // convert both lhs and rhs to real 4163 return approxEqual(real(lhs), real(rhs), maxRelDiff, maxAbsDiff); 4164 } 4165 else 4166 { 4167 // two numbers 4168 //static assert(is(T : real) && is(U : real)); 4169 if (rhs == 0) 4170 { 4171 return fabs(lhs) <= maxAbsDiff; 4172 } 4173 static if (is(typeof(lhs.infinity)) && is(typeof(rhs.infinity))) 4174 { 4175 if (lhs == lhs.infinity && rhs == rhs.infinity || 4176 lhs == -lhs.infinity && rhs == -rhs.infinity) return true; 4177 } 4178 return fabs((lhs - rhs) / rhs) <= maxRelDiff 4179 || maxAbsDiff != 0 && fabs(lhs - rhs) <= maxAbsDiff; 4180 } 4181 } 4182 } 4183 4184 /** 4185 Returns $(D approxEqual(lhs, rhs, 1e-2, 1e-5)). 4186 */ 4187 bool approxEqual(T, U)(T lhs, U rhs) 4188 { 4189 return approxEqual(lhs, rhs, 1e-2, 1e-5); 4190 } 4191 }