Coverage for pygeodesy/fstats.py: 99%

307 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-06 16:50 -0400

1 

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

3 

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 

9 

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 

22 

23# from math import sqrt # from .fmath 

24 

25__all__ = _ALL_LAZY.fstats 

26__version__ = '24.05.06' 

27 

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 

34 

35 

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) 

43 

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}) 

47 

48 

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 

53 

54 

55class _FstatsNamed(_Named): 

56 '''(INTERNAL) Base class. 

57 ''' 

58 _n = 0 

59 

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 

66 

67 def __float__(self): # PYCHOK no cover 

68 '''Not implemented.''' 

69 return _NotImplemented(self) 

70 

71 def __int__(self): # PYCHOK no cover 

72 '''Not implemented.''' 

73 return _NotImplemented(self) 

74 

75 def __len__(self): 

76 '''Return the I{total} number of accumulated values (C{int}). 

77 ''' 

78 return self._n 

79 

80 def __neg__(self): # PYCHOK no cover 

81 '''Not implemented.''' 

82 return _NotImplemented(self) 

83 

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

85 '''Not implemented.''' 

86 return _NotImplemented(self, other) 

87 

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)) 

92 

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 

99 

100 copy = fcopy 

101 

102 

103class _FstatsBase(_FstatsNamed): 

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

105 ''' 

106 _Ms = () 

107 

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 

115 

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

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

118 self._notOverloaded(xs, sample=sample) 

119 

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

121 '''Accumulate and return the current count. 

122 

123 @see: Method C{fadd}. 

124 ''' 

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

126 

127 def fmean(self, xs=None): 

128 '''Accumulate and return the current mean. 

129 

130 @kwarg xs: Iterable with additional values (C{Scalar}s). 

131 

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

133 

134 @see: Method C{fadd}. 

135 ''' 

136 if xs: 

137 self.fadd(xs) 

138 return self._M1.fsum() 

139 

140 def fmean_(self, *xs): 

141 '''Accumulate and return the current mean. 

142 

143 @see: Method C{fmean}. 

144 ''' 

145 return self.fmean(xs) 

146 

147 def fstdev(self, xs=None, sample=False): 

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

149 

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}). 

153 

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

155 

156 @see: Method C{fadd}. 

157 ''' 

158 v = self.fvariance(xs, sample=sample) 

159 return sqrt(v) if v > 0 else _0_0 

160 

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

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

163 

164 @see: Method C{fstdev}. 

165 ''' 

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

167 

168 def fvariance(self, xs=None, sample=False): 

169 '''Accumulate and return the current variance. 

170 

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}). 

174 

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

176 

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 

181 

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

183 '''Accumulate and return the current variance. 

184 

185 @see: Method C{fvariance}. 

186 ''' 

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

188 

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))) 

201 

202 @property_RO 

203 def _M1(self): 

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

205 return self._Ms[0] 

206 

207 @property_RO 

208 def _M2(self): 

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

210 return self._Ms[1] 

211 

212 

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. 

218 

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. 

224 

225 @kwarg xs: Iterable with initial values (C{Scalar}s). 

226 @kwarg name: Optional name (C{str}). 

227 

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) 

235 

236 def __iadd__(self, other): 

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

238 

239 @arg other: An L{Fcook} instance or C{Scalar}s, meaning 

240 one or more C{scalar} or L{Fsum} instances. 

241 

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

243 

244 @raise TypeError: Invalid B{C{other}} type. 

245 

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

247 

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 

257 

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) 

265 

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 

272 

273 A3 += B3 

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

275 A3 += Dn3 * (na - nb) 

276 

277 A2 += B2 

278 A2 += Dn2 * (nab / n_) 

279 

280 B1n = B1 * nb # if other is self 

281 A1 *= na 

282 A1 += B1n 

283 A1 *= 1 / n_ # /= chokes PyChecker 

284 

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 

292 

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

