Coverage for pygeodesy/fstats.py: 98%

327 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-15 16:36 -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 isscalar, isodd, _xinstanceof, \ 

11 _xiterable, _xsubclassof, _zip 

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

13from pygeodesy.errors import _AssertionError, _ValueError, _xError 

14from pygeodesy.fmath import Fsqrt, Fmt 

15from pygeodesy.fsums import _2finite, Fsum, _iadd_op_, _isFsumTuple 

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

21 

22__all__ = _ALL_LAZY.fstats 

23__version__ = '24.05.10' 

24 

25 

26def _2Floats(**xs): 

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

28 ''' 

29 try: 

30 name, xs = xs.popitem() 

31 except Exception as X: 

32 raise _AssertionError(xs=xs, cause=X) 

33 

34 try: 

35 i = None 

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

37 yield x._Fsum if _isFsumTuple(x) else _2finite(x) 

38 except Exception as X: 

39 raise _xError(X, name, xs) if i is None else \ 

40 _xError(X, Fmt.INDEX(name, i), x) 

41 

42 

43def _sampled(n, sample): 

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

45 ''' 

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

47 

48 

49class _FstatsNamed(_Named): 

50 '''(INTERNAL) Base class. 

51 ''' 

52 _n = 0 

53 

54 def __add__(self, other): 

55 '''Sum of this and an other instance or a C{scalar} or an 

56 L{Fsum}, L{Fsum2Tuple} or 

57 . 

58 ''' 

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

60 f += other 

61 return f 

62 

63 def __float__(self): # PYCHOK no cover 

64 '''Not implemented.''' 

65 return _NotImplemented(self) 

66 

67 def __int__(self): # PYCHOK no cover 

68 '''Not implemented.''' 

69 return _NotImplemented(self) 

70 

71 def __len__(self): 

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

73 ''' 

74 return self._n 

75 

76 def __neg__(self): # PYCHOK no cover 

77 '''Not implemented.''' 

78 return _NotImplemented(self) 

79 

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

81 '''Not implemented.''' 

82 return _NotImplemented(self, other) 

83 

84 def __str__(self): 

85 n = self.name 

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

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

88 

89 def copy(self, deep=False, name=NN): 

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

91 ''' 

92 n = name or self.copy.__name__ 

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

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

95 

96 fcopy = copy # for backward compatibility 

97 

98 

99class _FstatsBase(_FstatsNamed): 

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

101 ''' 

102 _Ms = () 

103 

104 def _copy(self, d, s): 

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

106 ''' 

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

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

109 d._n = s._n 

110 return d 

111 

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

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

114 self._notOverloaded(xs, sample=sample) 

115 

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

117 '''Accumulate and return the current count. 

118 

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

120 ''' 

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

122 

123 def fmean(self, xs=None): 

124 '''Accumulate and return the current mean. 

125 

126 @kwarg xs: Iterable of additional values (each C{scalar} or 

127 an L{Fsum} or L{Fsum2Tuple} instance). 

128 

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

130 

131 @see: Method C{fadd}. 

132 ''' 

133 return float(self._Mean(xs)) 

134 

135 def fmean_(self, *xs): 

136 '''Accumulate and return the current mean. 

137 

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

139 ''' 

140 return self.fmean(xs) 

141 

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

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

144 

145 @arg xs: Iterable of additional values (each C{scalar} or an 

146 L{Fsum} or L{Fsum2Tuple} instance). 

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

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

149 

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

151 

152 @see: Method C{fadd}. 

153 ''' 

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

155 

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

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

158 

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

160 ''' 

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

162 

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

164 '''Accumulate and return the current variance. 

165 

166 @arg xs: Iterable of additional values (each C{scalar} or an 

167 L{Fsum} or L{Fsum2Tuple} instance). 

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

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

170 

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

172 

173 @see: Method C{fadd}. 

174 ''' 

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

176 

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

