1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

from __future__ import division, print_function, absolute_import 

 

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 (http://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