Coverage for pygeodesy/fstats.py: 99%

307 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-12-02 13:46 -0500

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, _xError 

13# from pygeodesy.errors import _xError # from .constants 

14from pygeodesy.fmath import hypot2, sqrt 

15from pygeodesy.fsums import _2float, Fsum, Fmt 

16from pygeodesy.interns import NN, _iadd_op_, _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 # from .fmath 

24 

25__all__ = _ALL_LAZY.fstats 

26__version__ = '23.09.22' 

27 

28_Floats = Fsum, 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, ys=False): 

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

38 ''' 

39 if ys: 

40 def _2f(i, x): 

41 return _2float(index=i, ys=x) 

42 else: 

43 def _2f(i, x): # PYCHOK redef 

44 return _2float(index=i, xs=x) 

45 

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

47 yield x if isinstance(x, _Floats) else _2f(i, x) 

48 

49 

50def _sampled(n, sample): 

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

52 ''' 

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

54 

55 

56class _FstatsNamed(_Named): 

57 '''(INTERNAL) Base class. 

58 ''' 

59 _n = 0 

60 

61 def __add__(self, other): 

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

63 ''' 

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

65 f += other 

66 return f 

67 

68 def __float__(self): # PYCHOK no cover 

69 '''Not implemented.''' 

70 return _NotImplemented(self) 

71 

72 def __int__(self): # PYCHOK no cover 

73 '''Not implemented.''' 

74 return _NotImplemented(self) 

75 

76 def __len__(self): 

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

78 ''' 

79 return self._n 

80 

81 def __neg__(self): # PYCHOK no cover 

82 '''Not implemented.''' 

83 return _NotImplemented(self) 

84 

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

86 '''Not implemented.''' 

87 return _NotImplemented(self, other) 

88 

89 def __str__(self): 

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

91 

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

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

94 ''' 

95 n = name or self.fcopy.__name__ 

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

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

98 

99 copy = fcopy 

100 

101 

102class _FstatsBase(_FstatsNamed): 

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

104 ''' 

105 _Ms = () 

106 

107 def _copy(self, c, s): 

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

109 ''' 

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

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

112 c._n = s._n 

113 return c 

114 

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

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

117 notOverloaded(self, xs, sample=sample) 

118 

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

120 '''Accumulate and return the current count. 

121 

122 @see: Method C{fadd}. 

123 ''' 

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

125 

126 def fmean(self, xs=None): 

127 '''Accumulate and return the current mean. 

128 

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

130 

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

132 

133 @see: Method C{fadd}. 

134 ''' 

135 if xs: 

136 self.fadd(xs) 

137 return self._M1.fsum() 

138 

139 def fmean_(self, *xs): 

140 '''Accumulate and return the current mean. 

141 

142 @see: Method C{fmean}. 

143 ''' 

144 return self.fmean(xs) 

145 

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

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

148 

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

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

151 I{population} standard deviation (C{bool}). 

152 

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

154 

155 @see: Method C{fadd}. 

156 ''' 

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

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

159 

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

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

162 

163 @see: Method C{fstdev}. 

164 ''' 

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

166 

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

168 '''Accumulate and return the current variance. 

169 

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

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

172 I{population} variance (C{bool}). 

173 

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

175 

176 @see: Method C{fadd}. 

177 ''' 

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

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

180 

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

182 '''Accumulate and return the current variance. 

183 

184 @see: Method C{fvariance}. 

185 ''' 

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

187 

188 def _iadd_other(self, other): 

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

190 ''' 

191 if isinstance(other, _Scalar): 

192 self.fadd_(other) 

193 else: 

194 try: 

195 if not islistuple(other): 

196 raise TypeError(_SPACE_(_invalid_, _other_)) 

197 self.fadd(other) 

198 except Exception as x: 

199 raise _xError(x, _SPACE_(self, _iadd_op_, repr(other))) 

200 

201 @property_RO 

202 def _M1(self): 

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

204 return self._Ms[0] 

205 

206 @property_RO 

207 def _M2(self): 

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

209 return self._Ms[1] 

210 

211 

212class Fcook(_FstatsBase): 

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

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

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

216 and Jarque-Bera normality. 

217 

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

219 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

220 ''' 

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

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

223 

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

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

226 

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

228 ''' 

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

230 if name: 

231 self.name = name 

232 if xs: 

233 self.fadd(xs) 

234 

235 def __iadd__(self, other): 

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

237 

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

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

