Coverage for pygeodesy/fstats.py: 98%

327 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-06-10 14:08 -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 _odd_, _SPACE_ 

17from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY 

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

19 property_RO 

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

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

22 

23__all__ = _ALL_LAZY.fstats 

24__version__ = '24.05.21' 

25 

26 

27def _2Floats(**xs): 

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

29 ''' 

30 try: 

31 name, xs = xs.popitem() 

32 except Exception as X: 

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

34 

35 try: 

36 i = None 

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

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

39 except Exception as X: 

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

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

42 

43 

44def _sampled(n, sample): 

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

46 ''' 

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

48 

49 

50class _FstatsNamed(_Named): 

51 '''(INTERNAL) Base class. 

52 ''' 

53 _n = 0 

54 

55 def __add__(self, other): 

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

57 L{Fsum}, L{Fsum2Tuple} or 

58 . 

59 ''' 

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

61 f += other 

62 return f 

63 

64 def __float__(self): # PYCHOK no cover 

65 '''Not implemented.''' 

66 return _NotImplemented(self) 

67 

68 def __int__(self): # PYCHOK no cover 

69 '''Not implemented.''' 

70 return _NotImplemented(self) 

71 

72 def __len__(self): 

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

74 ''' 

75 return self._n 

76 

77 def __neg__(self): # PYCHOK no cover 

78 '''Not implemented.''' 

79 return _NotImplemented(self) 

80 

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

82 '''Not implemented.''' 

83 return _NotImplemented(self, other) 

84 

85 def __str__(self): 

86 n = self.name 

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

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

89 

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

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

92 

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

94 

95 @return: The copy instance. 

96 ''' 

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

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

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

100 

101 fcopy = copy # for backward compatibility 

102 

103 

104class _FstatsBase(_FstatsNamed): 

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

106 ''' 

107 _Ms = () 

108 

109 def _copy(self, d, s): 

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

111 ''' 

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

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

114 d._n = s._n 

115 return d 

116 

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

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

119 self._notOverloaded(xs, sample=sample) 

120 

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

122 '''Accumulate and return the current count. 

123 

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

125 ''' 

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

127 

128 def fmean(self, xs=None): 

129 '''Accumulate and return the current mean. 

130 

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

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

133 

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

135 

136 @see: Method C{fadd}. 

137 ''' 

138 return float(self._Mean(xs)) 

139 

140 def fmean_(self, *xs): 

141 '''Accumulate and return the current mean. 

142 

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

144 ''' 

145 return self.fmean(xs) 

146 

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

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

149 

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

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

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

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

154 

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

156 

157 @see: Method C{fadd}. 

158 ''' 

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

160 

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

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

163 

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

165 ''' 

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

167 

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

169 '''Accumulate and return the current variance. 

170 

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

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

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

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

175 

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

177 

178 @see: Method C{fadd}. 

179 ''' 

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

181 

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

183 '''Accumulate and return the current variance. 

184 

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

186 ''' 

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

188 

189 def _iadd_other(self, other): 

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

191 ''' 

192 try: 

193 if _isFsumTuple(other): 

194 self.fadd_(other._Fsum) 

195 elif isscalar(other): 

196 self.fadd_(_2finite(other)) 

197 elif _xiterable(other): 

198 self.fadd(other) 

199 except Exception as X: 

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

201 raise _xError(X, t) 

202 

203 @property_RO 

204 def _M1(self): 

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

206 return self._Ms[0] 

207 

208 @property_RO 

209 def _M2(self): 

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

211 return self._Ms[1] 

212 

213 def _Mean(self, xs=None): 

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

215 ''' 

216 if xs: 

217 self.fadd(xs) 

218 return self._M1 # .copy() 

219 

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

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

222 ''' 

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

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

225 

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

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

228 ''' 

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

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

231 

232 

233class Fcook(_FstatsBase): 

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

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

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

237 and Jarque-Bera normality. 

238 

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

240 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

241 ''' 

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

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

244 

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

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

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

248 

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

250 ''' 

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

252 if name: 

253 self.name = name 

254 if xs: 

255 self.fadd(xs) 

256 

257 def __iadd__(self, other): 

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

259 

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

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

262 or L{Fsum2Tuple} instance). 

263 

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

265 

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

267 

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

269 

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

271 ''' 

272 if isinstance(other, Fcook): 

273 nb = len(other) 

