kolmogorov_smirnov.hpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // Kolmogorov-Smirnov 1st order asymptotic distribution
  2. // Copyright Evan Miller 2020
  3. //
  4. // Use, modification and distribution are subject to the
  5. // Boost Software License, Version 1.0. (See accompanying file
  6. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  7. //
  8. // The Kolmogorov-Smirnov test in statistics compares two empirical distributions,
  9. // or an empirical distribution against any theoretical distribution. It makes
  10. // use of a specific distribution which doesn't have a formal name, but which
  11. // is often called the Kolmogorv-Smirnov distribution for lack of anything
  12. // better. This file implements the limiting form of this distribution, first
  13. // identified by Andrey Kolmogorov in
  14. //
  15. // Kolmogorov, A. (1933) "Sulla Determinazione Empirica di una Legge di
  16. // Distribuzione." Giornale dell' Istituto Italiano degli Attuari
  17. //
  18. // This limiting form of the CDF is a first-order Taylor expansion that is
  19. // easily implemented by the fourth Jacobi Theta function (setting z=0). The
  20. // PDF is then implemented here as a derivative of the Theta function. Note
  21. // that this derivative is with respect to x, which enters into \tau, and not
  22. // with respect to the z argument, which is always zero, and so the derivative
  23. // identities in DLMF 20.4 do not apply here.
  24. //
  25. // A higher order order expansion is possible, and was first outlined by
  26. //
  27. // Pelz W, Good IJ (1976). "Approximating the Lower Tail-Areas of the
  28. // Kolmogorov-Smirnov One-sample Statistic." Journal of the Royal Statistical
  29. // Society B.
  30. //
  31. // The terms in this expansion get fairly complicated, and as far as I know the
  32. // Pelz-Good expansion is not used in any statistics software. Someone could
  33. // consider updating this implementation to use the Pelz-Good expansion in the
  34. // future, but the math gets considerably hairier with each additional term.
  35. //
  36. // A formula for an exact version of the Kolmogorov-Smirnov test is laid out in
  37. // Equation 2.4.4 of
  38. //
  39. // Durbin J (1973). "Distribution Theory for Tests Based on the Sample
  40. // Distribution Func- tion." In SIAM CBMS-NSF Regional Conference Series in
  41. // Applied Mathematics. SIAM, Philadelphia, PA.
  42. //
  43. // which is available in book form from Amazon and others. This exact version
  44. // involves taking powers of large matrices. To do that right you need to
  45. // compute eigenvalues and eigenvectors, which are beyond the scope of Boost.
  46. // (Some recent work indicates the exact form can also be computed via FFT, see
  47. // https://cran.r-project.org/web/packages/KSgeneral/KSgeneral.pdf).
  48. //
  49. // Even if the CDF of the exact distribution could be computed using Boost
  50. // libraries (which would be cumbersome), the PDF would present another
  51. // difficulty. Therefore I am limiting this implementation to the asymptotic
  52. // form, even though the exact form has trivial values for certain specific
  53. // values of x and n. For more on trivial values see
  54. //
  55. // Ruben H, Gambino J (1982). "The Exact Distribution of Kolmogorov's Statistic
  56. // Dn for n <= 10." Annals of the Institute of Statistical Mathematics.
  57. //
  58. // For a good bibliography and overview of the various algorithms, including
  59. // both exact and asymptotic forms, see
  60. // https://www.jstatsoft.org/article/view/v039i11
  61. //
  62. // As for this implementation: the distribution is parameterized by n (number
  63. // of observations) in the spirit of chi-squared's degrees of freedom. It then
  64. // takes a single argument x. In terms of the Kolmogorov-Smirnov statistical
  65. // test, x represents the distribution of D_n, where D_n is the maximum
  66. // difference between the CDFs being compared, that is,
  67. //
  68. // D_n = sup|F_n(x) - G(x)|
  69. //
  70. // In the exact distribution, x is confined to the support [0, 1], but in this
  71. // limiting approximation, we allow x to exceed unity (similar to how a normal
  72. // approximation always spills over any boundaries).
  73. //
  74. // As mentioned previously, the CDF is implemented using the \tau
  75. // parameterization of the fourth Jacobi Theta function as
  76. //
  77. // CDF=theta_4(0|2*x*x*n/pi)
  78. //
  79. // The PDF is a hand-coded derivative of that function. Actually, there are two
  80. // (independent) derivatives, as separate code paths are used for "small x"
  81. // (2*x*x*n < pi) and "large x", mirroring the separate code paths in the
  82. // Jacobi Theta implementation to achieve fast convergence. Quantiles are
  83. // computed using a Newton-Raphson iteration from an initial guess that I
  84. // arrived at by trial and error.
  85. //
  86. // The mean and variance are implemented using simple closed-form expressions.
  87. // Skewness and kurtosis use slightly more complicated closed-form expressions
  88. // that involve the zeta function. The mode is calculated at run-time by
  89. // maximizing the PDF. If you have an analytical solution for the mode, feel
  90. // free to plop it in.
  91. //
  92. // The CDF and PDF could almost certainly be re-implemented and sped up using a
  93. // polynomial or rational approximation, since the only meaningful argument is
  94. // x * sqrt(n). But that is left as an exercise for the next maintainer.
  95. //
  96. // In the future, the Pelz-Good approximation could be added. I suggest adding
  97. // a second parameter representing the order, e.g.
  98. //
  99. // kolmogorov_smirnov_dist<>(100) // N=100, order=1
  100. // kolmogorov_smirnov_dist<>(100, 1) // N=100, order=1, i.e. Kolmogorov's formula
  101. // kolmogorov_smirnov_dist<>(100, 4) // N=100, order=4, i.e. Pelz-Good formula
  102. //
  103. // The exact distribution could be added to the API with a special order
  104. // parameter (e.g. 0 or infinity), or a separate distribution type altogether
  105. // (e.g. kolmogorov_smirnov_exact_distribution).
  106. //
  107. #ifndef BOOST_MATH_DISTRIBUTIONS_KOLMOGOROV_SMIRNOV_HPP
  108. #define BOOST_MATH_DISTRIBUTIONS_KOLMOGOROV_SMIRNOV_HPP
  109. #include <boost/math/distributions/fwd.hpp>
  110. #include <boost/math/distributions/complement.hpp>
  111. #include <boost/math/distributions/detail/common_error_handling.hpp>
  112. #include <boost/math/special_functions/jacobi_theta.hpp>
  113. #include <boost/math/tools/tuple.hpp>
  114. #include <boost/math/tools/roots.hpp> // Newton-Raphson
  115. #include <boost/math/tools/minima.hpp> // For the mode
  116. namespace boost { namespace math {
  117. namespace detail {
  118. template <class RealType>
  119. inline RealType kolmogorov_smirnov_quantile_guess(RealType p) {
  120. // Choose a starting point for the Newton-Raphson iteration
  121. if (p > 0.9)
  122. return RealType(1.8) - 5 * (1 - p);
  123. if (p < 0.3)
  124. return p + RealType(0.45);
  125. return p + RealType(0.3);
  126. }
  127. // d/dk (theta2(0, 1/(2*k*k/M_PI))/sqrt(2*k*k*M_PI))
  128. template <class RealType, class Policy>
  129. RealType kolmogorov_smirnov_pdf_small_x(RealType x, RealType n, const Policy&) {
  130. BOOST_MATH_STD_USING
  131. RealType value = RealType(0), delta = RealType(0), last_delta = RealType(0);
  132. RealType eps = policies::get_epsilon<RealType, Policy>();
  133. int i = 0;
  134. RealType pi2 = constants::pi_sqr<RealType>();
  135. RealType x2n = x*x*n;
  136. if (x2n*x2n == 0.0) {
  137. return static_cast<RealType>(0);
  138. }
  139. while (true) {
  140. delta = exp(-RealType(i+0.5)*RealType(i+0.5)*pi2/(2*x2n)) * (RealType(i+0.5)*RealType(i+0.5)*pi2 - x2n);
  141. if (delta == 0.0)
  142. break;
  143. if (last_delta != 0.0 && fabs(delta/last_delta) < eps)
  144. break;
  145. value += delta + delta;
  146. last_delta = delta;
  147. i++;
  148. }
  149. return value * sqrt(n) * constants::root_half_pi<RealType>() / (x2n*x2n);
  150. }
  151. // d/dx (theta4(0, 2*x*x*n/M_PI))
  152. template <class RealType, class Policy>
  153. inline RealType kolmogorov_smirnov_pdf_large_x(RealType x, RealType n, const Policy&) {
  154. BOOST_MATH_STD_USING
  155. RealType value = RealType(0), delta = RealType(0), last_delta = RealType(0);
  156. RealType eps = policies::get_epsilon<RealType, Policy>();
  157. int i = 1;
  158. while (true) {
  159. delta = 8*x*i*i*exp(-2*i*i*x*x*n);
  160. if (delta == 0.0)
  161. break;
  162. if (last_delta != 0.0 && fabs(delta / last_delta) < eps)
  163. break;
  164. if (i%2 == 0)
  165. delta = -delta;
  166. value += delta;
  167. last_delta = delta;
  168. i++;
  169. }
  170. return value * n;
  171. }
  172. } // detail
  173. template <class RealType = double, class Policy = policies::policy<> >
  174. class kolmogorov_smirnov_distribution
  175. {
  176. public:
  177. typedef RealType value_type;
  178. typedef Policy policy_type;
  179. // Constructor
  180. kolmogorov_smirnov_distribution( RealType n ) : n_obs_(n)
  181. {
  182. RealType result;
  183. detail::check_df(
  184. "boost::math::kolmogorov_smirnov_distribution<%1%>::kolmogorov_smirnov_distribution", n_obs_, &result, Policy());
  185. }
  186. RealType number_of_observations()const
  187. {
  188. return n_obs_;
  189. }
  190. private:
  191. RealType n_obs_; // positive integer
  192. };
  193. typedef kolmogorov_smirnov_distribution<double> kolmogorov_k; // Convenience typedef for double version.
  194. #ifdef __cpp_deduction_guides
  195. template <class RealType>
  196. kolmogorov_smirnov_distribution(RealType)->kolmogorov_smirnov_distribution<typename boost::math::tools::promote_args<RealType>::type>;
  197. #endif
  198. namespace detail {
  199. template <class RealType, class Policy>
  200. struct kolmogorov_smirnov_quantile_functor
  201. {
  202. kolmogorov_smirnov_quantile_functor(const boost::math::kolmogorov_smirnov_distribution<RealType, Policy> dist, RealType const& p)
  203. : distribution(dist), prob(p)
  204. {
  205. }
  206. boost::math::tuple<RealType, RealType> operator()(RealType const& x)
  207. {
  208. RealType fx = cdf(distribution, x) - prob; // Difference cdf - value - to minimize.
  209. RealType dx = pdf(distribution, x); // pdf is 1st derivative.
  210. // return both function evaluation difference f(x) and 1st derivative f'(x).
  211. return boost::math::make_tuple(fx, dx);
  212. }
  213. private:
  214. const boost::math::kolmogorov_smirnov_distribution<RealType, Policy> distribution;
  215. RealType prob;
  216. };
  217. template <class RealType, class Policy>
  218. struct kolmogorov_smirnov_complementary_quantile_functor
  219. {
  220. kolmogorov_smirnov_complementary_quantile_functor(const boost::math::kolmogorov_smirnov_distribution<RealType, Policy> dist, RealType const& p)
  221. : distribution(dist), prob(p)
  222. {
  223. }
  224. boost::math::tuple<RealType, RealType> operator()(RealType const& x)
  225. {
  226. RealType fx = cdf(complement(distribution, x)) - prob; // Difference cdf - value - to minimize.
  227. RealType dx = -pdf(distribution, x); // pdf is the negative of the derivative of (1-CDF)
  228. // return both function evaluation difference f(x) and 1st derivative f'(x).
  229. return boost::math::make_tuple(fx, dx);
  230. }
  231. private:
  232. const boost::math::kolmogorov_smirnov_distribution<RealType, Policy> distribution;
  233. RealType prob;
  234. };
  235. template <class RealType, class Policy>
  236. struct kolmogorov_smirnov_negative_pdf_functor
  237. {
  238. RealType operator()(RealType const& x) {
  239. if (2*x*x < constants::pi<RealType>()) {
  240. return -kolmogorov_smirnov_pdf_small_x(x, static_cast<RealType>(1), Policy());
  241. }
  242. return -kolmogorov_smirnov_pdf_large_x(x, static_cast<RealType>(1), Policy());
  243. }
  244. };
  245. } // namespace detail
  246. template <class RealType, class Policy>
  247. inline const std::pair<RealType, RealType> range(const kolmogorov_smirnov_distribution<RealType, Policy>& /*dist*/)
  248. { // Range of permissible values for random variable x.
  249. using boost::math::tools::max_value;
  250. return std::pair<RealType, RealType>(static_cast<RealType>(0), max_value<RealType>());
  251. }
  252. template <class RealType, class Policy>
  253. inline const std::pair<RealType, RealType> support(const kolmogorov_smirnov_distribution<RealType, Policy>& /*dist*/)
  254. { // Range of supported values for random variable x.
  255. // This is range where cdf rises from 0 to 1, and outside it, the pdf is zero.
  256. // In the exact distribution, the upper limit would be 1.
  257. using boost::math::tools::max_value;
  258. return std::pair<RealType, RealType>(static_cast<RealType>(0), max_value<RealType>());
  259. }
  260. template <class RealType, class Policy>
  261. inline RealType pdf(const kolmogorov_smirnov_distribution<RealType, Policy>& dist, const RealType& x)
  262. {
  263. BOOST_FPU_EXCEPTION_GUARD
  264. BOOST_MATH_STD_USING // for ADL of std functions.
  265. RealType n = dist.number_of_observations();
  266. RealType error_result;
  267. static const char* function = "boost::math::pdf(const kolmogorov_smirnov_distribution<%1%>&, %1%)";
  268. if(false == detail::check_x_not_NaN(function, x, &error_result, Policy()))
  269. return error_result;
  270. if(false == detail::check_df(function, n, &error_result, Policy()))
  271. return error_result;
  272. if (x < 0 || !(boost::math::isfinite)(x))
  273. {
  274. return policies::raise_domain_error<RealType>(
  275. function, "Kolmogorov-Smirnov parameter was %1%, but must be > 0 !", x, Policy());
  276. }
  277. if (2*x*x*n < constants::pi<RealType>()) {
  278. return detail::kolmogorov_smirnov_pdf_small_x(x, n, Policy());
  279. }
  280. return detail::kolmogorov_smirnov_pdf_large_x(x, n, Policy());
  281. } // pdf
  282. template <class RealType, class Policy>
  283. inline RealType cdf(const kolmogorov_smirnov_distribution<RealType, Policy>& dist, const RealType& x)
  284. {
  285. BOOST_MATH_STD_USING // for ADL of std function exp.
  286. static const char* function = "boost::math::cdf(const kolmogorov_smirnov_distribution<%1%>&, %1%)";
  287. RealType error_result;
  288. RealType n = dist.number_of_observations();
  289. if(false == detail::check_x_not_NaN(function, x, &error_result, Policy()))
  290. return error_result;
  291. if(false == detail::check_df(function, n, &error_result, Policy()))
  292. return error_result;
  293. if((x < 0) || !(boost::math::isfinite)(x)) {
  294. return policies::raise_domain_error<RealType>(
  295. function, "Random variable parameter was %1%, but must be between > 0 !", x, Policy());
  296. }
  297. if (x*x*n == 0)
  298. return 0;
  299. return jacobi_theta4tau(RealType(0), 2*x*x*n/constants::pi<RealType>(), Policy());
  300. } // cdf
  301. template <class RealType, class Policy>
  302. inline RealType cdf(const complemented2_type<kolmogorov_smirnov_distribution<RealType, Policy>, RealType>& c) {
  303. BOOST_MATH_STD_USING // for ADL of std function exp.
  304. RealType x = c.param;
  305. static const char* function = "boost::math::cdf(const complemented2_type<const kolmogorov_smirnov_distribution<%1%>&, %1%>)";
  306. RealType error_result;
  307. kolmogorov_smirnov_distribution<RealType, Policy> const& dist = c.dist;
  308. RealType n = dist.number_of_observations();
  309. if(false == detail::check_x_not_NaN(function, x, &error_result, Policy()))
  310. return error_result;
  311. if(false == detail::check_df(function, n, &error_result, Policy()))
  312. return error_result;
  313. if((x < 0) || !(boost::math::isfinite)(x))
  314. return policies::raise_domain_error<RealType>(
  315. function, "Random variable parameter was %1%, but must be between > 0 !", x, Policy());
  316. if (x*x*n == 0)
  317. return 1;
  318. if (2*x*x*n > constants::pi<RealType>())
  319. return -jacobi_theta4m1tau(RealType(0), 2*x*x*n/constants::pi<RealType>(), Policy());
  320. return RealType(1) - jacobi_theta4tau(RealType(0), 2*x*x*n/constants::pi<RealType>(), Policy());
  321. } // cdf (complemented)
  322. template <class RealType, class Policy>
  323. inline RealType quantile(const kolmogorov_smirnov_distribution<RealType, Policy>& dist, const RealType& p)
  324. {
  325. BOOST_MATH_STD_USING
  326. static const char* function = "boost::math::quantile(const kolmogorov_smirnov_distribution<%1%>&, %1%)";
  327. // Error check:
  328. RealType error_result;
  329. RealType n = dist.number_of_observations();
  330. if(false == detail::check_probability(function, p, &error_result, Policy()))
  331. return error_result;
  332. if(false == detail::check_df(function, n, &error_result, Policy()))
  333. return error_result;
  334. RealType k = detail::kolmogorov_smirnov_quantile_guess(p) / sqrt(n);
  335. const int get_digits = policies::digits<RealType, Policy>();// get digits from policy,
  336. std::uintmax_t max_iter = policies::get_max_root_iterations<Policy>(); // and max iterations.
  337. RealType result = tools::newton_raphson_iterate(detail::kolmogorov_smirnov_quantile_functor<RealType, Policy>(dist, p),
  338. k, RealType(0), RealType(1), get_digits, max_iter);
  339. if (max_iter >= policies::get_max_root_iterations<Policy>())
  340. {
  341. return policies::raise_evaluation_error<RealType>(function, "Unable to locate solution in a reasonable time:" // LCOV_EXCL_LINE
  342. " either there is no answer to quantile or the answer is infinite. Current best guess is %1%", result, Policy()); // LCOV_EXCL_LINE
  343. }
  344. return result;
  345. } // quantile
  346. template <class RealType, class Policy>
  347. inline RealType quantile(const complemented2_type<kolmogorov_smirnov_distribution<RealType, Policy>, RealType>& c) {
  348. BOOST_MATH_STD_USING
  349. static const char* function = "boost::math::quantile(const kolmogorov_smirnov_distribution<%1%>&, %1%)";
  350. kolmogorov_smirnov_distribution<RealType, Policy> const& dist = c.dist;
  351. RealType n = dist.number_of_observations();
  352. // Error check:
  353. RealType error_result;
  354. RealType p = c.param;
  355. if(false == detail::check_probability(function, p, &error_result, Policy()))
  356. return error_result;
  357. if(false == detail::check_df(function, n, &error_result, Policy()))
  358. return error_result;
  359. RealType k = detail::kolmogorov_smirnov_quantile_guess(RealType(1-p)) / sqrt(n);
  360. const int get_digits = policies::digits<RealType, Policy>();// get digits from policy,
  361. std::uintmax_t max_iter = policies::get_max_root_iterations<Policy>(); // and max iterations.
  362. RealType result = tools::newton_raphson_iterate(
  363. detail::kolmogorov_smirnov_complementary_quantile_functor<RealType, Policy>(dist, p),
  364. k, RealType(0), RealType(1), get_digits, max_iter);
  365. if (max_iter >= policies::get_max_root_iterations<Policy>())
  366. {
  367. return policies::raise_evaluation_error<RealType>(function, "Unable to locate solution in a reasonable time:" // LCOV_EXCL_LINE
  368. " either there is no answer to quantile or the answer is infinite. Current best guess is %1%", result, Policy()); // LCOV_EXCL_LINE
  369. }
  370. return result;
  371. } // quantile (complemented)
  372. template <class RealType, class Policy>
  373. inline RealType mode(const kolmogorov_smirnov_distribution<RealType, Policy>& dist)
  374. {
  375. BOOST_MATH_STD_USING
  376. static const char* function = "boost::math::mode(const kolmogorov_smirnov_distribution<%1%>&)";
  377. RealType n = dist.number_of_observations();
  378. RealType error_result;
  379. if(false == detail::check_df(function, n, &error_result, Policy()))
  380. return error_result;
  381. std::pair<RealType, RealType> r = boost::math::tools::brent_find_minima(
  382. detail::kolmogorov_smirnov_negative_pdf_functor<RealType, Policy>(),
  383. static_cast<RealType>(0), static_cast<RealType>(1), policies::digits<RealType, Policy>());
  384. return r.first / sqrt(n);
  385. }
  386. // Mean and variance come directly from
  387. // https://www.jstatsoft.org/article/view/v008i18 Section 3
  388. template <class RealType, class Policy>
  389. inline RealType mean(const kolmogorov_smirnov_distribution<RealType, Policy>& dist)
  390. {
  391. BOOST_MATH_STD_USING
  392. static const char* function = "boost::math::mean(const kolmogorov_smirnov_distribution<%1%>&)";
  393. RealType n = dist.number_of_observations();
  394. RealType error_result;
  395. if(false == detail::check_df(function, n, &error_result, Policy()))
  396. return error_result;
  397. return constants::root_half_pi<RealType>() * constants::ln_two<RealType>() / sqrt(n);
  398. }
  399. template <class RealType, class Policy>
  400. inline RealType variance(const kolmogorov_smirnov_distribution<RealType, Policy>& dist)
  401. {
  402. static const char* function = "boost::math::variance(const kolmogorov_smirnov_distribution<%1%>&)";
  403. RealType n = dist.number_of_observations();
  404. RealType error_result;
  405. if(false == detail::check_df(function, n, &error_result, Policy()))
  406. return error_result;
  407. return (constants::pi_sqr_div_six<RealType>()
  408. - constants::pi<RealType>() * constants::ln_two<RealType>() * constants::ln_two<RealType>()) / (2*n);
  409. }
  410. // Skewness and kurtosis come from integrating the PDF
  411. // The alternating series pops out a Dirichlet eta function which is related to the zeta function
  412. template <class RealType, class Policy>
  413. inline RealType skewness(const kolmogorov_smirnov_distribution<RealType, Policy>& dist)
  414. {
  415. BOOST_MATH_STD_USING
  416. static const char* function = "boost::math::skewness(const kolmogorov_smirnov_distribution<%1%>&)";
  417. RealType n = dist.number_of_observations();
  418. RealType error_result;
  419. if(false == detail::check_df(function, n, &error_result, Policy()))
  420. return error_result;
  421. RealType ex3 = RealType(0.5625) * constants::root_half_pi<RealType>() * constants::zeta_three<RealType>() / n / sqrt(n);
  422. RealType mean = boost::math::mean(dist);
  423. RealType var = boost::math::variance(dist);
  424. return (ex3 - 3 * mean * var - mean * mean * mean) / var / sqrt(var);
  425. }
  426. template <class RealType, class Policy>
  427. inline RealType kurtosis(const kolmogorov_smirnov_distribution<RealType, Policy>& dist)
  428. {
  429. BOOST_MATH_STD_USING
  430. static const char* function = "boost::math::kurtosis(const kolmogorov_smirnov_distribution<%1%>&)";
  431. RealType n = dist.number_of_observations();
  432. RealType error_result;
  433. if(false == detail::check_df(function, n, &error_result, Policy()))
  434. return error_result;
  435. RealType ex4 = 7 * constants::pi_sqr_div_six<RealType>() * constants::pi_sqr_div_six<RealType>() / 20 / n / n;
  436. RealType mean = boost::math::mean(dist);
  437. RealType var = boost::math::variance(dist);
  438. RealType skew = boost::math::skewness(dist);
  439. return (ex4 - 4 * mean * skew * var * sqrt(var) - 6 * mean * mean * var - mean * mean * mean * mean) / var / var;
  440. }
  441. template <class RealType, class Policy>
  442. inline RealType kurtosis_excess(const kolmogorov_smirnov_distribution<RealType, Policy>& dist)
  443. {
  444. static const char* function = "boost::math::kurtosis_excess(const kolmogorov_smirnov_distribution<%1%>&)";
  445. RealType n = dist.number_of_observations();
  446. RealType error_result;
  447. if(false == detail::check_df(function, n, &error_result, Policy()))
  448. return error_result;
  449. return kurtosis(dist) - 3;
  450. }
  451. }}
  452. #endif