240 

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

242 

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

244 

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

246 

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

248 ''' 

249 if isinstance(other, Fcook): 

250 nb = len(other) 

251 if nb > 0: 

252 na = len(self) 

253 if na > 0: 

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

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

256 

257 n = na + nb 

258 n_ = float(n) 

259 D = A1 - B1 # b1 - a1 

260 Dn = D / n_ 

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

262 nab = na * nb 

263 Dn3 = Dn2 * (D * nab) 

264 

265 na2 = na**2 

266 nb2 = nb**2 

267 A4 += B4 

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

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

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

271 

272 A3 += B3 

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

274 A3 += Dn3 * (na - nb) 

275 

276 A2 += B2 

277 A2 += Dn2 * (nab / n_) 

278 

279 B1n = B1 * nb # if other is self 

280 A1 *= na 

281 A1 += B1n 

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

283 

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

285 self._n = n 

286 else: 

287 self._copy(self, other) 

288 else: 

289 self._iadd_other(other) 

290 return self 

291 

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

293 '''Accumulate and return the current count. 

294 

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

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

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

298 I{population} count (C{bool}). 

299 

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

301 

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

303 

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

305 

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

307 

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

309 Algorithms_for_calculating_variance>}. 

310 ''' 

311 n = self._n 

312 if xs: 

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

314 for x in _2Floats(xs): 

315 n1 = n 

316 n += 1 

317 D = x - M1 

318 Dn = D / n 

319 if Dn: 

320 Dn2 = Dn**2 

321 if n1 > 1: 

322 T1 = D * (Dn * n1) 

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

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

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

326 T1 = D * Dn 

327 T2 = _0_0 

328 T3 = T1 * Dn2 

329 else: 

330 T1 = T2 = T3 = _0_0 

331 M4 += T3 

332 M4 -= M3 * (Dn * _4_0) 

333 M4 += M2 * (Dn2 * _6_0) 

334 

335 M3 += T2 

336 M3 -= M2 * (Dn * _3_0) 

337 

338 M2 += T1 

339 M1 += Dn 

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

341 self._n = n 

342 return _sampled(n, sample) 

343 

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

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

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

347 

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

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

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

351 

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

353 

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

355 ''' 

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

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

358 s = self.fskewness(sample=sample) 

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

360 

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

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

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

364 

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

366 ''' 

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

368 

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

370 '''Accumulate and return the current kurtosis. 

371 

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

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

374 I{population} kurtosis (C{bool}). 

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

376 

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

378 

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

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

381 

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

383 ''' 

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

385 if n > 0: 

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

387 m2 = float(M2 * M2) 

388 if m2: 

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

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

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

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

393 x *= n**2 / d 

394 if excess: 

395 K -= x 

396 k = K.fsum() 

397 return k 

398 

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

400 '''Accumulate and return the current kurtosis. 

401 

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

403 ''' 

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

405 

406 def fmedian(self, xs=None): 

407 '''Accumulate and return the current median. 

408 

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

410 

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

412 

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

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

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

416 and method L{Fcook.fadd}. 

417 ''' 

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

419 # median = mean - skewness * stdef / 3 

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

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

422 

423 def fmedian_(self, *xs): 

424 '''Accumulate and return the current median. 

425 

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

427 ''' 

428 return self.fmedian(xs) 

429 

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

431 '''Accumulate and return the current skewness. 

432 

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

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

435 I{population} skewness (C{bool}). 

436 

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

438 

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

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

441 

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

443 ''' 

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

445 if n > 0: 

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

447 m = float(M2**3) 

448 if m > 0: 

449 S = M3 * sqrt(float(n) / m) 

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

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

452 s = S.fsum() 

453 return s 

454 

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

456 '''Accumulate and return the current skewness. 

457 

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

459 ''' 

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

461 

462 def toFwelford(self, name=NN): 

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

464 ''' 

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

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

467 f._n = self._n 

468 return f 

469 

470 

471class Fwelford(_FstatsBase): 

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

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

474 

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

476 ''' 

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

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

479 

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

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

482 

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

484 ''' 

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

486 if name: 

487 self.name = name 

488 if xs: 

489 self.fadd(xs) 

490 

491 def __iadd__(self, other): 

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

493 

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

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

496 

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

498 

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

500 

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

502 

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

504 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