294 '''Accumulate and return the current count. 

295 

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}). 

300 

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

302 

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

304 

305 @raise TypeError: Non-scalar B{C{xs}} value. 

306 

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

308 

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) 

335 

336 M3 += T2 

337 M3 -= M2 * (Dn * _3_0) 

338 

339 M2 += T1 

340 M1 += Dn 

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

342 self._n = n 

343 return _sampled(n, sample) 

344 

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. 

348 

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. 

352 

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

354 

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 

361 

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. 

365 

366 @see: Method L{Fcook.fjb}. 

367 ''' 

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

369 

370 def fkurtosis(self, xs=None, sample=False, excess=True): 

371 '''Accumulate and return the current kurtosis. 

372 

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. 

377 

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

379 

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

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

382 

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 

399 

400 def fkurtosis_(self, *xs, **sample_excess): 

401 '''Accumulate and return the current kurtosis. 

402 

403 @see: Method L{Fcook.fkurtosis}. 

404 ''' 

405 return self.fkurtosis(xs, **sample_excess) 

406 

407 def fmedian(self, xs=None): 

408 '''Accumulate and return the current median. 

409 

410 @kwarg xs: Iterable with additional values (C{Scalar}s). 

411 

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

413 

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 

423 

424 def fmedian_(self, *xs): 

425 '''Accumulate and return the current median. 

426 

427 @see: Method L{Fcook.fmedian}. 

428 ''' 

429 return self.fmedian(xs) 

430 

431 def fskewness(self, xs=None, sample=False): 

432 '''Accumulate and return the current skewness. 

433 

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}). 

437 

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

439 

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

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

442 

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 

455 

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

457 '''Accumulate and return the current skewness. 

458 

459 @see: Method L{Fcook.fskewness}. 

460 ''' 

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

462 

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 

470 

471 

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. 

475 

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. 

480 

481 @kwarg xs: Iterable with initial values (C{Scalar}s). 

482 @kwarg name: Optional name (C{str}). 

483 

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) 

491 

492 def __iadd__(self, other): 

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

494 

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. 

497 

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

499 

500 @raise TypeError: Invalid B{C{other}} type. 

501 

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

503 

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 

514 

515 n = na + nb 

516 n_ = _Float(n) 

517 

518 D = M_ - M 

519 D *= D # D**2 

520 D *= na * nb / n_ 

521 S += D 

522 S += S_ 

523 

524 Mn = M_ * nb # if other is self 

525 M *= na 

526 M += Mn 

527 M *= 1 / n_ # /= chokes PyChecker 

528 

529# self._Ms = M, S 

530 self._n = n 

531 else: 

532 self._copy(self, other) 

533 

534 elif _isAn(other, Fcook): 

535 self += other.toFwelford() 

536 else: 

537 self._iadd_other(other) 

538 return self 

539 

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

541 '''Accumulate and return the current count. 

542 

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}). 

547 

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

549 

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

551 

552 @raise TypeError: Non-scalar B{C{xs}} value. 

553 

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) 

568 

569 

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. 

577 

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}). 

583 

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 

591 

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) 

597 

598 def __iadd__(self, other): 

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

600 

601 @arg other: An L{Flinear} instance or C{Scalar} pairs, 

602 meaning C{scalar} or L{Fsum} instances. 

603 

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

605 

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. 

609 

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

611 

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 

640 

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 

650 

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

652 '''Accumulate and return the current count. 

653 

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}). 

660 

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

662 

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

664 

665 @raise TypeError: Non-scalar B{C{xs}} or B{C{ys}} value. 

666 

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) 

683 

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

685 '''Accumulate and return the current count. 

686 

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

688 positional values (C{Scalar}s). 

689 

690 @see: Method C{Flinear.fadd}. 

691 ''' 

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

693 

694 def fcorrelation(self, sample=False): 

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

696 

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) 

702 

703 def fintercept(self, sample=False): 

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

705 

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))) 

711 

712 def fslope(self, sample=False): 

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

714 

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) 

719 

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 

725 

726 @property_RO 

727 def x(self): 

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

729 ''' 

730 return self._X 

731 

732 @property_RO 

733 def y(self): 

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

735 ''' 

736 return self._Y 

737 

738 

739__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

740 

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.