Coverage for pygeodesy/fstats.py: 99%
303 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-31 10:52 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-31 10:52 -0400
2# -*- coding: utf-8 -*-
4u'''Classes for running statistics and regreesions based on
5L{pygeodesy.Fsum}, precision floating point summation.
6'''
7# make sure int/int division yields float quotient, see .basics
8from __future__ import division as _; del _ # PYCHOK semicolon
10from pygeodesy.basics import isodd, islistuple, _xinstanceof, \
11 _xsubclassof, _zip
12from pygeodesy.constants import _0_0, _1_5, _2_0, _3_0, _4_0, _6_0
13from pygeodesy.errors import _xError
14from pygeodesy.fmath import hypot2, sqrt
15from pygeodesy.fsums import _2float, Fmt, Fsum
16from pygeodesy.interns import NN, _iadd_, _invalid_, _other_, _SPACE_
17from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY
18from pygeodesy.named import _Named, _NotImplemented, notOverloaded, \
19 property_RO
20# from pygeodesy.props import property_RO # from .named
21# from pygeodesy.streprs import Fmt # from .fsums
23# from math import sqrt # pow from .fmath
25__all__ = _ALL_LAZY.fstats
26__version__ = '23.03.29'
28_Float = Fsum, float
29_Scalar = _Float + (int,) # XXX basics._Ints is ABCMeta
30try:
31 _Scalar += (long,)
32except NameError: # Python 3+
33 pass
36def _2Floats(xs, ys=False):
37 '''(INTERNAL) Yield each value as C{float} or L{Fsum}.
38 '''
39 for i, x in enumerate(xs):
40 yield x if isinstance(x, _Float) else (_2float(index=i, ys=x)
41 if ys else _2float(index=i, xs=x))
44def _sampled(n, sample):
45 '''(INTERNAL) Return the sample or the entire count.
46 '''
47 return (n - 1) if sample and n > 0 else n
50class _FstatsNamed(_Named):
51 '''(INTERNAL) Base class.
52 '''
53 _n = 0
55 def __add__(self, other):
56 '''Sum of this and a scalar, an L{Fsum} or an other instance.
57 '''
58 f = self.fcopy(name=self.__add__.__name__) # PYCHOK expected
59 f += other
60 return f
62 def __float__(self): # PYCHOK no cover
63 '''Not implemented.'''
64 return _NotImplemented(self)
66 def __int__(self): # PYCHOK no cover
67 '''Not implemented.'''
68 return _NotImplemented(self)
70 def __len__(self):
71 '''Return the I{total} number of accumulated values (C{int}).
72 '''
73 return self._n
75 def __neg__(self): # PYCHOK no cover
76 '''Not implemented.'''
77 return _NotImplemented(self)
79 def __radd__(self, other): # PYCHOK no cover
80 '''Not implemented.'''
81 return _NotImplemented(self, other)
83 def __str__(self):
84 return Fmt.SQUARE(self.named3, len(self))
86 def fcopy(self, deep=False, name=NN):
87 '''Copy this instance, C{shallow} or B{C{deep}}.
88 '''
89 n = name or self.fcopy.__name__
90 f = _Named.copy(self, deep=deep, name=n)
91 return self._copy(f, self) # PYCHOK expected
93 copy = fcopy
96class _FstatsBase(_FstatsNamed):
97 '''(INTERNAL) Base running stats class.
98 '''
99 _Ms = ()
101 def _copy(self, c, s):
102 '''(INTERNAL) Copy C{B{c} = B{s}}.
103 '''
104 _xinstanceof(self.__class__, c=c, s=s)
105 c._Ms = tuple(M.fcopy() for M in s._Ms) # deep=False
106 c._n = s._n
107 return c
109 def fadd(self, xs, sample=False): # PYCHOK no cover
110 '''(INTERNAL) I{Must be overloaded}, see function C{notOverloaded}.
111 '''
112 notOverloaded(self, xs, sample=sample)
114 def fadd_(self, *xs, **sample):
115 '''Accumulate and return the current count.
117 @see: Method C{fadd}.
118 '''
119 return self.fadd(xs, **sample)
121 def fmean(self, xs=None):
122 '''Accumulate and return the current mean.
124 @kwarg xs: Iterable with additional values (C{Scalar}s).
126 @return: Current, running mean (C{float}).
128 @see: Method C{fadd}.
129 '''
130 if xs:
131 self.fadd(xs)
132 return self._M1.fsum()
134 def fmean_(self, *xs):
135 '''Accumulate and return the current mean.
137 @see: Method C{fmean}.
138 '''
139 return self.fmean(xs)
141 def fstdev(self, xs=None, sample=False):
142 '''Accumulate and return the current standard deviation.
144 @kwarg xs: Iterable with additional values (C{Scalar}).
145 @kwarg sample: Return the I{sample} instead of the entire
146 I{population} value (C{bool}).
148 @return: Current, running (sample) standard deviation (C{float}).
150 @see: Method C{fadd}.
151 '''
152 v = self.fvariance(xs, sample=sample)
153 return sqrt(v) if v > 0 else _0_0
155 def fstdev_(self, *xs, **sample):
156 '''Accumulate and return the current standard deviation.
158 @see: Method C{fstdev}.
159 '''
160 return self.fstdev(xs, **sample)
162 def fvariance(self, xs=None, sample=False):
163 '''Accumulate and return the current variance.
165 @kwarg xs: Iterable with additional values (C{Scalar}s).
166 @kwarg sample: Return the I{sample} instead of the entire
167 I{population} value (C{bool}).
169 @return: Current, running (sample) variance (C{float}).
171 @see: Method C{fadd}.
172 '''
173 n = self.fadd(xs, sample=sample)
174 return float(self._M2 / float(n)) if n > 0 else _0_0
176 def fvariance_(self, *xs, **sample):
177 '''Accumulate and return the current variance.
179 @see: Method C{fvariance}.
180 '''
181 return self.fvariance(xs, **sample)
183 def _iadd_other(self, other):
184 '''(INTERNAL) Add Scalar or Scalars.
185 '''
186 if isinstance(other, _Scalar):
187 self.fadd_(other)
188 else:
189 try:
190 if not islistuple(other):
191 raise TypeError(_SPACE_(_invalid_, _other_))
192 self.fadd(other)
193 except Exception as x:
194 raise _xError(x, _SPACE_(self, _iadd_, repr(other)))
196 @property_RO
197 def _M1(self):
198 '''(INTERNAL) get the 1st Moment accumulator.'''
199 return self._Ms[0]
201 @property_RO
202 def _M2(self):
203 '''(INTERNAL) get the 2nd Moment accumulator.'''
204 return self._Ms[1]
207class Fcook(_FstatsBase):
208 '''U{Cook<https://www.JohnDCook.com/blog/skewness_kurtosis>}'s
209 C{RunningStats} computing the running mean, median and
210 (sample) kurtosis, skewness, variance, standard deviation
211 and Jarque-Bera normality.
213 @see: L{Fwelford} and U{Higher-order statistics<https://
214 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
215 '''
216 def __init__(self, xs=None, name=NN):
217 '''New L{Fcook} stats accumulator.
219 @kwarg xs: Iterable with initial values (C{Scalar}s).
220 @kwarg name: Optional name (C{str}).
222 @see: Method L{Fcook.fadd}.
223 '''
224 self._Ms = tuple(Fsum() for _ in range(4)) # 1st, 2nd ... Moment
225 if name:
226 self.name = name
227 if xs:
228 self.fadd(xs)
230 def __iadd__(self, other):
231 '''Add B{C{other}} to this L{Fcook} instance.
233 @arg other: An L{Fcook} instance or C{Scalar}s, meaning
234 one or more C{scalar} or L{Fsum} instances.
236 @return: This instance, updated (L{Fcook}).
238 @raise TypeError: Invalid B{C{other}} type.
240 @raise ValueError: Invalid B{C{other}}.
242 @see: Method L{Fcook.fadd}.
243 '''
244 if isinstance(other, Fcook):
245 nb = len(other)
246 if nb > 0:
247 na = len(self)
248 if na > 0:
249 A1, A2, A3, A4 = self._Ms
250 B1, B2, B3, B4 = other._Ms
252 n = na + nb
253 n_ = float(n)
254 D = A1 - B1 # b1 - a1
255 Dn = D / n_
256 Dn2 = Dn**2 # d**2 / n**2
257 nab = na * nb
258 Dn3 = Dn2 * (D * nab)
260 na2 = na**2
261 nb2 = nb**2
262 A4 += B4
263 A4 += (B3 * na - (A3 * nb)) * (Dn * _4_0)
264 A4 += (B2 * na2 + (A2 * nb2)) * (Dn2 * _6_0)
265 A4 += (Dn * Dn3) * (na2 - nab + nb2) # d**4 / n**3
267 A3 += B3
268 A3 += (A2 * na - (B2 * nb)) * (Dn * _3_0)
269 A3 += Dn3 * (na - nb)
271 A2 += B2
272 A2 += Dn2 * (nab / n_)
274 B1n = B1 * nb # if other is self
275 A1 *= na
276 A1 += B1n
277 A1 *= 1 / n_ # /= chokes PyChecker
279# self._Ms = A1, A2, A3, A4
280 self._n = n
281 else:
282 self._copy(self, other)
283 else:
284 self._iadd_other(other)
285 return self
287 def fadd(self, xs, sample=False):
288 '''Accumulate and return the current count.
290 @arg xs: Iterable with additional values (C{Scalar}s,
291 meaning C{scalar} or L{Fsum} instances).
292 @kwarg sample: Return the I{sample} instead of the entire
293 I{population} value (C{bool}).
295 @return: Current, running (sample) count (C{int}).
297 @raise OverflowError: Partial C{2sum} overflow.
299 @raise TypeError: Non-scalar B{C{xs}} value.
301 @raise ValueError: Invalid or non-finite B{C{xs}} value.
303 @see: U{online_kurtosis<https://WikiPedia.org/wiki/
304 Algorithms_for_calculating_variance>}.
305 '''
306 n = self._n
307 if xs:
308 M1, M2, M3, M4 = self._Ms
309 for x in _2Floats(xs):
310 n1 = n
311 n += 1
312 D = x - M1
313 Dn = D / n
314 if Dn:
315 Dn2 = Dn**2
316 if n1 > 1:
317 T1 = D * (Dn * n1)
318 T2 = T1 * (Dn * (n1 - 1))
319 T3 = T1 * (Dn2 * (n**2 - 3 * n1))
320 elif n1 > 0: # n1 == 1, n == 2
321 T1 = D * Dn
322 T2 = _0_0
323 T3 = T1 * Dn2
324 else:
325 T1 = T2 = T3 = _0_0
326 M4 += T3
327 M4 -= M3 * (Dn * _4_0)
328 M4 += M2 * (Dn2 * _6_0)
330 M3 += T2
331 M3 -= M2 * (Dn * _3_0)
333 M2 += T1
334 M1 += Dn
335# self._Ms = M1, M2, M3, M4
336 self._n = n
337 return _sampled(n, sample)
339 def fjb(self, xs=None, sample=True, excess=True):
340 '''Accumulate and compute the current U{Jarque-Bera
341 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
343 @kwarg xs: Iterable with additional values (C{Scalar}s).
344 @kwarg sample: Return the I{sample} value (C{bool}), default.
345 @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
347 @return: Current, running (sample) Jarque-Bera normality (C{float}).
349 @see: Method L{Fcook.fadd}.
350 '''
351 n = self.fadd(xs, sample=sample)
352 k = self.fkurtosis(sample=sample, excess=excess) / _2_0
353 s = self.fskewness(sample=sample)
354 return n * hypot2(k, s) / _6_0
356 def fjb_(self, *xs, **sample_excess):
357 '''Accumulate and compute the current U{Jarque-Bera
358 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
360 @see: Method L{Fcook.fjb}.
361 '''
362 return self.fjb(xs, **sample_excess)
364 def fkurtosis(self, xs=None, sample=False, excess=True):
365 '''Accumulate and return the current kurtosis.
367 @kwarg xs: Iterable with additional values (C{Scalar}s).
368 @kwarg sample: Return the I{sample} instead of the entire
369 I{population} value (C{bool}).
370 @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
372 @return: Current, running (sample) kurtosis or I{excess} kurtosis (C{float}).
374 @see: U{Kurtosis Formula<https://www.Macroption.com/kurtosis-formula>}
375 and U{Mantalos<https://www.researchgate.net/publication/
376 227440210_Three_different_measures_of_sample_skewness_and_kurtosis_and_their_effects_on_the_JarqueBera_test_for_normality>}.
378 @see: Method L{Fcook.fadd}.
379 '''
380 k, n = _0_0, self.fadd(xs, sample=sample)
381 if n > 0:
382 _, M2, _, M4 = self._Ms
383 m2 = float(M2 * M2)
384 if m2:
385 K, x = (M4 * (n / m2)), _3_0
386 if sample and 2 < n < len(self):
387 d = float((n - 1) * (n - 2))
388 K *= (n + 1) * (n + 2) / d
389 x *= n**2 / d
390 if excess:
391 K -= x
392 k = K.fsum()
393 return k
395 def fkurtosis_(self, *xs, **sample_excess):
396 '''Accumulate and return the current kurtosis.
398 @see: Method L{Fcook.fkurtosis}.
399 '''
400 return self.fkurtosis(xs, **sample_excess)
402 def fmedian(self, xs=None):
403 '''Accumulate and return the current median.
405 @kwarg xs: Iterable with additional values (C{Scalar}s).
407 @return: Current, running median (C{float}).
409 @see: U{Pearson's Skewness Coefficients<https://MathWorld.Wolfram.com/
410 PearsonsSkewnessCoefficients.html>}, U{Skewness & Kurtosis Simplified
411 https://TowardsDataScience.com/skewness-kurtosis-simplified-1338e094fc85>}
412 and method L{Fcook.fadd}.
413 '''
414 # skewness = 3 * (mean - median) / stdev, i.e.
415 # median = mean - skewness * stdef / 3
416 m = float(self._M1) if xs is None else self.fmean(xs)
417 return m - self.fskewness() * self.fstdev() / _3_0
419 def fmedian_(self, *xs):
420 '''Accumulate and return the current median.
422 @see: Method L{Fcook.fmedian}.
423 '''
424 return self.fmedian(xs)
426 def fskewness(self, xs=None, sample=False):
427 '''Accumulate and return the current skewness.
429 @kwarg xs: Iterable with additional values (C{Scalar}s).
430 @kwarg sample: Return the I{sample} instead of the entire
431 I{population} value (C{bool}).
433 @return: Current, running (sample) skewness (C{float}).
435 @see: U{Skewness Formula<https://www.Macroption.com/skewness-formula/>}
436 and U{Mantalos<https://www.researchgate.net/publication/
437 227440210_Three_different_measures_of_sample_skewness_and_kurtosis_and_their_effects_on_the_JarqueBera_test_for_normality>}.
439 @see: Method L{Fcook.fadd}.
440 '''
441 s, n = _0_0, self.fadd(xs, sample=sample)
442 if n > 0:
443 _, M2, M3, _ = self._Ms
444 m2 = pow(float(M2), _1_5)
445 if m2:
446 S = M3 * (sqrt(float(n)) / m2)
447 if sample and 1 < n < len(self):
448 S *= (n + 1) / float(n - 1)
449 s = S.fsum()
450 return s
452 def fskewness_(self, *xs, **sample):
453 '''Accumulate and return the current skewness.
455 @see: Method L{Fcook.fskewness}.
456 '''
457 return self.fskewness(xs, **sample)
459 def toFwelford(self, name=NN):
460 '''Return an L{Fwelford} equivalent.
461 '''
462 f = Fwelford(name=name or self.name)
463 f._Ms = self._M1.fcopy(), self._M2.fcopy() # deep=False
464 f._n = self._n
465 return f
468class Fwelford(_FstatsBase):
469 '''U{Welford<https://WikiPedia.org/wiki/Algorithms_for_calculating_variance>}'s
470 accumulator computing the running mean, (sample) variance and standard deviation.
472 @see: U{Cook<https://www.JohnDCook.com/blog/standard_deviation/>} and L{Fcook}.
473 '''
474 def __init__(self, xs=None, name=NN):
475 '''New L{Fwelford} stats accumulator.
477 @kwarg xs: Iterable with initial values (C{Scalar}s).
478 @kwarg name: Optional name (C{str}).
480 @see: Method L{Fwelford.fadd}.
481 '''
482 self._Ms = Fsum(), Fsum() # 1st and 2nd Moment
483 if name:
484 self.name = name
485 if xs:
486 self.fadd(xs)
488 def __iadd__(self, other):
489 '''Add B{C{other}} to this L{Fwelford} instance.
491 @arg other: An L{Fwelford} or L{Fcook} instance or C{Scalar}s,
492 meaning one or more C{scalar} or L{Fsum} instances.
494 @return: This instance, updated (L{Fwelford}).
496 @raise TypeError: Invalid B{C{other}} type.
498 @raise ValueError: Invalid B{C{other}}.
500 @see: Method L{Fwelford.fadd} and U{Parallel algorithm<https//
501 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
502 '''
503 if isinstance(other, Fwelford):
504 nb = len(other)
505 if nb > 0:
506 na = len(self)
507 if na > 0:
508 M, S = self._Ms
509 M_, S_ = other._Ms
511 n = na + nb
512 n_ = float(n)
514 D = M_ - M
515 D *= D # D**2
516 D *= na * nb / n_
517 S += D
518 S += S_
520 Mn = M_ * nb # if other is self
521 M *= na
522 M += Mn
523 M *= 1 / n_ # /= chokes PyChecker
525# self._Ms = M, S
526 self._n = n
527 else:
528 self._copy(self, other)
530 elif isinstance(other, Fcook):
531 self += other.toFwelford()
532 else:
533 self._iadd_other(other)
534 return self
536 def fadd(self, xs, sample=False):
537 '''Accumulate and return the current count.
539 @arg xs: Iterable with additional values (C{Scalar}s,
540 meaning C{scalar} or L{Fsum} instances).
541 @kwarg sample: Return the I{sample} instead of the entire
542 I{population} value (C{bool}).
544 @return: Current, running (sample) count (C{int}).
546 @raise OverflowError: Partial C{2sum} overflow.
548 @raise TypeError: Non-scalar B{C{xs}} value.
550 @raise ValueError: Invalid or non-finite B{C{xs}} value.
551 '''
552 n = self._n
553 if xs:
554 M, S = self._Ms
555 for x in _2Floats(xs):
556 n += 1
557 D = x - M
558 M += D / n
559 D *= x - M
560 S += D
561# self._Ms = M, S
562 self._n = n
563 return _sampled(n, sample)
566class Flinear(_FstatsNamed):
567 '''U{Cook<https://www.JohnDCook.com/blog/running_regression>}'s
568 C{RunningRegression} computing the running slope, intercept
569 and correlation of a linear regression.
570 '''
571 def __init__(self, xs=None, ys=None, Fstats=Fwelford, name=NN):
572 '''New L{Flinear} regression accumulator.
574 @kwarg xs: Iterable with initial C{x} values (C{Scalar}s).
575 @kwarg ys: Iterable with initial C{y} values (C{Scalar}s).
576 @kwarg Fstats: Stats class for C{x} and C{y} values (L{Fcook}
577 or L{Fwelford}).
578 @kwarg name: Optional name (C{str}).
580 @raise TypeError: Invalid B{C{Fs}}, not L{Fcook} or
581 L{Fwelford}.
582 @see: Method L{Flinear.fadd}.
583 '''
584 _xsubclassof(Fcook, Fwelford, Fstats=Fstats)
585 if name:
586 self.name = name
588 self._S = Fsum(name=name)
589 self._X = Fstats(name=name)
590 self._Y = Fstats(name=name)
591 if xs and ys:
592 self.fadd(xs, ys)
594 def __iadd__(self, other):
595 '''Add B{C{other}} to this instance.
597 @arg other: An L{Flinear} instance or C{Scalar} pairs,
598 meaning C{scalar} or L{Fsum} instances.
600 @return: This instance, updated (L{Flinear}).
602 @raise TypeError: Invalid B{C{other}} or the B{C{other}}
603 and these C{x} and C{y} accumulators
604 are not compatible.
606 @raise ValueError: Invalid or odd-length B{C{other}}.
608 @see: Method L{Flinear.fadd_}.
609 '''
610 if isinstance(other, Flinear):
611 if len(other) > 0:
612 if len(self) > 0:
613 n = other._n
614 S = other._S
615 X = other._X
616 Y = other._Y
617 D = (X._M1 - self._X._M1) * \
618 (Y._M1 - self._Y._M1) * \
619 (n * self._n / float(n + self._n))
620 self._n += n
621 self._S += S + D
622 self._X += X
623 self._Y += Y
624 else:
625 self._copy(self, other)
626 else:
627 try:
628 if not islistuple(other):
629 raise TypeError(_SPACE_(_invalid_, _other_))
630 elif isodd(len(other)):
631 raise ValueError(Fmt.PAREN(isodd=Fmt.PAREN(len=_other_)))
632 self.fadd_(*other)
633 except Exception as x:
634 raise _xError(x, _SPACE_(self, _iadd_, repr(other)))
635 return self
637 def _copy(self, c, s):
638 '''(INTERNAL) Copy C{B{c} = B{s}}.
639 '''
640 _xinstanceof(Flinear, c=c, s=s)
641 c._n = s._n
642 c._S = s._S.fcopy(deep=False)
643 c._X = s._X.fcopy(deep=False)
644 c._Y = s._Y.fcopy(deep=False)
645 return c
647 def fadd(self, xs, ys, sample=False):
648 '''Accumulate and return the current count.
650 @arg xs: Iterable with additional C{x} values (C{Scalar}s),
651 meaning C{scalar} or L{Fsum} instances).
652 @arg ys: Iterable with additional C{y} values (C{Scalar}s,
653 meaning C{scalar} or L{Fsum} instances).
654 @kwarg sample: Return the I{sample} instead of the entire
655 I{population} value (C{bool}).
657 @return: Current, running (sample) count (C{int}).
659 @raise OverflowError: Partial C{2sum} overflow.
661 @raise TypeError: Non-scalar B{C{xs}} or B{C{ys}} value.
663 @raise ValueError: Invalid or non-finite B{C{xs}} or B{C{ys}} value.
664 '''
665 n = self._n
666 if xs and ys:
667 S = self._S
668 X = self._X
669 Y = self._Y
670 for x, y in _zip(_2Floats(xs), _2Floats(ys, ys=True)): # strict=True
671 n1 = n
672 n += 1
673 if n1 > 0:
674 S += (X._M1 - x) * (Y._M1 - y) * (n1 / float(n))
675 X += x
676 Y += y
677 self._n = n
678 return _sampled(n, sample)
680 def fadd_(self, *x_ys, **sample):
681 '''Accumulate and return the current count.
683 @arg x_ys: Individual, alternating C{x, y, x, y, ...}
684 positional values (C{Scalar}s).
686 @see: Method C{Flinear.fadd}.
687 '''
688 return self.fadd(x_ys[0::2], x_ys[1::2], **sample)
690 def fcorrelation(self, sample=False):
691 '''Return the current, running (sample) correlation (C{float}).
693 @kwarg sample: Return the I{sample} instead of the entire
694 I{population} value (C{bool}).
695 '''
696 return self._sampled(self.x.fstdev(sample=sample) *
697 self.y.fstdev(sample=sample), sample)
699 def fintercept(self, sample=False):
700 '''Return the current, running (sample) intercept (C{float}).
702 @kwarg sample: Return the I{sample} instead of the entire
703 I{population} value (C{bool}).
704 '''
705 return float(self.y._M1 -
706 (self.x._M1 * self.fslope(sample=sample)))
708 def fslope(self, sample=False):
709 '''Return the current, running (sample) slope (C{float}).
711 @kwarg sample: Return the I{sample} instead of the entire
712 I{population} value (C{bool}).
713 '''
714 return self._sampled(self.x.fvariance(sample=sample), sample)
716 def _sampled(self, t, sample):
717 '''(INTERNAL) Compute the sampled or entire population result.
718 '''
719 t *= float(_sampled(self._n, sample))
720 return float(self._S / t) if t else _0_0
722 @property_RO
723 def x(self):
724 '''Get the C{x} accumulator (L{Fcook} or L{Fwelford}).
725 '''
726 return self._X
728 @property_RO
729 def y(self):
730 '''Get the C{y} accumulator (L{Fcook} or L{Fwelford}).
731 '''
732 return self._Y
735__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed)
737# **) MIT License
738#
739# Copyright (C) 2021-2023 -- mrJean1 at Gmail -- All Rights Reserved.
740#
741# Permission is hereby granted, free of charge, to any person obtaining a
742# copy of this software and associated documentation files (the "Software"),
743# to deal in the Software without restriction, including without limitation
744# the rights to use, copy, modify, merge, publish, distribute, sublicense,
745# and/or sell copies of the Software, and to permit persons to whom the
746# Software is furnished to do so, subject to the following conditions:
747#
748# The above copyright notice and this permission notice shall be included
749# in all copies or substantial portions of the Software.
750#
751# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
752# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
753# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
754# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
755# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
756# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
757# OTHER DEALINGS IN THE SOFTWARE.