505 ''' 

506 if isinstance(other, Fwelford): 

507 nb = len(other) 

508 if nb > 0: 

509 na = len(self) 

510 if na > 0: 

511 M, S = self._Ms 

512 M_, S_ = other._Ms 

513 

514 n = na + nb 

515 n_ = float(n) 

516 

517 D = M_ - M 

518 D *= D # D**2 

519 D *= na * nb / n_ 

520 S += D 

521 S += S_ 

522 

523 Mn = M_ * nb # if other is self 

524 M *= na 

525 M += Mn 

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

527 

528# self._Ms = M, S 

529 self._n = n 

530 else: 

531 self._copy(self, other) 

532 

533 elif isinstance(other, Fcook): 

534 self += other.toFwelford() 

535 else: 

536 self._iadd_other(other) 

537 return self 

538 

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

540 '''Accumulate and return the current count. 

541 

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

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

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

545 I{population} count (C{bool}). 

546 

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

548 

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

550 

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

552 

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

554 ''' 

555 n = self._n 

556 if xs: 

557 M, S = self._Ms 

558 for x in _2Floats(xs): 

559 n += 1 

560 D = x - M 

561 M += D / n 

562 D *= x - M 

563 S += D 

564# self._Ms = M, S 

565 self._n = n 

566 return _sampled(n, sample) 

567 

568 

569class Flinear(_FstatsNamed): 

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

571 C{RunningRegression} computing the running slope, intercept 

572 and correlation of a linear regression. 

573 ''' 

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

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

576 

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

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

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

580 or L{Fwelford}). 

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

582 

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

584 L{Fwelford}. 

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

586 ''' 

587 _xsubclassof(Fcook, Fwelford, Fstats=Fstats) 

588 if name: 

589 self.name = name 

590 

591 self._S = Fsum(name=name) 

592 self._X = Fstats(name=name) 

593 self._Y = Fstats(name=name) 

594 if xs and ys: 

595 self.fadd(xs, ys) 

596 

597 def __iadd__(self, other): 

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

599 

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

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

602 

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

604 

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

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

607 are not compatible. 

608 

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

610 

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

612 ''' 

613 if isinstance(other, Flinear): 

614 if len(other) > 0: 

615 if len(self) > 0: 

616 n = other._n 

617 S = other._S 

618 X = other._X 

619 Y = other._Y 

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

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

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

623 self._n += n 

624 self._S += S + D 

625 self._X += X 

626 self._Y += Y 

627 else: 

628 self._copy(self, other) 

629 else: 

630 try: 

631 if not islistuple(other): 

632 raise TypeError(_SPACE_(_invalid_, _other_)) 

633 elif isodd(len(other)): 

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

635 self.fadd_(*other) 

636 except Exception as x: 

637 raise _xError(x, _SPACE_(self, _iadd_op_, repr(other))) 

638 return self 

639 

640 def _copy(self, c, s): 

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

642 ''' 

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

644 c._n = s._n 

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

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

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

648 return c 

649 

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

651 '''Accumulate and return the current count. 

652 

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

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

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

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

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

658 I{population} count (C{bool}). 

659 

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

661 

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

663 

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

665 

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

667 ''' 

668 n = self._n 

669 if xs and ys: 

670 S = self._S 

671 X = self._X 

672 Y = self._Y 

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

674 n1 = n 

675 n += 1 

676 if n1 > 0: 

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

678 X += x 

679 Y += y 

680 self._n = n 

681 return _sampled(n, sample) 

682 

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

684 '''Accumulate and return the current count. 

685 

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

687 positional values (C{Scalar}s). 

688 

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

690 ''' 

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

692 

693 def fcorrelation(self, sample=False): 

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

695 

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

697 I{population} correlation (C{bool}). 

698 ''' 

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

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

701 

702 def fintercept(self, sample=False): 

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

704 

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

706 I{population} intercept (C{bool}). 

707 ''' 

708 return float(self.y._M1 - 

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

710 

711 def fslope(self, sample=False): 

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

713 

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

715 I{population} slope (C{bool}). 

716 ''' 

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

718 

719 def _sampled(self, t, sample): 

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

721 ''' 

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

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

724 

725 @property_RO 

726 def x(self): 

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

728 ''' 

729 return self._X 

730 

731 @property_RO 

732 def y(self): 

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

734 ''' 

735 return self._Y 

736 

737 

738__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

739 

740# **) MIT License 

741# 

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

743# 

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

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

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

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

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

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

750# 

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

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

753# 

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

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

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

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

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

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

760# OTHER DEALINGS IN THE SOFTWARE.