Skip to content

Commit 7a37063

Browse files
Add implementation of lmoment since scipy v0.15 is not yet released
The plan is to exclusively rely on scipy for the functionality in the future, but for now to provide a substitute that doesn't rely on a version of scipy that is still under development (as of Dec 2024). Co-authored-by: Christopher Polster <christopher.polster@ecmwf.int>
1 parent 909f63d commit 7a37063

File tree

1 file changed

+57
-1
lines changed

1 file changed

+57
-1
lines changed

src/earthkit/meteo/stats/array/distributions.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,59 @@ def ppf(self, x):
3434
"""Evaluate the inverse CDF (percent point function)"""
3535

3636

37+
# Temporary drop-in replacement for scipy.stats.lmoment from scipy v0.15
38+
def _lmoment(sample, order=(1, 2), axis=0):
39+
"""Compute first 2 L-moments of dataset along first axis"""
40+
if len(order) != 2 or order[0] != 1 or order[1] != 2:
41+
raise NotImplementedError
42+
if axis != 0:
43+
raise NotImplementedError
44+
45+
nmoments = 3
46+
47+
# At least four values needed to make a sample L-moments estimation
48+
nvalues, *rest_shape = sample.shape
49+
if nvalues < 4:
50+
raise ValueError("Insufficient number of values to perform sample L-moments estimation")
51+
52+
sample = np.sort(sample, axis=0) # ascending order
53+
54+
sums = np.zeros_like(sample, shape=(nmoments, *rest_shape))
55+
56+
for i in range(1, nvalues + 1):
57+
z = i
58+
term = sample[i - 1]
59+
sums[0] = sums[0] + term
60+
for j in range(1, nmoments):
61+
z -= 1
62+
term = term * z
63+
sums[j] = sums[j] + term
64+
65+
y = float(nvalues)
66+
z = float(nvalues)
67+
sums[0] = sums[0] / z
68+
for j in range(1, nmoments):
69+
y = y - 1.0
70+
z = z * y
71+
sums[j] = sums[j] / z
72+
73+
k = nmoments
74+
p0 = -1.0
75+
for _ in range(2):
76+
ak = float(k)
77+
p0 = -p0
78+
p = p0
79+
temp = p * sums[0]
80+
for i in range(1, k):
81+
ai = i
82+
p = -p * (ak + ai - 1.0) * (ak - ai) / (ai * ai)
83+
temp = temp + (p * sums[i])
84+
sums[k - 1] = temp
85+
k = k - 1
86+
87+
return np.stack([sums[0], sums[1]])
88+
89+
3790
class MaxGumbel(ContinuousDistribution):
3891
"""Gumbel distribution for extreme values
3992
@@ -61,7 +114,10 @@ def fit(cls, sample, axis=0):
61114
axis: int
62115
The axis along which to compute the parameters.
63116
"""
64-
from scipy.stats import lmoment
117+
try:
118+
from scipy.stats import lmoment
119+
except ImportError:
120+
lmoment = _lmoment
65121

66122
lmom = lmoment(sample, axis=axis, order=[1, 2])
67123
sigma = lmom[1] / np.log(2)

0 commit comments

Comments
 (0)