Coverage for pygeodesy/fstats.py: 98%

325 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-06 12:20 -0500

1 

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

3 

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

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

6multiplication. 

7''' 

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

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

10 

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

12 _xiterable, _xsubclassof, _zip 

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

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

15 _xsError 

16from pygeodesy.fmath import Fsqrt, Fmt 

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

18from pygeodesy.interns import _odd_, _SPACE_ 

19from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY 

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

21 property_RO 

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

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

24 

25__all__ = _ALL_LAZY.fstats 

26__version__ = '24.10.08' 

27 

28 

29def _sampled(n, sample): 

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

31 ''' 

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

33 

34 

35def _Xs(**name_xs): 

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

37 ''' 

38 name, xs = _xkwds_item2(name_xs) 

39 try: 

40 i, x = 0, xs 

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

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

43 except Exception as X: 

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

45 

46 

47class _FstatsNamed(_Named): 

48 '''(INTERNAL) Base class. 

49 ''' 

50 _n = 0 

51 

52 def __add__(self, other): 

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

54 or L{Fsum2Tuple}. 

55 ''' 

56 f = self.copy(name=self.__add__.__name__) # PYCHOK expected 

57 f += other 

58 return f 

59 

60 def __float__(self): # PYCHOK no cover 

61 '''Not implemented.''' 

62 return _NotImplemented(self) 

63 

64 def __int__(self): # PYCHOK no cover 

65 '''Not implemented.''' 

66 return _NotImplemented(self) 

67 

68 def __len__(self): 

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

70 ''' 

71 return self._n 

72 

73 def __neg__(self): # PYCHOK no cover 

74 '''Not implemented.''' 

75 return _NotImplemented(self) 

76 

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

78 '''Not implemented.''' 

79 return _NotImplemented(self, other) 

80 

81 def __str__(self): 

82 n = self.name 

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

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

85 

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

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

88 

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

90 

91 @return: The copy instance. 

92 ''' 

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

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

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

96 

97 fcopy = copy # for backward compatibility 

98 

99 

100class _FstatsBase(_FstatsNamed): 

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

102 ''' 

103 _Ms = () 

104 

105 def _copy(self, d, s): 

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

107 ''' 

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

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

110 d._n = s._n 

111 return d 

112 

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

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

115 self._notOverloaded(xs, sample=sample) 

116 

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

118 '''Accumulate and return the current count. 

119 

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

121 ''' 

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

123 

124 def fmean(self, xs=None): 

125 '''Accumulate and return the current mean. 

126 

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

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

129 

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

131 

132 @see: Method C{fadd}. 

133 ''' 

134 return float(self._Mean(xs)) 

135 

136 def fmean_(self, *xs): 

137 '''Accumulate and return the current mean. 

138 

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

140 ''' 

141 return self.fmean(xs) 

142 

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

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

145 

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

147 or L{Fsum2Tuple}). 

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

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

150 

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

152 

153 @see: Method C{fadd}. 

154 ''' 

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

156 

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

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

159 

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

161 ''' 

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

163 

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

165 '''Accumulate and return the current variance. 

166 

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

168 or L{Fsum2Tuple}). 

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

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

171 

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

173 

174 @see: Method C{fadd}. 

175 ''' 

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

177 

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

179 '''Accumulate and return the current variance. 

180 

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

182 ''' 

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

184 

185 def _iadd_other(self, other): 

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

187 ''' 

188 try: 

189 if _isFsum_2Tuple(other): 

190 self.fadd_(other._Fsum) 

191 elif isscalar(other): 

192 self.fadd_(_2finite(other)) 

193 elif _xiterable(other): 

194 self.fadd(other) 

195 except Exception as X: 

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

197 raise _xError(X, t) 

198 

199 @property_RO 

200 def _M1(self): 

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

202 return self._Ms[0] 

203 

204 @property_RO 

205 def _M2(self): 

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

207 return self._Ms[1] 

208 

209 def _Mean(self, xs=None): 

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

211 ''' 

212 if xs: 

213 self.fadd(xs) 

214 return self._M1 # .copy() 

215 

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

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

218 ''' 

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

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

221 

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

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

224 ''' 

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

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

227 

228 

229class Fcook(_FstatsBase): 

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

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

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

233 and Jarque-Bera normality. 

234 

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

236 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

237 ''' 

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

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

240 

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

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

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

244 

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

246 ''' 

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

248 if name: 

249 self.name = name 

250 if xs: 

251 self.fadd(xs) 

252 

253 def __iadd__(self, other): 

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

