robustmodels 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robustmodels/__init__.py +12 -0
- robustmodels/_validation.py +40 -0
- robustmodels/covariance.py +1191 -0
- robustmodels/location.py +241 -0
- robustmodels/scale.py +758 -0
- robustmodels-0.1.0.dist-info/METADATA +151 -0
- robustmodels-0.1.0.dist-info/RECORD +9 -0
- robustmodels-0.1.0.dist-info/WHEEL +4 -0
- robustmodels-0.1.0.dist-info/licenses/LICENSE +28 -0
robustmodels/location.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Robust univariate location estimators.
|
|
2
|
+
|
|
3
|
+
Every function takes a one-dimensional sample of finite values and returns a
|
|
4
|
+
float; an empty sample gives ``nan``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ._validation import as_1d, check_count, check_option, check_range
|
|
10
|
+
from .scale import _bisquare_weights, _matrix_median, _pairwise_rows, _tau_estimates, mad
|
|
11
|
+
|
|
12
|
+
__all__ = ["hodges_lehmann", "m_location", "tau_location", "trimmed_mean", "winsorized_mean"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def hodges_lehmann(x):
|
|
16
|
+
"""Hodges-Lehmann estimator: the median of the Walsh averages.
|
|
17
|
+
|
|
18
|
+
Median of ``(x_i + x_j) / 2`` over ``i <= j``. Breakdown point 29.3%,
|
|
19
|
+
Gaussian efficiency 95.5%. Computed exactly in O(n log n) time.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
x : array_like
|
|
24
|
+
One-dimensional sample of finite values.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
float
|
|
29
|
+
``nan`` for an empty sample.
|
|
30
|
+
|
|
31
|
+
References
|
|
32
|
+
----------
|
|
33
|
+
Hodges, J. L. and Lehmann, E. L. (1963). Estimates of location based on rank
|
|
34
|
+
tests. *Annals of Mathematical Statistics*, 34(2), 598-611.
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> from robustmodels.location import hodges_lehmann
|
|
39
|
+
>>> hodges_lehmann([1, 2, 2, 3, 4, 100])
|
|
40
|
+
3.0
|
|
41
|
+
"""
|
|
42
|
+
x = np.sort(as_1d(x))
|
|
43
|
+
if x.size == 0:
|
|
44
|
+
return np.nan
|
|
45
|
+
first, last = _pairwise_rows(x.size, diagonal=True)
|
|
46
|
+
return _matrix_median(lambda i, j: (x[i] + x[j]) / 2, first, last)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def trimmed_mean(x, proportion=0.1):
|
|
50
|
+
"""Trimmed mean, as ``scipy.stats.trim_mean``.
|
|
51
|
+
|
|
52
|
+
Mean after removing ``floor(proportion * n)`` observations from each end of
|
|
53
|
+
the sorted sample. Breakdown point close to ``proportion``.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
x : array_like
|
|
58
|
+
One-dimensional sample of finite values.
|
|
59
|
+
proportion : float in [0, 0.5), default 0.1
|
|
60
|
+
Fraction removed from each end.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
float
|
|
65
|
+
``nan`` for an empty sample.
|
|
66
|
+
|
|
67
|
+
Examples
|
|
68
|
+
--------
|
|
69
|
+
>>> from robustmodels.location import trimmed_mean
|
|
70
|
+
>>> trimmed_mean([1, 2, 2, 3, 4, 100], proportion=0.2)
|
|
71
|
+
2.75
|
|
72
|
+
"""
|
|
73
|
+
x = np.sort(as_1d(x))
|
|
74
|
+
count = _trim_count(x.size, proportion)
|
|
75
|
+
if x.size == 0:
|
|
76
|
+
return np.nan
|
|
77
|
+
return float(x[count : x.size - count].mean())
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def winsorized_mean(x, proportion=0.1):
|
|
81
|
+
"""Winsorized mean.
|
|
82
|
+
|
|
83
|
+
Mean after replacing the ``k = floor(proportion * n)`` smallest observations
|
|
84
|
+
by the ``(k + 1)``-th smallest, and the ``k`` largest by the ``(k + 1)``-th
|
|
85
|
+
largest. Breakdown point close to ``proportion``.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
x : array_like
|
|
90
|
+
One-dimensional sample of finite values.
|
|
91
|
+
proportion : float in [0, 0.5), default 0.1
|
|
92
|
+
Fraction clipped at each end.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
float
|
|
97
|
+
``nan`` for an empty sample.
|
|
98
|
+
|
|
99
|
+
Examples
|
|
100
|
+
--------
|
|
101
|
+
>>> from robustmodels.location import winsorized_mean
|
|
102
|
+
>>> round(winsorized_mean([1, 2, 2, 3, 4, 100], proportion=0.2), 4)
|
|
103
|
+
2.8333
|
|
104
|
+
"""
|
|
105
|
+
x = np.sort(as_1d(x))
|
|
106
|
+
count = _trim_count(x.size, proportion)
|
|
107
|
+
if x.size == 0:
|
|
108
|
+
return np.nan
|
|
109
|
+
clipped = np.clip(x, x[count], x[-1 - count])
|
|
110
|
+
return float(clipped.mean())
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _trim_count(n, proportion):
|
|
114
|
+
check_range("proportion", proportion, 0, 0.5, high_open=True)
|
|
115
|
+
return int(proportion * n)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def tau_location(x, *, c1=4.5):
|
|
119
|
+
"""Tau location of Maronna & Zamar (2002): one bisquare-weighted mean.
|
|
120
|
+
|
|
121
|
+
Weighted mean with weights ``(1 - (r_i / c1)^2)^2`` for ``|r_i| < c1`` and 0
|
|
122
|
+
otherwise, where ``r_i = (x_i - median) / MAD0`` and ``MAD0`` is the raw
|
|
123
|
+
(unscaled) MAD. This is the location used by
|
|
124
|
+
:func:`robustmodels.scale.tau_scale`. Returns the median if ``MAD0`` is zero
|
|
125
|
+
or every weight is zero.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
x : array_like
|
|
130
|
+
One-dimensional sample of finite values.
|
|
131
|
+
c1 : float, default 4.5
|
|
132
|
+
Bisquare tuning constant.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
float
|
|
137
|
+
``nan`` for an empty sample.
|
|
138
|
+
|
|
139
|
+
References
|
|
140
|
+
----------
|
|
141
|
+
Maronna, R. A. and Zamar, R. H. (2002). Robust estimates of location and
|
|
142
|
+
dispersion for high-dimensional datasets. *Technometrics*, 44(4), 307-317.
|
|
143
|
+
|
|
144
|
+
Examples
|
|
145
|
+
--------
|
|
146
|
+
>>> from robustmodels.location import tau_location
|
|
147
|
+
>>> round(tau_location([1, 2, 2, 3, 4, 100]), 4)
|
|
148
|
+
2.3918
|
|
149
|
+
"""
|
|
150
|
+
x = as_1d(x)
|
|
151
|
+
check_range("c1", c1, 0, np.inf, low_open=True)
|
|
152
|
+
if x.size == 0:
|
|
153
|
+
return np.nan
|
|
154
|
+
location, _ = _tau_estimates(x, c1)
|
|
155
|
+
return location
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
PSI_C = {"huber": 1.345, "bisquare": 4.685}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def m_location(x, *, psi="huber", c=None, scale=None, tol=1e-9, max_iter=500):
|
|
162
|
+
"""M-estimator of location with Huber's or Tukey's bisquare psi function.
|
|
163
|
+
|
|
164
|
+
Solves ``sum(psi((x_i - mu) / s)) = 0`` for ``mu`` by iteration from the
|
|
165
|
+
median, with the scale ``s`` held fixed. With the MAD as scale the breakdown
|
|
166
|
+
point is 50%; the default tuning constants give 95% Gaussian efficiency.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
x : array_like
|
|
171
|
+
One-dimensional sample of finite values.
|
|
172
|
+
psi : {"huber", "bisquare"}, default "huber"
|
|
173
|
+
Huber's monotone psi or Tukey's redescending bisquare psi.
|
|
174
|
+
c : float, optional
|
|
175
|
+
Tuning constant. Defaults to 1.345 for "huber" and 4.685 for "bisquare".
|
|
176
|
+
scale : float, optional
|
|
177
|
+
Fixed scale ``s``. Defaults to the MAD about the median. A zero scale
|
|
178
|
+
returns the median.
|
|
179
|
+
tol : float, default 1e-9
|
|
180
|
+
Stop when the location changes by less than ``tol * s``.
|
|
181
|
+
max_iter : int, default 500
|
|
182
|
+
Maximum number of iterations.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
float
|
|
187
|
+
``nan`` for an empty sample. If the bisquare gives every observation zero
|
|
188
|
+
weight, the current location is returned.
|
|
189
|
+
|
|
190
|
+
References
|
|
191
|
+
----------
|
|
192
|
+
Huber, P. J. (1964). Robust estimation of a location parameter. *Annals of
|
|
193
|
+
Mathematical Statistics*, 35(1), 73-101.
|
|
194
|
+
|
|
195
|
+
Beaton, A. E. and Tukey, J. W. (1974). The fitting of power series, meaning
|
|
196
|
+
polynomials, illustrated on band-spectroscopic data. *Technometrics*, 16(2),
|
|
197
|
+
147-185.
|
|
198
|
+
|
|
199
|
+
Examples
|
|
200
|
+
--------
|
|
201
|
+
>>> from robustmodels.location import m_location
|
|
202
|
+
>>> round(m_location([1, 2, 2, 3, 4, 100]), 4)
|
|
203
|
+
2.7988
|
|
204
|
+
>>> round(m_location([1, 2, 2, 3, 4, 100], psi="bisquare"), 4)
|
|
205
|
+
2.3869
|
|
206
|
+
"""
|
|
207
|
+
x = as_1d(x)
|
|
208
|
+
check_option("psi", psi, PSI_C)
|
|
209
|
+
c = PSI_C[psi] if c is None else c
|
|
210
|
+
_check_parameters(c, scale, tol, max_iter)
|
|
211
|
+
if x.size == 0:
|
|
212
|
+
return np.nan
|
|
213
|
+
location = float(np.median(x))
|
|
214
|
+
scale = mad(x, center=location) if scale is None else scale
|
|
215
|
+
if scale == 0:
|
|
216
|
+
return location
|
|
217
|
+
for _ in range(max_iter):
|
|
218
|
+
updated = _location_step(x, location, scale, psi, c)
|
|
219
|
+
if abs(updated - location) < tol * scale:
|
|
220
|
+
return float(updated)
|
|
221
|
+
location = updated
|
|
222
|
+
return float(location)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _check_parameters(c, scale, tol, max_iter):
|
|
226
|
+
check_range("c", c, 0, np.inf, low_open=True)
|
|
227
|
+
check_range("tol", tol, 0, np.inf, low_open=True)
|
|
228
|
+
check_count("max_iter", max_iter)
|
|
229
|
+
if scale is not None:
|
|
230
|
+
check_range("scale", scale, 0, np.inf)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _location_step(x, location, scale, psi, c):
|
|
234
|
+
if psi == "huber":
|
|
235
|
+
clipped = np.clip(x, location - c * scale, location + c * scale)
|
|
236
|
+
return float(clipped.mean())
|
|
237
|
+
weights = _bisquare_weights((x - location) / scale, c)
|
|
238
|
+
total = weights.sum()
|
|
239
|
+
if total == 0:
|
|
240
|
+
return location
|
|
241
|
+
return float(np.dot(weights, x) / total)
|