178 '''Accumulate and return the current variance. 

179 

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

181 ''' 

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

183 

184 def _iadd_other(self, other): 

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

186 ''' 

187 try: 

188 if _isFsumTuple(other): 

189 self.fadd_(other._Fsum) 

190 elif isscalar(other): 

191 self.fadd_(_2finite(other)) 

192 elif _xiterable(other): 

193 self.fadd(other) 

194 except Exception as X: 

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

196 raise _xError(X, t) 

197 

198 @property_RO 

199 def _M1(self): 

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

201 return self._Ms[0] 

202 

203 @property_RO 

204 def _M2(self): 

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

206 return self._Ms[1] 

207 

208 def _Mean(self, xs=None): 

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

210 ''' 

211 if xs: 

212 self.fadd(xs) 

213 return self._M1 # .copy() 

214 

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

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

217 ''' 

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

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

220 

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

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

223 ''' 

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

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

226 

227 

228class Fcook(_FstatsBase): 

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

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

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

232 and Jarque-Bera normality. 

233 

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

235 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

236 ''' 

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

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

239 

240 @arg xs: Iterable of additional values (each C{scalar} or 

241 an L{Fsum} or L{Fsum2Tuple} instance). 

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

243 

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

245 ''' 

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

247 if name: 

248 self.name = name 

249 if xs: 

250 self.fadd(xs) 

251 

252 def __iadd__(self, other): 

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

254 

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

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

257 or L{Fsum2Tuple} instance). 

258 

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

260 

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

262 

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

264 

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

266 ''' 

267 if isinstance(other, Fcook): 

268 nb = len(other) 

269 if nb > 0: 

270 na = len(self) 

271 if na > 0: 

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

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

274 

275 n = na + nb 

276 _n = _1_0 / n 

277 D = A1 - B1 # b1 - a1 

278 Dn = D * _n 

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

280 nab = na * nb 

281 Dn3 = Dn2 * (D * nab) 

282 

283 na2 = na**2 

284 nb2 = nb**2 

285 A4 += B4 

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

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

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

289 

290 A3 += B3 

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

292 A3 += Dn3 * (na - nb) 

293 

294 A2 += B2 

295 A2 += Dn2 * (nab * _n) 

296 

297 B1n = B1 * nb # if other is self 

298 A1 *= na 

299 A1 += B1n 

300 A1 *= _n 

301 

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

303 self._n = n 

304 else: 

305 self._copy(self, other) 

306 else: 

307 self._iadd_other(other) 

308 return self 

309 

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

311 '''Accumulate and return the current count. 

312 

313 @arg xs: Iterable of additional values (each C{scalar} or an 

314 L{Fsum} or L{Fsum2Tuple} instance). 

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

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

317 

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

319 

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

321 

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

323 

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

325 

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

327 Algorithms_for_calculating_variance>}. 

328 ''' 

329 n = self._n 

330 if xs: 

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

332 for x in _2Floats(xs=xs): # PYCHOK yield 

333 n1 = n 

334 n += 1 

335 D = x - M1 

336 Dn = D / n 

337 if Dn: 

338 Dn2 = Dn**2 

339 if n1 > 1: 

340 T1 = D * (Dn * n1) 

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

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

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

344 T1 = D * Dn 

345 T2 = _0_0 

346 T3 = T1 * Dn2 

347 else: 

348 T1 = T2 = T3 = _0_0 

349 M4 += T3 

350 M4 -= M3 * (Dn * _4_0) 

351 M4 += M2 * (Dn2 * _6_0) 

352 

353 M3 += T2 

354 M3 -= M2 * (Dn * _3_0) 

355 

356 M2 += T1 

357 M1 += Dn 

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

359 self._n = n 

360 return _sampled(n, sample) 

361 

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

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

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

365 

366 @kwarg xs: Iterable of additional values (each C{scalar} or an 

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

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

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

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

371 

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

373 

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

375 ''' 

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

