Coverage for pygeodesy/fstats.py: 98%

1

2# -*- coding: utf-8 -*-

3

4 u'''Classes for I{running} statistics and regressions based on

5 L{pygeodesy.Fsum}, precision floating point summation and accurate

6 multiplication.

7'''

8# make sure int/int division yields float quotient, see .basics

9 from __future__ import division as _; del _ # noqa: E702 ;

10

11 from pygeodesy.basics import isscalar, isodd, _xinstanceof, \

12 _xiterable, _xsubclassof, _zip, typename

13 from pygeodesy.constants import _0_0, _1_0, _2_0, _3_0, _4_0, _6_0

14 from pygeodesy.errors import _ValueError, _xError, _xkwds_item2, \

15 _xsError

16 from pygeodesy.fmath import Fsqrt, Fmt

17 from pygeodesy.fsums import _2finite, Fsum, _iadd_op_, _isFsum_2Tuple

18# from pygeodesy.internals import typename # from .basics

19 from pygeodesy.interns import _odd_, _SPACE_

20 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY

21 from pygeodesy.named import _name__, _Named, _NotImplemented, \

22 property_RO

23# from pygeodesy.props import property_RO # from .named

24# from pygeodesy.streprs import Fmt # from .fmath

25

26 __all__ = _ALL_LAZY.fstats

27 __version__ = '25.05.12'

28

29

30 def _sampled(n, sample):

31 '''(INTERNAL) Return the sample or the entire count.

32 '''

33 return (n - 1) if sample and n > 0 else n

34

35

36 def _Xs(**name_xs):

37 '''(INTERNAL) Yield all C{xs} as C{float} or L{Fsum}.

38 '''

39 name, xs = _xkwds_item2(name_xs)

40 try:

41 i, x = 0, xs

42 for i, x in enumerate(xs): # don't unravel Fsums

43 yield x._Fsum if _isFsum_2Tuple(x) else x

44 except Exception as X:

45 raise _xsError(X, xs, i, x, name)

46

47

48 class _FstatsNamed(_Named):

49 '''(INTERNAL) Base class.

50 '''

51 _n = 0

52

53 def __add__(self, other):

54 '''Sum of this and an other instance, a C{scalar}, an L{Fsum}

55 or L{Fsum2Tuple}.

56 '''

57 f = self.copy(name__=self.__add__) # PYCHOK expected

58 f += other

59 return f

60

61 def __float__(self): # PYCHOK no cover

62 '''Not implemented.'''

63 return _NotImplemented(self)

64

65 def __int__(self): # PYCHOK no cover

66 '''Not implemented.'''

67 return _NotImplemented(self)

68

69 def __len__(self):

70 '''Return the I{total} number of accumulated C{Scalars} (C{int}).

71 '''

72 return self._n

73

74 def __neg__(self): # PYCHOK no cover

75 '''Not implemented.'''

76 return _NotImplemented(self)

77

78 def __radd__(self, other): # PYCHOK no cover

79 '''Not implemented.'''

80 return _NotImplemented(self, other)

81

82 def __str__(self):

83 n = self.name

84 n = _SPACE_(self.classname, n) if n else self.classname

85 return Fmt.SQUARE(n, len(self))

86

87 def copy(self, deep=False, **name):

88 '''Copy this instance, C{shallow} or B{C{deep}}.

89

90 @kwarg name: Optional, overriding C{B{name}="copy"} (C{str}).

91

92 @return: The copy instance.

93 '''

94 n = _name__(name, name__=self.copy)

95 f = _Named.copy(self, deep=deep, name=n)

96 return self._copy(f, self) # PYCHOK expected

97

98 fcopy = copy # for backward compatibility

99

100

101 class _FstatsBase(_FstatsNamed):

102 '''(INTERNAL) Base running stats class.

103 '''

104 _Ms = ()

105

106 def _copy(self, d, s):

107 '''(INTERNAL) Copy C{B{d} = B{s}}.

108 '''

109 _xinstanceof(self.__class__, d=d, s=s)

110 d._Ms = tuple(M.copy() for M in s._Ms) # deep=False

111 d._n = s._n

112 return d

113

114 def fadd(self, xs, sample=False): # PYCHOK no cover

115 '''I{Must be overloaded}.'''

