pygeodesy 24.3.24__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. pygeodesy/wgrs.py +439 -0
pygeodesy/fstats.py ADDED
@@ -0,0 +1,760 @@
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 isodd, islistuple, _xinstanceof, \
11
+ _xsubclassof, _zip
12
+ from pygeodesy.constants import _0_0, _2_0, _3_0, _4_0, _6_0, _xError
13
+ # from pygeodesy.errors import _xError # from .constants
14
+ from pygeodesy.fmath import hypot2, sqrt
15
+ from pygeodesy.fsums import _2float, Fsum, Fmt
16
+ from pygeodesy.interns import NN, _iadd_op_, _invalid_, _other_, _SPACE_
17
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY
18
+ from pygeodesy.named import _Named, _NotImplemented, notOverloaded, \
19
+ property_RO
20
+ # from pygeodesy.props import property_RO # from .named
21
+ # from pygeodesy.streprs import Fmt # from .fsums
22
+
23
+ # from math import sqrt # from .fmath
24
+
25
+ __all__ = _ALL_LAZY.fstats
26
+ __version__ = '23.09.22'
27
+
28
+ _Floats = Fsum, float
29
+ _Scalar = _Floats + (int,) # XXX basics._Ints is ABCMeta in Python 2
30
+ try:
31
+ _Scalar += (long,)
32
+ except NameError: # Python 3+
33
+ pass
34
+
35
+
36
+ def _2Floats(xs, ys=False):
37
+ '''(INTERNAL) Yield each value as C{float} or L{Fsum}.
38
+ '''
39
+ if ys:
40
+ def _2f(i, x):
41
+ return _2float(index=i, ys=x)
42
+ else:
43
+ def _2f(i, x): # PYCHOK redef
44
+ return _2float(index=i, xs=x)
45
+
46
+ for i, x in enumerate(xs): # don't unravel Fsums
47
+ yield x if isinstance(x, _Floats) else _2f(i, x)
48
+
49
+
50
+ def _sampled(n, sample):
51
+ '''(INTERNAL) Return the sample or the entire count.
52
+ '''
53
+ return (n - 1) if sample and n > 0 else n
54
+
55
+
56
+ class _FstatsNamed(_Named):
57
+ '''(INTERNAL) Base class.
58
+ '''
59
+ _n = 0
60
+
61
+ def __add__(self, other):
62
+ '''Sum of this and a scalar, an L{Fsum} or an other instance.
63
+ '''
64
+ f = self.fcopy(name=self.__add__.__name__) # PYCHOK expected
65
+ f += other
66
+ return f
67
+
68
+ def __float__(self): # PYCHOK no cover
69
+ '''Not implemented.'''
70
+ return _NotImplemented(self)
71
+
72
+ def __int__(self): # PYCHOK no cover
73
+ '''Not implemented.'''
74
+ return _NotImplemented(self)
75
+
76
+ def __len__(self):
77
+ '''Return the I{total} number of accumulated values (C{int}).
78
+ '''
79
+ return self._n
80
+
81
+ def __neg__(self): # PYCHOK no cover
82
+ '''Not implemented.'''
83
+ return _NotImplemented(self)
84
+
85
+ def __radd__(self, other): # PYCHOK no cover
86
+ '''Not implemented.'''
87
+ return _NotImplemented(self, other)
88
+
89
+ def __str__(self):
90
+ return Fmt.SQUARE(self.named3, len(self))
91
+
92
+ def fcopy(self, deep=False, name=NN):
93
+ '''Copy this instance, C{shallow} or B{C{deep}}.
94
+ '''
95
+ n = name or self.fcopy.__name__
96
+ f = _Named.copy(self, deep=deep, name=n)
97
+ return self._copy(f, self) # PYCHOK expected
98
+
99
+ copy = fcopy
100
+
101
+
102
+ class _FstatsBase(_FstatsNamed):
103
+ '''(INTERNAL) Base running stats class.
104
+ '''
105
+ _Ms = ()
106
+
107
+ def _copy(self, c, s):
108
+ '''(INTERNAL) Copy C{B{c} = B{s}}.
109
+ '''
110
+ _xinstanceof(self.__class__, c=c, s=s)
111
+ c._Ms = tuple(M.fcopy() for M in s._Ms) # deep=False
112
+ c._n = s._n
113
+ return c
114
+
115
+ def fadd(self, xs, sample=False): # PYCHOK no cover
116
+ '''I{Must be overloaded}.'''
117
+ notOverloaded(self, xs, sample=sample)
118
+
119
+ def fadd_(self, *xs, **sample):
120
+ '''Accumulate and return the current count.
121
+
122
+ @see: Method C{fadd}.
123
+ '''
124
+ return self.fadd(xs, **sample)
125
+
126
+ def fmean(self, xs=None):
127
+ '''Accumulate and return the current mean.
128
+
129
+ @kwarg xs: Iterable with additional values (C{Scalar}s).
130
+
131
+ @return: Current, running mean (C{float}).
132
+
133
+ @see: Method C{fadd}.
134
+ '''
135
+ if xs:
136
+ self.fadd(xs)
137
+ return self._M1.fsum()
138
+
139
+ def fmean_(self, *xs):
140
+ '''Accumulate and return the current mean.
141
+
142
+ @see: Method C{fmean}.
143
+ '''
144
+ return self.fmean(xs)
145
+
146
+ def fstdev(self, xs=None, sample=False):
147
+ '''Accumulate and return the current standard deviation.
148
+
149
+ @kwarg xs: Iterable with additional values (C{Scalar}).
150
+ @kwarg sample: Return the I{sample} instead of the entire
151
+ I{population} standard deviation (C{bool}).
152
+
153
+ @return: Current, running (sample) standard deviation (C{float}).
154
+
155
+ @see: Method C{fadd}.
156
+ '''
157
+ v = self.fvariance(xs, sample=sample)
158
+ return sqrt(v) if v > 0 else _0_0
159
+
160
+ def fstdev_(self, *xs, **sample):
161
+ '''Accumulate and return the current standard deviation.
162
+
163
+ @see: Method C{fstdev}.
164
+ '''
165
+ return self.fstdev(xs, **sample)
166
+
167
+ def fvariance(self, xs=None, sample=False):
168
+ '''Accumulate and return the current variance.
169
+
170
+ @kwarg xs: Iterable with additional values (C{Scalar}s).
171
+ @kwarg sample: Return the I{sample} instead of the entire
172
+ I{population} variance (C{bool}).
173
+
174
+ @return: Current, running (sample) variance (C{float}).
175
+
176
+ @see: Method C{fadd}.
177
+ '''
178
+ n = self.fadd(xs, sample=sample)
179
+ return float(self._M2 / float(n)) if n > 0 else _0_0
180
+
181
+ def fvariance_(self, *xs, **sample):
182
+ '''Accumulate and return the current variance.
183
+
184
+ @see: Method C{fvariance}.
185
+ '''
186
+ return self.fvariance(xs, **sample)
187
+
188
+ def _iadd_other(self, other):
189
+ '''(INTERNAL) Add Scalar or Scalars.
190
+ '''
191
+ if isinstance(other, _Scalar):
192
+ self.fadd_(other)
193
+ else:
194
+ try:
195
+ if not islistuple(other):
196
+ raise TypeError(_SPACE_(_invalid_, _other_))
197
+ self.fadd(other)
198
+ except Exception as x:
199
+ raise _xError(x, _SPACE_(self, _iadd_op_, repr(other)))
200
+
201
+ @property_RO
202
+ def _M1(self):
203
+ '''(INTERNAL) get the 1st Moment accumulator.'''
204
+ return self._Ms[0]
205
+
206
+ @property_RO
207
+ def _M2(self):
208
+ '''(INTERNAL) get the 2nd Moment accumulator.'''
209
+ return self._Ms[1]
210
+
211
+
212
+ class Fcook(_FstatsBase):
213
+ '''U{Cook<https://www.JohnDCook.com/blog/skewness_kurtosis>}'s
214
+ C{RunningStats} computing the running mean, median and
215
+ (sample) kurtosis, skewness, variance, standard deviation
216
+ and Jarque-Bera normality.
217
+
218
+ @see: L{Fwelford} and U{Higher-order statistics<https://
219
+ WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
220
+ '''
221
+ def __init__(self, xs=None, name=NN):
222
+ '''New L{Fcook} stats accumulator.
223
+
224
+ @kwarg xs: Iterable with initial values (C{Scalar}s).
225
+ @kwarg name: Optional name (C{str}).
226
+
227
+ @see: Method L{Fcook.fadd}.
228
+ '''
229
+ self._Ms = tuple(Fsum() for _ in range(4)) # 1st, 2nd ... Moment
230
+ if name:
231
+ self.name = name
232
+ if xs:
233
+ self.fadd(xs)
234
+
235
+ def __iadd__(self, other):
236
+ '''Add B{C{other}} to this L{Fcook} instance.
237
+
238
+ @arg other: An L{Fcook} instance or C{Scalar}s, meaning
239
+ one or more C{scalar} or L{Fsum} instances.
240
+
241
+ @return: This instance, updated (L{Fcook}).
242
+
243
+ @raise TypeError: Invalid B{C{other}} type.
244
+
245
+ @raise ValueError: Invalid B{C{other}}.
246
+
247
+ @see: Method L{Fcook.fadd}.
248
+ '''
249
+ if isinstance(other, Fcook):
250
+ nb = len(other)
251
+ if nb > 0:
252
+ na = len(self)
253
+ if na > 0:
254
+ A1, A2, A3, A4 = self._Ms
255
+ B1, B2, B3, B4 = other._Ms
256
+
257
+ n = na + nb
258
+ n_ = float(n)
259
+ D = A1 - B1 # b1 - a1
260
+ Dn = D / n_
261
+ Dn2 = Dn**2 # d**2 / n**2
262
+ nab = na * nb
263
+ Dn3 = Dn2 * (D * nab)
264
+
265
+ na2 = na**2
266
+ nb2 = nb**2
267
+ A4 += B4
268
+ A4 += (B3 * na - (A3 * nb)) * (Dn * _4_0)
269
+ A4 += (B2 * na2 + (A2 * nb2)) * (Dn2 * _6_0)
270
+ A4 += (Dn * Dn3) * (na2 - nab + nb2) # d**4 / n**3
271
+
272
+ A3 += B3
273
+ A3 += (A2 * na - (B2 * nb)) * (Dn * _3_0)
274
+ A3 += Dn3 * (na - nb)
275
+
276
+ A2 += B2
277
+ A2 += Dn2 * (nab / n_)
278
+
279
+ B1n = B1 * nb # if other is self
280
+ A1 *= na
281
+ A1 += B1n
282
+ A1 *= 1 / n_ # /= chokes PyChecker
283
+
284
+ # self._Ms = A1, A2, A3, A4
285
+ self._n = n
286
+ else:
287
+ self._copy(self, other)
288
+ else:
289
+ self._iadd_other(other)
290
+ return self
291
+
292
+ def fadd(self, xs, sample=False):
293
+ '''Accumulate and return the current count.
294
+
295
+ @arg xs: Iterable with additional values (C{Scalar}s,
296
+ meaning C{scalar} or L{Fsum} instances).
297
+ @kwarg sample: Return the I{sample} instead of the entire
298
+ I{population} count (C{bool}).
299
+
300
+ @return: Current, running (sample) count (C{int}).
301
+
302
+ @raise OverflowError: Partial C{2sum} overflow.
303
+
304
+ @raise TypeError: Non-scalar B{C{xs}} value.
305
+
306
+ @raise ValueError: Invalid or non-finite B{C{xs}} value.
307
+
308
+ @see: U{online_kurtosis<https://WikiPedia.org/wiki/
309
+ Algorithms_for_calculating_variance>}.
310
+ '''
311
+ n = self._n
312
+ if xs:
313
+ M1, M2, M3, M4 = self._Ms
314
+ for x in _2Floats(xs):
315
+ n1 = n
316
+ n += 1
317
+ D = x - M1
318
+ Dn = D / n
319
+ if Dn:
320
+ Dn2 = Dn**2
321
+ if n1 > 1:
322
+ T1 = D * (Dn * n1)
323
+ T2 = T1 * (Dn * (n1 - 1))
324
+ T3 = T1 * (Dn2 * (n**2 - 3 * n1))
325
+ elif n1 > 0: # n1 == 1, n == 2
326
+ T1 = D * Dn
327
+ T2 = _0_0
328
+ T3 = T1 * Dn2
329
+ else:
330
+ T1 = T2 = T3 = _0_0
331
+ M4 += T3
332
+ M4 -= M3 * (Dn * _4_0)
333
+ M4 += M2 * (Dn2 * _6_0)
334
+
335
+ M3 += T2
336
+ M3 -= M2 * (Dn * _3_0)
337
+
338
+ M2 += T1
339
+ M1 += Dn
340
+ # self._Ms = M1, M2, M3, M4
341
+ self._n = n
342
+ return _sampled(n, sample)
343
+
344
+ def fjb(self, xs=None, sample=True, excess=True):
345
+ '''Accumulate and compute the current U{Jarque-Bera
346
+ <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
347
+
348
+ @kwarg xs: Iterable with additional values (C{Scalar}s).
349
+ @kwarg sample: Return the I{sample} normality (C{bool}), default.
350
+ @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
351
+
352
+ @return: Current, running (sample) Jarque-Bera normality (C{float}).
353
+
354
+ @see: Method L{Fcook.fadd}.
355
+ '''
356
+ n = self.fadd(xs, sample=sample)
357
+ k = self.fkurtosis(sample=sample, excess=excess) / _2_0
358
+ s = self.fskewness(sample=sample)
359
+ return n * hypot2(k, s) / _6_0
360
+
361
+ def fjb_(self, *xs, **sample_excess):
362
+ '''Accumulate and compute the current U{Jarque-Bera
363
+ <https://WikiPedia.org/wiki/Jarque–Bera_test>} normality.
364
+
365
+ @see: Method L{Fcook.fjb}.
366
+ '''
367
+ return self.fjb(xs, **sample_excess)
368
+
369
+ def fkurtosis(self, xs=None, sample=False, excess=True):
370
+ '''Accumulate and return the current kurtosis.
371
+
372
+ @kwarg xs: Iterable with additional values (C{Scalar}s).
373
+ @kwarg sample: Return the I{sample} instead of the entire
374
+ I{population} kurtosis (C{bool}).
375
+ @kwarg excess: Return the I{excess} kurtosis (C{bool}), default.
376
+
377
+ @return: Current, running (sample) kurtosis or I{excess} kurtosis (C{float}).
378
+
379
+ @see: U{Kurtosis Formula<https://www.Macroption.com/kurtosis-formula>}
380
+ and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.
381
+
382
+ @see: Method L{Fcook.fadd}.
383
+ '''
384
+ k, n = _0_0, self.fadd(xs, sample=sample)
385
+ if n > 0:
386
+ _, M2, _, M4 = self._Ms
387
+ m2 = float(M2 * M2)
388
+ if m2:
389
+ K, x = (M4 * (n / m2)), _3_0
390
+ if sample and 2 < n < len(self):
391
+ d = float((n - 1) * (n - 2))
392
+ K *= (n + 1) * (n + 2) / d
393
+ x *= n**2 / d
394
+ if excess:
395
+ K -= x
396
+ k = K.fsum()
397
+ return k
398
+
399
+ def fkurtosis_(self, *xs, **sample_excess):
400
+ '''Accumulate and return the current kurtosis.
401
+
402
+ @see: Method L{Fcook.fkurtosis}.
403
+ '''
404
+ return self.fkurtosis(xs, **sample_excess)
405
+
406
+ def fmedian(self, xs=None):
407
+ '''Accumulate and return the current median.
408
+
409
+ @kwarg xs: Iterable with additional values (C{Scalar}s).
410
+
411
+ @return: Current, running median (C{float}).
412
+
413
+ @see: U{Pearson's Skewness Coefficients<https://MathWorld.Wolfram.com/
414
+ PearsonsSkewnessCoefficients.html>}, U{Skewness & Kurtosis Simplified
415
+ https://TowardsDataScience.com/skewness-kurtosis-simplified-1338e094fc85>}
416
+ and method L{Fcook.fadd}.
417
+ '''
418
+ # skewness = 3 * (mean - median) / stdev, i.e.
419
+ # median = mean - skewness * stdef / 3
420
+ m = float(self._M1) if xs is None else self.fmean(xs)
421
+ return m - self.fskewness() * self.fstdev() / _3_0
422
+
423
+ def fmedian_(self, *xs):
424
+ '''Accumulate and return the current median.
425
+
426
+ @see: Method L{Fcook.fmedian}.
427
+ '''
428
+ return self.fmedian(xs)
429
+
430
+ def fskewness(self, xs=None, sample=False):
431
+ '''Accumulate and return the current skewness.
432
+
433
+ @kwarg xs: Iterable with additional values (C{Scalar}s).
434
+ @kwarg sample: Return the I{sample} instead of the entire
435
+ I{population} skewness (C{bool}).
436
+
437
+ @return: Current, running (sample) skewness (C{float}).
438
+
439
+ @see: U{Skewness Formula<https://www.Macroption.com/skewness-formula/>}
440
+ and U{Mantalos<https://www.ResearchGate.net/publication/227440210>}.
441
+
442
+ @see: Method L{Fcook.fadd}.
443
+ '''
444
+ s, n = _0_0, self.fadd(xs, sample=sample)
445
+ if n > 0:
446
+ _, M2, M3, _ = self._Ms
447
+ m = float(M2**3)
448
+ if m > 0:
449
+ S = M3 * sqrt(float(n) / m)
450
+ if sample and 1 < n < len(self):
451
+ S *= (n + 1) / float(n - 1)
452
+ s = S.fsum()
453
+ return s
454
+
455
+ def fskewness_(self, *xs, **sample):
456
+ '''Accumulate and return the current skewness.
457
+
458
+ @see: Method L{Fcook.fskewness}.
459
+ '''
460
+ return self.fskewness(xs, **sample)
461
+
462
+ def toFwelford(self, name=NN):
463
+ '''Return an L{Fwelford} equivalent.
464
+ '''
465
+ f = Fwelford(name=name or self.name)
466
+ f._Ms = self._M1.fcopy(), self._M2.fcopy() # deep=False
467
+ f._n = self._n
468
+ return f
469
+
470
+
471
+ class Fwelford(_FstatsBase):
472
+ '''U{Welford<https://WikiPedia.org/wiki/Algorithms_for_calculating_variance>}'s
473
+ accumulator computing the running mean, (sample) variance and standard deviation.
474
+
475
+ @see: U{Cook<https://www.JohnDCook.com/blog/standard_deviation/>} and L{Fcook}.
476
+ '''
477
+ def __init__(self, xs=None, name=NN):
478
+ '''New L{Fwelford} stats accumulator.
479
+
480
+ @kwarg xs: Iterable with initial values (C{Scalar}s).
481
+ @kwarg name: Optional name (C{str}).
482
+
483
+ @see: Method L{Fwelford.fadd}.
484
+ '''
485
+ self._Ms = Fsum(), Fsum() # 1st and 2nd Moment
486
+ if name:
487
+ self.name = name
488
+ if xs:
489
+ self.fadd(xs)
490
+
491
+ def __iadd__(self, other):
492
+ '''Add B{C{other}} to this L{Fwelford} instance.
493
+
494
+ @arg other: An L{Fwelford} or L{Fcook} instance or C{Scalar}s,
495
+ meaning one or more C{scalar} or L{Fsum} instances.
496
+
497
+ @return: This instance, updated (L{Fwelford}).
498
+
499
+ @raise TypeError: Invalid B{C{other}} type.
500
+
501
+ @raise ValueError: Invalid B{C{other}}.
502
+
503
+ @see: Method L{Fwelford.fadd} and U{Parallel algorithm<https//
504
+ WikiPedia.org/wiki/Algorithms_for_calculating_variance>}.
505
+ '''
506
+ if isinstance(other, Fwelford):
507
+ nb = len(other)
508
+ if nb > 0:
509
+ na = len(self)
510
+ if na > 0:
511
+ M, S = self._Ms
512
+ M_, S_ = other._Ms
513
+
514
+ n = na + nb
515
+ n_ = float(n)
516
+
517
+ D = M_ - M
518
+ D *= D # D**2
519
+ D *= na * nb / n_
520
+ S += D
521
+ S += S_
522
+
523
+ Mn = M_ * nb # if other is self
524
+ M *= na
525
+ M += Mn
526
+ M *= 1 / n_ # /= chokes PyChecker
527
+
528
+ # self._Ms = M, S
529
+ self._n = n
530
+ else:
531
+ self._copy(self, other)
532
+
533
+ elif isinstance(other, Fcook):
534
+ self += other.toFwelford()
535
+ else:
536
+ self._iadd_other(other)
537
+ return self
538
+
539
+ def fadd(self, xs, sample=False):
540
+ '''Accumulate and return the current count.
541
+
542
+ @arg xs: Iterable with additional values (C{Scalar}s,
543
+ meaning C{scalar} or L{Fsum} instances).
544
+ @kwarg sample: Return the I{sample} instead of the entire
545
+ I{population} count (C{bool}).
546
+
547
+ @return: Current, running (sample) count (C{int}).
548
+
549
+ @raise OverflowError: Partial C{2sum} overflow.
550
+
551
+ @raise TypeError: Non-scalar B{C{xs}} value.
552
+
553
+ @raise ValueError: Invalid or non-finite B{C{xs}} value.
554
+ '''
555
+ n = self._n
556
+ if xs:
557
+ M, S = self._Ms
558
+ for x in _2Floats(xs):
559
+ n += 1
560
+ D = x - M
561
+ M += D / n
562
+ D *= x - M
563
+ S += D
564
+ # self._Ms = M, S
565
+ self._n = n
566
+ return _sampled(n, sample)
567
+
568
+
569
+ class Flinear(_FstatsNamed):
570
+ '''U{Cook<https://www.JohnDCook.com/blog/running_regression>}'s
571
+ C{RunningRegression} computing the running slope, intercept
572
+ and correlation of a linear regression.
573
+ '''
574
+ def __init__(self, xs=None, ys=None, Fstats=Fwelford, name=NN):
575
+ '''New L{Flinear} regression accumulator.
576
+
577
+ @kwarg xs: Iterable with initial C{x} values (C{Scalar}s).
578
+ @kwarg ys: Iterable with initial C{y} values (C{Scalar}s).
579
+ @kwarg Fstats: Stats class for C{x} and C{y} values (L{Fcook}
580
+ or L{Fwelford}).
581
+ @kwarg name: Optional name (C{str}).
582
+
583
+ @raise TypeError: Invalid B{C{Fs}}, not L{Fcook} or
584
+ L{Fwelford}.
585
+ @see: Method L{Flinear.fadd}.
586
+ '''
587
+ _xsubclassof(Fcook, Fwelford, Fstats=Fstats)
588
+ if name:
589
+ self.name = name
590
+
591
+ self._S = Fsum(name=name)
592
+ self._X = Fstats(name=name)
593
+ self._Y = Fstats(name=name)
594
+ if xs and ys:
595
+ self.fadd(xs, ys)
596
+
597
+ def __iadd__(self, other):
598
+ '''Add B{C{other}} to this instance.
599
+
600
+ @arg other: An L{Flinear} instance or C{Scalar} pairs,
601
+ meaning C{scalar} or L{Fsum} instances.
602
+
603
+ @return: This instance, updated (L{Flinear}).
604
+
605
+ @raise TypeError: Invalid B{C{other}} or the B{C{other}}
606
+ and these C{x} and C{y} accumulators
607
+ are not compatible.
608
+
609
+ @raise ValueError: Invalid or odd-length B{C{other}}.
610
+
611
+ @see: Method L{Flinear.fadd_}.
612
+ '''
613
+ if isinstance(other, Flinear):
614
+ if len(other) > 0:
615
+ if len(self) > 0:
616
+ n = other._n
617
+ S = other._S
618
+ X = other._X
619
+ Y = other._Y
620
+ D = (X._M1 - self._X._M1) * \
621
+ (Y._M1 - self._Y._M1) * \
622
+ (n * self._n / float(n + self._n))
623
+ self._n += n
624
+ self._S += S + D
625
+ self._X += X
626
+ self._Y += Y
627
+ else:
628
+ self._copy(self, other)
629
+ else:
630
+ try:
631
+ if not islistuple(other):
632
+ raise TypeError(_SPACE_(_invalid_, _other_))
633
+ elif isodd(len(other)):
634
+ raise ValueError(Fmt.PAREN(isodd=Fmt.PAREN(len=_other_)))
635
+ self.fadd_(*other)
636
+ except Exception as x:
637
+ raise _xError(x, _SPACE_(self, _iadd_op_, repr(other)))
638
+ return self
639
+
640
+ def _copy(self, c, s):
641
+ '''(INTERNAL) Copy C{B{c} = B{s}}.
642
+ '''
643
+ _xinstanceof(Flinear, c=c, s=s)
644
+ c._n = s._n
645
+ c._S = s._S.fcopy(deep=False)
646
+ c._X = s._X.fcopy(deep=False)
647
+ c._Y = s._Y.fcopy(deep=False)
648
+ return c
649
+
650
+ def fadd(self, xs, ys, sample=False):
651
+ '''Accumulate and return the current count.
652
+
653
+ @arg xs: Iterable with additional C{x} values (C{Scalar}s),
654
+ meaning C{scalar} or L{Fsum} instances).
655
+ @arg ys: Iterable with additional C{y} values (C{Scalar}s,
656
+ meaning C{scalar} or L{Fsum} instances).
657
+ @kwarg sample: Return the I{sample} instead of the entire
658
+ I{population} count (C{bool}).
659
+
660
+ @return: Current, running (sample) count (C{int}).
661
+
662
+ @raise OverflowError: Partial C{2sum} overflow.
663
+
664
+ @raise TypeError: Non-scalar B{C{xs}} or B{C{ys}} value.
665
+
666
+ @raise ValueError: Invalid or non-finite B{C{xs}} or B{C{ys}} value.
667
+ '''
668
+ n = self._n
669
+ if xs and ys:
670
+ S = self._S
671
+ X = self._X
672
+ Y = self._Y
673
+ for x, y in _zip(_2Floats(xs), _2Floats(ys, ys=True)): # strict=True
674
+ n1 = n
675
+ n += 1
676
+ if n1 > 0:
677
+ S += (X._M1 - x) * (Y._M1 - y) * (n1 / float(n))
678
+ X += x
679
+ Y += y
680
+ self._n = n
681
+ return _sampled(n, sample)
682
+
683
+ def fadd_(self, *x_ys, **sample):
684
+ '''Accumulate and return the current count.
685
+
686
+ @arg x_ys: Individual, alternating C{x, y, x, y, ...}
687
+ positional values (C{Scalar}s).
688
+
689
+ @see: Method C{Flinear.fadd}.
690
+ '''
691
+ return self.fadd(x_ys[0::2], x_ys[1::2], **sample)
692
+
693
+ def fcorrelation(self, sample=False):
694
+ '''Return the current, running (sample) correlation (C{float}).
695
+
696
+ @kwarg sample: Return the I{sample} instead of the entire
697
+ I{population} correlation (C{bool}).
698
+ '''
699
+ return self._sampled(self.x.fstdev(sample=sample) *
700
+ self.y.fstdev(sample=sample), sample)
701
+
702
+ def fintercept(self, sample=False):
703
+ '''Return the current, running (sample) intercept (C{float}).
704
+
705
+ @kwarg sample: Return the I{sample} instead of the entire
706
+ I{population} intercept (C{bool}).
707
+ '''
708
+ return float(self.y._M1 -
709
+ (self.x._M1 * self.fslope(sample=sample)))
710
+
711
+ def fslope(self, sample=False):
712
+ '''Return the current, running (sample) slope (C{float}).
713
+
714
+ @kwarg sample: Return the I{sample} instead of the entire
715
+ I{population} slope (C{bool}).
716
+ '''
717
+ return self._sampled(self.x.fvariance(sample=sample), sample)
718
+
719
+ def _sampled(self, t, sample):
720
+ '''(INTERNAL) Compute the sampled or entire population result.
721
+ '''
722
+ t *= float(_sampled(self._n, sample))
723
+ return float(self._S / t) if t else _0_0
724
+
725
+ @property_RO
726
+ def x(self):
727
+ '''Get the C{x} accumulator (L{Fcook} or L{Fwelford}).
728
+ '''
729
+ return self._X
730
+
731
+ @property_RO
732
+ def y(self):
733
+ '''Get the C{y} accumulator (L{Fcook} or L{Fwelford}).
734
+ '''
735
+ return self._Y
736
+
737
+
738
+ __all__ += _ALL_DOCS(_FstatsBase, _FstatsNamed)
739
+
740
+ # **) MIT License
741
+ #
742
+ # Copyright (C) 2021-2024 -- mrJean1 at Gmail -- All Rights Reserved.
743
+ #
744
+ # Permission is hereby granted, free of charge, to any person obtaining a
745
+ # copy of this software and associated documentation files (the "Software"),
746
+ # to deal in the Software without restriction, including without limitation
747
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
748
+ # and/or sell copies of the Software, and to permit persons to whom the
749
+ # Software is furnished to do so, subject to the following conditions:
750
+ #
751
+ # The above copyright notice and this permission notice shall be included
752
+ # in all copies or substantial portions of the Software.
753
+ #
754
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
755
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
756
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
757
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
758
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
759
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
760
+ # OTHER DEALINGS IN THE SOFTWARE.