Coverage for pygeodesy/fstats.py: 99%

303 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-05-20 11:54 -0400

1 

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

3 

4u'''Classes for running statistics and regreesions based on 

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

6''' 

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

8from __future__ import division as _; del _ # PYCHOK semicolon 

9 

10from pygeodesy.basics import isodd, islistuple, _xinstanceof, \ 

11 _xsubclassof, _zip 

12from pygeodesy.constants import _0_0, _1_5, _2_0, _3_0, _4_0, _6_0 

13from pygeodesy.errors import _xError 

14from pygeodesy.fmath import hypot2, sqrt 

15from pygeodesy.fsums import _2float, Fmt, Fsum 

16from pygeodesy.interns import NN, _iadd_, _invalid_, _other_, _SPACE_ 

17from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY 

18from pygeodesy.named import _Named, _NotImplemented, notOverloaded, \ 

19 property_RO 

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

21# from pygeodesy.streprs import Fmt # from .fsums 

22 

23# from math import sqrt # pow from .fmath 

24 

25__all__ = _ALL_LAZY.fstats 

26__version__ = '23.03.29' 

27 

28_Float = Fsum, float 

29_Scalar = _Float + (int,) # XXX basics._Ints is ABCMeta 

30try: 

31 _Scalar += (long,) 

32except NameError: # Python 3+ 

33 pass 

34 

35 

36def _2Floats(xs, ys=False): 

37 '''(INTERNAL) Yield each value as C{float} or L{Fsum}. 

38 ''' 

39 for i, x in enumerate(xs): 

40 yield x if isinstance(x, _Float) else (_2float(index=i, ys=x) 

41 if ys else _2float(index=i, xs=x)) 

42 

43 

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 

48 

49 

50class _FstatsNamed(_Named): 

51 '''(INTERNAL) Base class. 

52 ''' 

53 _n = 0 

54 

55 def __add__(self, other): 

56 '''Sum of this and a scalar, an L{Fsum} or an other instance. 

57 ''' 

58 f = self.fcopy(name=self.__add__.__name__) # PYCHOK expected 

59 f += other 

60 return f 

61 

62 def __float__(self): # PYCHOK no cover 

63 '''Not implemented.''' 

64 return _NotImplemented(self) 

65 

66 def __int__(self): # PYCHOK no cover 

67 '''Not implemented.''' 

68 return _NotImplemented(self) 

69 

70 def __len__(self): 

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

72 ''' 

73 return self._n 

74 

75 def __neg__(self): # PYCHOK no cover 

76 '''Not implemented.''' 

77 return _NotImplemented(self) 

78 

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

80 '''Not implemented.''' 

81 return _NotImplemented(self, other) 

82 

83 def __str__(self): 

84 return Fmt.SQUARE(self.named3, len(self)) 

85 

86 def fcopy(self, deep=False, name=NN): 

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

88 ''' 

89 n = name or self.fcopy.__name__ 

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

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

92 

93 copy = fcopy 

94 

95 

96class _FstatsBase(_FstatsNamed): 

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

98 ''' 

99 _Ms = () 

100 

101 def _copy(self, c, s): 

102 '''(INTERNAL) Copy C{B{c} = B{s}}. 

103 ''' 

104 _xinstanceof(self.__class__, c=c, s=s) 

105 c._Ms = tuple(M.fcopy() for M in s._Ms) # deep=False 

106 c._n = s._n 

107 return c 

108 

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

110 '''(INTERNAL) I{Must be overloaded}, see function C{notOverloaded}. 

111 ''' 

112 notOverloaded(self, xs, sample=sample) 

113 

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

115 '''Accumulate and return the current count. 

116 

117 @see: Method C{fadd}. 

118 ''' 

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

120 

121 def fmean(self, xs=None): 

122 '''Accumulate and return the current mean. 

123 

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

125 

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

127 

128 @see: Method C{fadd}. 

129 ''' 

130 if xs: 

131 self.fadd(xs) 

132 return self._M1.fsum() 

133 

134 def fmean_(self, *xs): 

135 '''Accumulate and return the current mean. 

136 

137 @see: Method C{fmean}. 

138 ''' 

139 return self.fmean(xs) 

140 

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

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

143 

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

145 @kwarg sample: Return the I{sample} instead of the entire 

146 I{population} value (C{bool}). 

147 

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

149 

150 @see: Method C{fadd}. 

151 ''' 

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

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

154 

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

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

157 

158 @see: Method C{fstdev}. 

159 ''' 

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

161 

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

163 '''Accumulate and return the current variance. 

164 

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

166 @kwarg sample: Return the I{sample} instead of the entire 

167 I{population} value (C{bool}). 

168 

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

170 