116 self._notOverloaded(xs, sample=sample)

117

118 def fadd_(self, *xs, **sample):

119 '''Accumulate and return the current count.

120

121 @see: Method C{fadd} for further details.

122 '''

123 return self.fadd(xs, **sample)

124

125 def fmean(self, xs=None):

126 '''Accumulate and return the current mean.

127

128 @kwarg xs: Iterable of additional values (each C{scalar},

129 an L{Fsum} or L{Fsum2Tuple}).

130

131 @return: Current, running mean (C{float}).

132

133 @see: Method C{fadd}.

134 '''

135 return float(self._Mean(xs))

136

137 def fmean_(self, *xs):

138 '''Accumulate and return the current mean.

139

140 @see: Method C{fmean} for further details.

141 '''

142 return self.fmean(xs)

143

144 def fstdev(self, xs=None, **sample):

145 '''Accumulate and return the current standard deviation.

146

147 @arg xs: Iterable of additional values (each C{scalar}, an L{Fsum}

148 or L{Fsum2Tuple}).

149 @kwarg sample: Use C{B{sample}=True} for the I{sample} deviation

150 instead of the I{population} deviation (C{bool}).

151

152 @return: Current, running (sample) standard deviation (C{float}).

153

154 @see: Method C{fadd}.

155 '''

156 return float(self._Stdev(xs, **sample))

157

158 def fstdev_(self, *xs, **sample):

159 '''Accumulate and return the current standard deviation.

160

161 @see: Method C{fstdev} for further details.

162 '''

163 return self.fstdev(xs, **sample)

164

165 def fvariance(self, xs=None, **sample):

166 '''Accumulate and return the current variance.

167

168 @arg xs: Iterable of additional values (each C{scalar}, an L{Fsum}

169 or L{Fsum2Tuple}).

170 @kwarg sample: Use C{B{sample}=True} for the I{sample} variance

171 instead of the I{population} variance (C{bool}).

172

173 @return: Current, running (sample) variance (C{float}).

174

175 @see: Method C{fadd}.

176 '''

177 return float(self._Variance(xs, **sample))

178

179 def fvariance_(self, *xs, **sample):

180 '''Accumulate and return the current variance.

181

182 @see: Method C{fvariance} for further details.

183 '''

184 return self.fvariance(xs, **sample)

185

186 def _iadd_other(self, other):

187 '''(INTERNAL) Add one or several values.

188 '''

189 try:

190 if _isFsum_2Tuple(other):

191 self.fadd_(other._Fsum)

192 elif isscalar(other):

193 self.fadd_(_2finite(other))

194 elif _xiterable(other):

195 self.fadd(other)

196 except Exception as X:

197 t = _SPACE_(self, _iadd_op_, repr(other))

198 raise _xError(X, t)

199

200 @property_RO

201 def _M1(self):

202 '''(INTERNAL) get the 1st Moment accumulator.'''

203 return self._Ms[0]

204

205 @property_RO

206 def _M2(self):

207 '''(INTERNAL) get the 2nd Moment accumulator.'''

208 return self._Ms[1]

209

210 def _Mean(self, xs=None):

211 '''(INTERNAL) Return the current mean as L{Fsum}.

212 '''

213 if xs:

214 self.fadd(xs)

215 return self._M1 # .copy()

216

217 def _Stdev(self, xs=None, **sample):

218 '''(INTERNAL) Return the current (sample) standard deviation as L{Fsum}.

219 '''

220 V = self._Variance(xs, **sample)

221 return Fsqrt(V) if V > 0 else _0_0

222

223 def _Variance(self, xs=None, **sample):

224 '''(INTERNAL) Return the current (sample) variance as L{Fsum}.

225 '''

226 n = self.fadd(xs, **sample)

227 return (self._M2 / n) if n > 0 else _0_0

228

229

230 class Fcook(_FstatsBase):

231 '''U{Cook<https://www.JohnDCook.com/blog/skewness_kurtosis>}'s

232 C{RunningStats} computing the running mean, median and

233 (sample) kurtosis, skewness, variance, standard deviation

234 and Jarque-Bera normality.

235

236 @see: L{Fwelford} and U{Higher-order statistics<https://

237 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.

238 '''

239 def __init__(self, xs=None, **name):

