123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- import numpy as np
- from numpy import poly1d
- from scipy.special import beta
- # The following code was used to generate the Pade coefficients for the
- # Tukey Lambda variance function. Version 0.17 of mpmath was used.
- #---------------------------------------------------------------------------
- # import mpmath as mp
- #
- # mp.mp.dps = 60
- #
- # one = mp.mpf(1)
- # two = mp.mpf(2)
- #
- # def mpvar(lam):
- # if lam == 0:
- # v = mp.pi**2 / three
- # else:
- # v = (two / lam**2) * (one / (one + two*lam) -
- # mp.beta(lam + one, lam + one))
- # return v
- #
- # t = mp.taylor(mpvar, 0, 8)
- # p, q = mp.pade(t, 4, 4)
- # print("p =", [mp.fp.mpf(c) for c in p])
- # print("q =", [mp.fp.mpf(c) for c in q])
- #---------------------------------------------------------------------------
- # Pade coefficients for the Tukey Lambda variance function.
- _tukeylambda_var_pc = [3.289868133696453, 0.7306125098871127,
- -0.5370742306855439, 0.17292046290190008,
- -0.02371146284628187]
- _tukeylambda_var_qc = [1.0, 3.683605511659861, 4.184152498888124,
- 1.7660926747377275, 0.2643989311168465]
- # numpy.poly1d instances for the numerator and denominator of the
- # Pade approximation to the Tukey Lambda variance.
- _tukeylambda_var_p = poly1d(_tukeylambda_var_pc[::-1])
- _tukeylambda_var_q = poly1d(_tukeylambda_var_qc[::-1])
- def tukeylambda_variance(lam):
- """Variance of the Tukey Lambda distribution.
- Parameters
- ----------
- lam : array_like
- The lambda values at which to compute the variance.
- Returns
- -------
- v : ndarray
- The variance. For lam < -0.5, the variance is not defined, so
- np.nan is returned. For lam = 0.5, np.inf is returned.
- Notes
- -----
- In an interval around lambda=0, this function uses the [4,4] Pade
- approximation to compute the variance. Otherwise it uses the standard
- formula (https://en.wikipedia.org/wiki/Tukey_lambda_distribution). The
- Pade approximation is used because the standard formula has a removable
- discontinuity at lambda = 0, and does not produce accurate numerical
- results near lambda = 0.
- """
- lam = np.asarray(lam)
- shp = lam.shape
- lam = np.atleast_1d(lam).astype(np.float64)
- # For absolute values of lam less than threshold, use the Pade
- # approximation.
- threshold = 0.075
- # Play games with masks to implement the conditional evaluation of
- # the distribution.
- # lambda < -0.5: var = nan
- low_mask = lam < -0.5
- # lambda == -0.5: var = inf
- neghalf_mask = lam == -0.5
- # abs(lambda) < threshold: use Pade approximation
- small_mask = np.abs(lam) < threshold
- # else the "regular" case: use the explicit formula.
- reg_mask = ~(low_mask | neghalf_mask | small_mask)
- # Get the 'lam' values for the cases where they are needed.
- small = lam[small_mask]
- reg = lam[reg_mask]
- # Compute the function for each case.
- v = np.empty_like(lam)
- v[low_mask] = np.nan
- v[neghalf_mask] = np.inf
- if small.size > 0:
- # Use the Pade approximation near lambda = 0.
- v[small_mask] = _tukeylambda_var_p(small) / _tukeylambda_var_q(small)
- if reg.size > 0:
- v[reg_mask] = (2.0 / reg**2) * (1.0 / (1.0 + 2 * reg) -
- beta(reg + 1, reg + 1))
- v.shape = shp
- return v
- # The following code was used to generate the Pade coefficients for the
- # Tukey Lambda kurtosis function. Version 0.17 of mpmath was used.
- #---------------------------------------------------------------------------
- # import mpmath as mp
- #
- # mp.mp.dps = 60
- #
- # one = mp.mpf(1)
- # two = mp.mpf(2)
- # three = mp.mpf(3)
- # four = mp.mpf(4)
- #
- # def mpkurt(lam):
- # if lam == 0:
- # k = mp.mpf(6)/5
- # else:
- # numer = (one/(four*lam+one) - four*mp.beta(three*lam+one, lam+one) +
- # three*mp.beta(two*lam+one, two*lam+one))
- # denom = two*(one/(two*lam+one) - mp.beta(lam+one,lam+one))**2
- # k = numer / denom - three
- # return k
- #
- # # There is a bug in mpmath 0.17: when we use the 'method' keyword of the
- # # taylor function and we request a degree 9 Taylor polynomial, we actually
- # # get degree 8.
- # t = mp.taylor(mpkurt, 0, 9, method='quad', radius=0.01)
- # t = [mp.chop(c, tol=1e-15) for c in t]
- # p, q = mp.pade(t, 4, 4)
- # print("p =", [mp.fp.mpf(c) for c in p])
- # print("q =", [mp.fp.mpf(c) for c in q])
- #---------------------------------------------------------------------------
- # Pade coefficients for the Tukey Lambda kurtosis function.
- _tukeylambda_kurt_pc = [1.2, -5.853465139719495, -22.653447381131077,
- 0.20601184383406815, 4.59796302262789]
- _tukeylambda_kurt_qc = [1.0, 7.171149192233599, 12.96663094361842,
- 0.43075235247853005, -2.789746758009912]
- # numpy.poly1d instances for the numerator and denominator of the
- # Pade approximation to the Tukey Lambda kurtosis.
- _tukeylambda_kurt_p = poly1d(_tukeylambda_kurt_pc[::-1])
- _tukeylambda_kurt_q = poly1d(_tukeylambda_kurt_qc[::-1])
- def tukeylambda_kurtosis(lam):
- """Kurtosis of the Tukey Lambda distribution.
- Parameters
- ----------
- lam : array_like
- The lambda values at which to compute the variance.
- Returns
- -------
- v : ndarray
- The variance. For lam < -0.25, the variance is not defined, so
- np.nan is returned. For lam = 0.25, np.inf is returned.
- """
- lam = np.asarray(lam)
- shp = lam.shape
- lam = np.atleast_1d(lam).astype(np.float64)
- # For absolute values of lam less than threshold, use the Pade
- # approximation.
- threshold = 0.055
- # Use masks to implement the conditional evaluation of the kurtosis.
- # lambda < -0.25: kurtosis = nan
- low_mask = lam < -0.25
- # lambda == -0.25: kurtosis = inf
- negqrtr_mask = lam == -0.25
- # lambda near 0: use Pade approximation
- small_mask = np.abs(lam) < threshold
- # else the "regular" case: use the explicit formula.
- reg_mask = ~(low_mask | negqrtr_mask | small_mask)
- # Get the 'lam' values for the cases where they are needed.
- small = lam[small_mask]
- reg = lam[reg_mask]
- # Compute the function for each case.
- k = np.empty_like(lam)
- k[low_mask] = np.nan
- k[negqrtr_mask] = np.inf
- if small.size > 0:
- k[small_mask] = _tukeylambda_kurt_p(small) / _tukeylambda_kurt_q(small)
- if reg.size > 0:
- numer = (1.0 / (4 * reg + 1) - 4 * beta(3 * reg + 1, reg + 1) +
- 3 * beta(2 * reg + 1, 2 * reg + 1))
- denom = 2 * (1.0/(2 * reg + 1) - beta(reg + 1, reg + 1))**2
- k[reg_mask] = numer / denom - 3
- # The return value will be a numpy array; resetting the shape ensures that
- # if `lam` was a scalar, the return value is a 0-d array.
- k.shape = shp
- return k
|