Coverage for pygeodesy/fstats.py: 99%

307 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-02 14:35 -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, _xError 

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

14from pygeodesy.fmath import hypot2, sqrt 

15from pygeodesy.fsums import _2float, Fsum, _iadd_op_, Fmt 

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

17from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY 

18from pygeodesy.named import _Named, _NotImplemented, property_RO 

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

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

21 

22# from math import sqrt # from .fmath 

23 

24__all__ = _ALL_LAZY.fstats 

25__version__ = '24.04.26' 

26 

27_Floats = Fsum, float 

28_Scalar = _Floats + (int,) # XXX basics._Ints is ABCMeta in Python 2 

29try: 

30 _Scalar += (long,) 

31except NameError: # Python 3+ 

32 pass 

33 

34 

35def _2Floats(xs, ys=False): 

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

37 ''' 

38 if ys: 

39 def _2f(i, x): 

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

41 else: 

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

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

44 

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

46 yield x if isinstance(x, _Floats) else _2f(i, 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 return Fmt.SQUARE(self.named3, len(self)) 

90 

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

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

93 ''' 

94 n = name or self.fcopy.__name__ 

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

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

97 

98 copy = fcopy 

99 

100 

101class _FstatsBase(_FstatsNamed): 

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

103 ''' 

104 _Ms = () 

105 

106 def _copy(self, c, s): 

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

108 ''' 

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

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

111 c._n = s._n 

112 return c 

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

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 with additional values (C{Scalar}s). 

129 

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

131 

132 @see: Method C{fadd}. 

133 ''' 

134 if xs: 

135 self.fadd(xs) 

136 return self._M1.fsum() 

137 

138 def fmean_(self, *xs): 

139 '''Accumulate and return the current mean. 

140 

141 @see: Method C{fmean}. 

142 ''' 

143 return self.fmean(xs) 

144 

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

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

147 

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

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

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

151 

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

153 

154 @see: Method C{fadd}. 

155 ''' 

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

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

158 

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

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

161 

162 @see: Method C{fstdev}. 

163 ''' 

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

165 

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

167 '''Accumulate and return the current variance. 

168 

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

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

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

172 

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

174 

175 @see: Method C{fadd}. 

176 ''' 

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

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

179 

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

181 '''Accumulate and return the current variance. 

182 

183 @see: Method C{fvariance}. 

184 ''' 

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

186 

187 def _iadd_other(self, other): 

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

189 ''' 

190 if isinstance(other, _Scalar): 

191 self.fadd_(other) 

192 else: 

193 try: 

194 if not islistuple(other): 

195 raise TypeError(_SPACE_(_invalid_, _other_)) 

196 self.fadd(other) 

197 except Exception as x: 

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

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 

211class Fcook(_FstatsBase): 

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

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

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

215 and Jarque-Bera normality. 

216 

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

218 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

219 ''' 

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

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

222 

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

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

225 

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

227 ''' 

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

229 if name: 

230 self.name = name 

231 if xs: 

232 self.fadd(xs) 

233 

234 def __iadd__(self, other): 

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

236 

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

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

239 

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

241 

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

243 

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

245 

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

247 ''' 

248 if isinstance(other, Fcook): 

249 nb = len(other) 

250 if nb > 0: 

251 na = len(self) 

252 if na > 0: 

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

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

255 

256 n = na + nb 

257 n_ = float(n) 

258 D = A1 - B1 # b1 - a1 

259 Dn = D / n_ 

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

261 nab = na * nb 

262 Dn3 = Dn2 * (D * nab) 

263 

264 na2 = na**2 

265 nb2 = nb**2 

266 A4 += B4 

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

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

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

270 

271 A3 += B3 

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

273 A3 += Dn3 * (na - nb) 

274 

275 A2 += B2 

276 A2 += Dn2 * (nab / n_) 

277 

278 B1n = B1 * nb # if other is self 

279 A1 *= na 

280 A1 += B1n 

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

282 

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

284 self._n = n 

285 else: 

286 self._copy(self, other) 

287 else: 

288 self._iadd_other(other) 

289 return self 

290 

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

292 '''Accumulate and return the current count. 