240 '''New L{Fcook} stats accumulator.

241

242 @arg xs: Iterable of additional values (each C{scalar}, an

243 L{Fsum} or L{Fsum2Tuple}).

244 @kwarg name: Optional C{B{name}=NN} (C{str}).

245

246 @see: Method L{Fcook.fadd}.

247 '''

248 self._Ms = tuple(Fsum() for _ in range(4)) # 1st, 2nd ... Moment

249 if name:

250 self.name = name

251 if xs:

252 self.fadd(xs)

253

254 def __iadd__(self, other):

255 '''Add B{C{other}} to this L{Fcook} instance.

256

257 @arg other: An L{Fcook} instance or value or iterable

258 of values (each C{scalar}, an L{Fsum} or

259 L{Fsum2Tuple}).

260

261 @return: This instance, updated (L{Fcook}).

262

263 @raise TypeError: Invalid B{C{other}}.

264

265 @raise ValueError: Invalid or non-finite B{C{other}}.

266

267 @see: Method L{Fcook.fadd}.

268 '''

269 if isinstance(other, Fcook):

270 nb = len(other)

271 if nb > 0:

272 na = len(self)

273 if na > 0:

274 A1, A2, A3, A4 = self._Ms

275 B1, B2, B3, B4 = other._Ms

276

277 n = na + nb

278 _n = _1_0 / n

279 D = A1 - B1 # b1 - a1

280 Dn = D * _n

281 Dn2 = Dn**2 # d**2 / n**2

282 nab = na * nb

283 Dn3 = Dn2 * (D * nab)

284

285 na2 = na**2

286 nb2 = nb**2

287 A4 += B4

288 A4 += (B3 * na - (A3 * nb)) * (Dn * _4_0)

289 A4 += (B2 * na2 + (A2 * nb2)) * (Dn2 * _6_0)

290 A4 += (Dn * Dn3) * (na2 - nab + nb2) # d**4 / n**3

291

292 A3 += B3

293 A3 += (A2 * na - (B2 * nb)) * (Dn * _3_0)

294 A3 += Dn3 * (na - nb)

295

296 A2 += B2

297 A2 += Dn2 * (nab * _n)

298

299 B1n = B1 * nb # if other is self

300 A1 *= na

301 A1 += B1n

302 A1 *= _n

303

304# self._Ms = A1, A2, A3, A4

305 self._n = n

306 else:

307 self._copy(self, other)

308 else:

309 self._iadd_other(other)

310 return self

311

312 def fadd(self, xs, sample=False):

313 '''Accumulate and return the current count.

314

315 @arg xs: Iterable of additional values (each C{scalar}, an

316 L{Fsum} or L{Fsum2Tuple}).

317 @kwarg sample: Use C{B{sample}=True} for the I{sample} count

318 instead of the I{population} count (C{bool}).

319

320 @return: Current, running (sample) count (C{int}).

321

322 @raise OverflowError: Partial C{2sum} overflow.

323

324 @raise TypeError: Invalid B{C{xs}}.

325

326 @raise ValueError: Invalid or non-finite B{C{xs}}.

327

328 @see: U{online_kurtosis<https://WikiPedia.org/wiki/

329 Algorithms_for_calculating_variance>}.

330 '''

331 n = self._n

332 if xs:

333 M1, M2, M3, M4 = self._Ms

334 for x in _Xs(xs=xs): # PYCHOK yield

335 n1 = n

336 n += 1

337 D = x - M1

338 Dn = D / n

339 if Dn:

340 Dn2 = Dn**2

341 if n1 > 1:

342 T1 = D * (Dn * n1)

343 T2 = T1 * (Dn * (n1 - 1))

344 T3 = T1 * (Dn2 * (n**2 - 3 * n1))

345 elif n1 > 0: # n1 == 1, n == 2

346 T1 = D * Dn

347 T2 = _0_0

348 T3 = T1 * Dn2

349 else:

350 T1 = T2 = T3 = _0_0

351 M4 += T3

352 M4 -= M3 * (Dn * _4_0)

353 M4 += M2 * (Dn2 * _6_0)

354

355 M3 += T2

356 M3 -= M2 * (Dn * _3_0)

357

358 M2 += T1

359 M1 += Dn

360# self._Ms = M1, M2, M3, M4

361 self._n = n

362 return _sampled(n, sample)

