Circular.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <?php
  2. namespace MathPHP\Statistics;
  3. /**
  4. * Circular statistics (directional statistics)
  5. * https://en.wikipedia.org/wiki/Directional_statistics
  6. * https://ncss-wpengine.netdna-ssl.com/wp-content/themes/ncss/pdf/Procedures/NCSS/Circular_Data_Analysis.pdf
  7. */
  8. class Circular
  9. {
  10. /**
  11. * Mean of circular quantities (circular mean)
  12. * Mean direction of circular data.
  13. * A mean which is sometimes better-suited for quantities like angles, daytimes, and fractional parts of real numbers.
  14. * https://en.wikipedia.org/wiki/Mean_of_circular_quantities
  15. * _
  16. * α = atan2(∑sin αⱼ, ∑cos αⱼ)
  17. *
  18. * @param array<float> $angles
  19. *
  20. * @return float mean direction of circular data
  21. */
  22. public static function mean(array $angles): float
  23. {
  24. $∑sinαⱼ = \array_sum(\array_map(
  25. function ($αⱼ) {
  26. return \sin($αⱼ);
  27. },
  28. $angles
  29. ));
  30. $∑cosαⱼ = \array_sum(\array_map(
  31. function ($αⱼ) {
  32. return \cos($αⱼ);
  33. },
  34. $angles
  35. ));
  36. return \atan2($∑sinαⱼ, $∑cosαⱼ);
  37. }
  38. /**
  39. * Resultant length (R)
  40. * https://en.wikipedia.org/wiki/Directional_statistics#Moments
  41. * https://ncss-wpengine.netdna-ssl.com/wp-content/themes/ncss/pdf/Procedures/NCSS/Circular_Data_Analysis.pdf
  42. *
  43. * S = ∑sin θᵢ
  44. * C = ∑cos θᵢ
  45. * R² = S² + C²
  46. * R = √(S² + C²)
  47. *
  48. * @param array<float> $angles
  49. *
  50. * @return float
  51. */
  52. public static function resultantLength(array $angles): float
  53. {
  54. $S = \array_sum(\array_map(
  55. function ($θᵢ) {
  56. return \sin($θᵢ);
  57. },
  58. $angles
  59. ));
  60. $C = \array_sum(\array_map(
  61. function ($θᵢ) {
  62. return \cos($θᵢ);
  63. },
  64. $angles
  65. ));
  66. $S² = $S ** 2;
  67. $C² = $C ** 2;
  68. $R² = $S² + $C²;
  69. $R = \sqrt($R²);
  70. return $R;
  71. }
  72. /**
  73. * Mean resultant length - MRL (ρ)
  74. * https://en.wikipedia.org/wiki/Directional_statistics#Moments
  75. * https://ncss-wpengine.netdna-ssl.com/wp-content/themes/ncss/pdf/Procedures/NCSS/Circular_Data_Analysis.pdf
  76. *
  77. * S = ∑sin θᵢ
  78. * C = ∑cos θᵢ
  79. * R² = S² + C²
  80. * R = √(S² + C²)
  81. *
  82. * _ R
  83. * R = -
  84. * n
  85. *
  86. * _
  87. * ρ = R
  88. *
  89. * @param array<float> $angles
  90. *
  91. * @return float
  92. */
  93. public static function meanResultantLength(array $angles): float
  94. {
  95. $n = \count($angles);
  96. $R = self::resultantLength($angles);
  97. $ρ = $R / $n;
  98. return $ρ;
  99. }
  100. /**
  101. * Circular variance
  102. * https://en.wikipedia.org/wiki/Directional_statistics#Measures_of_location_and_spread
  103. * https://www.ebi.ac.uk/thornton-srv/software/PROCHECK/nmr_manual/man_cv.html
  104. * https://ncss-wpengine.netdna-ssl.com/wp-content/themes/ncss/pdf/Procedures/NCSS/Circular_Data_Analysis.pdf
  105. * _
  106. * Var(θ) = 1 - R
  107. * Var(θ) = 1 - ρ
  108. *
  109. * @param array<float> $angles
  110. *
  111. * @return float
  112. */
  113. public static function variance(array $angles): float
  114. {
  115. $ρ = self::meanResultantLength($angles);
  116. return 1 - $ρ;
  117. }
  118. /**
  119. * Circular standard deviation
  120. * https://en.wikipedia.org/wiki/Directional_statistics#Measures_of_location_and_spread
  121. * https://ncss-wpengine.netdna-ssl.com/wp-content/themes/ncss/pdf/Procedures/NCSS/Circular_Data_Analysis.pdf
  122. *
  123. * _______
  124. * / _
  125. * ν = √ -2ln(R)
  126. *
  127. * _
  128. * Where R = ρ = mean resultant length
  129. *
  130. * @param array<float> $angles
  131. *
  132. * @return float
  133. */
  134. public static function standardDeviation(array $angles): float
  135. {
  136. $ρ = self::meanResultantLength($angles);
  137. $√⟮−2ln⟮R⟯⟯ = \sqrt(-2 * \log($ρ));
  138. return $√⟮−2ln⟮R⟯⟯;
  139. }
  140. /**
  141. * Get a report of all the descriptive circular statistics over a list of angles
  142. * Includes mean, resultant length, mean resultant length, variance, standard deviation.
  143. *
  144. * @param array<float> $angles
  145. *
  146. * @return array{
  147. * n: int,
  148. * mean: float,
  149. * resultant_length: float,
  150. * mean_resultant_length: float,
  151. * variance: float,
  152. * sd: float,
  153. * }
  154. */
  155. public static function describe(array $angles): array
  156. {
  157. return [
  158. 'n' => \count($angles),
  159. 'mean' => self::mean($angles),
  160. 'resultant_length' => self::resultantLength($angles),
  161. 'mean_resultant_length' => self::meanResultantLength($angles),
  162. 'variance' => self::variance($angles),
  163. 'sd' => self::standardDeviation($angles),
  164. ];
  165. }
  166. }