CircularTest.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. namespace MathPHP\Tests\Statistics;
  3. use MathPHP\Statistics\Circular;
  4. class CircularTest extends \PHPUnit\Framework\TestCase
  5. {
  6. /**
  7. * @test mean
  8. * @dataProvider dataProviderForMean
  9. * @param array $angles
  10. * @param float $expected
  11. */
  12. public function testMean(array $angles, float $expected)
  13. {
  14. // When
  15. $mean = Circular::mean($angles);
  16. // Then
  17. $this->assertEqualsWithDelta($expected, $mean, 0.000001);
  18. }
  19. /**
  20. * Test data made with R package circular's function mean.circular()
  21. * https://cran.r-project.org/web/packages/circular/circular.pdf
  22. * @return array [angles, mean]
  23. */
  24. public function dataProviderForMean(): array
  25. {
  26. $π = \M_PI;
  27. return [
  28. [[0, 2 * $π], 0],
  29. [[0, 0.5 * $π], 0.7853982],
  30. [[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], 0.5],
  31. [[0 * $π, 0.1 * $π, 0.2 * $π, 0.3 * $π, 0.4 * $π, 0.5 * $π, 0.6 * $π, 0.7 * $π, 0.8 * $π, 0.9 * $π, 1 * $π], 1.570796],
  32. [[0, 0, 90], .5226276],
  33. [[1.4 * $π, 1.7 * $π, 1.75 * $π, 2.54 * $π, 4.32 * $π], -0.4242655],
  34. [[5, 60, 340], -1.423654],
  35. [[5, 50, 150, 250], -0.9253517],
  36. [[10, 20, 30], -1.991149],
  37. [[355, 5, 15], -2.935443],
  38. // In this test case, we end up with
  39. // sin(0) + sin(π) = 0 + 0 = 0
  40. // \cos(0) + \cos(π) = 1 - 1 = 0
  41. // So it seems like it should end up as atan2(0, 0),
  42. // but since the sum of sins isn't perfectly 0, it is a very small floating point number,
  43. // like atan2(1.2246467991474E-16, 0),
  44. // which ends up as arctan(infinity) which equals 1.57079633.
  45. // R mean.circular results in NA,
  46. // but tested with Python scipi.stats.circmean(), it results in 1.5707963267948966,
  47. // which matches our PHP answer.
  48. [[0, $π], 1.5707963267948966],
  49. ];
  50. }
  51. /**
  52. * @test resultantLength
  53. * @dataProvider dataProviderForResultantLength
  54. * @param array $angles
  55. * @param float $expected
  56. */
  57. public function testResultantLength(array $angles, float $expected)
  58. {
  59. // When
  60. $length = Circular::resultantLength($angles);
  61. // Then
  62. $this->assertEqualsWithDelta($expected, $length, 0.00001);
  63. }
  64. /**
  65. * Test data made with custom R function:
  66. * resultantLength <- function(x) {
  67. * sinSum = sum(sin(x))
  68. * cosSum = sum(cos(x))
  69. * R = sqrt(sinSum^2 + cosSum^2)
  70. * return(R)
  71. * }
  72. * @return array [angles, length]
  73. */
  74. public function dataProviderForResultantLength(): array
  75. {
  76. $π = \M_PI;
  77. return [
  78. [[0, $π], 1.224647e-16],
  79. [[0, 0.5, $π], 1],
  80. [[0, 2 * $π], 2],
  81. [[0, 0.5 * $π], 1.414214],
  82. [[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], 10.4581],
  83. [[0 * $π, 0.1 * $π, 0.2 * $π, 0.3 * $π, 0.4 * $π, 0.5 * $π, 0.6 * $π, 0.7 * $π, 0.8 * $π, 0.9 * $π, 1 * $π], 6.313752],
  84. [[0, 0, 90], 1.791007],
  85. [[1.4 * $π, 1.7 * $π, 1.75 * $π, 2.54 * $π, 4.32 * $π], 1.532213],
  86. [[5, 60, 340], 0.6201251],
  87. [[5, 50, 150, 250], 3.63869],
  88. [[10, 20, 30], 0.6781431],
  89. [[355, 5, 15], 1.507955],
  90. ];
  91. }
  92. /**
  93. * @test meanResultantLength
  94. * @dataProvider dataProviderForMeanResultantLength
  95. * @param array $angles
  96. * @param float $expected
  97. */
  98. public function testMeanResultantLength(array $angles, float $expected)
  99. {
  100. // When
  101. $length = Circular::meanResultantLength($angles);
  102. // Then
  103. $this->assertEqualsWithDelta($expected, $length, 0.000001);
  104. }
  105. /**
  106. * Test data made with custom R function:
  107. * meanResultantLength <- function(x) {
  108. * n = length(x)
  109. * sinSum = sum(sin(x))
  110. * cosSum = sum(cos(x))
  111. * rho = sqrt(sinSum^2 + cosSum^2) / n
  112. * return(rho)
  113. * }
  114. * @return array [angles, length]
  115. */
  116. public function dataProviderForMeanResultantLength(): array
  117. {
  118. $π = \M_PI;
  119. return [
  120. [[0, $π], 6.123234e-17],
  121. [[0, 0.5, $π], 0.3333333],
  122. [[0, 2 * $π], 1],
  123. [[0, 0.5 * $π], 0.7071068],
  124. [[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], 0.9507365],
  125. [[0 * $π, 0.1 * $π, 0.2 * $π, 0.3 * $π, 0.4 * $π, 0.5 * $π, 0.6 * $π, 0.7 * $π, 0.8 * $π, 0.9 * $π, 1 * $π], 0.5739774],
  126. [[0, 0, 90], 0.5970023],
  127. [[1.4 * $π, 1.7 * $π, 1.75 * $π, 2.54 * $π, 4.32 * $π], 0.3064425],
  128. [[5, 60, 340], 0.2067084],
  129. [[5, 50, 150, 250], 0.9096725],
  130. [[10, 20, 30], 0.2260477],
  131. [[355, 5, 15], 0.5026515],
  132. ];
  133. }
  134. /**
  135. * @test variance
  136. * @dataProvider dataProviderForVariance
  137. * @param array $angles
  138. * @param float $expected
  139. */
  140. public function testVariance(array $angles, float $expected)
  141. {
  142. // When
  143. $variance = Circular::variance($angles);
  144. // Then
  145. $this->assertEqualsWithDelta($expected, $variance, 0.000001);
  146. }
  147. /**
  148. * Test data made with R package circular's function var.circular()
  149. * https://cran.r-project.org/web/packages/circular/circular.pdf
  150. * @return array [angles, variance]
  151. */
  152. public function dataProviderForVariance(): array
  153. {
  154. $π = \M_PI;
  155. return [
  156. [[0, $π], 1],
  157. [[0, 0.5, $π], 0.6666667],
  158. [[0, 2 * $π], 0],
  159. [[0, 0.5 * $π], 0.2928932],
  160. [[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], 0.04926349],
  161. [[0 * $π, 0.1 * $π, 0.2 * $π, 0.3 * $π, 0.4 * $π, 0.5 * $π, 0.6 * $π, 0.7 * $π, 0.8 * $π, 0.9 * $π, 1 * $π], 0.4260226],
  162. [[0, 0, 90], 0.4029977],
  163. [[1.4 * $π, 1.7 * $π, 1.75 * $π, 2.54 * $π, 4.32 * $π], 0.6935575],
  164. [[5, 60, 340], 0.7932916],
  165. [[5, 50, 150, 250], 0.09032747],
  166. [[10, 20, 30], 0.7739523],
  167. [[355, 5, 15], 0.4973485],
  168. ];
  169. }
  170. /**
  171. * @test standardDeviation
  172. * @dataProvider dataProviderForStandardDeviation
  173. * @param array $angles
  174. * @param float $expected
  175. */
  176. public function testStandardDeviation(array $angles, float $expected)
  177. {
  178. // When
  179. $sd = Circular::standardDeviation($angles);
  180. // Then
  181. $this->assertEqualsWithDelta($expected, $sd, 0.000001);
  182. }
  183. /**
  184. * Test data made with R package circular's function sd.circular()
  185. * https://cran.r-project.org/web/packages/circular/circular.pdf
  186. * @return array [angles, standardDeviation]
  187. */
  188. public function dataProviderForStandardDeviation(): array
  189. {
  190. $π = \M_PI;
  191. return [
  192. [[0, $π], 8.640817],
  193. [[0, 0.5, $π], 1.482304],
  194. [[0, 2 * $π], 0],
  195. [[0, 0.5 * $π], 0.8325546],
  196. [[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], 0.3178626],
  197. [[0 * $π, 0.1 * $π, 0.2 * $π, 0.3 * $π, 0.4 * $π, 0.5 * $π, 0.6 * $π, 0.7 * $π, 0.8 * $π, 0.9 * $π, 1 * $π], 1.053722],
  198. [[0, 0, 90], 1.015711],
  199. [[1.4 * $π, 1.7 * $π, 1.75 * $π, 2.54 * $π, 4.32 * $π], 1.538002],
  200. [[5, 60, 340], 1.775639],
  201. [[5, 50, 150, 250], 0.4351335],
  202. [[10, 20, 30], 1.724534],
  203. [[355, 5, 15], 1.172909],
  204. ];
  205. }
  206. /**
  207. * @test describe
  208. */
  209. public function testDescribe()
  210. {
  211. // Given
  212. $values = [5, 15, 355];
  213. // When
  214. $stats = Circular::describe($values);
  215. // Then
  216. $this->assertTrue(\is_array($stats));
  217. $this->assertArrayHasKey('n', $stats);
  218. $this->assertArrayHasKey('mean', $stats);
  219. $this->assertArrayHasKey('resultant_length', $stats);
  220. $this->assertArrayHasKey('mean_resultant_length', $stats);
  221. $this->assertArrayHasKey('variance', $stats);
  222. $this->assertArrayHasKey('sd', $stats);
  223. // And
  224. $this->assertTrue(\is_int($stats['n']));
  225. $this->assertTrue(\is_float($stats['mean']));
  226. $this->assertTrue(\is_float($stats['resultant_length']));
  227. $this->assertTrue(\is_float($stats['mean_resultant_length']));
  228. $this->assertTrue(\is_float($stats['variance']));
  229. $this->assertTrue(\is_float($stats['sd']));
  230. }
  231. }