363

364 def fjb(self, xs=None, excess=True, sample=True):

365 '''Accumulate and compute the current U{Jarque-Bera

366 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.

367

368 @kwarg xs: Iterable of additional values (each C{scalar}, an

369 L{Fsum} or L{Fsum2Tuple}).

370 @kwarg excess: Apply the I{excess} kurtosis (C{bool}), default.

371 @kwarg sample: Use C{B{sample}=False} for the I{population}

372 normality instead of the I{sample} one (C{bool}).

373

374 @return: Current, running (sample) Jarque-Bera normality (C{float}).

375

376 @see: Method L{Fcook.fadd}.

377 '''

378 return float(self._JarqueBera(xs, excess, sample=sample))

379

380 def fjb_(self, *xs, **sample_excess):

381 '''Accumulate and compute the current U{Jarque-Bera

382 <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.

383

384 @see: Method L{Fcook.fjb} for further details.

385 '''

386 return self.fjb(xs, **sample_excess)

387

388 def fkurtosis(self, xs=None, excess=True, **sample):

389 '''Accumulate and return the current kurtosis.

390

391 @arg xs: Iterable of additional values (each C{scalar}, an L{Fsum}

392 or L{Fsum2Tuple}).

393 @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.

394 @kwarg sample: Use C{B{sample}=True} for the I{sample} kurtosis

395 instead of the I{population} kurtosis (C{bool}).

396

397 @return: Current, running (sample) kurtosis or I{excess} kurtosis (C{float}).

398

399 @see: U{Kurtosis Formula<https://www.Macroption.com/kurtosis-formula>}

400 and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.

401

402 @see: Method L{Fcook.fadd}.

403 '''

404 n = self.fadd(xs, **sample)

405 return float(self._Kurtosis(n, excess, **sample))

406

407 def fkurtosis_(self, *xs, **excess_sample):

408 '''Accumulate and return the current kurtosis.

409

410 @see: Method L{Fcook.fkurtosis} for further details.

411 '''

412 return self.fkurtosis(xs, **excess_sample)

413

414 def fmedian(self, xs=None):

415 '''Accumulate and return the current median.

416

417 @arg xs: Iterable of additional values (each C{scalar}, an L{Fsum} or

418 L{Fsum2Tuple}).

419

420 @return: Current, running median (C{float}).

421

422 @see: U{Pearson's Skewness Coefficients<https://MathWorld.Wolfram.com/

423 PearsonsSkewnessCoefficients.html>}, U{Skewness & Kurtosis Simplified

424 https://TowardsDataScience.com/skewness-kurtosis-simplified-1338e094fc85>}

425 and method L{Fcook.fadd}.

426 '''

427 return float(self._Median(xs))

428

429 def fmedian_(self, *xs):

430 '''Accumulate and return the current median.

431

432 @see: Method L{Fcook.fmedian} for further details.

433 '''

434 return self.fmedian(xs)

435

436 def fskewness(self, xs=None, **sample):

437 '''Accumulate and return the current skewness.

438

439 @arg xs: Iterable of additional values (each C{scalar}, an L{Fsum}

440 or L{Fsum2Tuple}).

441 @kwarg sample: Use C{B{sample}=True} for the I{sample} skewness

442 instead of the I{population} skewness (C{bool}).

443

444 @return: Current, running (sample) skewness (C{float}).

445

446 @see: U{Skewness Formula<https://www.Macroption.com/skewness-formula/>}

447 and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.

448

449 @see: Method L{Fcook.fadd}.

450 '''

451 n = self.fadd(xs, **sample)

452 return float(self._Skewness(n, **sample))

453

454 def fskewness_(self, *xs, **sample):

455 '''Accumulate and return the current skewness.

456

457 @see: Method L{Fcook.fskewness} for further details.

458 '''

459 return self.fskewness(xs, **sample)

460

461 def _JarqueBera(self, xs, excess, **sample):

462 '''(INTERNAL) Return the (sample) Jarque-Bera normality as L{Fsum}.

463 '''

464 N, n = _0_0, self.fadd(xs, **sample)

465 if n > 0:

466 K = self._Kurtosis(n, excess, **sample) / _2_0

467 S = self._Skewness(n, **sample)

