Coverage for pygeodesy/fstats.py: 99%
307 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-06 16:50 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-06 16:50 -0400
2# -*- coding: utf-8 -*-
4u'''Classes for I{running} statistics and regressions 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, _2_0, _3_0, _4_0, _6_0
13from pygeodesy.errors import _AssertionError, _xError
14from pygeodesy.fmath import hypot2, sqrt
15from pygeodesy.fsums import _Float, _2float, Fsum, _iadd_op_, \
16 _isAn, _Fsum_Fsum2Tuple_types, Fmt
17from pygeodesy.interns import NN, _invalid_, _other_, _SPACE_
18from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY
19from pygeodesy.named import _Named, _NotImplemented, property_RO
20# from pygeodesy.props import property_RO # from .named
21# from pygeodesy.streprs import Fmt # from .fsums
23# from math import sqrt # from .fmath
25__all__ = _ALL_LAZY.fstats
26__version__ = '24.05.06'
28_Floats = _Fsum_Fsum2Tuple_types + (_Float,)
29_Scalar = _Floats + (int,) # XXX basics._Ints is ABCMeta in Python 2
30try:
31 _Scalar += (long,)
32except NameError: # Python 3+
33 pass
36def _2Floats(**xs):
37 '''(INTERNAL) Yield each value as C{float} or L{Fsum}.
38 '''
39 try:
40 _s, xs = xs.popitem()
41 except Exception as x:
42 raise _AssertionError(xs=xs, cause=x)
44 for i, x in enumerate(xs): # don't unravel Fsums
45 yield x if _isAn(x, _Floats) else \
46 _2float(index=i, **{_s: x})
49def _sampled(n, sample):
50 '''(INTERNAL) Return the sample or the entire count.
51 '''
52 return (n - 1) if sample and n > 0 else n
55class _FstatsNamed(_Named):
56 '''(INTERNAL) Base class.
57 '''
58 _n = 0
60 def __add__(self, other):
61 '''Sum of this and a scalar, an L{Fsum} or an other instance.
62 '''
63 f = self.fcopy(name=self.__add__.__name__) # PYCHOK expected
64 f += other
65 return f
67 def __float__(self): # PYCHOK no cover
68 '''Not implemented.'''
69 return _NotImplemented(self)
71 def __int__(self): # PYCHOK no cover
72 '''Not implemented.'''
73 return _NotImplemented(self)
75 def __len__(self):
76 '''Return the I{total} number of accumulated values (C{int}).
77 '''
78 return self._n
80 def __neg__(self): # PYCHOK no cover
81 '''Not implemented.'''
82 return _NotImplemented(self)
84 def __radd__(self, other): # PYCHOK no cover
85 '''Not implemented.'''
86 return _NotImplemented(self, other)
88 def __str__(self):
89 n = self.name
90 n = _SPACE_(self.classname, n) if n else self.classname
91 return Fmt.SQUARE(n, len(self))
93 def fcopy(self, deep=False, name=NN):
94 '''Copy this instance, C{shallow} or B{C{deep}}.
95 '''
96 n = name or self.fcopy.__name__
97 f = _Named.copy(self, deep=deep, name=n)
98 return self._copy(f, self) # PYCHOK expected
100 copy = fcopy
103class _FstatsBase(_FstatsNamed):
104 '''(INTERNAL) Base running stats class.
105 '''
106 _Ms = ()
108 def _copy(self, c, s):
109 '''(INTERNAL) Copy C{B{c} = B{s}}.
110 '''
111 _xinstanceof(self.__class__, c=c, s=s)
112 c._Ms = tuple(M.fcopy() for M in s._Ms) # deep=False
113 c._n = s._n
114 return c
116 def fadd(self, xs, sample=False): # PYCHOK no cover
117 '''I{Must be overloaded}.'''
118 self._notOverloaded(xs, sample=sample)
120 def fadd_(self, *xs, **sample):
121 '''Accumulate and return the current count.
123 @see: Method C{fadd}.
124 '''
125 return self.fadd(xs, **sample)
127 def fmean(self, xs=None):
128 '''Accumulate and return the current mean.
130 @kwarg xs: Iterable with additional values (C{Scalar}s).
132 @return: Current, running mean (C{float}).
134 @see: Method C{fadd}.
135 '''
136 if xs:
137 self.fadd(xs)
138 return self._M1.fsum()
140 def fmean_(self, *xs):
141 '''Accumulate and return the current mean.
143 @see: Method C{fmean}.
144 '''
145 return self.fmean(xs)
147 def fstdev(self, xs=None, sample=False):
148 '''Accumulate and return the current standard deviation.
150 @kwarg xs: Iterable with additional values (C{Scalar}).
151 @kwarg sample: Return the I{sample} instead of the entire
152 I{population} standard deviation (C{bool}).
154 @return: Current, running (sample) standard deviation (C{float}).
156 @see: Method C{fadd}.
157 '''
158 v = self.fvariance(xs, sample=sample)
159 return sqrt(v) if v > 0 else _0_0
161 def fstdev_(self, *xs, **sample):
162 '''Accumulate and return the current standard deviation.
164 @see: Method C{fstdev}.
165 '''
166 return self.fstdev(xs, **sample)
168 def fvariance(self, xs=None, sample=False):
169 '''Accumulate and return the current variance.
171 @kwarg xs: Iterable with additional values (C{Scalar}s).
172 @kwarg sample: Return the I{sample} instead of the entire
173 I{population} variance (C{bool}).
175 @return: Current, running (sample) variance (C{float}).
177 @see: Method C{fadd}.
178 '''
179 n = self.fadd(xs, sample=sample)
180 return _Float(self._M2 / _Float(n)) if n > 0 else _0_0
182 def fvariance_(self, *xs, **sample):
183 '''Accumulate and return the current variance.
185 @see: Method C{fvariance}.
186 '''
187 return self.fvariance(xs, **sample)
189 def _iadd_other(self, other):
190 '''(INTERNAL) Add Scalar or Scalars.
191 '''
192 if _isAn(other, _Scalar):
193 self.fadd_(other)
194 else:
195 try:
196 if not islistuple(other):
197 raise TypeError(_SPACE_(_invalid_, _other_))
198 self.fadd(other)
199 except Exception as x:
200 raise _xError(x, _SPACE_(self, _iadd_op_, repr(other)))
202 @property_RO
203 def _M1(self):
204 '''(INTERNAL) get the 1st Moment accumulator.'''
205 return self._Ms[0]
207 @property_RO
208 def _M2(self):
209 '''(INTERNAL) get the 2nd Moment accumulator.'''
210 return self._Ms[1]
213class Fcook(_FstatsBase):
214 '''U{Cook<https://www.JohnDCook.com/blog/skewness_kurtosis>}'s
215 C{RunningStats} computing the running mean, median and
216 (sample) kurtosis, skewness, variance, standard deviation
217 and Jarque-Bera normality.
219 @see: L{Fwelford} and U{Higher-order statistics<https://
220 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
221 '''
222 def __init__(self, xs=None, name=NN):
223 '''New L{Fcook} stats accumulator.
225 @kwarg xs: Iterable with initial values (C{Scalar}s).
226 @kwarg name: Optional name (C{str}).
228 @see: Method L{Fcook.fadd}.
229 '''
230 self._Ms = tuple(Fsum() for _ in range(4)) # 1st, 2nd ... Moment
231 if name:
232 self.name = name
233 if xs:
234 self.fadd(xs)
236 def __iadd__(self, other):
237 '''Add B{C{other}} to this L{Fcook} instance.
239 @arg other: An L{Fcook} instance or C{Scalar}s, meaning
240 one or more C{scalar} or L{Fsum} instances.
242 @return: This instance, updated (L{Fcook}).
244 @raise TypeError: Invalid B{C{other}} type.
246 @raise ValueError: Invalid B{C{other}}.
248 @see: Method L{Fcook.fadd}.
249 '''
250 if _isAn(other, Fcook):
251 nb = len(other)
252 if nb > 0:
253 na = len(self)
254 if na > 0:
255 A1, A2, A3, A4 = self._Ms
256 B1, B2, B3, B4 = other._Ms
258 n = na + nb
259 n_ = _Float(n)
260 D = A1 - B1 # b1 - a1
261 Dn = D / n_
262 Dn2 = Dn**2 # d**2 / n**2
263 nab = na * nb
264 Dn3 = Dn2 * (D * nab)
266 na2 = na**2
267 nb2 = nb**2
268 A4 += B4
269 A4 += (B3 * na - (A3 * nb)) * (Dn * _4_0)
270 A4 += (B2 * na2 + (A2 * nb2)) * (Dn2 * _6_0)
271 A4 += (Dn * Dn3) * (na2 - nab + nb2) # d**4 / n**3
273 A3 += B3
274 A3 += (A2 * na - (B2 * nb)) * (Dn * _3_0)
275 A3 += Dn3 * (na - nb)
277 A2 += B2
278 A2 += Dn2 * (nab / n_)
280 B1n = B1 * nb # if other is self
281 A1 *= na
282 A1 += B1n
283 A1 *= 1 / n_ # /= chokes PyChecker
285# self._Ms = A1, A2, A3, A4
286 self._n = n
287 else:
288 self._copy(self, other)
289 else:
290 self._iadd_other(other)
291 return self
293 def fadd(self, xs, sample=False):
294 '''Accumulate and return the current count.
296 @arg xs: Iterable with additional values (C{Scalar}s,
297 meaning C{scalar} or L{Fsum} instances).
298 @kwarg sample: Return the I{sample} instead of the entire
299 I{population} count (C{bool}).
301 @return: Current, running (sample) count (C{int}).
303 @raise OverflowError: Partial C{2sum} overflow.
305 @raise TypeError: Non-scalar B{C{xs}} value.
307 @raise ValueError: Invalid or non-finite B{C{xs}} value.
309 @see: U{online_kurtosis<https://WikiPedia.org/wiki/
310 Algorithms_for_calculating_variance>}.
311 '''
312 n = self._n
313 if xs:
314 M1, M2, M3, M4 = self._Ms
315 for x in _2Floats(xs=xs):
316 n1 = n
317 n += 1
318 D = x - M1
319 Dn = D / n
320 if Dn:
321 Dn2 = Dn**2
322 if n1 > 1:
323 T1 = D * (Dn * n1)
324 T2 = T1 * (Dn * (n1 - 1))
325 T3 = T1 * (Dn2 * (n**2 - 3 * n1))
326 elif n1 > 0: # n1 == 1, n == 2
327 T1 = D * Dn
328 T2 = _0_0
329 T3 = T1 * Dn2
330 else:
331 T1 = T2 = T3 = _0_0
332 M4 += T3
333 M4 -= M3 * (Dn * _4_0)
334 M4 += M2 * (Dn2 * _6_0)
336 M3 += T2
337 M3 -= M2 * (Dn * _3_0)
339 M2 += T1
340 M1 += Dn
341# self._Ms = M1, M2, M3, M4
342 self._n = n
343 return _sampled(n, sample)
345 def fjb(self, xs=None, sample=True, excess=True):
346 '''Accumulate and compute the current U{Jarque-Bera
347 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
349 @kwarg xs: Iterable with additional values (C{Scalar}s).
350 @kwarg sample: Return the I{sample} normality (C{bool}), default.
351 @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
353 @return: Current, running (sample) Jarque-Bera normality (C{float}).
355 @see: Method L{Fcook.fadd}.
356 '''
357 n = self.fadd(xs, sample=sample)
358 k = self.fkurtosis(sample=sample, excess=excess) / _2_0
359 s = self.fskewness(sample=sample)
360 return n * hypot2(k, s) / _6_0
362 def fjb_(self, *xs, **sample_excess):
363 '''Accumulate and compute the current U{Jarque-Bera
364 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
366 @see: Method L{Fcook.fjb}.
367 '''
368 return self.fjb(xs, **sample_excess)
370 def fkurtosis(self, xs=None, sample=False, excess=True):
371 '''Accumulate and return the current kurtosis.
373 @kwarg xs: Iterable with additional values (C{Scalar}s).
374 @kwarg sample: Return the I{sample} instead of the entire
375 I{population} kurtosis (C{bool}).
376 @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
378 @return: Current, running (sample) kurtosis or I{excess} kurtosis (C{float}).
380 @see: U{Kurtosis Formula<https://www.Macroption.com/kurtosis-formula>}
381 and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.
383 @see: Method L{Fcook.fadd}.
384 '''
385 k, n = _0_0, self.fadd(xs, sample=sample)
386 if n > 0:
387 _, M2, _, M4 = self._Ms
388 m2 = _Float(M2 * M2)
389 if m2:
390 K, x = (M4 * (n / m2)), _3_0
391 if sample and 2 < n < len(self):
392 d = _Float((n - 1) * (n - 2))
393 K *= (n + 1) * (n + 2) / d
394 x *= n**2 / d
395 if excess:
396 K -= x
397 k = K.fsum()
398 return k
400 def fkurtosis_(self, *xs, **sample_excess):
401 '''Accumulate and return the current kurtosis.
403 @see: Method L{Fcook.fkurtosis}.
404 '''
405 return self.fkurtosis(xs, **sample_excess)
407 def fmedian(self, xs=None):
408 '''Accumulate and return the current median.
410 @kwarg xs: Iterable with additional values (C{Scalar}s).
412 @return: Current, running median (C{float}).
414 @see: U{Pearson's Skewness Coefficients<https://MathWorld.Wolfram.com/
415 PearsonsSkewnessCoefficients.html>}, U{Skewness & Kurtosis Simplified
416 https://TowardsDataScience.com/skewness-kurtosis-simplified-1338e094fc85>}
417 and method L{Fcook.fadd}.
418 '''
419 # skewness = 3 * (mean - median) / stdev, i.e.
420 # median = mean - skewness * stdef / 3
421 m = _Float(self._M1) if xs is None else self.fmean(xs)
422 return m - self.fskewness() * self.fstdev() / _3_0
424 def fmedian_(self, *xs):
425 '''Accumulate and return the current median.
427 @see: Method L{Fcook.fmedian}.
428 '''
429 return self.fmedian(xs)
431 def fskewness(self, xs=None, sample=False):
432 '''Accumulate and return the current skewness.
434 @kwarg xs: Iterable with additional values (C{Scalar}s).
435 @kwarg sample: Return the I{sample} instead of the entire
436 I{population} skewness (C{bool}).
438 @return: Current, running (sample) skewness (C{float}).
440 @see: U{Skewness Formula<https://www.Macroption.com/skewness-formula/>}
441 and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.
443 @see: Method L{Fcook.fadd}.
444 '''
445 s, n = _0_0, self.fadd(xs, sample=sample)
446 if n > 0:
447 _, M2, M3, _ = self._Ms
448 m = _Float(M2**3)
449 if m > 0:
450 S = M3 * sqrt(_Float(n) / m)
451 if sample and 1 < n < len(self):
452 S *= (n + 1) / _Float(n - 1)
453 s = S.fsum()
454 return s
456 def fskewness_(self, *xs, **sample):
457 '''Accumulate and return the current skewness.
459 @see: Method L{Fcook.fskewness}.
460 '''
461 return self.fskewness(xs, **sample)
463 def toFwelford(self, name=NN):
464 '''Return an L{Fwelford} equivalent.
465 '''
466 f = Fwelford(name=name or self.name)
467 f._Ms = self._M1.fcopy(), self._M2.fcopy() # deep=False
468 f._n = self._n
469 return f
472class Fwelford(_FstatsBase):
473 '''U{Welford<https://WikiPedia.org/wiki/Algorithms_for_calculating_variance>}'s
474 accumulator computing the running mean, (sample) variance and standard deviation.
476 @see: U{Cook<https://www.JohnDCook.com/blog/standard_deviation/>} and L{Fcook}.
477 '''
478 def __init__(self, xs=None, name=NN):
479 '''New L{Fwelford} stats accumulator.
481 @kwarg xs: Iterable with initial values (C{Scalar}s).
482 @kwarg name: Optional name (C{str}).
484 @see: Method L{Fwelford.fadd}.
485 '''
486 self._Ms = Fsum(), Fsum() # 1st and 2nd Moment
487 if name:
488 self.name = name
489 if xs:
490 self.fadd(xs)
492 def __iadd__(self, other):
493 '''Add B{C{other}} to this L{Fwelford} instance.
495 @arg other: An L{Fwelford} or L{Fcook} instance or C{Scalar}s,
496 meaning one or more C{scalar} or L{Fsum} instances.
498 @return: This instance, updated (L{Fwelford}).
500 @raise TypeError: Invalid B{C{other}} type.
502 @raise ValueError: Invalid B{C{other}}.
504 @see: Method L{Fwelford.fadd} and U{Parallel algorithm<https//
505 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
506 '''
507 if _isAn(other, Fwelford):
508 nb = len(other)
509 if nb > 0:
510 na = len(self)
511 if na > 0:
512 M, S = self._Ms
513 M_, S_ = other._Ms
515 n = na + nb
516 n_ = _Float(n)
518 D = M_ - M
519 D *= D # D**2
520 D *= na * nb / n_
521 S += D
522 S += S_
524 Mn = M_ * nb # if other is self
525 M *= na
526 M += Mn
527 M *= 1 / n_ # /= chokes PyChecker
529# self._Ms = M, S
530 self._n = n
531 else:
532 self._copy(self, other)
534 elif _isAn(other, Fcook):
535 self += other.toFwelford()
536 else:
537 self._iadd_other(other)
538 return self
540 def fadd(self, xs, sample=False):
541 '''Accumulate and return the current count.
543 @arg xs: Iterable with additional values (C{Scalar}s,
544 meaning C{scalar} or L{Fsum} instances).
545 @kwarg sample: Return the I{sample} instead of the entire
546 I{population} count (C{bool}).
548 @return: Current, running (sample) count (C{int}).
550 @raise OverflowError: Partial C{2sum} overflow.
552 @raise TypeError: Non-scalar B{C{xs}} value.
554 @raise ValueError: Invalid or non-finite B{C{xs}} value.
555 '''
556 n = self._n
557 if xs:
558 M, S = self._Ms
559 for x in _2Floats(xs=xs):
560 n += 1
561 D = x - M
562 M += D / n
563 D *= x - M
564 S += D
565# self._Ms = M, S
566 self._n = n
567 return _sampled(n, sample)
570class Flinear(_FstatsNamed):
571 '''U{Cook<https://www.JohnDCook.com/blog/running_regression>}'s
572 C{RunningRegression} computing the running slope, intercept
573 and correlation of a linear regression.
574 '''
575 def __init__(self, xs=None, ys=None, Fstats=Fwelford, name=NN):
576 '''New L{Flinear} regression accumulator.
578 @kwarg xs: Iterable with initial C{x} values (C{Scalar}s).
579 @kwarg ys: Iterable with initial C{y} values (C{Scalar}s).
580 @kwarg Fstats: Stats class for C{x} and C{y} values (L{Fcook}
581 or L{Fwelford}).
582 @kwarg name: Optional name (C{str}).
584 @raise TypeError: Invalid B{C{Fs}}, not L{Fcook} or
585 L{Fwelford}.
586 @see: Method L{Flinear.fadd}.
587 '''
588 _xsubclassof(Fcook, Fwelford, Fstats=Fstats)
589 if name:
590 self.name = name
592 self._S = Fsum(name=name)
593 self._X = Fstats(name=name)
594 self._Y = Fstats(name=name)
595 if xs and ys:
596 self.fadd(xs, ys)
598 def __iadd__(self, other):
599 '''Add B{C{other}} to this instance.
601 @arg other: An L{Flinear} instance or C{Scalar} pairs,
602 meaning C{scalar} or L{Fsum} instances.
604 @return: This instance, updated (L{Flinear}).
606 @raise TypeError: Invalid B{C{other}} or the B{C{other}}
607 and these C{x} and C{y} accumulators
608 are not compatible.
610 @raise ValueError: Invalid or odd-length B{C{other}}.
612 @see: Method L{Flinear.fadd_}.
613 '''
614 if _isAn(other, Flinear):
615 if len(other) > 0:
616 if len(self) > 0:
617 n = other._n
618 S = other._S
619 X = other._X
620 Y = other._Y
621 D = (X._M1 - self._X._M1) * \
622 (Y._M1 - self._Y._M1) * \
623 (n * self._n / _Float(n + self._n))
624 self._n += n
625 self._S += S + D
626 self._X += X
627 self._Y += Y
628 else:
629 self._copy(self, other)
630 else:
631 try:
632 if not islistuple(other):
633 raise TypeError(_SPACE_(_invalid_, _other_))
634 elif isodd(len(other)):
635 raise ValueError(Fmt.PAREN(isodd=Fmt.PAREN(len=_other_)))
636 self.fadd_(*other)
637 except Exception as x:
638 raise _xError(x, _SPACE_(self, _iadd_op_, repr(other)))
639 return self
641 def _copy(self, c, s):
642 '''(INTERNAL) Copy C{B{c} = B{s}}.
643 '''
644 _xinstanceof(Flinear, c=c, s=s)
645 c._n = s._n
646 c._S = s._S.fcopy(deep=False)
647 c._X = s._X.fcopy(deep=False)
648 c._Y = s._Y.fcopy(deep=False)
649 return c
651 def fadd(self, xs, ys, sample=False):
652 '''Accumulate and return the current count.
654 @arg xs: Iterable with additional C{x} values (C{Scalar}s),
655 meaning C{scalar} or L{Fsum} instances).
656 @arg ys: Iterable with additional C{y} values (C{Scalar}s,
657 meaning C{scalar} or L{Fsum} instances).
658 @kwarg sample: Return the I{sample} instead of the entire
659 I{population} count (C{bool}).
661 @return: Current, running (sample) count (C{int}).
663 @raise OverflowError: Partial C{2sum} overflow.
665 @raise TypeError: Non-scalar B{C{xs}} or B{C{ys}} value.
667 @raise ValueError: Invalid or non-finite B{C{xs}} or B{C{ys}} value.
668 '''
669 n = self._n
670 if xs and ys:
671 S = self._S
672 X = self._X
673 Y = self._Y
674 for x, y in _zip(_2Floats(xs=xs), _2Floats(ys=ys)): # strict=True
675 n1 = n
676 n += 1
677 if n1 > 0:
678 S += (X._M1 - x) * (Y._M1 - y) * (n1 / _Float(n))
679 X += x
680 Y += y
681 self._n = n
682 return _sampled(n, sample)
684 def fadd_(self, *x_ys, **sample):
685 '''Accumulate and return the current count.
687 @arg x_ys: Individual, alternating C{x, y, x, y, ...}
688 positional values (C{Scalar}s).
690 @see: Method C{Flinear.fadd}.
691 '''
692 return self.fadd(x_ys[0::2], x_ys[1::2], **sample)
694 def fcorrelation(self, sample=False):
695 '''Return the current, running (sample) correlation (C{float}).
697 @kwarg sample: Return the I{sample} instead of the entire
698 I{population} correlation (C{bool}).
699 '''
700 return self._sampled(self.x.fstdev(sample=sample) *
701 self.y.fstdev(sample=sample), sample)
703 def fintercept(self, sample=False):
704 '''Return the current, running (sample) intercept (C{float}).
706 @kwarg sample: Return the I{sample} instead of the entire
707 I{population} intercept (C{bool}).
708 '''
709 return _Float(self.y._M1 -
710 (self.x._M1 * self.fslope(sample=sample)))
712 def fslope(self, sample=False):
713 '''Return the current, running (sample) slope (C{float}).
715 @kwarg sample: Return the I{sample} instead of the entire
716 I{population} slope (C{bool}).
717 '''
718 return self._sampled(self.x.fvariance(sample=sample), sample)
720 def _sampled(self, t, sample):
721 '''(INTERNAL) Compute the sampled or entire population result.
722 '''
723 t *= _Float(_sampled(self._n, sample))
724 return _Float(self._S / t) if t else _0_0
726 @property_RO
727 def x(self):
728 '''Get the C{x} accumulator (L{Fcook} or L{Fwelford}).
729 '''
730 return self._X
732 @property_RO
733 def y(self):
734 '''Get the C{y} accumulator (L{Fcook} or L{Fwelford}).
735 '''
736 return self._Y
739__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed)
741# **) MIT License
742#
743# Copyright (C) 2021-2024 -- mrJean1 at Gmail -- All Rights Reserved.
744#
745# Permission is hereby granted, free of charge, to any person obtaining a
746# copy of this software and associated documentation files (the "Software"),
747# to deal in the Software without restriction, including without limitation
748# the rights to use, copy, modify, merge, publish, distribute, sublicense,
749# and/or sell copies of the Software, and to permit persons to whom the
750# Software is furnished to do so, subject to the following conditions:
751#
752# The above copyright notice and this permission notice shall be included
753# in all copies or substantial portions of the Software.
754#
755# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
756# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
757# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
758# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
759# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
760# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
761# OTHER DEALINGS IN THE SOFTWARE.