255 

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

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

258 L{Fsum2Tuple}). 

259 

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

261 

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

263 

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

265 

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

267 ''' 

268 if isinstance(other, Fcook): 

269 nb = len(other) 

270 if nb > 0: 

271 na = len(self) 

272 if na > 0: 

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

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

275 

276 n = na + nb 

277 _n = _1_0 / n 

278 D = A1 - B1 # b1 - a1 

279 Dn = D * _n 

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

281 nab = na * nb 

282 Dn3 = Dn2 * (D * nab) 

283 

284 na2 = na**2 

285 nb2 = nb**2 

286 A4 += B4 

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

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

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

290 

291 A3 += B3 

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

293 A3 += Dn3 * (na - nb) 

294 

295 A2 += B2 

296 A2 += Dn2 * (nab * _n) 

297 

298 B1n = B1 * nb # if other is self 

299 A1 *= na 

300 A1 += B1n 

301 A1 *= _n 

302 

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

304 self._n = n 

305 else: 

306 self._copy(self, other) 

307 else: 

308 self._iadd_other(other) 

309 return self 

310 

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

312 '''Accumulate and return the current count. 

313 

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

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

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

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

318 

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

320 

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

322 

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

324 

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

326 

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

328 Algorithms_for_calculating_variance>}. 

329 ''' 

330 n = self._n 

331 if xs: 

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

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

334 n1 = n 

335 n += 1 

336 D = x - M1 

337 Dn = D / n 

338 if Dn: 

339 Dn2 = Dn**2 

340 if n1 > 1: 

341 T1 = D * (Dn * n1) 

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

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

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

345 T1 = D * Dn 

346 T2 = _0_0 

347 T3 = T1 * Dn2 

348 else: 

349 T1 = T2 = T3 = _0_0 

350 M4 += T3 

351 M4 -= M3 * (Dn * _4_0) 

352 M4 += M2 * (Dn2 * _6_0) 

353 

354 M3 += T2 

355 M3 -= M2 * (Dn * _3_0) 

356 

357 M2 += T1 

358 M1 += Dn 

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

360 self._n = n 

361 return _sampled(n, sample) 

362 

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

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

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

366 

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

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

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

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

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

372 

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

374 

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

376 ''' 

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

378 

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

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

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

382 

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

384 ''' 

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

386 

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

388 '''Accumulate and return the current kurtosis. 

389 

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

391 or L{Fsum2Tuple}). 

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

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

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

395 

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

397 

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

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

400 

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

402 ''' 

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

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

405 

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

407 '''Accumulate and return the current kurtosis. 

408 

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

410 ''' 

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

412 

413 def fmedian(self, xs=None): 

414 '''Accumulate and return the current median. 

415 

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

417 L{Fsum2Tuple}). 

418 

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

420 

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

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

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

424 and method L{Fcook.fadd}. 

425 ''' 

426 return float(self._Median(xs)) 

427 

428 def fmedian_(self, *xs): 

429 '''Accumulate and return the current median. 

430 

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

432 ''' 

433 return self.fmedian(xs) 

434 

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

436 '''Accumulate and return the current skewness. 

437 

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

439 or L{Fsum2Tuple}). 

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

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

442 

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

444 

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

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

447 

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

449 ''' 

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

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

452 

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

454 '''Accumulate and return the current skewness. 

455 

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

457 ''' 

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

459 

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

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

462 ''' 

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

464 if n > 0: 

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

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

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

468 return N 

469 

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

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

472 ''' 

473 K = _0_0 

474 if n > 0: 

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

476 M = M2**2 

477 if M > 0: 

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

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

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

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

482 x *= n**2 / d 

483 if excess: 

484 K -= x 

485 return K 

486 

487 def _Median(self, xs=None): 

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

489 ''' 

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

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

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

493 self._Stdev()) / _3_0 

494 

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

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

497 ''' 

498 S = _0_0 

499 if n > 0: 

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

501 M = M2**3 

502 if M > 0: 

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

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

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

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

507 return S 

508 

509 def toFwelford(self, **name): 

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

511 

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

513 ''' 

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

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

516 f._n = self._n 

517 return f 

518 

519 

520class Fwelford(_FstatsBase): 

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

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

523 

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

525 ''' 

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

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

528 

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

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

531 

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

533 ''' 

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

535 if name: 

536 self.name = name 

537 if xs: 

538 self.fadd(xs) 

539 

540 def __iadd__(self, other): 

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

542 

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

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

545 L{Fsum2Tuple}). 

546 

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