468 N = (K**2 + S**2) * (n / _6_0) # Fpowers(2, K, S) * ...

469 return N

470

471 def _Kurtosis(self, n, excess, sample=False):

472 '''(INTERNAL) Return the (sample) kurtosis as L{Fsum} or C{0.0}.

473 '''

474 K = _0_0

475 if n > 0:

476 _, M2, _, M4 = self._Ms

477 M = M2**2

478 if M > 0:

479 K, x = M.rdiv(M4 * n, raiser=False), _3_0

480 if sample and 2 < n < len(self):

481 d = (n - 1) * (n - 2)

482 K *= (n + 1) * (n + 2) / d

483 x *= n**2 / d

484 if excess:

485 K -= x

486 return K

487

488 def _Median(self, xs=None):

489 '''(INTERNAL) Return the median as L{Fsum}.

490 '''

491 # skewness = 3 * (mean - median) / stdev, i.e.

492 # median = mean - (skewness * stdef) / 3

493 return self._Mean(xs) - (self._Skewness(self._n) *

494 self._Stdev()) / _3_0

495

496 def _Skewness(self, n, sample=False):

497 '''(INTERNAL) Return the (sample) skewness as L{Fsum} or C{0.0}.

498 '''

499 S = _0_0

500 if n > 0:

501 _, M2, M3, _ = self._Ms

502 M = M2**3

503 if M > 0:

504 M = M.rdiv(n, raiser=False)

505 S = M3 * Fsqrt(M, raiser=False)

506 if sample and 1 < n < len(self):

507 S *= (n + 1) / (n - 1)

508 return S

509

510 def toFwelford(self, **name):

511 '''Return a L{Fwelford} equivalent.

512

513 @kwarg name: Optional C{B{name}=NN} (C{str}).

514 '''

515 f = Fwelford(name=self._name__(name))

516 f._Ms = self._M1.copy(), self._M2.copy() # deep=False

517 f._n = self._n

518 return f

519

520

521 class Fwelford(_FstatsBase):

522 '''U{Welford<https://WikiPedia.org/wiki/Algorithms_for_calculating_variance>}'s

523 accumulator computing the running mean, (sample) variance and standard deviation.

524

525 @see: U{Cook<https://www.JohnDCook.com/blog/standard_deviation/>} and L{Fcook}.

526 '''

527 def __init__(self, xs=None, **name):

528 '''New L{Fwelford} stats accumulator.

529

530 @arg xs: Iterable of initial values (each C{scalar}, an L{Fsum} or L{Fsum2Tuple}).

531 @kwarg name: Optional C{B{name}=NN} (C{str}).

532

533 @see: Method L{Fwelford.fadd}.

534 '''

535 self._Ms = Fsum(), Fsum() # 1st and 2nd Moment

536 if name:

537 self.name = name

538 if xs:

539 self.fadd(xs)

540

541 def __iadd__(self, other):

542 '''Add B{C{other}} to this L{Fwelford} instance.

543

544 @arg other: An L{Fwelford} or L{Fcook} instance or value or an

545 iterable of values (each C{scalar}, an L{Fsum} or

546 L{Fsum2Tuple}).

547

548 @return: This instance, updated (L{Fwelford}).

549

550 @raise TypeError: Invalid B{C{other}}.

551

552 @raise ValueError: Invalid B{C{other}}.

553

554 @see: Method L{Fwelford.fadd} and U{Parallel algorithm<https//

555 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.

556 '''

557 if isinstance(other, Fwelford):

558 nb = len(other)

559 if nb > 0:

560 na = len(self)

561 if na > 0:

562 M, S = self._Ms

563 M_, S_ = other._Ms

564

565 n = na + nb

566 _n = _1_0 / n

567

568 D = M_ - M

569 D *= D # D**2

570 D *= na * nb * _n

571 S += D

572 S += S_

573

574 Mn = M_ * nb # if other is self

575 M *= na

576 M += Mn

577 M *= _n

578

579# self._Ms = M, S

580 self._n = n

581 else:

582 self._copy(self, other)

583

584 elif isinstance(other, Fcook):

585 self += other.toFwelford()

586 else:

587 self._iadd_other(other)

588 return self

589

590 def fadd(self, xs, sample=False):