377 

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

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

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

381 

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

383 ''' 

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

385 

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

387 '''Accumulate and return the current kurtosis. 

388 

389 @arg xs: Iterable of additional values (each C{scalar} or an 

390 L{Fsum} or L{Fsum2Tuple} instance). 

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

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

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

394 

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

396 

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

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

399 

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

401 ''' 

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

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

404 

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

406 '''Accumulate and return the current kurtosis. 

407 

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

409 ''' 

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

411 

412 def fmedian(self, xs=None): 

413 '''Accumulate and return the current median. 

414 

415 @arg xs: Iterable of additional values (each C{scalar} or an 

416 L{Fsum} or L{Fsum2Tuple} instance). 

417 

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

419 

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

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

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

423 and method L{Fcook.fadd}. 

424 ''' 

425 return float(self._Median(xs)) 

426 

427 def fmedian_(self, *xs): 

428 '''Accumulate and return the current median. 

429 

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

431 ''' 

432 return self.fmedian(xs) 

433 

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

435 '''Accumulate and return the current skewness. 

436 

437 @arg xs: Iterable of additional values (each C{scalar} or an 

438 L{Fsum} or L{Fsum2Tuple} instance). 

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

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

441 

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

443 

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

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

446 

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

448 ''' 

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

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

451 

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

453 '''Accumulate and return the current skewness. 

454 

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

456 ''' 

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

458 

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

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

461 ''' 

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

463 if n > 0: 

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

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

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

467 return N 

468 

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

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

471 ''' 

472 K = _0_0 

473 if n > 0: 

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

475 M = M2**2 

476 if M > 0: 

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

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

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

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

481 x *= n**2 / d 

482 if excess: 

483 K -= x 

484 return K 

485 

486 def _Median(self, xs=None): 

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

488 ''' 

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

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

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

492 self._Stdev()) / _3_0 

493 

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

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

496 ''' 

497 S = _0_0 

498 if n > 0: 

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

500 M = M2**3 

501 if M > 0: 

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

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

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

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

506 return S 

507 

508 def toFwelford(self, name=NN): 

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

510 ''' 

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

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

513 f._n = self._n 

514 return f 

515 

516 

517class Fwelford(_FstatsBase): 

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

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

520 

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

522 ''' 

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

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

525 

526 @arg xs: Iterable of initial values (each C{scalar} or an 

527 L{Fsum} or L{Fsum2Tuple} instance). 

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

529 

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

531 ''' 

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

533 if name: 

534 self.name = name 

535 if xs: 

536 self.fadd(xs) 

537 

538 def __iadd__(self, other): 

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

540 

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

542 or an iterable of values (each C{scalar} or 

543 an L{Fsum} or L{Fsum2Tuple} instance). 

544 

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

546 

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

548 

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

550 

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

552 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

553 ''' 

554 if isinstance(other, Fwelford): 

555 nb = len(other) 

556 if nb > 0: 

557 na = len(self) 

558 if na > 0: 

559 M, S = self._Ms 

560 M_, S_ = other._Ms 

561 

562 n = na + nb 

563 _n = _1_0 / n 

564 

565 D = M_ - M 

566 D *= D # D**2 

567 D *= na * nb * _n 

568 S += D 

569 S += S_ 

570 

571 Mn = M_ * nb # if other is self 

572 M *= na 

573 M += Mn 

574 M *= _n 

575 

576# self._Ms = M, S 

577 self._n = n 

578 else: 

579 self._copy(self, other) 

580 

581 elif isinstance(other, Fcook): 

582 self += other.toFwelford() 

583 else: 

584 self._iadd_other(other) 

585 return self 

586 

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

588 '''Accumulate and return the current count. 

589 

590 @arg xs: Iterable of additional values (each C{scalar} or an 

591 L{Fsum} or L{Fsum2Tuple} instance). 

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

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

594 

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

596 

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

598 

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

600 

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