293 

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

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

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

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

298 

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

300 

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

302 

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

304 

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

306 

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

308 Algorithms_for_calculating_variance>}. 

309 ''' 

310 n = self._n 

311 if xs: 

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

313 for x in _2Floats(xs): 

314 n1 = n 

315 n += 1 

316 D = x - M1 

317 Dn = D / n 

318 if Dn: 

319 Dn2 = Dn**2 

320 if n1 > 1: 

321 T1 = D * (Dn * n1) 

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

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

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

325 T1 = D * Dn 

326 T2 = _0_0 

327 T3 = T1 * Dn2 

328 else: 

329 T1 = T2 = T3 = _0_0 

330 M4 += T3 

331 M4 -= M3 * (Dn * _4_0) 

332 M4 += M2 * (Dn2 * _6_0) 

333 

334 M3 += T2 

335 M3 -= M2 * (Dn * _3_0) 

336 

337 M2 += T1 

338 M1 += Dn 

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

340 self._n = n 

341 return _sampled(n, sample) 

342 

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

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

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

346 

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

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

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

350 

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

352 

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

354 ''' 

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

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

357 s = self.fskewness(sample=sample) 

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

359 

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

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

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

363 

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

365 ''' 

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

367 

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

369 '''Accumulate and return the current kurtosis. 

370 

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

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

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

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

375 

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

377 

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

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

380 

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

382 ''' 

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

384 if n > 0: 

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

386 m2 = float(M2 * M2) 

387 if m2: 

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

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

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

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

392 x *= n**2 / d 

393 if excess: 

394 K -= x 

395 k = K.fsum() 

396 return k 

397 

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

399 '''Accumulate and return the current kurtosis. 

400 

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

402 ''' 

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

404 

405 def fmedian(self, xs=None): 

406 '''Accumulate and return the current median. 

407 

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

409 

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

411 

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

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

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

415 and method L{Fcook.fadd}. 

416 ''' 

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

418 # median = mean - skewness * stdef / 3 

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

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

421 

422 def fmedian_(self, *xs): 

423 '''Accumulate and return the current median. 

424 

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

426 ''' 

427 return self.fmedian(xs) 

428 

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

430 '''Accumulate and return the current skewness. 

431 

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

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

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

435 

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

437 

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

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

440 

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

442 ''' 

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

444 if n > 0: 

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

446 m = float(M2**3) 

447 if m > 0: 

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

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

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

451 s = S.fsum() 

452 return s 

453 

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

455 '''Accumulate and return the current skewness. 

456 

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

458 ''' 

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

460 

461 def toFwelford(self, name=NN): 

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

463 ''' 

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

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

466 f._n = self._n 

467 return f 

468 

469 

470class Fwelford(_FstatsBase): 

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

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

473 

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

475 ''' 

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

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

478 

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

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

481 

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

483 ''' 

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

485 if name: 

486 self.name = name 

487 if xs: 

488 self.fadd(xs) 

489 

490 def __iadd__(self, other): 

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

492 

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

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

495 

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

497 

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

499 

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

501 

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

503 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

504 ''' 

505 if isinstance(other, Fwelford): 

506 nb = len(other) 

507 if nb > 0: 

508 na = len(self) 

509 if na > 0: 

510 M, S = self._Ms 

511 M_, S_ = other._Ms 

512 

513 n = na + nb 

514 n_ = float(n) 

515 

516 D = M_ - M 

517 D *= D # D**2 

518 D *= na * nb / n_ 

519 S += D 

520 S += S_ 

521 

522 Mn = M_ * nb # if other is self 

523 M *= na 

524 M += Mn 

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

526 

527# self._Ms = M, S 

528 self._n = n 

529 else: 

530 self._copy(self, other) 

531 

532 elif isinstance(other, Fcook): 

533 self += other.toFwelford() 

534 else: 

535 self._iadd_other(other) 

536 return self 

537 

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

539 '''Accumulate and return the current count. 