591 '''Accumulate and return the current count.

592

593 @arg xs: Iterable of additional values (each C{scalar}, an

594 L{Fsum} or L{Fsum2Tuple}).

595 @kwarg sample: Use C{B{sample}=True} for the I{sample} count

596 instead of the I{population} count (C{bool}).

597

598 @return: Current, running (sample) count (C{int}).

599

600 @raise OverflowError: Partial C{2sum} overflow.

601

602 @raise TypeError: Invalid B{C{xs}}.

603

604 @raise ValueError: Invalid or non-finite B{C{xs}}.

605 '''

606 n = self._n

607 if xs:

608 M, S = self._Ms

609 for x in _Xs(xs=xs): # PYCHOK yield

610 n += 1

611 D = x - M

612 M += D / n

613 D *= x - M

614 S += D

615# self._Ms = M, S

616 self._n = n

617 return _sampled(n, sample)

618

619

620 class Flinear(_FstatsNamed):

621 '''U{Cook<https://www.JohnDCook.com/blog/running_regression>}'s

622 C{RunningRegression} computing the running slope, intercept

623 and correlation of a linear regression.

624 '''

625 def __init__(self, xs=None, ys=None, Fstats=Fwelford, **name):

626 '''New L{Flinear} regression accumulator.

627

628 @kwarg xs: Iterable of initial C{x} values (each C{scalar},

629 an L{Fsum} or L{Fsum2Tuple}).

630 @kwarg ys: Iterable of initial C{y} values (each C{scalar},

631 an L{Fsum} or L{Fsum2Tuple}).

632 @kwarg Fstats: Class for C{xs} and C{ys} values (L{Fcook} or

633 L{Fwelford}).

634 @kwarg name: Optional C{B{name}=NN} (C{str}).

635

636 @raise TypeError: B{C{Fstats}} not L{Fcook} or L{Fwelford}.

637

638 @see: Method L{Flinear.fadd}.

639 '''

640 _xsubclassof(Fcook, Fwelford, Fstats=Fstats)

641 if name:

642 self.name = name

643

644 self._S = Fsum(name=name)

645 self._X = Fstats(name=name)

646 self._Y = Fstats(name=name)

647 if xs and ys:

648 self.fadd(xs, ys)

649

650 def __iadd__(self, other):

651 '''Add B{C{other}} to this instance.

652

653 @arg other: An L{Flinear} instance or an iterable of

654 C{x_ys} values, see method C{fadd_}.

655

656 @return: This instance, updated (L{Flinear}).

657

658 @raise TypeError: Invalid B{C{other}} or the B{C{other}}

659 and these C{x} and C{y} accumulators

660 are not compatible.

661

662 @raise ValueError: Invalid or odd-length B{C{other}}.

663

664 @see: Method L{Flinear.fadd_}.

665 '''

666 if isinstance(other, Flinear):

667 if len(other) > 0:

668 if len(self) > 0:

669 n = other._n

670 D = (other._X._M1 - self._X._M1) * \

671 (other._Y._M1 - self._Y._M1) * \

672 (n * self._n / (self._n + n))

673 self._S += other._S + D

674 self._X += other._X

675 self._Y += other._Y

676 self._n += n

677 else:

678 self._copy(self, other)

679 else:

680 try:

681 if _xiterable(other):

682 self.fadd_(*other)

683 except Exception as X:

684 op = _SPACE_(self, _iadd_op_, repr(other))

685 raise _xError(X, op)

686 return self

687

688 def _copy(self, d, s):

689 '''(INTERNAL) Copy C{B{d} = B{s}}.

690 '''

691 _xinstanceof(Flinear, d=d, s=s)

692 d._S = s._S.copy(deep=False)

693 d._X = s._X.copy(deep=False)

694 d._Y = s._Y.copy(deep=False)

695 d._n = s._n

696 return d

697

698 def _Correlation(self, **sample):

699 '''(INTERNAL) Return the current (sample) correlation as L{Fsum}.

700 '''

701 return self._Sampled(self._X._Stdev(**sample) *

702 self._Y._Stdev(**sample), **sample)

703

704 def fadd(self, xs, ys, sample=False):