602 ''' 

603 n = self._n 

604 if xs: 

605 M, S = self._Ms 

606 for x in _2Floats(xs=xs): # PYCHOK yield 

607 n += 1 

608 D = x - M 

609 M += D / n 

610 D *= x - M 

611 S += D 

612# self._Ms = M, S 

613 self._n = n 

614 return _sampled(n, sample) 

615 

616 

617class Flinear(_FstatsNamed): 

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

619 C{RunningRegression} computing the running slope, intercept 

620 and correlation of a linear regression. 

621 ''' 

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

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

624 

625 @kwarg xs: Iterable of initial C{x} values (each C{scalar} or 

626 an L{Fsum} or L{Fsum2Tuple} instance). 

627 @kwarg ys: Iterable of initial C{y} values (each C{scalar} or 

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

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

630 L{Fwelford}). 

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

632 

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

634 

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

636 ''' 

637 _xsubclassof(Fcook, Fwelford, Fstats=Fstats) 

638 if name: 

639 self.name = name 

640 

641 self._S = Fsum(name=name) 

642 self._X = Fstats(name=name) 

643 self._Y = Fstats(name=name) 

644 if xs and ys: 

645 self.fadd(xs, ys) 

646 

647 def __iadd__(self, other): 

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

649 

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

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

652 

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

654 

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

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

657 are not compatible. 

658 

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

660 

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

662 ''' 

663 if isinstance(other, Flinear): 

664 if len(other) > 0: 

665 if len(self) > 0: 

666 n = other._n 

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

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

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

670 self._S += other._S + D 

671 self._X += other._X 

672 self._Y += other._Y 

673 self._n += n 

674 else: 

675 self._copy(self, other) 

676 else: 

677 try: 

678 if _xiterable(other): 

679 self.fadd_(*other) 

680 except Exception as X: 

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

682 raise _xError(X, op) 

683 return self 

684 

685 def _copy(self, d, s): 

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

687 ''' 

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

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

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

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

692 d._n = s._n 

693 return d 

694 

695 def _Correlation(self, **sample): 

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

697 ''' 

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

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

700 

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

702 '''Accumulate and return the current count. 

703 

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

705 or an L{Fsum} or L{Fsum2Tuple} instance). 

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

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

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

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

710 

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

712 

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

714 

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

716 

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

718 ''' 

719 n = self._n 

720 if xs and ys: 

721 S = self._S 

722 X = self._X 

723 Y = self._Y 

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

725 n1 = n 

726 n += 1 

727 if n1 > 0: 

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

729 X += x 

730 Y += y 

731 self._n = n 

732 return _sampled(n, sample) 

733 

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

735 '''Accumulate and return the current count. 

736 

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

738 (each C{scalar} or an L{Fsum} or L{Fsum2Tuple} 

739 instance). 

740 

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

742 ''' 

743 if isodd(len(x_ys)): 

744 t = _SPACE_(_odd_, len.__name__) 

745 raise _ValueError(t, len(x_ys)) 

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

747 

748 def fcorrelation(self, **sample): 

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

750 

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

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

753 ''' 

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

755 

756 def fintercept(self, **sample): 

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

758 

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

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

761 ''' 

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

763 

764 def fslope(self, **sample): 

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

766 

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

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

769 ''' 

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

771 

772 def _Intercept(self, **sample): 

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

774 ''' 

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

776 

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

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

779 ''' 

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

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

782 

783 def _Slope(self, **sample): 

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

785 ''' 

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

787 

788 @property_RO 

789 def x(self): 

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

791 ''' 

792 return self._X # .copy() 

793 

794 @property_RO 

795 def y(self): 

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

797 ''' 

798 return self._Y # .copy() 

799 

800 

801__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

802 

803# **) MIT License 

804# 

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

806# 

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

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

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

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

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

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

813# 

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

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

816# 

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

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

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

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

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

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

823# OTHER DEALINGS IN THE SOFTWARE.