548 

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

550 

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

552 

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

554 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

555 ''' 

556 if isinstance(other, Fwelford): 

557 nb = len(other) 

558 if nb > 0: 

559 na = len(self) 

560 if na > 0: 

561 M, S = self._Ms 

562 M_, S_ = other._Ms 

563 

564 n = na + nb 

565 _n = _1_0 / n 

566 

567 D = M_ - M 

568 D *= D # D**2 

569 D *= na * nb * _n 

570 S += D 

571 S += S_ 

572 

573 Mn = M_ * nb # if other is self 

574 M *= na 

575 M += Mn 

576 M *= _n 

577 

578# self._Ms = M, S 

579 self._n = n 

580 else: 

581 self._copy(self, other) 

582 

583 elif isinstance(other, Fcook): 

584 self += other.toFwelford() 

585 else: 

586 self._iadd_other(other) 

587 return self 

588 

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

590 '''Accumulate and return the current count. 

591 

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

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

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

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

596 

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

598 

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

600 

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

602 

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

604 ''' 

605 n = self._n 

606 if xs: 

607 M, S = self._Ms 

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

609 n += 1 

610 D = x - M 

611 M += D / n 

612 D *= x - M 

613 S += D 

614# self._Ms = M, S 

615 self._n = n 

616 return _sampled(n, sample) 

617 

618 

619class Flinear(_FstatsNamed): 

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

621 C{RunningRegression} computing the running slope, intercept 

622 and correlation of a linear regression. 

623 ''' 

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

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

626 

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

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

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

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

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

632 L{Fwelford}). 

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

634 

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

636 

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

638 ''' 

639 _xsubclassof(Fcook, Fwelford, Fstats=Fstats) 

640 if name: 

641 self.name = name 

642 

643 self._S = Fsum(name=name) 

644 self._X = Fstats(name=name) 

645 self._Y = Fstats(name=name) 

646 if xs and ys: 

647 self.fadd(xs, ys) 

648 

649 def __iadd__(self, other): 

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

651 

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

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

654 

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

656 

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

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

659 are not compatible. 

660 

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

662 

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

664 ''' 

665 if isinstance(other, Flinear): 

666 if len(other) > 0: 

667 if len(self) > 0: 

668 n = other._n 

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

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

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

672 self._S += other._S + D 

673 self._X += other._X 

674 self._Y += other._Y 

675 self._n += n 

676 else: 

677 self._copy(self, other) 

678 else: 

679 try: 

680 if _xiterable(other): 

681 self.fadd_(*other) 

682 except Exception as X: 

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

684 raise _xError(X, op) 

685 return self 

686 

687 def _copy(self, d, s): 

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

689 ''' 

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

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

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

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

694 d._n = s._n 

695 return d 

696 

697 def _Correlation(self, **sample): 

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

699 ''' 

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

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

702 

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

704 '''Accumulate and return the current count. 

705 

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

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

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

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

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

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

712 

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

714 

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

716 

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

718 

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

720 ''' 

721 n = self._n 

722 if xs and ys: 

723 S = self._S 

724 X = self._X 

725 Y = self._Y 

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

727 n1 = n 

728 n += 1 

729 if n1 > 0: 

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

731 X += x 

732 Y += y 

733 self._n = n 

734 return _sampled(n, sample) 

735 

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

737 '''Accumulate and return the current count. 

738 

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

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

741 

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

743 ''' 

744 if isodd(len(x_ys)): 

745 t = _SPACE_(_odd_, len.__name__) 

746 raise _ValueError(t, len(x_ys)) 

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

748 

749 def fcorrelation(self, **sample): 

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

751 

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

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

754 ''' 

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

756 

757 def fintercept(self, **sample): 

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

759 

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

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

762 ''' 

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

764 

765 def fslope(self, **sample): 

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

767 

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

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

770 ''' 

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

772 

773 def _Intercept(self, **sample): 

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

775 ''' 

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

777 

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

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

780 ''' 

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

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

783 

784 def _Slope(self, **sample): 

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

786 ''' 

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

788 

789 @property_RO 

790 def x(self): 

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

792 ''' 

793 return self._X # .copy() 

794 

795 @property_RO 

796 def y(self): 

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

798 ''' 

799 return self._Y # .copy() 

800 

801 

802__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

803 

804# **) MIT License 

805# 

806# Copyright (C) 2021-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

807# 

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

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

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

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

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

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

814# 

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

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

817# 

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

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

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

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

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

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

824# OTHER DEALINGS IN THE SOFTWARE.