705 '''Accumulate and return the current count.

706

707 @arg xs: Iterable of additional C{x} values (each C{scalar},

708 an L{Fsum} or L{Fsum2Tuple}).

709 @arg ys: Iterable of additional C{y} values (each C{scalar},

710 an L{Fsum} or L{Fsum2Tuple}).

711 @kwarg sample: Use C{B{sample}=True} for the I{sample} count

712 instead of the I{population} count (C{bool}).

713

714 @return: Current, running (sample) count (C{int}).

715

716 @raise OverflowError: Partial C{2sum} overflow.

717

718 @raise TypeError: Invalid B{C{xs}} or B{C{ys}}.

719

720 @raise ValueError: Invalid or non-finite B{C{xs}} or B{C{ys}}.

721 '''

722 n = self._n

723 if xs and ys:

724 S = self._S

725 X = self._X

726 Y = self._Y

727 for x, y in _zip(_Xs(xs=xs), _Xs(ys=ys)): # PYCHOK strict=True

728 n1 = n

729 n += 1

730 if n1 > 0:

731 S += (X._M1 - x) * (Y._M1 - y) * (n1 / n)

732 X += x

733 Y += y

734 self._n = n

735 return _sampled(n, sample)

736

737 def fadd_(self, *x_ys, **sample):

738 '''Accumulate and return the current count.

739

740 @arg x_ys: Individual, alternating C{x, y, x, y, ...} values

741 (each C{scalar}, an L{Fsum} or L{Fsum2Tuple}).

742

743 @see: Method C{Flinear.fadd} for further details.

744 '''

745 if isodd(len(x_ys)):

746 t = _SPACE_(_odd_, typename(len))

747 raise _ValueError(t, len(x_ys))

748 return self.fadd(x_ys[0::2], x_ys[1::2], **sample)

749

750 def fcorrelation(self, **sample):

751 '''Return the current, running (sample) correlation (C{float}).

752

753 @kwarg sample: Use C{B{sample}=True} for the I{sample} correlation

754 instead of the I{population} correlation (C{bool}).

755 '''

756 return float(self._Correlation(**sample))

757

758 def fintercept(self, **sample):

759 '''Return the current, running (sample) intercept (C{float}).

760

761 @kwarg sample: Use C{B{sample}=True} for the I{sample} intercept

762 instead of the I{population} intercept (C{bool}).

763 '''

764 return float(self._Intercept(**sample))

765

766 def fslope(self, **sample):

767 '''Return the current, running (sample) slope (C{float}).

768

769 @kwarg sample: Use C{B{sample}=True} for the I{sample} slope

770 instead of the I{population} slope (C{bool}).

771 '''

772 return float(self._Slope(**sample))

773

774 def _Intercept(self, **sample):

775 '''(INTERNAL) Return the current (sample) intercept as L{Fsum}.

776 '''

777 return self._Y._M1 - self._X._M1 * self._Slope(**sample)

778

779 def _Sampled(self, T, sample=False):

780 '''(INTERNAL) Compute the sampled or entire population result.

781 '''

782 T *= _sampled(self._n, sample)

783 return self._S.copy().fdiv(T, raiser=False) if T else T

784

785 def _Slope(self, **sample):

786 '''(INTERNAL) Return the current (sample) slope as L{Fsum}.

787 '''

788 return self._Sampled(self._X._Variance(**sample), **sample)

789

790 @property_RO

791 def x(self):

792 '''Get the C{x} accumulator (L{Fcook} or L{Fwelford}).

793 '''

794 return self._X # .copy()

795

796 @property_RO

797 def y(self):

798 '''Get the C{y} accumulator (L{Fcook} or L{Fwelford}).

799 '''

800 return self._Y # .copy()

801

802

803 __all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed)

804

805# **) MIT License

806#

807# Copyright (C) 2021-2026 -- mrJean1 at Gmail -- All Rights Reserved.

808#

809# Permission is hereby granted, free of charge, to any person obtaining a

810# copy of this software and associated documentation files (the "Software"),

811# to deal in the Software without restriction, including without limitation

812# the rights to use, copy, modify, merge, publish, distribute, sublicense,

813# and/or sell copies of the Software, and to permit persons to whom the

814# Software is furnished to do so, subject to the following conditions:

815#

816# The above copyright notice and this permission notice shall be included

817# in all copies or substantial portions of the Software.

818#

819# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

820# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

821# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

822# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

823# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

824# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

825# OTHER DEALINGS IN THE SOFTWARE.