171 @see: Method C{fadd}. 

172 ''' 

173 n = self.fadd(xs, sample=sample) 

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

175 

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

177 '''Accumulate and return the current variance. 

178 

179 @see: Method C{fvariance}. 

180 ''' 

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

182 

183 def _iadd_other(self, other): 

184 '''(INTERNAL) Add Scalar or Scalars. 

185 ''' 

186 if isinstance(other, _Scalar): 

187 self.fadd_(other) 

188 else: 

189 try: 

190 if not islistuple(other): 

191 raise TypeError(_SPACE_(_invalid_, _other_)) 

192 self.fadd(other) 

193 except Exception as x: 

194 raise _xError(x, _SPACE_(self, _iadd_, repr(other))) 

195 

196 @property_RO 

197 def _M1(self): 

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

199 return self._Ms[0] 

200 

201 @property_RO 

202 def _M2(self): 

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

204 return self._Ms[1] 

205 

206 

207class Fcook(_FstatsBase): 

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

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

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

211 and Jarque-Bera normality. 

212 

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

214 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

215 ''' 

216 def __init__(self, xs=None, name=NN): 

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

218 

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

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

221 

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

223 ''' 

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

225 if name: 

226 self.name = name 

227 if xs: 

228 self.fadd(xs) 

229 

230 def __iadd__(self, other): 

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

232 

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

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

235 

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

237 

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

239 

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

241 

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

243 ''' 

244 if isinstance(other, Fcook): 

245 nb = len(other) 

246 if nb > 0: 

247 na = len(self) 

248 if na > 0: 

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

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

251 

252 n = na + nb 

253 n_ = float(n) 

254 D = A1 - B1 # b1 - a1 

255 Dn = D / n_ 

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

257 nab = na * nb 

258 Dn3 = Dn2 * (D * nab) 

259 

260 na2 = na**2 

261 nb2 = nb**2 

262 A4 += B4 

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

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

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

266 

267 A3 += B3 

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

269 A3 += Dn3 * (na - nb) 

270 

271 A2 += B2 

272 A2 += Dn2 * (nab / n_) 

273 

274 B1n = B1 * nb # if other is self 

275 A1 *= na 

276 A1 += B1n 

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

278 

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

280 self._n = n 

281 else: 

282 self._copy(self, other) 

283 else: 

284 self._iadd_other(other) 

285 return self 

286 

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

288 '''Accumulate and return the current count. 

289 

290 @arg xs: Iterable with additional values (C{Scalar}s, 

291 meaning C{scalar} or L{Fsum} instances). 

292 @kwarg sample: Return the I{sample} instead of the entire 

293 I{population} value (C{bool}). 

294 

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

296 

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

298 

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

300 

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

302 

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

304 Algorithms_for_calculating_variance>}. 

305 ''' 

306 n = self._n 

307 if xs: 

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

309 for x in _2Floats(xs): 

310 n1 = n 

311 n += 1 

312 D = x - M1 

313 Dn = D / n 

314 if Dn: 

315 Dn2 = Dn**2 

316 if n1 > 1: 

317 T1 = D * (Dn * n1) 

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

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

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

321 T1 = D * Dn 

322 T2 = _0_0 

323 T3 = T1 * Dn2 

324 else: 

325 T1 = T2 = T3 = _0_0 

326 M4 += T3 

327 M4 -= M3 * (Dn * _4_0) 

328 M4 += M2 * (Dn2 * _6_0) 

329 

330 M3 += T2 

331 M3 -= M2 * (Dn * _3_0) 

332 

333 M2 += T1 

334 M1 += Dn 

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

336 self._n = n 

337 return _sampled(n, sample) 

338 

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

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

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

342 

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

344 @kwarg sample: Return the I{sample} value (C{bool}), default. 

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

346 

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

348 

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

350 ''' 

351 n = self.fadd(xs, sample=sample) 

352 k = self.fkurtosis(sample=sample, excess=excess) / _2_0 

353 s = self.fskewness(sample=sample) 

354 return n * hypot2(k, s) / _6_0 

355 

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

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

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

359 

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

361 ''' 

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

363 

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

365 '''Accumulate and return the current kurtosis. 

366 

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

368 @kwarg sample: Return the I{sample} instead of the entire 

369 I{population} value (C{bool}). 

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

371 

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

373 

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

375 and U{Mantalos<https://www.researchgate.net/publication/ 

376 227440210_Three_different_measures_of_sample_skewness_and_kurtosis_and_their_effects_on_the_JarqueBera_test_for_normality>}. 

377 

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

379 ''' 

380 k, n = _0_0, self.fadd(xs, sample=sample) 

381 if n > 0: 

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

383 m2 = float(M2 * M2) 

