Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/statsmodels/robust/norms.py : 27%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
3# TODO: add plots to weighting functions for online docs.
6class RobustNorm(object):
7 """
8 The parent class for the norms used for robust regression.
10 Lays out the methods expected of the robust norms to be used
11 by statsmodels.RLM.
13 See Also
14 --------
15 statsmodels.rlm
17 Notes
18 -----
19 Currently only M-estimators are available.
21 References
22 ----------
23 PJ Huber. 'Robust Statistics' John Wiley and Sons, Inc., New York, 1981.
25 DC Montgomery, EA Peck. 'Introduction to Linear Regression Analysis',
26 John Wiley and Sons, Inc., New York, 2001.
28 R Venables, B Ripley. 'Modern Applied Statistics in S'
29 Springer, New York, 2002.
30 """
32 def rho(self, z):
33 """
34 The robust criterion estimator function.
36 Abstract method:
38 -2 loglike used in M-estimator
39 """
40 raise NotImplementedError
42 def psi(self, z):
43 """
44 Derivative of rho. Sometimes referred to as the influence function.
46 Abstract method:
48 psi = rho'
49 """
50 raise NotImplementedError
52 def weights(self, z):
53 """
54 Returns the value of psi(z) / z
56 Abstract method:
58 psi(z) / z
59 """
60 raise NotImplementedError
62 def psi_deriv(self, z):
63 """
64 Derivative of psi. Used to obtain robust covariance matrix.
66 See statsmodels.rlm for more information.
68 Abstract method:
70 psi_derive = psi'
71 """
72 raise NotImplementedError
74 def __call__(self, z):
75 """
76 Returns the value of estimator rho applied to an input
77 """
78 return self.rho(z)
81class LeastSquares(RobustNorm):
82 """
83 Least squares rho for M-estimation and its derived functions.
85 See Also
86 --------
87 statsmodels.robust.norms.RobustNorm
88 """
90 def rho(self, z):
91 """
92 The least squares estimator rho function
94 Parameters
95 ----------
96 z : ndarray
97 1d array
99 Returns
100 -------
101 rho : ndarray
102 rho(z) = (1/2.)*z**2
103 """
105 return z**2 * 0.5
107 def psi(self, z):
108 """
109 The psi function for the least squares estimator
111 The analytic derivative of rho
113 Parameters
114 ----------
115 z : array_like
116 1d array
118 Returns
119 -------
120 psi : ndarray
121 psi(z) = z
122 """
124 return np.asarray(z)
126 def weights(self, z):
127 """
128 The least squares estimator weighting function for the IRLS algorithm.
130 The psi function scaled by the input z
132 Parameters
133 ----------
134 z : array_like
135 1d array
137 Returns
138 -------
139 weights : ndarray
140 weights(z) = np.ones(z.shape)
141 """
143 z = np.asarray(z)
144 return np.ones(z.shape, np.float64)
146 def psi_deriv(self, z):
147 """
148 The derivative of the least squares psi function.
150 Returns
151 -------
152 psi_deriv : ndarray
153 ones(z.shape)
155 Notes
156 -----
157 Used to estimate the robust covariance matrix.
158 """
159 return np.ones(z.shape, np.float64)
162class HuberT(RobustNorm):
163 """
164 Huber's T for M estimation.
166 Parameters
167 ----------
168 t : float, optional
169 The tuning constant for Huber's t function. The default value is
170 1.345.
172 See Also
173 --------
174 statsmodels.robust.norms.RobustNorm
175 """
177 def __init__(self, t=1.345):
178 self.t = t
180 def _subset(self, z):
181 """
182 Huber's T is defined piecewise over the range for z
183 """
184 z = np.asarray(z)
185 return np.less_equal(np.abs(z), self.t)
187 def rho(self, z):
188 r"""
189 The robust criterion function for Huber's t.
191 Parameters
192 ----------
193 z : array_like
194 1d array
196 Returns
197 -------
198 rho : ndarray
199 rho(z) = .5*z**2 for \|z\| <= t
201 rho(z) = \|z\|*t - .5*t**2 for \|z\| > t
202 """
203 z = np.asarray(z)
204 test = self._subset(z)
205 return (test * 0.5 * z**2 +
206 (1 - test) * (np.abs(z) * self.t - 0.5 * self.t**2))
208 def psi(self, z):
209 r"""
210 The psi function for Huber's t estimator
212 The analytic derivative of rho
214 Parameters
215 ----------
216 z : array_like
217 1d array
219 Returns
220 -------
221 psi : ndarray
222 psi(z) = z for \|z\| <= t
224 psi(z) = sign(z)*t for \|z\| > t
225 """
226 z = np.asarray(z)
227 test = self._subset(z)
228 return test * z + (1 - test) * self.t * np.sign(z)
230 def weights(self, z):
231 r"""
232 Huber's t weighting function for the IRLS algorithm
234 The psi function scaled by z
236 Parameters
237 ----------
238 z : array_like
239 1d array
241 Returns
242 -------
243 weights : ndarray
244 weights(z) = 1 for \|z\| <= t
246 weights(z) = t/\|z\| for \|z\| > t
247 """
248 z = np.asarray(z)
249 test = self._subset(z)
250 absz = np.abs(z)
251 absz[test] = 1.0
252 return test + (1 - test) * self.t / absz
254 def psi_deriv(self, z):
255 """
256 The derivative of Huber's t psi function
258 Notes
259 -----
260 Used to estimate the robust covariance matrix.
261 """
262 return np.less_equal(np.abs(z), self.t)
265# TODO: untested, but looks right. RamsayE not available in R or SAS?
266class RamsayE(RobustNorm):
267 """
268 Ramsay's Ea for M estimation.
270 Parameters
271 ----------
272 a : float, optional
273 The tuning constant for Ramsay's Ea function. The default value is
274 0.3.
276 See Also
277 --------
278 statsmodels.robust.norms.RobustNorm
279 """
281 def __init__(self, a=.3):
282 self.a = a
284 def rho(self, z):
285 r"""
286 The robust criterion function for Ramsay's Ea.
288 Parameters
289 ----------
290 z : array_like
291 1d array
293 Returns
294 -------
295 rho : ndarray
296 rho(z) = a**-2 * (1 - exp(-a*\|z\|)*(1 + a*\|z\|))
297 """
298 z = np.asarray(z)
299 return (1 - np.exp(-self.a * np.abs(z)) *
300 (1 + self.a * np.abs(z))) / self.a**2
302 def psi(self, z):
303 r"""
304 The psi function for Ramsay's Ea estimator
306 The analytic derivative of rho
308 Parameters
309 ----------
310 z : array_like
311 1d array
313 Returns
314 -------
315 psi : ndarray
316 psi(z) = z*exp(-a*\|z\|)
317 """
318 z = np.asarray(z)
319 return z * np.exp(-self.a * np.abs(z))
321 def weights(self, z):
322 r"""
323 Ramsay's Ea weighting function for the IRLS algorithm
325 The psi function scaled by z
327 Parameters
328 ----------
329 z : array_like
330 1d array
332 Returns
333 -------
334 weights : ndarray
335 weights(z) = exp(-a*\|z\|)
336 """
338 z = np.asarray(z)
339 return np.exp(-self.a * np.abs(z))
341 def psi_deriv(self, z):
342 """
343 The derivative of Ramsay's Ea psi function.
345 Notes
346 -----
347 Used to estimate the robust covariance matrix.
348 """
349 a = self.a
350 x = np.exp(-a * np.abs(z))
351 dx = -a * x * np.sign(z)
352 y = z
353 dy = 1
354 return x * dy + y * dx
357class AndrewWave(RobustNorm):
358 """
359 Andrew's wave for M estimation.
361 Parameters
362 ----------
363 a : float, optional
364 The tuning constant for Andrew's Wave function. The default value is
365 1.339.
367 See Also
368 --------
369 statsmodels.robust.norms.RobustNorm
370 """
371 def __init__(self, a=1.339):
372 self.a = a
374 def _subset(self, z):
375 """
376 Andrew's wave is defined piecewise over the range of z.
377 """
378 z = np.asarray(z)
379 return np.less_equal(np.abs(z), self.a * np.pi)
381 def rho(self, z):
382 r"""
383 The robust criterion function for Andrew's wave.
385 Parameters
386 ----------
387 z : array_like
388 1d array
390 Returns
391 -------
392 rho : ndarray
393 rho(z) = a*(1-cos(z/a)) for \|z\| <= a*pi
395 rho(z) = 2*a for \|z\| > a*pi
396 """
398 a = self.a
399 z = np.asarray(z)
400 test = self._subset(z)
401 return (test * a * (1 - np.cos(z / a)) +
402 (1 - test) * 2 * a)
404 def psi(self, z):
405 r"""
406 The psi function for Andrew's wave
408 The analytic derivative of rho
410 Parameters
411 ----------
412 z : array_like
413 1d array
415 Returns
416 -------
417 psi : ndarray
418 psi(z) = sin(z/a) for \|z\| <= a*pi
420 psi(z) = 0 for \|z\| > a*pi
421 """
423 a = self.a
424 z = np.asarray(z)
425 test = self._subset(z)
426 return test * np.sin(z / a)
428 def weights(self, z):
429 r"""
430 Andrew's wave weighting function for the IRLS algorithm
432 The psi function scaled by z
434 Parameters
435 ----------
436 z : array_like
437 1d array
439 Returns
440 -------
441 weights : ndarray
442 weights(z) = sin(z/a)/(z/a) for \|z\| <= a*pi
444 weights(z) = 0 for \|z\| > a*pi
445 """
446 a = self.a
447 z = np.asarray(z)
448 test = self._subset(z)
449 ratio = z / a
450 small = np.abs(ratio) < np.finfo(np.double).eps
451 if np.any(small):
452 weights = np.ones_like(ratio)
453 large = ~small
454 ratio = ratio[large]
455 weights[large] = test[large] * np.sin(ratio) / ratio
456 else:
457 weights = test * np.sin(ratio) / ratio
458 return weights
460 def psi_deriv(self, z):
461 """
462 The derivative of Andrew's wave psi function
464 Notes
465 -----
466 Used to estimate the robust covariance matrix.
467 """
469 test = self._subset(z)
470 return test*np.cos(z / self.a)/self.a
473# TODO: this is untested
474class TrimmedMean(RobustNorm):
475 """
476 Trimmed mean function for M-estimation.
478 Parameters
479 ----------
480 c : float, optional
481 The tuning constant for Ramsay's Ea function. The default value is
482 2.0.
484 See Also
485 --------
486 statsmodels.robust.norms.RobustNorm
487 """
489 def __init__(self, c=2.):
490 self.c = c
492 def _subset(self, z):
493 """
494 Least trimmed mean is defined piecewise over the range of z.
495 """
497 z = np.asarray(z)
498 return np.less_equal(np.abs(z), self.c)
500 def rho(self, z):
501 r"""
502 The robust criterion function for least trimmed mean.
504 Parameters
505 ----------
506 z : array_like
507 1d array
509 Returns
510 -------
511 rho : ndarray
512 rho(z) = (1/2.)*z**2 for \|z\| <= c
514 rho(z) = 0 for \|z\| > c
515 """
517 z = np.asarray(z)
518 test = self._subset(z)
519 return test * z**2 * 0.5
521 def psi(self, z):
522 r"""
523 The psi function for least trimmed mean
525 The analytic derivative of rho
527 Parameters
528 ----------
529 z : array_like
530 1d array
532 Returns
533 -------
534 psi : ndarray
535 psi(z) = z for \|z\| <= c
537 psi(z) = 0 for \|z\| > c
538 """
539 z = np.asarray(z)
540 test = self._subset(z)
541 return test * z
543 def weights(self, z):
544 r"""
545 Least trimmed mean weighting function for the IRLS algorithm
547 The psi function scaled by z
549 Parameters
550 ----------
551 z : array_like
552 1d array
554 Returns
555 -------
556 weights : ndarray
557 weights(z) = 1 for \|z\| <= c
559 weights(z) = 0 for \|z\| > c
560 """
561 z = np.asarray(z)
562 test = self._subset(z)
563 return test
565 def psi_deriv(self, z):
566 """
567 The derivative of least trimmed mean psi function
569 Notes
570 -----
571 Used to estimate the robust covariance matrix.
572 """
573 test = self._subset(z)
574 return test
577class Hampel(RobustNorm):
578 """
580 Hampel function for M-estimation.
582 Parameters
583 ----------
584 a : float, optional
585 b : float, optional
586 c : float, optional
587 The tuning constants for Hampel's function. The default values are
588 a,b,c = 2, 4, 8.
590 See Also
591 --------
592 statsmodels.robust.norms.RobustNorm
593 """
595 def __init__(self, a=2., b=4., c=8.):
596 self.a = a
597 self.b = b
598 self.c = c
600 def _subset(self, z):
601 """
602 Hampel's function is defined piecewise over the range of z
603 """
604 z = np.abs(np.asarray(z))
605 t1 = np.less_equal(z, self.a)
606 t2 = np.less_equal(z, self.b) * np.greater(z, self.a)
607 t3 = np.less_equal(z, self.c) * np.greater(z, self.b)
608 return t1, t2, t3
610 def rho(self, z):
611 r"""
612 The robust criterion function for Hampel's estimator
614 Parameters
615 ----------
616 z : array_like
617 1d array
619 Returns
620 -------
621 rho : ndarray
622 rho(z) = (1/2.)*z**2 for \|z\| <= a
624 rho(z) = a*\|z\| - 1/2.*a**2 for a < \|z\| <= b
626 rho(z) = a*(c*\|z\|-(1/2.)*z**2)/(c-b) for b < \|z\| <= c
628 rho(z) = a*(b + c - a) for \|z\| > c
629 """
631 z = np.abs(z)
632 a = self.a
633 b = self.b
634 c = self.c
635 t1, t2, t3 = self._subset(z)
636 v = (t1 * z**2 * 0.5 +
637 t2 * (a * z - a**2 * 0.5) +
638 t3 * (a * (c * z - z**2 * 0.5) / (c - b) - 7 * a**2 / 6.) +
639 (1 - t1 + t2 + t3) * a * (b + c - a))
640 return v
642 def psi(self, z):
643 r"""
644 The psi function for Hampel's estimator
646 The analytic derivative of rho
648 Parameters
649 ----------
650 z : array_like
651 1d array
653 Returns
654 -------
655 psi : ndarray
656 psi(z) = z for \|z\| <= a
658 psi(z) = a*sign(z) for a < \|z\| <= b
660 psi(z) = a*sign(z)*(c - \|z\|)/(c-b) for b < \|z\| <= c
662 psi(z) = 0 for \|z\| > c
663 """
664 z = np.asarray(z)
665 a = self.a
666 b = self.b
667 c = self.c
668 t1, t2, t3 = self._subset(z)
669 s = np.sign(z)
670 z = np.abs(z)
671 v = s * (t1 * z +
672 t2 * a*s +
673 t3 * a*s * (c - z) / (c - b))
674 return v
676 def weights(self, z):
677 r"""
678 Hampel weighting function for the IRLS algorithm
680 The psi function scaled by z
682 Parameters
683 ----------
684 z : array_like
685 1d array
687 Returns
688 -------
689 weights : ndarray
690 weights(z) = 1 for \|z\| <= a
692 weights(z) = a/\|z\| for a < \|z\| <= b
694 weights(z) = a*(c - \|z\|)/(\|z\|*(c-b)) for b < \|z\| <= c
696 weights(z) = 0 for \|z\| > c
697 """
698 z = np.asarray(z)
699 a = self.a
700 b = self.b
701 c = self.c
702 t1, t2, t3 = self._subset(z)
704 v = np.zeros_like(z)
705 v[t1] = 1.0
706 abs_z = np.abs(z)
707 v[t2] = a / abs_z[t2]
708 abs_zt3 = abs_z[t3]
709 v[t3] = a * (c - abs_zt3) / (abs_zt3 * (c - b))
710 v[np.where(np.isnan(v))] = 1. # TODO: for some reason 0 returns a nan?
711 return v
713 def psi_deriv(self, z):
714 t1, t2, t3 = self._subset(z)
715 a, b, c = self.a, self.b, self.c
716 # default is t1
717 d = np.zeros_like(z)
718 d[t1] = 1.0
719 zt3 = z[t3]
720 d[t3] = (a * np.sign(zt3) * zt3) / (np.abs(zt3) * (c - b))
721 return d
724class TukeyBiweight(RobustNorm):
725 """
727 Tukey's biweight function for M-estimation.
729 Parameters
730 ----------
731 c : float, optional
732 The tuning constant for Tukey's Biweight. The default value is
733 c = 4.685.
735 Notes
736 -----
737 Tukey's biweight is sometime's called bisquare.
738 """
740 def __init__(self, c=4.685):
741 self.c = c
743 def _subset(self, z):
744 """
745 Tukey's biweight is defined piecewise over the range of z
746 """
747 z = np.abs(np.asarray(z))
748 return np.less_equal(z, self.c)
750 def rho(self, z):
751 r"""
752 The robust criterion function for Tukey's biweight estimator
754 Parameters
755 ----------
756 z : array_like
757 1d array
759 Returns
760 -------
761 rho : ndarray
762 rho(z) = -(1 - (z/c)**2)**3 * c**2/6. for \|z\| <= R
764 rho(z) = 0 for \|z\| > R
765 """
766 subset = self._subset(z)
767 return -(1 - (z / self.c)**2)**3 * subset * self.c**2 / 6.
769 def psi(self, z):
770 r"""
771 The psi function for Tukey's biweight estimator
773 The analytic derivative of rho
775 Parameters
776 ----------
777 z : array_like
778 1d array
780 Returns
781 -------
782 psi : ndarray
783 psi(z) = z*(1 - (z/c)**2)**2 for \|z\| <= R
785 psi(z) = 0 for \|z\| > R
786 """
788 z = np.asarray(z)
789 subset = self._subset(z)
790 return z * (1 - (z / self.c)**2)**2 * subset
792 def weights(self, z):
793 r"""
794 Tukey's biweight weighting function for the IRLS algorithm
796 The psi function scaled by z
798 Parameters
799 ----------
800 z : array_like
801 1d array
803 Returns
804 -------
805 weights : ndarray
806 psi(z) = (1 - (z/c)**2)**2 for \|z\| <= R
808 psi(z) = 0 for \|z\| > R
809 """
811 subset = self._subset(z)
812 return (1 - (z / self.c)**2)**2 * subset
814 def psi_deriv(self, z):
815 """
816 The derivative of Tukey's biweight psi function
818 Notes
819 -----
820 Used to estimate the robust covariance matrix.
821 """
822 subset = self._subset(z)
823 return subset * ((1 - (z/self.c)**2)**2
824 - (4*z**2/self.c**2) * (1-(z/self.c)**2))
827def estimate_location(a, scale, norm=None, axis=0, initial=None,
828 maxiter=30, tol=1.0e-06):
829 """
830 M-estimator of location using self.norm and a current
831 estimator of scale.
833 This iteratively finds a solution to
835 norm.psi((a-mu)/scale).sum() == 0
837 Parameters
838 ----------
839 a : ndarray
840 Array over which the location parameter is to be estimated
841 scale : ndarray
842 Scale parameter to be used in M-estimator
843 norm : RobustNorm, optional
844 Robust norm used in the M-estimator. The default is HuberT().
845 axis : int, optional
846 Axis along which to estimate the location parameter. The default is 0.
847 initial : ndarray, optional
848 Initial condition for the location parameter. Default is None, which
849 uses the median of a.
850 niter : int, optional
851 Maximum number of iterations. The default is 30.
852 tol : float, optional
853 Toleration for convergence. The default is 1e-06.
855 Returns
856 -------
857 mu : ndarray
858 Estimate of location
859 """
860 if norm is None:
861 norm = HuberT()
863 if initial is None:
864 mu = np.median(a, axis)
865 else:
866 mu = initial
868 for iter in range(maxiter):
869 W = norm.weights((a-mu)/scale)
870 nmu = np.sum(W*a, axis) / np.sum(W, axis)
871 if np.alltrue(np.less(np.abs(mu - nmu), scale * tol)):
872 return nmu
873 else:
874 mu = nmu
875 raise ValueError("location estimator failed to converge in %d iterations"
876 % maxiter)