_tukeylambda_stats.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from __future__ import division, print_function, absolute_import
  2. import numpy as np
  3. from numpy import poly1d
  4. from scipy.special import beta
  5. # The following code was used to generate the Pade coefficients for the
  6. # Tukey Lambda variance function. Version 0.17 of mpmath was used.
  7. #---------------------------------------------------------------------------
  8. # import mpmath as mp
  9. #
  10. # mp.mp.dps = 60
  11. #
  12. # one = mp.mpf(1)
  13. # two = mp.mpf(2)
  14. #
  15. # def mpvar(lam):
  16. # if lam == 0:
  17. # v = mp.pi**2 / three
  18. # else:
  19. # v = (two / lam**2) * (one / (one + two*lam) -
  20. # mp.beta(lam + one, lam + one))
  21. # return v
  22. #
  23. # t = mp.taylor(mpvar, 0, 8)
  24. # p, q = mp.pade(t, 4, 4)
  25. # print("p =", [mp.fp.mpf(c) for c in p])
  26. # print("q =", [mp.fp.mpf(c) for c in q])
  27. #---------------------------------------------------------------------------
  28. # Pade coefficients for the Tukey Lambda variance function.
  29. _tukeylambda_var_pc = [3.289868133696453, 0.7306125098871127,
  30. -0.5370742306855439, 0.17292046290190008,
  31. -0.02371146284628187]
  32. _tukeylambda_var_qc = [1.0, 3.683605511659861, 4.184152498888124,
  33. 1.7660926747377275, 0.2643989311168465]
  34. # numpy.poly1d instances for the numerator and denominator of the
  35. # Pade approximation to the Tukey Lambda variance.
  36. _tukeylambda_var_p = poly1d(_tukeylambda_var_pc[::-1])
  37. _tukeylambda_var_q = poly1d(_tukeylambda_var_qc[::-1])
  38. def tukeylambda_variance(lam):
  39. """Variance of the Tukey Lambda distribution.
  40. Parameters
  41. ----------
  42. lam : array_like
  43. The lambda values at which to compute the variance.
  44. Returns
  45. -------
  46. v : ndarray
  47. The variance. For lam < -0.5, the variance is not defined, so
  48. np.nan is returned. For lam = 0.5, np.inf is returned.
  49. Notes
  50. -----
  51. In an interval around lambda=0, this function uses the [4,4] Pade
  52. approximation to compute the variance. Otherwise it uses the standard
  53. formula (https://en.wikipedia.org/wiki/Tukey_lambda_distribution). The
  54. Pade approximation is used because the standard formula has a removable
  55. discontinuity at lambda = 0, and does not produce accurate numerical
  56. results near lambda = 0.
  57. """
  58. lam = np.asarray(lam)
  59. shp = lam.shape
  60. lam = np.atleast_1d(lam).astype(np.float64)
  61. # For absolute values of lam less than threshold, use the Pade
  62. # approximation.
  63. threshold = 0.075
  64. # Play games with masks to implement the conditional evaluation of
  65. # the distribution.
  66. # lambda < -0.5: var = nan
  67. low_mask = lam < -0.5
  68. # lambda == -0.5: var = inf
  69. neghalf_mask = lam == -0.5
  70. # abs(lambda) < threshold: use Pade approximation
  71. small_mask = np.abs(lam) < threshold
  72. # else the "regular" case: use the explicit formula.
  73. reg_mask = ~(low_mask | neghalf_mask | small_mask)
  74. # Get the 'lam' values for the cases where they are needed.
  75. small = lam[small_mask]
  76. reg = lam[reg_mask]
  77. # Compute the function for each case.
  78. v = np.empty_like(lam)
  79. v[low_mask] = np.nan
  80. v[neghalf_mask] = np.inf
  81. if small.size > 0:
  82. # Use the Pade approximation near lambda = 0.
  83. v[small_mask] = _tukeylambda_var_p(small) / _tukeylambda_var_q(small)
  84. if reg.size > 0:
  85. v[reg_mask] = (2.0 / reg**2) * (1.0 / (1.0 + 2 * reg) -
  86. beta(reg + 1, reg + 1))
  87. v.shape = shp
  88. return v
  89. # The following code was used to generate the Pade coefficients for the
  90. # Tukey Lambda kurtosis function. Version 0.17 of mpmath was used.
  91. #---------------------------------------------------------------------------
  92. # import mpmath as mp
  93. #
  94. # mp.mp.dps = 60
  95. #
  96. # one = mp.mpf(1)
  97. # two = mp.mpf(2)
  98. # three = mp.mpf(3)
  99. # four = mp.mpf(4)
  100. #
  101. # def mpkurt(lam):
  102. # if lam == 0:
  103. # k = mp.mpf(6)/5
  104. # else:
  105. # numer = (one/(four*lam+one) - four*mp.beta(three*lam+one, lam+one) +
  106. # three*mp.beta(two*lam+one, two*lam+one))
  107. # denom = two*(one/(two*lam+one) - mp.beta(lam+one,lam+one))**2
  108. # k = numer / denom - three
  109. # return k
  110. #
  111. # # There is a bug in mpmath 0.17: when we use the 'method' keyword of the
  112. # # taylor function and we request a degree 9 Taylor polynomial, we actually
  113. # # get degree 8.
  114. # t = mp.taylor(mpkurt, 0, 9, method='quad', radius=0.01)
  115. # t = [mp.chop(c, tol=1e-15) for c in t]
  116. # p, q = mp.pade(t, 4, 4)
  117. # print("p =", [mp.fp.mpf(c) for c in p])
  118. # print("q =", [mp.fp.mpf(c) for c in q])
  119. #---------------------------------------------------------------------------
  120. # Pade coefficients for the Tukey Lambda kurtosis function.
  121. _tukeylambda_kurt_pc = [1.2, -5.853465139719495, -22.653447381131077,
  122. 0.20601184383406815, 4.59796302262789]
  123. _tukeylambda_kurt_qc = [1.0, 7.171149192233599, 12.96663094361842,
  124. 0.43075235247853005, -2.789746758009912]
  125. # numpy.poly1d instances for the numerator and denominator of the
  126. # Pade approximation to the Tukey Lambda kurtosis.
  127. _tukeylambda_kurt_p = poly1d(_tukeylambda_kurt_pc[::-1])
  128. _tukeylambda_kurt_q = poly1d(_tukeylambda_kurt_qc[::-1])
  129. def tukeylambda_kurtosis(lam):
  130. """Kurtosis of the Tukey Lambda distribution.
  131. Parameters
  132. ----------
  133. lam : array_like
  134. The lambda values at which to compute the variance.
  135. Returns
  136. -------
  137. v : ndarray
  138. The variance. For lam < -0.25, the variance is not defined, so
  139. np.nan is returned. For lam = 0.25, np.inf is returned.
  140. """
  141. lam = np.asarray(lam)
  142. shp = lam.shape
  143. lam = np.atleast_1d(lam).astype(np.float64)
  144. # For absolute values of lam less than threshold, use the Pade
  145. # approximation.
  146. threshold = 0.055
  147. # Use masks to implement the conditional evaluation of the kurtosis.
  148. # lambda < -0.25: kurtosis = nan
  149. low_mask = lam < -0.25
  150. # lambda == -0.25: kurtosis = inf
  151. negqrtr_mask = lam == -0.25
  152. # lambda near 0: use Pade approximation
  153. small_mask = np.abs(lam) < threshold
  154. # else the "regular" case: use the explicit formula.
  155. reg_mask = ~(low_mask | negqrtr_mask | small_mask)
  156. # Get the 'lam' values for the cases where they are needed.
  157. small = lam[small_mask]
  158. reg = lam[reg_mask]
  159. # Compute the function for each case.
  160. k = np.empty_like(lam)
  161. k[low_mask] = np.nan
  162. k[negqrtr_mask] = np.inf
  163. if small.size > 0:
  164. k[small_mask] = _tukeylambda_kurt_p(small) / _tukeylambda_kurt_q(small)
  165. if reg.size > 0:
  166. numer = (1.0 / (4 * reg + 1) - 4 * beta(3 * reg + 1, reg + 1) +
  167. 3 * beta(2 * reg + 1, 2 * reg + 1))
  168. denom = 2 * (1.0/(2 * reg + 1) - beta(reg + 1, reg + 1))**2
  169. k[reg_mask] = numer / denom - 3
  170. # The return value will be a numpy array; resetting the shape ensures that
  171. # if `lam` was a scalar, the return value is a 0-d array.
  172. k.shape = shp
  173. return k