384 if m2: 

385 K, x = (M4 * (n / m2)), _3_0 

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

387 d = float((n - 1) * (n - 2)) 

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

389 x *= n**2 / d 

390 if excess: 

391 K -= x 

392 k = K.fsum() 

393 return k 

394 

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

396 '''Accumulate and return the current kurtosis. 

397 

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

399 ''' 

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

401 

402 def fmedian(self, xs=None): 

403 '''Accumulate and return the current median. 

404 

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

406 

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

408 

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

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

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

412 and method L{Fcook.fadd}. 

413 ''' 

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

415 # median = mean - skewness * stdef / 3 

416 m = float(self._M1) if xs is None else self.fmean(xs) 

417 return m - self.fskewness() * self.fstdev() / _3_0 

418 

419 def fmedian_(self, *xs): 

420 '''Accumulate and return the current median. 

421 

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

423 ''' 

424 return self.fmedian(xs) 

425 

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

427 '''Accumulate and return the current skewness. 

428 

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

430 @kwarg sample: Return the I{sample} instead of the entire 

431 I{population} value (C{bool}). 

432 

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

434 

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

436 and U{Mantalos<https://www.researchgate.net/publication/ 

437 227440210_Three_different_measures_of_sample_skewness_and_kurtosis_and_their_effects_on_the_JarqueBera_test_for_normality>}. 

438 

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

440 ''' 

441 s, n = _0_0, self.fadd(xs, sample=sample) 

442 if n > 0: 

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

444 m2 = pow(float(M2), _1_5) 

445 if m2: 

446 S = M3 * (sqrt(float(n)) / m2) 

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

448 S *= (n + 1) / float(n - 1) 

449 s = S.fsum() 

450 return s 

451 

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

453 '''Accumulate and return the current skewness. 

454 

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

456 ''' 

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

458 

459 def toFwelford(self, name=NN): 

460 '''Return an L{Fwelford} equivalent. 

461 ''' 

462 f = Fwelford(name=name or self.name) 

463 f._Ms = self._M1.fcopy(), self._M2.fcopy() # deep=False 

464 f._n = self._n 

465 return f 

466 

467 

468class Fwelford(_FstatsBase): 

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

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

471 

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

473 ''' 

474 def __init__(self, xs=None, name=NN): 

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

476 

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

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

479 

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

481 ''' 

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

483 if name: 

484 self.name = name 

485 if xs: 

486 self.fadd(xs) 

487 

488 def __iadd__(self, other): 

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

490 

491 @arg other: An L{Fwelford} or L{Fcook} instance or C{Scalar}s, 

492 meaning one or more C{scalar} or L{Fsum} instances. 

493 

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

495 

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

497 

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

499 

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

501 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

502 ''' 

503 if isinstance(other, Fwelford): 

504 nb = len(other) 

505 if nb > 0: 

506 na = len(self) 

507 if na > 0: 

508 M, S = self._Ms 

509 M_, S_ = other._Ms 

510 

511 n = na + nb 

512 n_ = float(n) 

513 

514 D = M_ - M 

515 D *= D # D**2 

516 D *= na * nb / n_ 

517 S += D 

518 S += S_ 

519 

520 Mn = M_ * nb # if other is self 

521 M *= na 

522 M += Mn 

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

524 

525# self._Ms = M, S 

526 self._n = n 

527 else: 

528 self._copy(self, other) 

529 

530 elif isinstance(other, Fcook): 

531 self += other.toFwelford() 

532 else: 

533 self._iadd_other(other) 

534 return self 

535 

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

537 '''Accumulate and return the current count. 

538 

539 @arg xs: Iterable with additional values (C{Scalar}s, 

540 meaning C{scalar} or L{Fsum} instances). 

541 @kwarg sample: Return the I{sample} instead of the entire 

542 I{population} value (C{bool}). 

543 

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

545 

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

547 

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

549 

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

551 ''' 

552 n = self._n 

553 if xs: 

554 M, S = self._Ms 

555 for x in _2Floats(xs): 

556 n += 1 

557 D = x - M 

558 M += D / n 

559 D *= x - M 

560 S += D 

561# self._Ms = M, S 

562 self._n = n 

563 return _sampled(n, sample) 

564 

565 

566class Flinear(_FstatsNamed): 

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

568 C{RunningRegression} computing the running slope, intercept 

569 and correlation of a linear regression. 

570 ''' 

571 def __init__(self, xs=None, ys=None, Fstats=Fwelford, name=NN): 

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

573 

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

575 @kwarg ys: Iterable with initial C{y} values (C{Scalar}s). 

576 @kwarg Fstats: Stats class for C{x} and C{y} values (L{Fcook} 

577 or L{Fwelford}). 

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