540 

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

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

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

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

545 

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

547 

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

549 

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

551 

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

553 ''' 

554 n = self._n 

555 if xs: 

556 M, S = self._Ms 

557 for x in _2Floats(xs): 

558 n += 1 

559 D = x - M 

560 M += D / n 

561 D *= x - M 

562 S += D 

563# self._Ms = M, S 

564 self._n = n 

565 return _sampled(n, sample) 

566 

567 

568class Flinear(_FstatsNamed): 

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

570 C{RunningRegression} computing the running slope, intercept 

571 and correlation of a linear regression. 

572 ''' 

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

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

575 

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

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

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

579 or L{Fwelford}). 

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

581 

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

583 L{Fwelford}. 

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

585 ''' 

586 _xsubclassof(Fcook, Fwelford, Fstats=Fstats) 

587 if name: 

588 self.name = name 

589 

590 self._S = Fsum(name=name) 

591 self._X = Fstats(name=name) 

592 self._Y = Fstats(name=name) 

593 if xs and ys: 

594 self.fadd(xs, ys) 

595 

596 def __iadd__(self, other): 

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

598 

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

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

601 

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

603 

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

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

606 are not compatible. 

607 

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

609 

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

611 ''' 

612 if isinstance(other, Flinear): 

613 if len(other) > 0: 

614 if len(self) > 0: 

615 n = other._n 

616 S = other._S 

617 X = other._X 

618 Y = other._Y 

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

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

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

622 self._n += n 

623 self._S += S + D 

624 self._X += X 

625 self._Y += Y 

626 else: 

627 self._copy(self, other) 

628 else: 

629 try: 

630 if not islistuple(other): 

631 raise TypeError(_SPACE_(_invalid_, _other_)) 

632 elif isodd(len(other)): 

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

634 self.fadd_(*other) 

635 except Exception as x: 

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

637 return self 

638 

639 def _copy(self, c, s): 

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

641 ''' 

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

643 c._n = s._n 

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

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

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

647 return c 

648 

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

650 '''Accumulate and return the current count. 

651 

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

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

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

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

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

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

658 

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

660 

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

662 

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

664 

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

666 ''' 

667 n = self._n 

668 if xs and ys: 

669 S = self._S 

670 X = self._X 

671 Y = self._Y 

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

673 n1 = n 

674 n += 1 

675 if n1 > 0: 

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

677 X += x 

678 Y += y 

679 self._n = n 

680 return _sampled(n, sample) 

681 

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

683 '''Accumulate and return the current count. 

684 

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

686 positional values (C{Scalar}s). 

687 

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

689 ''' 

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

691 

692 def fcorrelation(self, sample=False): 

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

694 

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

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

697 ''' 

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

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

700 

701 def fintercept(self, sample=False): 

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

703 

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

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

706 ''' 

707 return float(self.y._M1 - 

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

709 

710 def fslope(self, sample=False): 

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

712 

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

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

715 ''' 

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

717 

718 def _sampled(self, t, sample): 

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

720 ''' 

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

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

723 

724 @property_RO 

725 def x(self): 

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

727 ''' 

728 return self._X 

729 

730 @property_RO 

731 def y(self): 

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

733 ''' 

734 return self._Y 

735 

736 

737__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

738 

739# **) MIT License 

740# 

741# Copyright (C) 2021-2024 -- mrJean1 at Gmail -- All Rights Reserved. 

742# 

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

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

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

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

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

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

749# 

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

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

752# 

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

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

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

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

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

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

759# OTHER DEALINGS IN THE SOFTWARE.