1

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

3

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

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

6'''

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

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

9

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

11 _xiterable, _xsubclassof, _zip

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

13 from pygeodesy.errors import _AssertionError, _ValueError, _xError

14 from pygeodesy.fmath import Fsqrt, Fmt

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

16 from pygeodesy.interns import _odd_, _SPACE_

17 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY

18 from 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

27 def _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

44 def _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

50 class _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

104 class _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

233 class 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

524 class 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

624 class 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.