200 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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
 |