579 

580 @raise TypeError: Invalid B{C{Fs}}, not L{Fcook} or 

581 L{Fwelford}. 

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

583 ''' 

584 _xsubclassof(Fcook, Fwelford, Fstats=Fstats) 

585 if name: 

586 self.name = name 

587 

588 self._S = Fsum(name=name) 

589 self._X = Fstats(name=name) 

590 self._Y = Fstats(name=name) 

591 if xs and ys: 

592 self.fadd(xs, ys) 

593 

594 def __iadd__(self, other): 

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

596 

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

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

599 

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

601 

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

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

604 are not compatible. 

605 

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

607 

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

609 ''' 

610 if isinstance(other, Flinear): 

611 if len(other) > 0: 

612 if len(self) > 0: 

613 n = other._n 

614 S = other._S 

615 X = other._X 

616 Y = other._Y 

617 D = (X._M1 - self._X._M1) * \ 

618 (Y._M1 - self._Y._M1) * \ 

619 (n * self._n / float(n + self._n)) 

620 self._n += n 

621 self._S += S + D 

622 self._X += X 

623 self._Y += Y 

624 else: 

625 self._copy(self, other) 

626 else: 

627 try: 

628 if not islistuple(other): 

629 raise TypeError(_SPACE_(_invalid_, _other_)) 

630 elif isodd(len(other)): 

631 raise ValueError(Fmt.PAREN(isodd=Fmt.PAREN(len=_other_))) 

632 self.fadd_(*other) 

633 except Exception as x: 

634 raise _xError(x, _SPACE_(self, _iadd_, repr(other))) 

635 return self 

636 

637 def _copy(self, c, s): 

638 '''(INTERNAL) Copy C{B{c} = B{s}}. 

639 ''' 

640 _xinstanceof(Flinear, c=c, s=s) 

641 c._n = s._n 

642 c._S = s._S.fcopy(deep=False) 

643 c._X = s._X.fcopy(deep=False) 

644 c._Y = s._Y.fcopy(deep=False) 

645 return c 

646 

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

648 '''Accumulate and return the current count. 

649 

650 @arg xs: Iterable with additional C{x} values (C{Scalar}s), 

651 meaning C{scalar} or L{Fsum} instances). 

652 @arg ys: Iterable with additional C{y} values (C{Scalar}s, 

653 meaning C{scalar} or L{Fsum} instances). 

654 @kwarg sample: Return the I{sample} instead of the entire 

655 I{population} value (C{bool}). 

656 

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

658 

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

660 

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

662 

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

664 ''' 

665 n = self._n 

666 if xs and ys: 

667 S = self._S 

668 X = self._X 

669 Y = self._Y 

670 for x, y in _zip(_2Floats(xs), _2Floats(ys, ys=True)): # strict=True 

671 n1 = n 

672 n += 1 

673 if n1 > 0: 

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

675 X += x 

676 Y += y 

677 self._n = n 

678 return _sampled(n, sample) 

679 

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

681 '''Accumulate and return the current count. 

682 

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

684 positional values (C{Scalar}s). 

685 

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

687 ''' 

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

689 

690 def fcorrelation(self, sample=False): 

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

692 

693 @kwarg sample: Return the I{sample} instead of the entire 

694 I{population} value (C{bool}). 

695 ''' 

696 return self._sampled(self.x.fstdev(sample=sample) * 

697 self.y.fstdev(sample=sample), sample) 

698 

699 def fintercept(self, sample=False): 

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

701 

702 @kwarg sample: Return the I{sample} instead of the entire 

703 I{population} value (C{bool}). 

704 ''' 

705 return float(self.y._M1 - 

706 (self.x._M1 * self.fslope(sample=sample))) 

707 

708 def fslope(self, sample=False): 

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

710 

711 @kwarg sample: Return the I{sample} instead of the entire 

712 I{population} value (C{bool}). 

713 ''' 

714 return self._sampled(self.x.fvariance(sample=sample), sample) 

715 

716 def _sampled(self, t, sample): 

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

718 ''' 

719 t *= float(_sampled(self._n, sample)) 

720 return float(self._S / t) if t else _0_0 

721 

722 @property_RO 

723 def x(self): 

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

725 ''' 

726 return self._X 

727 

728 @property_RO 

729 def y(self): 

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

731 ''' 

732 return self._Y 

733 

734 

735__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

736 

737# **) MIT License 

738# 

739# Copyright (C) 2021-2023 -- mrJean1 at Gmail -- All Rights Reserved. 

740# 

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

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

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

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

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

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

747# 

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

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

750# 

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

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

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

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

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

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

757# OTHER DEALINGS IN THE SOFTWARE.