Coverage for pygeodesy/fstats.py: 98%
327 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -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 isscalar, isodd, _xinstanceof, \
11 _xiterable, _xsubclassof, _zip
12from pygeodesy.constants import _0_0, _1_0, _2_0, _3_0, _4_0, _6_0
13from pygeodesy.errors import _AssertionError, _ValueError, _xError
14from pygeodesy.fmath import Fsqrt, Fmt
15from pygeodesy.fsums import _2finite, Fsum, _iadd_op_, _isFsumTuple
16from pygeodesy.interns import _odd_, _SPACE_
17from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY
18from pygeodesy.named import _name__, _Named, _NotImplemented, \
19 property_RO
20# from pygeodesy.props import property_RO # from .named
21# from pygeodesy.streprs import Fmt # from .fmath
23__all__ = _ALL_LAZY.fstats
24__version__ = '24.05.21'
27def _2Floats(**xs):
28 '''(INTERNAL) Yield each value as C{float} or L{Fsum}.
29 '''
30 try:
31 name, xs = xs.popitem()
32 except Exception as X:
33 raise _AssertionError(xs=xs, cause=X)
35 try:
36 i = None
37 for i, x in enumerate(_xiterable(xs)): # don't unravel Fsums
38 yield x._Fsum if _isFsumTuple(x) else _2finite(x)
39 except Exception as X:
40 raise _xError(X, name, xs) if i is None else \
41 _xError(X, Fmt.INDEX(name, i), 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 an other instance or a C{scalar} or an
57 L{Fsum}, L{Fsum2Tuple} or
58 .
59 '''
60 f = self.copy(name=self.__add__.__name__) # PYCHOK expected
61 f += other
62 return f
64 def __float__(self): # PYCHOK no cover
65 '''Not implemented.'''
66 return _NotImplemented(self)
68 def __int__(self): # PYCHOK no cover
69 '''Not implemented.'''
70 return _NotImplemented(self)
72 def __len__(self):
73 '''Return the I{total} number of accumulated C{Scalars} (C{int}).
74 '''
75 return self._n
77 def __neg__(self): # PYCHOK no cover
78 '''Not implemented.'''
79 return _NotImplemented(self)
81 def __radd__(self, other): # PYCHOK no cover
82 '''Not implemented.'''
83 return _NotImplemented(self, other)
85 def __str__(self):
86 n = self.name
87 n = _SPACE_(self.classname, n) if n else self.classname
88 return Fmt.SQUARE(n, len(self))
90 def copy(self, deep=False, **name):
91 '''Copy this instance, C{shallow} or B{C{deep}}.
93 @kwarg name: Optional, overriding C{B{name}="copy"} (C{str}).
95 @return: The copy instance.
96 '''
97 n = _name__(name, name__=self.copy)
98 f = _Named.copy(self, deep=deep, name=n)
99 return self._copy(f, self) # PYCHOK expected
101 fcopy = copy # for backward compatibility
104class _FstatsBase(_FstatsNamed):
105 '''(INTERNAL) Base running stats class.
106 '''
107 _Ms = ()
109 def _copy(self, d, s):
110 '''(INTERNAL) Copy C{B{c} = B{s}}.
111 '''
112 _xinstanceof(self.__class__, d=d, s=s)
113 d._Ms = tuple(M.copy() for M in s._Ms) # deep=False
114 d._n = s._n
115 return d
117 def fadd(self, xs, sample=False): # PYCHOK no cover
118 '''I{Must be overloaded}.'''
119 self._notOverloaded(xs, sample=sample)
121 def fadd_(self, *xs, **sample):
122 '''Accumulate and return the current count.
124 @see: Method C{fadd} for further details.
125 '''
126 return self.fadd(xs, **sample)
128 def fmean(self, xs=None):
129 '''Accumulate and return the current mean.
131 @kwarg xs: Iterable of additional values (each C{scalar} or
132 an L{Fsum} or L{Fsum2Tuple} instance).
134 @return: Current, running mean (C{float}).
136 @see: Method C{fadd}.
137 '''
138 return float(self._Mean(xs))
140 def fmean_(self, *xs):
141 '''Accumulate and return the current mean.
143 @see: Method C{fmean} for further details.
144 '''
145 return self.fmean(xs)
147 def fstdev(self, xs=None, **sample):
148 '''Accumulate and return the current standard deviation.
150 @arg xs: Iterable of additional values (each C{scalar} or an
151 L{Fsum} or L{Fsum2Tuple} instance).
152 @kwarg sample: Use C{B{sample}=True} for the I{sample} deviation
153 instead of the I{population} deviation (C{bool}).
155 @return: Current, running (sample) standard deviation (C{float}).
157 @see: Method C{fadd}.
158 '''
159 return float(self._Stdev(xs, **sample))
161 def fstdev_(self, *xs, **sample):
162 '''Accumulate and return the current standard deviation.
164 @see: Method C{fstdev} for further details.
165 '''
166 return self.fstdev(xs, **sample)
168 def fvariance(self, xs=None, **sample):
169 '''Accumulate and return the current variance.
171 @arg xs: Iterable of additional values (each C{scalar} or an
172 L{Fsum} or L{Fsum2Tuple} instance).
173 @kwarg sample: Use C{B{sample}=True} for the I{sample} variance
174 instead of the I{population} variance (C{bool}).
176 @return: Current, running (sample) variance (C{float}).
178 @see: Method C{fadd}.
179 '''
180 return float(self._Variance(xs, **sample))
182 def fvariance_(self, *xs, **sample):
183 '''Accumulate and return the current variance.
185 @see: Method C{fvariance} for further details.
186 '''
187 return self.fvariance(xs, **sample)
189 def _iadd_other(self, other):
190 '''(INTERNAL) Add one or several values.
191 '''
192 try:
193 if _isFsumTuple(other):
194 self.fadd_(other._Fsum)
195 elif isscalar(other):
196 self.fadd_(_2finite(other))
197 elif _xiterable(other):
198 self.fadd(other)
199 except Exception as X:
200 t = _SPACE_(self, _iadd_op_, repr(other))
201 raise _xError(X, t)
203 @property_RO
204 def _M1(self):
205 '''(INTERNAL) get the 1st Moment accumulator.'''
206 return self._Ms[0]
208 @property_RO
209 def _M2(self):
210 '''(INTERNAL) get the 2nd Moment accumulator.'''
211 return self._Ms[1]
213 def _Mean(self, xs=None):
214 '''(INTERNAL) Return the current mean as L{Fsum}.
215 '''
216 if xs:
217 self.fadd(xs)
218 return self._M1 # .copy()
220 def _Stdev(self, xs=None, **sample):
221 '''(INTERNAL) Return the current (sample) standard deviation as L{Fsum}.
222 '''
223 V = self._Variance(xs, **sample)
224 return Fsqrt(V) if V > 0 else _0_0
226 def _Variance(self, xs=None, **sample):
227 '''(INTERNAL) Return the current (sample) variance as L{Fsum}.
228 '''
229 n = self.fadd(xs, **sample)
230 return (self._M2 / n) if n > 0 else _0_0
233class Fcook(_FstatsBase):
234 '''U{Cook<https://www.JohnDCook.com/blog/skewness_kurtosis>}'s
235 C{RunningStats} computing the running mean, median and
236 (sample) kurtosis, skewness, variance, standard deviation
237 and Jarque-Bera normality.
239 @see: L{Fwelford} and U{Higher-order statistics<https://
240 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
241 '''
242 def __init__(self, xs=None, **name):
243 '''New L{Fcook} stats accumulator.
245 @arg xs: Iterable of additional values (each C{scalar} or
246 an L{Fsum} or L{Fsum2Tuple} instance).
247 @kwarg name: Optional C{B{name}=NN} (C{str}).
249 @see: Method L{Fcook.fadd}.
250 '''
251 self._Ms = tuple(Fsum() for _ in range(4)) # 1st, 2nd ... Moment
252 if name:
253 self.name = name
254 if xs:
255 self.fadd(xs)
257 def __iadd__(self, other):
258 '''Add B{C{other}} to this L{Fcook} instance.
260 @arg other: An L{Fcook} instance or value or iterable
261 of values (each C{scalar} or an L{Fsum}
262 or L{Fsum2Tuple} instance).
264 @return: This instance, updated (L{Fcook}).
266 @raise TypeError: Invalid B{C{other}}.
268 @raise ValueError: Invalid or non-finite B{C{other}}.
270 @see: Method L{Fcook.fadd}.
271 '''
272 if isinstance(other, Fcook):
273 nb = len(other)
274 if nb > 0:
275 na = len(self)
276 if na > 0:
277 A1, A2, A3, A4 = self._Ms
278 B1, B2, B3, B4 = other._Ms
280 n = na + nb
281 _n = _1_0 / n
282 D = A1 - B1 # b1 - a1
283 Dn = D * _n
284 Dn2 = Dn**2 # d**2 / n**2
285 nab = na * nb
286 Dn3 = Dn2 * (D * nab)
288 na2 = na**2
289 nb2 = nb**2
290 A4 += B4
291 A4 += (B3 * na - (A3 * nb)) * (Dn * _4_0)
292 A4 += (B2 * na2 + (A2 * nb2)) * (Dn2 * _6_0)
293 A4 += (Dn * Dn3) * (na2 - nab + nb2) # d**4 / n**3
295 A3 += B3
296 A3 += (A2 * na - (B2 * nb)) * (Dn * _3_0)
297 A3 += Dn3 * (na - nb)
299 A2 += B2
300 A2 += Dn2 * (nab * _n)
302 B1n = B1 * nb # if other is self
303 A1 *= na
304 A1 += B1n
305 A1 *= _n
307# self._Ms = A1, A2, A3, A4
308 self._n = n
309 else:
310 self._copy(self, other)
311 else:
312 self._iadd_other(other)
313 return self
315 def fadd(self, xs, sample=False):
316 '''Accumulate and return the current count.
318 @arg xs: Iterable of additional values (each C{scalar} or an
319 L{Fsum} or L{Fsum2Tuple} instance).
320 @kwarg sample: Use C{B{sample}=True} for the I{sample} count
321 instead of the I{population} count (C{bool}).
323 @return: Current, running (sample) count (C{int}).
325 @raise OverflowError: Partial C{2sum} overflow.
327 @raise TypeError: Invalid B{C{xs}}.
329 @raise ValueError: Invalid or non-finite B{C{xs}}.
331 @see: U{online_kurtosis<https://WikiPedia.org/wiki/
332 Algorithms_for_calculating_variance>}.
333 '''
334 n = self._n
335 if xs:
336 M1, M2, M3, M4 = self._Ms
337 for x in _2Floats(xs=xs): # PYCHOK yield
338 n1 = n
339 n += 1
340 D = x - M1
341 Dn = D / n
342 if Dn:
343 Dn2 = Dn**2
344 if n1 > 1:
345 T1 = D * (Dn * n1)
346 T2 = T1 * (Dn * (n1 - 1))
347 T3 = T1 * (Dn2 * (n**2 - 3 * n1))
348 elif n1 > 0: # n1 == 1, n == 2
349 T1 = D * Dn
350 T2 = _0_0
351 T3 = T1 * Dn2
352 else:
353 T1 = T2 = T3 = _0_0
354 M4 += T3
355 M4 -= M3 * (Dn * _4_0)
356 M4 += M2 * (Dn2 * _6_0)
358 M3 += T2
359 M3 -= M2 * (Dn * _3_0)
361 M2 += T1
362 M1 += Dn
363# self._Ms = M1, M2, M3, M4
364 self._n = n
365 return _sampled(n, sample)
367 def fjb(self, xs=None, excess=True, sample=True):
368 '''Accumulate and compute the current U{Jarque-Bera
369 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
371 @kwarg xs: Iterable of additional values (each C{scalar} or an
372 L{Fsum} or L{Fsum2Tuple}).
373 @kwarg excess: Apply the I{excess} kurtosis (C{bool}), default.
374 @kwarg sample: Use C{B{sample}=False} for the I{population}
375 normality instead of the I{sample} one (C{bool}).
377 @return: Current, running (sample) Jarque-Bera normality (C{float}).
379 @see: Method L{Fcook.fadd}.
380 '''
381 return float(self._JarqueBera(xs, excess, sample=sample))
383 def fjb_(self, *xs, **sample_excess):
384 '''Accumulate and compute the current U{Jarque-Bera
385 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
387 @see: Method L{Fcook.fjb} for further details.
388 '''
389 return self.fjb(xs, **sample_excess)
391 def fkurtosis(self, xs=None, excess=True, **sample):
392 '''Accumulate and return the current kurtosis.
394 @arg xs: Iterable of additional values (each C{scalar} or an
395 L{Fsum} or L{Fsum2Tuple} instance).
396 @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
397 @kwarg sample: Use C{B{sample}=True} for the I{sample} kurtosis
398 instead of the I{population} kurtosis (C{bool}).
400 @return: Current, running (sample) kurtosis or I{excess} kurtosis (C{float}).
402 @see: U{Kurtosis Formula<https://www.Macroption.com/kurtosis-formula>}
403 and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.
405 @see: Method L{Fcook.fadd}.
406 '''
407 n = self.fadd(xs, **sample)
408 return float(self._Kurtosis(n, excess, **sample))
410 def fkurtosis_(self, *xs, **excess_sample):
411 '''Accumulate and return the current kurtosis.
413 @see: Method L{Fcook.fkurtosis} for further details.
414 '''
415 return self.fkurtosis(xs, **excess_sample)
417 def fmedian(self, xs=None):
418 '''Accumulate and return the current median.
420 @arg xs: Iterable of additional values (each C{scalar} or an
421 L{Fsum} or L{Fsum2Tuple} instance).
423 @return: Current, running median (C{float}).
425 @see: U{Pearson's Skewness Coefficients<https://MathWorld.Wolfram.com/
426 PearsonsSkewnessCoefficients.html>}, U{Skewness & Kurtosis Simplified
427 https://TowardsDataScience.com/skewness-kurtosis-simplified-1338e094fc85>}
428 and method L{Fcook.fadd}.
429 '''
430 return float(self._Median(xs))
432 def fmedian_(self, *xs):
433 '''Accumulate and return the current median.
435 @see: Method L{Fcook.fmedian} for further details.
436 '''
437 return self.fmedian(xs)
439 def fskewness(self, xs=None, **sample):
440 '''Accumulate and return the current skewness.
442 @arg xs: Iterable of additional values (each C{scalar} or an
443 L{Fsum} or L{Fsum2Tuple} instance).
444 @kwarg sample: Use C{B{sample}=True} for the I{sample} skewness
445 instead of the I{population} skewness (C{bool}).
447 @return: Current, running (sample) skewness (C{float}).
449 @see: U{Skewness Formula<https://www.Macroption.com/skewness-formula/>}
450 and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.
452 @see: Method L{Fcook.fadd}.
453 '''
454 n = self.fadd(xs, **sample)
455 return float(self._Skewness(n, **sample))
457 def fskewness_(self, *xs, **sample):
458 '''Accumulate and return the current skewness.
460 @see: Method L{Fcook.fskewness} for further details.
461 '''
462 return self.fskewness(xs, **sample)
464 def _JarqueBera(self, xs, excess, **sample):
465 '''(INTERNAL) Return the (sample) Jarque-Bera normality as L{Fsum}.
466 '''
467 N, n = _0_0, self.fadd(xs, **sample)
468 if n > 0:
469 K = self._Kurtosis(n, excess, **sample) / _2_0
470 S = self._Skewness(n, **sample)
471 N = (K**2 + S**2) * (n / _6_0) # Fpowers(2, K, S) * ...
472 return N
474 def _Kurtosis(self, n, excess, sample=False):
475 '''(INTERNAL) Return the (sample) kurtosis as L{Fsum} or C{0.0}.
476 '''
477 K = _0_0
478 if n > 0:
479 _, M2, _, M4 = self._Ms
480 M = M2**2
481 if M > 0:
482 K, x = M.rdiv(M4 * n, raiser=False), _3_0
483 if sample and 2 < n < len(self):
484 d = (n - 1) * (n - 2)
485 K *= (n + 1) * (n + 2) / d
486 x *= n**2 / d
487 if excess:
488 K -= x
489 return K
491 def _Median(self, xs=None):
492 '''(INTERNAL) Return the median as L{Fsum}.
493 '''
494 # skewness = 3 * (mean - median) / stdev, i.e.
495 # median = mean - (skewness * stdef) / 3
496 return self._Mean(xs) - (self._Skewness(self._n) *
497 self._Stdev()) / _3_0
499 def _Skewness(self, n, sample=False):
500 '''(INTERNAL) Return the (sample) skewness as L{Fsum} or C{0.0}.
501 '''
502 S = _0_0
503 if n > 0:
504 _, M2, M3, _ = self._Ms
505 M = M2**3
506 if M > 0:
507 M = M.rdiv(n, raiser=False)
508 S = M3 * Fsqrt(M, raiser=False)
509 if sample and 1 < n < len(self):
510 S *= (n + 1) / (n - 1)
511 return S
513 def toFwelford(self, **name):
514 '''Return a L{Fwelford} equivalent.
516 @kwarg name: Optional C{B{name}=NN} (C{str}).
517 '''
518 f = Fwelford(name=self._name__(name))
519 f._Ms = self._M1.copy(), self._M2.copy() # deep=False
520 f._n = self._n
521 return f
524class Fwelford(_FstatsBase):
525 '''U{Welford<https://WikiPedia.org/wiki/Algorithms_for_calculating_variance>}'s
526 accumulator computing the running mean, (sample) variance and standard deviation.
528 @see: U{Cook<https://www.JohnDCook.com/blog/standard_deviation/>} and L{Fcook}.
529 '''
530 def __init__(self, xs=None, **name):
531 '''New L{Fwelford} stats accumulator.
533 @arg xs: Iterable of initial values (each C{scalar} or an
534 L{Fsum} or L{Fsum2Tuple} instance).
535 @kwarg name: Optional C{B{name}=NN} (C{str}).
537 @see: Method L{Fwelford.fadd}.
538 '''
539 self._Ms = Fsum(), Fsum() # 1st and 2nd Moment
540 if name:
541 self.name = name
542 if xs:
543 self.fadd(xs)
545 def __iadd__(self, other):
546 '''Add B{C{other}} to this L{Fwelford} instance.
548 @arg other: An L{Fwelford} or L{Fcook} instance or value
549 or an iterable of values (each C{scalar} or
550 an L{Fsum} or L{Fsum2Tuple} instance).
552 @return: This instance, updated (L{Fwelford}).
554 @raise TypeError: Invalid B{C{other}}.
556 @raise ValueError: Invalid B{C{other}}.
558 @see: Method L{Fwelford.fadd} and U{Parallel algorithm<https//
559 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
560 '''
561 if isinstance(other, Fwelford):
562 nb = len(other)
563 if nb > 0:
564 na = len(self)
565 if na > 0:
566 M, S = self._Ms
567 M_, S_ = other._Ms
569 n = na + nb
570 _n = _1_0 / n
572 D = M_ - M
573 D *= D # D**2
574 D *= na * nb * _n
575 S += D
576 S += S_
578 Mn = M_ * nb # if other is self
579 M *= na
580 M += Mn
581 M *= _n
583# self._Ms = M, S
584 self._n = n
585 else:
586 self._copy(self, other)
588 elif isinstance(other, Fcook):
589 self += other.toFwelford()
590 else:
591 self._iadd_other(other)
592 return self
594 def fadd(self, xs, sample=False):
595 '''Accumulate and return the current count.
597 @arg xs: Iterable of additional values (each C{scalar} or an
598 L{Fsum} or L{Fsum2Tuple} instance).
599 @kwarg sample: Use C{B{sample}=True} for the I{sample} count
600 instead of the I{population} count (C{bool}).
602 @return: Current, running (sample) count (C{int}).
604 @raise OverflowError: Partial C{2sum} overflow.
606 @raise TypeError: Invalid B{C{xs}}.
608 @raise ValueError: Invalid or non-finite B{C{xs}}.
609 '''
610 n = self._n
611 if xs:
612 M, S = self._Ms
613 for x in _2Floats(xs=xs): # PYCHOK yield
614 n += 1
615 D = x - M
616 M += D / n
617 D *= x - M
618 S += D
619# self._Ms = M, S
620 self._n = n
621 return _sampled(n, sample)
624class Flinear(_FstatsNamed):
625 '''U{Cook<https://www.JohnDCook.com/blog/running_regression>}'s
626 C{RunningRegression} computing the running slope, intercept
627 and correlation of a linear regression.
628 '''
629 def __init__(self, xs=None, ys=None, Fstats=Fwelford, **name):
630 '''New L{Flinear} regression accumulator.
632 @kwarg xs: Iterable of initial C{x} values (each C{scalar} or
633 an L{Fsum} or L{Fsum2Tuple} instance).
634 @kwarg ys: Iterable of initial C{y} values (each C{scalar} or
635 an L{Fsum} or L{Fsum2Tuple} instance).
636 @kwarg Fstats: Class for C{xs} and C{ys} values (L{Fcook} or
637 L{Fwelford}).
638 @kwarg name: Optional C{B{name}=NN} (C{str}).
640 @raise TypeError: B{C{Fstats}} not L{Fcook} or L{Fwelford}.
642 @see: Method L{Flinear.fadd}.
643 '''
644 _xsubclassof(Fcook, Fwelford, Fstats=Fstats)
645 if name:
646 self.name = name
648 self._S = Fsum(name=name)
649 self._X = Fstats(name=name)
650 self._Y = Fstats(name=name)
651 if xs and ys:
652 self.fadd(xs, ys)
654 def __iadd__(self, other):
655 '''Add B{C{other}} to this instance.
657 @arg other: An L{Flinear} instance or an iterable of
658 C{x_ys} values, see method C{fadd_}.
660 @return: This instance, updated (L{Flinear}).
662 @raise TypeError: Invalid B{C{other}} or the B{C{other}}
663 and these C{x} and C{y} accumulators
664 are not compatible.
666 @raise ValueError: Invalid or odd-length B{C{other}}.
668 @see: Method L{Flinear.fadd_}.
669 '''
670 if isinstance(other, Flinear):
671 if len(other) > 0:
672 if len(self) > 0:
673 n = other._n
674 D = (other._X._M1 - self._X._M1) * \
675 (other._Y._M1 - self._Y._M1) * \
676 (n * self._n / (self._n + n))
677 self._S += other._S + D
678 self._X += other._X
679 self._Y += other._Y
680 self._n += n
681 else:
682 self._copy(self, other)
683 else:
684 try:
685 if _xiterable(other):
686 self.fadd_(*other)
687 except Exception as X:
688 op = _SPACE_(self, _iadd_op_, repr(other))
689 raise _xError(X, op)
690 return self
692 def _copy(self, d, s):
693 '''(INTERNAL) Copy C{B{d} = B{s}}.
694 '''
695 _xinstanceof(Flinear, d=d, s=s)
696 d._S = s._S.copy(deep=False)
697 d._X = s._X.copy(deep=False)
698 d._Y = s._Y.copy(deep=False)
699 d._n = s._n
700 return d
702 def _Correlation(self, **sample):
703 '''(INTERNAL) Return the current (sample) correlation as L{Fsum}.
704 '''
705 return self._Sampled(self._X._Stdev(**sample) *
706 self._Y._Stdev(**sample), **sample)
708 def fadd(self, xs, ys, sample=False):
709 '''Accumulate and return the current count.
711 @arg xs: Iterable of additional C{x} values (each C{scalar}
712 or an L{Fsum} or L{Fsum2Tuple} instance).
713 @arg ys: Iterable of additional C{y} values (each C{scalar}
714 or an L{Fsum} or L{Fsum2Tuple} instance).
715 @kwarg sample: Use C{B{sample}=True} for the I{sample} count
716 instead of the I{population} count (C{bool}).
718 @return: Current, running (sample) count (C{int}).
720 @raise OverflowError: Partial C{2sum} overflow.
722 @raise TypeError: Invalid B{C{xs}} or B{C{ys}}.
724 @raise ValueError: Invalid or non-finite B{C{xs}} or B{C{ys}}.
725 '''
726 n = self._n
727 if xs and ys:
728 S = self._S
729 X = self._X
730 Y = self._Y
731 for x, y in _zip(_2Floats(xs=xs), _2Floats(ys=ys)): # PYCHOK strict=True
732 n1 = n
733 n += 1
734 if n1 > 0:
735 S += (X._M1 - x) * (Y._M1 - y) * (n1 / n)
736 X += x
737 Y += y
738 self._n = n
739 return _sampled(n, sample)
741 def fadd_(self, *x_ys, **sample):
742 '''Accumulate and return the current count.
744 @arg x_ys: Individual, alternating C{x, y, x, y, ...} values
745 (each C{scalar} or an L{Fsum} or L{Fsum2Tuple}
746 instance).
748 @see: Method C{Flinear.fadd} for further details.
749 '''
750 if isodd(len(x_ys)):
751 t = _SPACE_(_odd_, len.__name__)
752 raise _ValueError(t, len(x_ys))
753 return self.fadd(x_ys[0::2], x_ys[1::2], **sample)
755 def fcorrelation(self, **sample):
756 '''Return the current, running (sample) correlation (C{float}).
758 @kwarg sample: Use C{B{sample}=True} for the I{sample} correlation
759 instead of the I{population} correlation (C{bool}).
760 '''
761 return float(self._Correlation(**sample))
763 def fintercept(self, **sample):
764 '''Return the current, running (sample) intercept (C{float}).
766 @kwarg sample: Use C{B{sample}=True} for the I{sample} intercept
767 instead of the I{population} intercept (C{bool}).
768 '''
769 return float(self._Intercept(**sample))
771 def fslope(self, **sample):
772 '''Return the current, running (sample) slope (C{float}).
774 @kwarg sample: Use C{B{sample}=True} for the I{sample} slope
775 instead of the I{population} slope (C{bool}).
776 '''
777 return float(self._Slope(**sample))
779 def _Intercept(self, **sample):
780 '''(INTERNAL) Return the current (sample) intercept as L{Fsum}.
781 '''
782 return self._Y._M1 - self._X._M1 * self._Slope(**sample)
784 def _Sampled(self, T, sample=False):
785 '''(INTERNAL) Compute the sampled or entire population result.
786 '''
787 T *= _sampled(self._n, sample)
788 return self._S.copy().fdiv(T, raiser=False) if T else T
790 def _Slope(self, **sample):
791 '''(INTERNAL) Return the current (sample) slope as L{Fsum}.
792 '''
793 return self._Sampled(self._X._Variance(**sample), **sample)
795 @property_RO
796 def x(self):
797 '''Get the C{x} accumulator (L{Fcook} or L{Fwelford}).
798 '''
799 return self._X # .copy()
801 @property_RO
802 def y(self):
803 '''Get the C{y} accumulator (L{Fcook} or L{Fwelford}).
804 '''
805 return self._Y # .copy()
808__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed)
810# **) MIT License
811#
812# Copyright (C) 2021-2024 -- mrJean1 at Gmail -- All Rights Reserved.
813#
814# Permission is hereby granted, free of charge, to any person obtaining a
815# copy of this software and associated documentation files (the "Software"),
816# to deal in the Software without restriction, including without limitation
817# the rights to use, copy, modify, merge, publish, distribute, sublicense,
818# and/or sell copies of the Software, and to permit persons to whom the
819# Software is furnished to do so, subject to the following conditions:
820#
821# The above copyright notice and this permission notice shall be included
822# in all copies or substantial portions of the Software.
823#
824# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
825# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
826# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
827# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
828# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
829# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
830# OTHER DEALINGS IN THE SOFTWARE.