274 if nb > 0: 

275 na = len(self) 

276 if na > 0: 

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

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

279 

280 n = na + nb 

281 _n = _1_0 / n 

282 D = A1 - B1 # b1 - a1 

283 Dn = D * _n 

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

285 nab = na * nb 

286 Dn3 = Dn2 * (D * nab) 

287 

288 na2 = na**2 

289 nb2 = nb**2 

290 A4 += B4 

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

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

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

294 

295 A3 += B3 

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

297 A3 += Dn3 * (na - nb) 

298 

299 A2 += B2 

300 A2 += Dn2 * (nab * _n) 

301 

302 B1n = B1 * nb # if other is self 

303 A1 *= na 

304 A1 += B1n 

305 A1 *= _n 

306 

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

308 self._n = n 

309 else: 

310 self._copy(self, other) 

311 else: 

312 self._iadd_other(other) 

313 return self 

314 

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

316 '''Accumulate and return the current count. 

317 

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

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

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

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

322 

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

324 

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

326 

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

328 

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

330 

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

332 Algorithms_for_calculating_variance>}. 

333 ''' 

334 n = self._n 

335 if xs: 

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

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

338 n1 = n 

339 n += 1 

340 D = x - M1 

341 Dn = D / n 

342 if Dn: 

343 Dn2 = Dn**2 

344 if n1 > 1: 

345 T1 = D * (Dn * n1) 

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

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

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

349 T1 = D * Dn 

350 T2 = _0_0 

351 T3 = T1 * Dn2 

352 else: 

353 T1 = T2 = T3 = _0_0 

354 M4 += T3 

355 M4 -= M3 * (Dn * _4_0) 

356 M4 += M2 * (Dn2 * _6_0) 

357 

358 M3 += T2 

359 M3 -= M2 * (Dn * _3_0) 

360 

361 M2 += T1 

362 M1 += Dn 

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

364 self._n = n 

365 return _sampled(n, sample) 

366 

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

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

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

370 

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

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

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

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

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

376 

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

378 

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

380 ''' 

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

382 

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

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

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

386 

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

388 ''' 

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

390 

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

392 '''Accumulate and return the current kurtosis. 

393 

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

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

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

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

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

399 

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

401 

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

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

404 

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

406 ''' 

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

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

409 

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

411 '''Accumulate and return the current kurtosis. 

412 

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

414 ''' 

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

416 

417 def fmedian(self, xs=None): 

418 '''Accumulate and return the current median. 

419 

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

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

422 

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

424 

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

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

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

428 and method L{Fcook.fadd}. 

429 ''' 

430 return float(self._Median(xs)) 

431 

432 def fmedian_(self, *xs): 

433 '''Accumulate and return the current median. 

434 

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

436 ''' 

437 return self.fmedian(xs) 

438 

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

440 '''Accumulate and return the current skewness. 

441 

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

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

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

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

446 

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

448 

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

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

451 

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

453 ''' 

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

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

456 

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

458 '''Accumulate and return the current skewness. 

459 

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

461 ''' 

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

463 

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

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

466 ''' 

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

468 if n > 0: 

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

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

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

472 return N 

473 

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

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

476 ''' 

477 K = _0_0 

478 if n > 0: 

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

480 M = M2**2 

481 if M > 0: 

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

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

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

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

486 x *= n**2 / d 

487 if excess: 

488 K -= x 

489 return K 

490 

491 def _Median(self, xs=None): 

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

493 ''' 

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

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

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

497 self._Stdev()) / _3_0 

498 

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

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

501 ''' 

502 S = _0_0 

503 if n > 0: 

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

505 M = M2**3 

506 if M > 0: 

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

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

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

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

511 return S 

512 

513 def toFwelford(self, **name): 

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

515 

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

517 ''' 

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

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

520 f._n = self._n 

521 return f 

522 

523 

524class Fwelford(_FstatsBase): 

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

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

527 

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

529 ''' 

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

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

532 

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

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

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

536 

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

538 ''' 

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

540 if name: 

541 self.name = name 

542 if xs: 

543 self.fadd(xs) 

544 

545 def __iadd__(self, other): 

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

547 

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

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

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

551 

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

553 

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

555 

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

557 

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

559 WikiPedia.org/wiki/Algorithms_for_calculating_variance>}. 

560 ''' 

561 if isinstance(other, Fwelford): 

562 nb = len(other) 

