_tukeylambda_stats.py 6.7 KB

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