Experiment.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. namespace MathPHP\Statistics;
  3. use MathPHP\Exception;
  4. /**
  5. * Statistical experiments (Epidemiology methods, etc.)
  6. * - Risk ratio
  7. * - Odds ratio
  8. * - Likelihood ratio
  9. */
  10. class Experiment
  11. {
  12. /**
  13. * Z score for 95% confidence interval
  14. * @var float
  15. */
  16. private const Z = 1.96;
  17. /**
  18. * Normal lower tail probability for calculating P value
  19. * @var float
  20. */
  21. private const NORMAL_LOWER_TAIL_PROBABILITY = -0.717;
  22. /**
  23. * Normal upper tail probability for calculating P value
  24. * @var float
  25. */
  26. private const NORMAL_UPPER_TAIL_PROBABILITY = 0.416;
  27. /**
  28. * Risk ratio (relative risk) - RR
  29. * Computes risk ratio and 95% confidence interval.
  30. *
  31. * The ratio of the probability of an event occurring in an exposed group
  32. * to the probability of the event occurring in a comparison, non-exposed group.
  33. * https://en.wikipedia.org/wiki/Relative_risk
  34. * http://www.bmj.com/content/343/bmj.d2304
  35. *
  36. * P(event when exposed) a / (a + b)
  37. * RR = ------------------------- = -----------
  38. * P(event when non-exposed) c / (c + d)
  39. *
  40. * Standard error of the log relative risk:
  41. * ______________________
  42. * / 1 1 1 1
  43. * SS{ln(RR)} = / - + - - ----- - -----
  44. * √ a c a + b c + d
  45. *
  46. * CI Range(95%) = exp( ln(RR) - z × SS{ln(RR)} ) to exp( ln(RR) + z × SS{ln(RR)} )
  47. *
  48. * P = exp((-0.717 * z) - (0.416 * z²))
  49. *
  50. * @param int $a Exposed and event present
  51. * @param int $b Exposed and event absent
  52. * @param int $c Non-exposed and event present
  53. * @param int $d Non-exposed and event absent
  54. *
  55. * @return array{
  56. * RR: float,
  57. * ci_lower_bound: float,
  58. * ci_upper_bound: float,
  59. * p: float,
  60. * }
  61. */
  62. public static function riskRatio(int $a, int $b, int $c, int $d): array
  63. {
  64. // Risk ratio
  65. $RR = ($a / ($a + $b)) / ($c / ($c + $d));
  66. // Standard error of the log relative risk
  67. $ln⟮RR⟯ = \log($RR);
  68. $SS{ln⟮RR⟯} = \sqrt((1 / $a) + (1 / $c) - (1 / ($a + $b)) - (1 / ($c + $d)));
  69. // Z score for 95% confidence interval
  70. $z = 1.96;
  71. // Confidence interval
  72. $ci_lower_bound = \exp($ln⟮RR⟯ - ($z * $SS{ln⟮RR⟯}));
  73. $ci_upper_bound = \exp($ln⟮RR⟯ + ($z * $SS{ln⟮RR⟯}));
  74. // P-value (significance level)
  75. $est = \log($RR); // estimate of effect
  76. $l = \log($ci_lower_bound); // ln CI lower bound
  77. $u = \log($ci_upper_bound); // ln CI upper bound
  78. $SE = ($u - $l) / (2 * self::Z); // standard error
  79. $z = \abs($est / $SE); // test statistic z
  80. $p = \exp((self::NORMAL_LOWER_TAIL_PROBABILITY * $z) - (self::NORMAL_UPPER_TAIL_PROBABILITY * $z ** 2));
  81. return [
  82. 'RR' => $RR,
  83. 'ci_lower_bound' => $ci_lower_bound,
  84. 'ci_upper_bound' => $ci_upper_bound,
  85. 'p' => $p,
  86. ];
  87. }
  88. /**
  89. * Odds ratio (OR)
  90. * Computes odds ratio and 95% confidence ratio.
  91. *
  92. * Ratio which quantitatively describes the association between the presence/absence
  93. * of "A" and the presence/absence of "B" for individuals in the population.
  94. * https://en.wikipedia.org/wiki/Odds_ratio
  95. * http://www.bmj.com/content/343/bmj.d2304
  96. *
  97. * a / b
  98. * OR = -----
  99. * c / d
  100. *
  101. * Standard error of the log odds ratio:
  102. * ______________
  103. * / 1 1 1 1
  104. * SS{ln(OR)} = / - + - - - + -
  105. * √ a b c d
  106. *
  107. * CI Range(95%) = exp( ln(OR) - z × SS{ln(OR)} ) to exp( ln(OR) + z × SS{ln(OR)} )
  108. *
  109. * P = exp((-0.717 * z) - (0.416 * z²))
  110. *
  111. * @param int $a Exposed and event present
  112. * @param int $b Exposed and event absent
  113. * @param int $c Non-exposed and event present
  114. * @param int $d Non-exposed and event absent
  115. *
  116. * @return array{
  117. * OR: float,
  118. * ci_lower_bound: float,
  119. * ci_upper_bound: float,
  120. * p: float,
  121. * }
  122. */
  123. public static function oddsRatio(int $a, int $b, int $c, int $d): array
  124. {
  125. // Odds ratio
  126. $OR = ($a / $b) / ($c / $d);
  127. // Standard error of the log odds ratio
  128. $ln⟮OR⟯ = \log($OR);
  129. $SS{ln⟮OR⟯} = \sqrt((1 / $a) + (1 / $b) + (1 / $c) + (1 / $d));
  130. // Confidence interval
  131. $ci_lower_bound = \exp($ln⟮OR⟯ - (self::Z * $SS{ln⟮OR⟯}));
  132. $ci_upper_bound = \exp($ln⟮OR⟯ + (self::Z * $SS{ln⟮OR⟯}));
  133. // P-value (significance level)
  134. $est = \log($OR); // estimate of effect
  135. $l = \log($ci_lower_bound); // ln CI lower bound
  136. $u = \log($ci_upper_bound); // ln CI upper bound
  137. $SE = ($u - $l) / (2 * self::Z); // standard error
  138. $z = \abs($est / $SE); // test statistic z
  139. $p = \exp((self::NORMAL_LOWER_TAIL_PROBABILITY * $z) - (self::NORMAL_UPPER_TAIL_PROBABILITY * $z ** 2));
  140. return [
  141. 'OR' => $OR,
  142. 'ci_lower_bound' => $ci_lower_bound,
  143. 'ci_upper_bound' => $ci_upper_bound,
  144. 'p' => $p,
  145. ];
  146. }
  147. /**
  148. * Likelihood ratio
  149. * Computes positive and negative likelihood ratios from a, b, c, d variables.
  150. *
  151. * Used to analyze the goodness of a diagnostic tests.
  152. * https://en.wikipedia.org/wiki/Likelihood_ratios_in_diagnostic_testing
  153. *
  154. * a / (a + c)
  155. * LL+ = -----------
  156. * b / (b + d)
  157. *
  158. * c / (a + c)
  159. * LL- = -----------
  160. * d / (b + d)
  161. *
  162. * @param int $a Exposed and event present
  163. * @param int $b Exposed and event absent
  164. * @param int $c Non-exposed and event present
  165. * @param int $d Non-exposed and event absent
  166. *
  167. * @return array{
  168. * "LL+": float,
  169. * "LL-": float,
  170. * }
  171. */
  172. public static function likelihoodRatio(int $a, int $b, int $c, int $d): array
  173. {
  174. // LL+ Positive likelihood ratio
  175. $LL+ = ($a / ($a + $c)) / ($b / ($b + $d));
  176. // LL- Negative likelihood ratio
  177. $LL− = ($c / ($a + $c)) / ($d / ($b + $d));
  178. return [
  179. 'LL+' => $LL+,
  180. 'LL-' => $LL−,
  181. ];
  182. }
  183. /**
  184. * Likelihood ratio
  185. * Computes positive and negative likelihood ratios from sensitivity and specificity.
  186. *
  187. * Used to analyze the goodness of a diagnostic tests.
  188. * https://en.wikipedia.org/wiki/Likelihood_ratios_in_diagnostic_testing
  189. *
  190. * sensitivity
  191. * LL+ = ---------------
  192. * 1 - specificity
  193. *
  194. * 1 - sensitivity
  195. * LL- = ---------------
  196. * specificity
  197. *
  198. * @param float $sensitivity
  199. * @param float $specificity
  200. *
  201. * @return array{
  202. * "LL+": float,
  203. * "LL-": float,
  204. * }
  205. *
  206. * @throws Exception\OutOfBoundsException if sensitivity or specificity are > 1.0
  207. */
  208. public static function likelihoodRatioSS(float $sensitivity, float $specificity): array
  209. {
  210. if ($sensitivity > 1.0 || $specificity > 1.0) {
  211. throw new Exception\OutOfBoundsException('Sensitivity and specificity must be <= 1.0');
  212. }
  213. // LL+ Positive likelihood ratio
  214. $LL+ = $sensitivity / (1 - $specificity);
  215. // LL- Negative likelihood ratio
  216. $LL− = (1 - $sensitivity) / $specificity;
  217. return [
  218. 'LL+' => $LL+,
  219. 'LL-' => $LL−,
  220. ];
  221. }
  222. }