563 if nb > 0: 

564 na = len(self) 

565 if na > 0: 

566 M, S = self._Ms 

567 M_, S_ = other._Ms 

568 

569 n = na + nb 

570 _n = _1_0 / n 

571 

572 D = M_ - M 

573 D *= D # D**2 

574 D *= na * nb * _n 

575 S += D 

576 S += S_ 

577 

578 Mn = M_ * nb # if other is self 

579 M *= na 

580 M += Mn 

581 M *= _n 

582 

583# self._Ms = M, S 

584 self._n = n 

585 else: 

586 self._copy(self, other) 

587 

588 elif isinstance(other, Fcook): 

589 self += other.toFwelford() 

590 else: 

591 self._iadd_other(other) 

592 return self 

593 

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

595 '''Accumulate and return the current count. 

596 

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

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

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

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

601 

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

603 

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

605 

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

607 

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

609 ''' 

610 n = self._n 

611 if xs: 

612 M, S = self._Ms 

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

614 n += 1 

615 D = x - M 

616 M += D / n 

617 D *= x - M 

618 S += D 

619# self._Ms = M, S 

620 self._n = n 

621 return _sampled(n, sample) 

622 

623 

624class Flinear(_FstatsNamed): 

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

626 C{RunningRegression} computing the running slope, intercept 

627 and correlation of a linear regression. 

628 ''' 

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

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

631 

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

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

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

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

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

637 L{Fwelford}). 

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

639 

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

641 

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

643 ''' 

644 _xsubclassof(Fcook, Fwelford, Fstats=Fstats) 

645 if name: 

646 self.name = name 

647 

648 self._S = Fsum(name=name) 

649 self._X = Fstats(name=name) 

650 self._Y = Fstats(name=name) 

651 if xs and ys: 

652 self.fadd(xs, ys) 

653 

654 def __iadd__(self, other): 

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

656 

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

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

659 

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

661 

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

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

664 are not compatible. 

665 

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

667 

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

669 ''' 

670 if isinstance(other, Flinear): 

671 if len(other) > 0: 

672 if len(self) > 0: 

673 n = other._n 

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

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

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

677 self._S += other._S + D 

678 self._X += other._X 

679 self._Y += other._Y 

680 self._n += n 

681 else: 

682 self._copy(self, other) 

683 else: 

684 try: 

685 if _xiterable(other): 

686 self.fadd_(*other) 

687 except Exception as X: 

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

689 raise _xError(X, op) 

690 return self 

691 

692 def _copy(self, d, s): 

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

694 ''' 

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

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

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

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

699 d._n = s._n 

700 return d 

701 

702 def _Correlation(self, **sample): 

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

704 ''' 

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

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

707 

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

709 '''Accumulate and return the current count. 

710 

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

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

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

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

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

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

717 

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

719 

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

721 

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

723 

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

725 ''' 

726 n = self._n 

727 if xs and ys: 

728 S = self._S 

729 X = self._X 

730 Y = self._Y 

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

732 n1 = n 

733 n += 1 

734 if n1 > 0: 

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

736 X += x 

737 Y += y 

738 self._n = n 

739 return _sampled(n, sample) 

740 

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

742 '''Accumulate and return the current count. 

743 

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

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

746 instance). 

747 

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

749 ''' 

750 if isodd(len(x_ys)): 

751 t = _SPACE_(_odd_, len.__name__) 

752 raise _ValueError(t, len(x_ys)) 

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

754 

755 def fcorrelation(self, **sample): 

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

757 

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

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

760 ''' 

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

762 

763 def fintercept(self, **sample): 

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

765 

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

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

768 ''' 

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

770 

771 def fslope(self, **sample): 

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

773 

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

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

776 ''' 

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

778 

779 def _Intercept(self, **sample): 

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

781 ''' 

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

783 

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

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

786 ''' 

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

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

789 

790 def _Slope(self, **sample): 

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

792 ''' 

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

794 

795 @property_RO 

796 def x(self): 

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

798 ''' 

799 return self._X # .copy() 

800 

801 @property_RO 

802 def y(self): 

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

804 ''' 

805 return self._Y # .copy() 

806 

807 

808__all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed) 

809 

810# **) MIT License 

811# 

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

813# 

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

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

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

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

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

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

820# 

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

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

823# 

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

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

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

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

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

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

830# OTHER DEALINGS IN THE SOFTWARE.