|
3 | 3 |
|
4 | 4 | """ |
5 | 5 |
|
| 6 | +import bisect |
6 | 7 | import collections |
7 | 8 | import collections.abc |
8 | 9 | import copy |
@@ -2038,6 +2039,7 @@ def test_compare_to_variance(self): |
2038 | 2039 | expected = math.sqrt(statistics.variance(data)) |
2039 | 2040 | self.assertEqual(self.func(data), expected) |
2040 | 2041 |
|
| 2042 | + |
2041 | 2043 | class TestGeometricMean(unittest.TestCase): |
2042 | 2044 |
|
2043 | 2045 | def test_basics(self): |
@@ -2126,6 +2128,146 @@ def test_special_values(self): |
2126 | 2128 | with self.assertRaises(ValueError): |
2127 | 2129 | geometric_mean([Inf, -Inf]) |
2128 | 2130 |
|
| 2131 | + |
| 2132 | +class TestQuantiles(unittest.TestCase): |
| 2133 | + |
| 2134 | + def test_specific_cases(self): |
| 2135 | + # Match results computed by hand and cross-checked |
| 2136 | + # against the PERCENTILE.EXC function in MS Excel. |
| 2137 | + quantiles = statistics.quantiles |
| 2138 | + data = [120, 200, 250, 320, 350] |
| 2139 | + random.shuffle(data) |
| 2140 | + for n, expected in [ |
| 2141 | + (1, []), |
| 2142 | + (2, [250.0]), |
| 2143 | + (3, [200.0, 320.0]), |
| 2144 | + (4, [160.0, 250.0, 335.0]), |
| 2145 | + (5, [136.0, 220.0, 292.0, 344.0]), |
| 2146 | + (6, [120.0, 200.0, 250.0, 320.0, 350.0]), |
| 2147 | + (8, [100.0, 160.0, 212.5, 250.0, 302.5, 335.0, 357.5]), |
| 2148 | + (10, [88.0, 136.0, 184.0, 220.0, 250.0, 292.0, 326.0, 344.0, 362.0]), |
| 2149 | + (12, [80.0, 120.0, 160.0, 200.0, 225.0, 250.0, 285.0, 320.0, 335.0, |
| 2150 | + 350.0, 365.0]), |
| 2151 | + (15, [72.0, 104.0, 136.0, 168.0, 200.0, 220.0, 240.0, 264.0, 292.0, |
| 2152 | + 320.0, 332.0, 344.0, 356.0, 368.0]), |
| 2153 | + ]: |
| 2154 | + self.assertEqual(expected, quantiles(data, n=n)) |
| 2155 | + self.assertEqual(len(quantiles(data, n=n)), n - 1) |
| 2156 | + self.assertEqual(list(map(float, expected)), |
| 2157 | + quantiles(map(Decimal, data), n=n)) |
| 2158 | + self.assertEqual(list(map(Decimal, expected)), |
| 2159 | + quantiles(map(Decimal, data), n=n)) |
| 2160 | + self.assertEqual(list(map(Fraction, expected)), |
| 2161 | + quantiles(map(Fraction, data), n=n)) |
| 2162 | + # Invariant under tranlation and scaling |
| 2163 | + def f(x): |
| 2164 | + return 3.5 * x - 1234.675 |
| 2165 | + exp = list(map(f, expected)) |
| 2166 | + act = quantiles(map(f, data), n=n) |
| 2167 | + self.assertTrue(all(math.isclose(e, a) for e, a in zip(exp, act))) |
| 2168 | + # Quartiles of a standard normal distribution |
| 2169 | + for n, expected in [ |
| 2170 | + (1, []), |
| 2171 | + (2, [0.0]), |
| 2172 | + (3, [-0.4307, 0.4307]), |
| 2173 | + (4 ,[-0.6745, 0.0, 0.6745]), |
| 2174 | + ]: |
| 2175 | + actual = quantiles(statistics.NormalDist(), n=n) |
| 2176 | + self.assertTrue(all(math.isclose(e, a, abs_tol=0.0001) |
| 2177 | + for e, a in zip(expected, actual))) |
| 2178 | + |
| 2179 | + def test_specific_cases_inclusive(self): |
| 2180 | + # Match results computed by hand and cross-checked |
| 2181 | + # against the PERCENTILE.INC function in MS Excel |
| 2182 | + # and against the quaatile() function in SciPy. |
| 2183 | + quantiles = statistics.quantiles |
| 2184 | + data = [100, 200, 400, 800] |
| 2185 | + random.shuffle(data) |
| 2186 | + for n, expected in [ |
| 2187 | + (1, []), |
| 2188 | + (2, [300.0]), |
| 2189 | + (3, [200.0, 400.0]), |
| 2190 | + (4, [175.0, 300.0, 500.0]), |
| 2191 | + (5, [160.0, 240.0, 360.0, 560.0]), |
| 2192 | + (6, [150.0, 200.0, 300.0, 400.0, 600.0]), |
| 2193 | + (8, [137.5, 175, 225.0, 300.0, 375.0, 500.0,650.0]), |
| 2194 | + (10, [130.0, 160.0, 190.0, 240.0, 300.0, 360.0, 440.0, 560.0, 680.0]), |
| 2195 | + (12, [125.0, 150.0, 175.0, 200.0, 250.0, 300.0, 350.0, 400.0, |
| 2196 | + 500.0, 600.0, 700.0]), |
| 2197 | + (15, [120.0, 140.0, 160.0, 180.0, 200.0, 240.0, 280.0, 320.0, 360.0, |
| 2198 | + 400.0, 480.0, 560.0, 640.0, 720.0]), |
| 2199 | + ]: |
| 2200 | + self.assertEqual(expected, quantiles(data, n=n, method="inclusive")) |
| 2201 | + self.assertEqual(len(quantiles(data, n=n, method="inclusive")), n - 1) |
| 2202 | + self.assertEqual(list(map(float, expected)), |
| 2203 | + quantiles(map(Decimal, data), n=n, method="inclusive")) |
| 2204 | + self.assertEqual(list(map(Decimal, expected)), |
| 2205 | + quantiles(map(Decimal, data), n=n, method="inclusive")) |
| 2206 | + self.assertEqual(list(map(Fraction, expected)), |
| 2207 | + quantiles(map(Fraction, data), n=n, method="inclusive")) |
| 2208 | + # Invariant under tranlation and scaling |
| 2209 | + def f(x): |
| 2210 | + return 3.5 * x - 1234.675 |
| 2211 | + exp = list(map(f, expected)) |
| 2212 | + act = quantiles(map(f, data), n=n, method="inclusive") |
| 2213 | + self.assertTrue(all(math.isclose(e, a) for e, a in zip(exp, act))) |
| 2214 | + # Quartiles of a standard normal distribution |
| 2215 | + for n, expected in [ |
| 2216 | + (1, []), |
| 2217 | + (2, [0.0]), |
| 2218 | + (3, [-0.4307, 0.4307]), |
| 2219 | + (4 ,[-0.6745, 0.0, 0.6745]), |
| 2220 | + ]: |
| 2221 | + actual = quantiles(statistics.NormalDist(), n=n, method="inclusive") |
| 2222 | + self.assertTrue(all(math.isclose(e, a, abs_tol=0.0001) |
| 2223 | + for e, a in zip(expected, actual))) |
| 2224 | + |
| 2225 | + def test_equal_sized_groups(self): |
| 2226 | + quantiles = statistics.quantiles |
| 2227 | + total = 10_000 |
| 2228 | + data = [random.expovariate(0.2) for i in range(total)] |
| 2229 | + while len(set(data)) != total: |
| 2230 | + data.append(random.expovariate(0.2)) |
| 2231 | + data.sort() |
| 2232 | + |
| 2233 | + # Cases where the group size exactly divides the total |
| 2234 | + for n in (1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000): |
| 2235 | + group_size = total // n |
| 2236 | + self.assertEqual( |
| 2237 | + [bisect.bisect(data, q) for q in quantiles(data, n=n)], |
| 2238 | + list(range(group_size, total, group_size))) |
| 2239 | + |
| 2240 | + # When the group sizes can't be exactly equal, they should |
| 2241 | + # differ by no more than one |
| 2242 | + for n in (13, 19, 59, 109, 211, 571, 1019, 1907, 5261, 9769): |
| 2243 | + group_sizes = {total // n, total // n + 1} |
| 2244 | + pos = [bisect.bisect(data, q) for q in quantiles(data, n=n)] |
| 2245 | + sizes = {q - p for p, q in zip(pos, pos[1:])} |
| 2246 | + self.assertTrue(sizes <= group_sizes) |
| 2247 | + |
| 2248 | + def test_error_cases(self): |
| 2249 | + quantiles = statistics.quantiles |
| 2250 | + StatisticsError = statistics.StatisticsError |
| 2251 | + with self.assertRaises(TypeError): |
| 2252 | + quantiles() # Missing arguments |
| 2253 | + with self.assertRaises(TypeError): |
| 2254 | + quantiles([10, 20, 30], 13, n=4) # Too many arguments |
| 2255 | + with self.assertRaises(TypeError): |
| 2256 | + quantiles([10, 20, 30], 4) # n is a positional argument |
| 2257 | + with self.assertRaises(StatisticsError): |
| 2258 | + quantiles([10, 20, 30], n=0) # n is zero |
| 2259 | + with self.assertRaises(StatisticsError): |
| 2260 | + quantiles([10, 20, 30], n=-1) # n is negative |
| 2261 | + with self.assertRaises(TypeError): |
| 2262 | + quantiles([10, 20, 30], n=1.5) # n is not an integer |
| 2263 | + with self.assertRaises(ValueError): |
| 2264 | + quantiles([10, 20, 30], method='X') # method is unknown |
| 2265 | + with self.assertRaises(StatisticsError): |
| 2266 | + quantiles([10], n=4) # not enough data points |
| 2267 | + with self.assertRaises(TypeError): |
| 2268 | + quantiles([10, None, 30], n=4) # data is non-numeric |
| 2269 | + |
| 2270 | + |
2129 | 2271 | class TestNormalDist(unittest.TestCase): |
2130 | 2272 |
|
2131 | 2273 | # General note on precision: The pdf(), cdf(), and overlap() methods |
|
0 commit comments