pynumerics 2.3.0__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 (46) hide show
  1. pynumerics/__init__.py +73 -0
  2. pynumerics/_bin/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  3. pynumerics/_bin/Microsoft.CodeAnalysis.LICENSE.txt +27 -0
  4. pynumerics/_bin/Microsoft.CodeAnalysis.dll +0 -0
  5. pynumerics/_bin/Numerics.dll +0 -0
  6. pynumerics/_bin/pynumerics.runtimeconfig.json +15 -0
  7. pynumerics/_coverage.py +3825 -0
  8. pynumerics/_util.py +237 -0
  9. pynumerics/_version.py +40 -0
  10. pynumerics/bootstrap.py +593 -0
  11. pynumerics/callbacks.py +791 -0
  12. pynumerics/copulas.py +910 -0
  13. pynumerics/data.py +2233 -0
  14. pynumerics/differentiation.py +348 -0
  15. pynumerics/distributions.py +4043 -0
  16. pynumerics/download.py +623 -0
  17. pynumerics/enums.py +119 -0
  18. pynumerics/errors.py +245 -0
  19. pynumerics/functions.py +1142 -0
  20. pynumerics/goodness_of_fit.py +699 -0
  21. pynumerics/graphs.py +556 -0
  22. pynumerics/hypothesis_tests.py +445 -0
  23. pynumerics/identity.py +144 -0
  24. pynumerics/integration.py +677 -0
  25. pynumerics/linalg.py +1408 -0
  26. pynumerics/machine_learning.py +1335 -0
  27. pynumerics/marshal.py +733 -0
  28. pynumerics/mathematics.py +1053 -0
  29. pynumerics/mcmc.py +1406 -0
  30. pynumerics/multivariate.py +1539 -0
  31. pynumerics/ode.py +156 -0
  32. pynumerics/optimization.py +1736 -0
  33. pynumerics/probability.py +966 -0
  34. pynumerics/py.typed +0 -0
  35. pynumerics/regional.py +522 -0
  36. pynumerics/roots.py +274 -0
  37. pynumerics/runtime.py +384 -0
  38. pynumerics/sampling.py +1758 -0
  39. pynumerics/special.py +1079 -0
  40. pynumerics/statistics.py +1958 -0
  41. pynumerics/timeseries.py +1326 -0
  42. pynumerics-2.3.0.dist-info/METADATA +142 -0
  43. pynumerics-2.3.0.dist-info/RECORD +46 -0
  44. pynumerics-2.3.0.dist-info/WHEEL +5 -0
  45. pynumerics-2.3.0.dist-info/licenses/LICENSE +10 -0
  46. pynumerics-2.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1958 @@
1
+ """Descriptive statistics from ``Numerics.Data.Statistics``.
2
+
3
+ Thin calls into ``Statistics`` (sequential sums, Type-7 and weighted percentiles, ranks and
4
+ L-moments), ``Correlation`` (Pearson, Spearman and Kendall's tau-b), ``BoxCox``,
5
+ ``RunningStatistics`` (streaming moments), ``PlottingPositions`` and ``Autocorrelation``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import operator
11
+ from typing import Any
12
+
13
+ import numpy as np
14
+
15
+ from pynumerics import callbacks
16
+ from pynumerics._util import bind, bind_list, cached_overload, evaluate, loop, namespace, net_enum
17
+ from pynumerics.enums import AutocorrelationType, PlottingPosition, WeightType
18
+ from pynumerics.errors import translate_public
19
+ from pynumerics.marshal import _bound, to_net_doubles, to_net_matrix, to_numpy_doubles, to_numpy_ints, to_numpy_matrix
20
+
21
+
22
+ def _stats() -> Any:
23
+ """Return the ``Numerics.Data.Statistics`` namespace after starting the runtime."""
24
+ return namespace("Numerics.Data.Statistics")
25
+
26
+
27
+ def _statistics_member(name: str, *signature: str) -> Any:
28
+ """Return one overload of a ``Statistics`` method group, bound once per process.
29
+
30
+ ``signature`` names the parameter types: ``"list"`` (``IList<double>``), ``"double"``,
31
+ ``"bool"``, ``"int"``, ``"kind"`` (``PlottingPositions.PlottingPostionType``) and ``"weight"``
32
+ (``WeightType``). The bound overload is the member pythonnet would resolve for the same
33
+ arguments; binding it once removes the per-call resolution (see ``_util.cached_overload``).
34
+ """
35
+ def factory() -> Any:
36
+ stats = _stats()
37
+ import System # noqa: PLC0415 - the runtime is running
38
+ from System.Collections.Generic import IList # noqa: PLC0415
39
+
40
+ types = {"list": IList[System.Double], "double": System.Double, "bool": System.Boolean, "int": System.Int32,
41
+ "kind": stats.PlottingPositions.PlottingPostionType, "weight": stats.WeightType}
42
+ return getattr(stats.Statistics, name).Overloads[tuple(types[item] for item in signature)]
43
+
44
+ return cached_overload(f"Statistics.{name}({', '.join(signature)})", factory)
45
+
46
+
47
+ def mean(data: Any, weights: Any = None) -> float:
48
+ """Return the mean through ``Statistics.Mean`` (sequential summation).
49
+
50
+ Parameters
51
+ ----------
52
+ data : array_like
53
+ Values.
54
+ weights : array_like, optional
55
+ Weights for ``Statistics.Mean(IList, IList)``.
56
+
57
+ Returns
58
+ -------
59
+ float
60
+ The (weighted) mean.
61
+ """
62
+ if weights is None:
63
+ return float(_statistics_member("Mean", "list")(to_net_doubles(data)))
64
+ return float(_statistics_member("Mean", "list", "list")(to_net_doubles(data), to_net_doubles(weights)))
65
+
66
+
67
+ def variance(data: Any, weights: Any = None, weight_type: WeightType | str = WeightType.FREQUENCY) -> float:
68
+ """Return the sample variance through ``Statistics.Variance``.
69
+
70
+ Parameters
71
+ ----------
72
+ data : array_like
73
+ Values.
74
+ weights : array_like, optional
75
+ Weights; when given ``weight_type`` selects frequency or reliability weights.
76
+ weight_type : WeightType or str, optional
77
+ Weight interpretation; default frequency.
78
+
79
+ Returns
80
+ -------
81
+ float
82
+ The variance.
83
+ """
84
+ stats = _stats()
85
+ if weights is None:
86
+ return float(_statistics_member("Variance", "list")(to_net_doubles(data)))
87
+ member = _statistics_member("Variance", "list", "list", "weight")
88
+ return float(member(to_net_doubles(data), to_net_doubles(weights), net_enum(stats.WeightType, weight_type)))
89
+
90
+
91
+ def standard_deviation(data: Any, weights: Any = None, weight_type: WeightType | str = WeightType.FREQUENCY) -> float:
92
+ """Return the sample standard deviation through ``Statistics.StandardDeviation``.
93
+
94
+ Parameters
95
+ ----------
96
+ data : array_like
97
+ Values.
98
+ weights : array_like, optional
99
+ Weights; when given ``weight_type`` selects frequency or reliability weights.
100
+ weight_type : WeightType or str, optional
101
+ Weight interpretation; default frequency.
102
+
103
+ Returns
104
+ -------
105
+ float
106
+ The standard deviation.
107
+ """
108
+ stats = _stats()
109
+ if weights is None:
110
+ return float(_statistics_member("StandardDeviation", "list")(to_net_doubles(data)))
111
+ member = _statistics_member("StandardDeviation", "list", "list", "weight")
112
+ return float(member(to_net_doubles(data), to_net_doubles(weights), net_enum(stats.WeightType, weight_type)))
113
+
114
+
115
+ def median(data: Any, data_is_sorted: bool = False) -> float:
116
+ """Return the median through ``Statistics.Median``.
117
+
118
+ For an even count the result is the correctly rounded midpoint ``0.5 * (lo + hi)`` of the two
119
+ middle values, as ``numpy.median`` computes it; ``percentile(data, 0.5)`` (Type 7) can differ
120
+ from it in the last bit.
121
+
122
+ Parameters
123
+ ----------
124
+ data : array_like
125
+ Values.
126
+ data_is_sorted : bool, optional
127
+ Whether ``data`` is already ascending; default False.
128
+
129
+ Returns
130
+ -------
131
+ float
132
+ The median.
133
+ """
134
+ return float(_statistics_member("Median", "list", "bool")(to_net_doubles(data), bool(data_is_sorted)))
135
+
136
+
137
+ def percentile(data: Any, k: Any, weights: Any = None, data_is_sorted: bool = False) -> Any:
138
+ """Return percentiles through ``Statistics.Percentile``.
139
+
140
+ Unweighted percentiles are Type 7 (position (n − 1)k). Weighted percentiles use
141
+ ``Statistics.Percentile(IList, k, IList weights, bool)``, which reduces to Type 7 at
142
+ unit weights.
143
+
144
+ Parameters
145
+ ----------
146
+ data : array_like
147
+ Values.
148
+ k : float or array_like
149
+ Percentiles as fractions in [0, 1].
150
+ weights : array_like, optional
151
+ Weights, one per value.
152
+ data_is_sorted : bool, optional
153
+ Whether ``data`` is already ascending; default False.
154
+
155
+ Returns
156
+ -------
157
+ float or numpy.ndarray
158
+ Percentile values, shaped like ``k``.
159
+ """
160
+ values = to_net_doubles(data)
161
+ karr = np.asarray(k, dtype=np.float64)
162
+ scalar = karr.ndim == 0
163
+ ks = float(karr) if scalar else to_net_doubles(karr.ravel())
164
+ k_type = "double" if scalar else "list"
165
+ if weights is None:
166
+ result = _statistics_member("Percentile", "list", k_type, "bool")(values, ks, bool(data_is_sorted))
167
+ else:
168
+ result = _statistics_member("Percentile", "list", k_type, "list", "bool")(values, ks, to_net_doubles(weights),
169
+ bool(data_is_sorted))
170
+ return float(result) if scalar else to_numpy_doubles(result).reshape(karr.shape)
171
+
172
+
173
+ def plotting_position_percentile(data: Any, k: Any,
174
+ kind: PlottingPosition | str = PlottingPosition.WEIBULL,
175
+ data_is_sorted: bool = False, weights: Any = None) -> Any:
176
+ """Return percentiles interpolated between order statistics at plotting positions.
177
+
178
+ Calls ``Statistics.Percentile(IList, k, PlottingPostionType, bool)``: the i-th order
179
+ statistic sits at its plotting position and the percentile interpolates linearly between
180
+ them, held at the sample extremes outside the first and last positions. Weibull is
181
+ Hyndman-Fan type 6 (NumPy ``method="weibull"``), Hazen type 5 and Kaplan-Meier type 4. For
182
+ Weibull, Blom and Hazen the interpolation weight is formed from ``k`` and the two bracketing
183
+ positions, which is closer to exact rational evaluation than the fractional part of the rank.
184
+
185
+ With ``weights`` it calls ``Statistics.Percentile(IList, k, IList weights, PlottingPostionType,
186
+ bool)``, with an array ``k`` the list overload, which validates and sorts the sample once and
187
+ serves every probability in one walk, each value bitwise the scalar call. Point i sits at the
188
+ weighted position ``(c_(i-1) + (1 - alpha) w_i) / (W + (1 - 2 alpha) w_mean)``
189
+ (``(c_(i-1) + w_i / 2) / W`` for Hazen, ``c_i / W`` for Kaplan-Meier), zero weights included.
190
+
191
+ Parameters
192
+ ----------
193
+ data : array_like
194
+ Values.
195
+ k : float or array_like
196
+ Percentiles as fractions in [0, 1].
197
+ kind : PlottingPosition or str, optional
198
+ The plotting-position formula; default Weibull.
199
+ data_is_sorted : bool, optional
200
+ Whether ``data`` is already ascending; default False.
201
+ weights : array_like, optional
202
+ The non-negative weight of each value, aligned with ``data``; default unweighted.
203
+
204
+ Returns
205
+ -------
206
+ float or numpy.ndarray
207
+ Percentile values, shaped like ``k``.
208
+ """
209
+ kind_net = net_enum(_stats().PlottingPositions.PlottingPostionType, kind)
210
+ values = to_net_doubles(data)
211
+ karr = np.asarray(k, dtype=np.float64)
212
+ if weights is not None:
213
+ k_type = "double" if karr.ndim == 0 else "list"
214
+ member = _statistics_member("Percentile", "list", k_type, "list", "kind", "bool")
215
+ net_weights = to_net_doubles(weights)
216
+ if karr.ndim == 0:
217
+ return float(member(values, float(karr), net_weights, kind_net, bool(data_is_sorted)))
218
+ result = member(values, to_net_doubles(karr.ravel()), net_weights, kind_net, bool(data_is_sorted))
219
+ return to_numpy_doubles(result).reshape(karr.shape)
220
+ if karr.ndim == 0:
221
+ return float(_statistics_member("Percentile", "list", "double", "kind", "bool")(
222
+ values, float(karr), kind_net, bool(data_is_sorted)))
223
+ result = _statistics_member("Percentile", "list", "list", "kind", "bool")(
224
+ values, to_net_doubles(karr.ravel()), kind_net, bool(data_is_sorted))
225
+ return to_numpy_doubles(result).reshape(karr.shape)
226
+
227
+
228
+ def column_percentiles(data: Any, k: Any,
229
+ kind: PlottingPosition | str | None = None) -> tuple[np.ndarray, np.ndarray]:
230
+ """Return the percentiles of each column over its finite entries (``Statistics.ColumnPercentiles``).
231
+
232
+ Each column's finite entries go through ``Statistics.Percentile`` in one call for the
233
+ whole matrix, so every value is bitwise the per-column ``percentile`` (Type 7, the
234
+ default) or ``plotting_position_percentile`` (when ``kind`` is given) of the finite
235
+ entries; NaN and infinite entries are excluded rather than sorted in.
236
+
237
+ Parameters
238
+ ----------
239
+ data : array_like
240
+ Two-dimensional samples, one row per member (for example a realization) and one
241
+ column per variable (for example a curve node).
242
+ k : float or array_like
243
+ Percentiles as fractions in [0, 1].
244
+ kind : PlottingPosition or str, optional
245
+ A plotting-position formula (``"weibull"`` is Hyndman-Fan type 6) for
246
+ ``ColumnPercentiles(double[,], IList, PlottingPostionType, out int[])``; None (the
247
+ default) gives the Type 7 percentiles.
248
+
249
+ Returns
250
+ -------
251
+ percentiles : numpy.ndarray
252
+ Shape ``(len(k), columns)`` (``(columns,)`` for a scalar ``k``); NaN for a column
253
+ without a finite entry.
254
+ excluded : numpy.ndarray
255
+ The int32 number of non-finite entries excluded from each column.
256
+ """
257
+ stats = _stats()
258
+ karr = np.asarray(k, dtype=np.float64)
259
+ matrix, ks = to_net_matrix(data), to_net_doubles(karr.ravel())
260
+ if kind is None:
261
+ table, excluded = stats.Statistics.ColumnPercentiles(matrix, ks, None)
262
+ else:
263
+ kind_net = net_enum(stats.PlottingPositions.PlottingPostionType, kind)
264
+ table, excluded = stats.Statistics.ColumnPercentiles(matrix, ks, kind_net, None)
265
+ result = to_numpy_matrix(table)
266
+ if karr.ndim == 0:
267
+ result = result[0]
268
+ return result, to_numpy_ints(excluded)
269
+
270
+
271
+ def column_means(data: Any) -> tuple[np.ndarray, np.ndarray]:
272
+ """Return the mean of each column over its finite entries (``Statistics.ColumnMeans``).
273
+
274
+ Each column's finite entries go through ``Statistics.Mean`` (a sequential sum) in one call for
275
+ the whole matrix, so every value is bitwise :func:`mean` of the column's finite entries; NaN and
276
+ infinite entries are excluded, as in :func:`column_percentiles`.
277
+
278
+ Parameters
279
+ ----------
280
+ data : array_like
281
+ Two-dimensional samples, one row per member and one column per variable.
282
+
283
+ Returns
284
+ -------
285
+ means : numpy.ndarray
286
+ One mean per column; NaN for a column without a finite entry.
287
+ excluded : numpy.ndarray
288
+ The int32 number of non-finite entries excluded from each column.
289
+ """
290
+ means, excluded = _column_statistic("ColumnMeans", data)
291
+ return to_numpy_doubles(means), excluded
292
+
293
+
294
+ def _column_statistic(member: str, data: Any) -> tuple[Any, np.ndarray]:
295
+ """Call a ``Statistics`` column member through its ``(double[,], out int[])`` overload, bound once per process.
296
+
297
+ The matrix crosses in one copy; the overload is resolved on first use only.
298
+ """
299
+ def factory() -> Any:
300
+ group = getattr(_stats().Statistics, member)
301
+ import clr # noqa: PLC0415 - the runtime is running
302
+ import System # noqa: PLC0415
303
+
304
+ return group.Overloads[_bound()["doubles_matrix"], clr.GetClrType(System.Array[System.Int32]).MakeByRefType()]
305
+
306
+ values, excluded = cached_overload(f"Statistics.{member}(double[,], out int[])", factory)(to_net_matrix(data), None)
307
+ return values, to_numpy_ints(excluded)
308
+
309
+
310
+ def column_variances(data: Any) -> tuple[np.ndarray, np.ndarray]:
311
+ """Return the sample variance of each column over its finite entries (``Statistics.ColumnVariances``).
312
+
313
+ Every value is bitwise :func:`variance` (n - 1 divisor, corrected two-pass) of the column's
314
+ finite entries, computed for the whole matrix in one call; NaN and infinite entries are excluded,
315
+ as in :func:`column_means`. Equals ``numpy.nanvar(data, axis=0, ddof=1)`` to rounding when every
316
+ entry is finite or NaN.
317
+
318
+ Parameters
319
+ ----------
320
+ data : array_like
321
+ Two-dimensional samples, one row per member and one column per variable.
322
+
323
+ Returns
324
+ -------
325
+ variances : numpy.ndarray
326
+ One variance per column; NaN for a column with fewer than two finite entries.
327
+ excluded : numpy.ndarray
328
+ The int32 number of non-finite entries excluded from each column.
329
+ """
330
+ values, excluded = _column_statistic("ColumnVariances", data)
331
+ return to_numpy_doubles(values), excluded
332
+
333
+
334
+ def column_standard_deviations(data: Any) -> tuple[np.ndarray, np.ndarray]:
335
+ """Return the sample standard deviation of each column over its finite entries.
336
+
337
+ Wraps ``Statistics.ColumnStandardDeviations``.
338
+
339
+ Every value is bitwise :func:`standard_deviation` of the column's finite entries, the square
340
+ root of the :func:`column_variances` value.
341
+
342
+ Parameters
343
+ ----------
344
+ data : array_like
345
+ Two-dimensional samples, one row per member and one column per variable.
346
+
347
+ Returns
348
+ -------
349
+ standard_deviations : numpy.ndarray
350
+ One standard deviation per column; NaN for a column with fewer than two finite entries.
351
+ excluded : numpy.ndarray
352
+ The int32 number of non-finite entries excluded from each column.
353
+ """
354
+ values, excluded = _column_statistic("ColumnStandardDeviations", data)
355
+ return to_numpy_doubles(values), excluded
356
+
357
+
358
+ def column_linear_moments(data: Any) -> tuple[np.ndarray, np.ndarray]:
359
+ """Return the L-moments of each column over its finite entries (``Statistics.ColumnLinearMoments``).
360
+
361
+ Every column is bitwise :func:`linear_moments` of its finite entries. The L-CV of a column is
362
+ ``moments[1] / moments[0]``, bitwise :func:`linear_coefficient_of_variation`.
363
+
364
+ Parameters
365
+ ----------
366
+ data : array_like
367
+ Two-dimensional samples, one row per member and one column per variable.
368
+
369
+ Returns
370
+ -------
371
+ moments : numpy.ndarray
372
+ A (4, p) array with rows lambda1, lambda2, tau3 and tau4; NaN for a column with fewer than
373
+ four finite entries.
374
+ excluded : numpy.ndarray
375
+ The int32 number of non-finite entries excluded from each column.
376
+ """
377
+ values, excluded = _column_statistic("ColumnLinearMoments", data)
378
+ return to_numpy_matrix(values), excluded
379
+
380
+
381
+ def wilson_score_interval(successes: float, trials: float, confidence_level: float) -> tuple[float, float]:
382
+ """Return the Wilson score interval of a binomial proportion (``Statistics.WilsonScoreInterval``).
383
+
384
+ Parameters
385
+ ----------
386
+ successes : float
387
+ The number of successes, in [0, ``trials``]; may be fractional.
388
+ trials : float
389
+ The number of trials (positive).
390
+ confidence_level : float
391
+ The two-sided level in (0, 1).
392
+
393
+ Returns
394
+ -------
395
+ tuple of float
396
+ The lower and upper bounds, clamped to [0, 1].
397
+ """
398
+ bounds = _stats().Statistics.WilsonScoreInterval(float(successes), float(trials), float(confidence_level))
399
+ return float(bounds.Item1), float(bounds.Item2)
400
+
401
+
402
+ def linear_moments(data: Any, nmom: int | None = None) -> np.ndarray:
403
+ """Return the sample L-moments through ``Statistics.LinearMoments``.
404
+
405
+ With ``nmom`` it calls ``Statistics.LinearMoments(IList, int)``, the unbiased estimator of
406
+ Hosking's SAMLMR through probability weighted moments, whose first four values are bitwise those
407
+ without ``nmom``; ``nmom=5`` adds the ratio ``t5`` used in regional frequency analysis.
408
+
409
+ Parameters
410
+ ----------
411
+ data : array_like
412
+ Values (sorted by Numerics).
413
+ nmom : int, optional
414
+ The number of L-moments, at least 1; default the four-value member.
415
+
416
+ Returns
417
+ -------
418
+ numpy.ndarray
419
+ ``[l1, l2, t3, t4]``: the first two L-moments and the L-skewness and L-kurtosis
420
+ ratios, as Numerics returns them; with ``nmom``, ``[l1, l2, t3, ..., t_nmom]``. All NaN
421
+ when there are fewer than four values (fewer than ``nmom`` with ``nmom``).
422
+
423
+ Raises
424
+ ------
425
+ TypeError
426
+ If ``nmom`` is not an integer, for example a float.
427
+ ValueError
428
+ If ``nmom`` is less than 1.
429
+ """
430
+ if nmom is None:
431
+ return to_numpy_doubles(_statistics_member("LinearMoments", "list")(to_net_doubles(data)))
432
+ member = _statistics_member("LinearMoments", "list", "int")
433
+ return to_numpy_doubles(member(to_net_doubles(data), operator.index(nmom)))
434
+
435
+
436
+ def linear_coefficient_of_variation(data: Any) -> float:
437
+ """Return the sample L-coefficient of variation through ``Statistics.LinearCoefficientOfVariation``.
438
+
439
+ Parameters
440
+ ----------
441
+ data : array_like
442
+ Values (sorted by Numerics).
443
+
444
+ Returns
445
+ -------
446
+ float
447
+ ``l2 / l1``, bitwise the ratio of the first two entries of :func:`linear_moments`; NaN for
448
+ fewer than four values or any NaN.
449
+ """
450
+ return float(_stats().Statistics.LinearCoefficientOfVariation(to_net_doubles(data)))
451
+
452
+
453
+ def ranks(data: Any, return_ties: bool = False) -> np.ndarray | tuple[np.ndarray, np.ndarray]:
454
+ """Return the one-based average ranks of the values through ``Statistics.RanksInPlace``.
455
+
456
+ Values that compare equal, infinities included, share their average rank, as
457
+ ``scipy.stats.rankdata(method="average")`` assigns them. The input is not modified.
458
+
459
+ Parameters
460
+ ----------
461
+ data : array_like
462
+ Values, in any order.
463
+ return_ties : bool, optional
464
+ Also return the tie-run lengths of ``RanksInPlace(double[], out double[])``; default False.
465
+
466
+ Returns
467
+ -------
468
+ ranks : numpy.ndarray
469
+ One rank per value, in input order.
470
+ ties : numpy.ndarray
471
+ Only when ``return_ties`` is True: in sorted order, the last position of each run of k equal
472
+ values holds k - 1 and every other entry is zero.
473
+ """
474
+ s = _stats().Statistics
475
+ values = to_net_doubles(data)
476
+ if not return_ties:
477
+ return to_numpy_doubles(s.RanksInPlace(values))
478
+ result, ties = s.RanksInPlace(values, None)
479
+ return to_numpy_doubles(result), to_numpy_doubles(ties)
480
+
481
+
482
+ def _correlation(name: str, sample1: Any, sample2: Any) -> Any:
483
+ """Call a ``Correlation`` member on two samples, or on the columns of one 2-D array."""
484
+ correlation = _stats().Correlation
485
+ if sample2 is None:
486
+ return to_numpy_matrix(getattr(correlation, name)(to_net_matrix(sample1)))
487
+ return float(getattr(correlation, name)(to_net_doubles(sample1), to_net_doubles(sample2)))
488
+
489
+
490
+ def pearson(sample1: Any, sample2: Any = None) -> Any:
491
+ """Return the Pearson correlation through ``Correlation.Pearson``.
492
+
493
+ Parameters
494
+ ----------
495
+ sample1 : array_like
496
+ The first sample, or, when ``sample2`` is omitted, a 2-D array with one row per observation
497
+ and one column per variable.
498
+ sample2 : array_like, optional
499
+ The second sample, of the same length.
500
+
501
+ Returns
502
+ -------
503
+ float or numpy.ndarray
504
+ The coefficient, or the correlation matrix of the columns of ``sample1``.
505
+ """
506
+ return _correlation("Pearson", sample1, sample2)
507
+
508
+
509
+ def spearman(sample1: Any, sample2: Any = None) -> Any:
510
+ """Return the Spearman rank correlation through ``Correlation.Spearman``.
511
+
512
+ The Pearson correlation of the average ranks of :func:`ranks`.
513
+
514
+ Parameters
515
+ ----------
516
+ sample1 : array_like
517
+ The first sample, or, when ``sample2`` is omitted, a 2-D array with one row per observation
518
+ and one column per variable.
519
+ sample2 : array_like, optional
520
+ The second sample, of the same length.
521
+
522
+ Returns
523
+ -------
524
+ float or numpy.ndarray
525
+ The coefficient, or the rank correlation matrix of the columns of ``sample1``.
526
+ """
527
+ return _correlation("Spearman", sample1, sample2)
528
+
529
+
530
+ def kendalls_tau(sample1: Any, sample2: Any) -> float:
531
+ """Return Kendall's tau-b through ``Correlation.KendallsTau``.
532
+
533
+ ``(n_c - n_d) / sqrt(n_1 n_2)``, with ties counted as ``scipy.stats.kendalltau`` counts them.
534
+ Pairs are counted in O(n log n) for samples of 48 or more values (Knight, 1966).
535
+
536
+ Parameters
537
+ ----------
538
+ sample1, sample2 : array_like
539
+ Samples of the same length.
540
+
541
+ Returns
542
+ -------
543
+ float
544
+ Tau-b; NaN when either sample contains NaN or is constant.
545
+ """
546
+ return float(_stats().Correlation.KendallsTau(to_net_doubles(sample1), to_net_doubles(sample2)))
547
+
548
+
549
+ def box_cox_transform(values: Any, lam: float) -> Any:
550
+ """Return the Box-Cox transformation through ``BoxCox.Transform``.
551
+
552
+ ``(x^lam - 1) / lam``, or ``ln x`` for ``|lam| < 1e-8``.
553
+
554
+ Parameters
555
+ ----------
556
+ values : float or array_like
557
+ Positive values.
558
+ lam : float
559
+ The exponent, in [-5, 5].
560
+
561
+ Returns
562
+ -------
563
+ float or numpy.ndarray
564
+ The transformed values, shaped like ``values``; NaN for a non-positive value or ``|lam| > 5``.
565
+ """
566
+ box_cox = _stats().BoxCox
567
+ exponent = float(lam)
568
+ scalar = cached_overload("BoxCox.Transform(double, double)", lambda: bind(box_cox.Transform, 2))
569
+ return evaluate(values, lambda x: scalar(x, exponent), lambda v: box_cox.Transform(v, exponent))
570
+
571
+
572
+ def box_cox_inverse_transform(values: Any, lam: float) -> Any:
573
+ """Return the inverse Box-Cox transformation through ``BoxCox.InverseTransform``.
574
+
575
+ Parameters
576
+ ----------
577
+ values : float or array_like
578
+ Transformed values.
579
+ lam : float
580
+ The exponent, in [-5, 5].
581
+
582
+ Returns
583
+ -------
584
+ float or numpy.ndarray
585
+ The original-scale values, shaped like ``values``.
586
+ """
587
+ box_cox = _stats().BoxCox
588
+ exponent = float(lam)
589
+ scalar = cached_overload("BoxCox.InverseTransform(double, double)", lambda: bind(box_cox.InverseTransform, 2))
590
+ return evaluate(values, lambda y: scalar(y, exponent), lambda v: box_cox.InverseTransform(v, exponent))
591
+
592
+
593
+ def box_cox_derivative(values: Any, lam: float) -> Any:
594
+ """Return the derivative of the Box-Cox transformation through ``BoxCox.Derivative``.
595
+
596
+ ``x^(lam - 1)``, or ``1 / x`` where the transformation is the logarithm. Numerics has no array
597
+ overload, so an array is evaluated element by element with the scalar member.
598
+
599
+ Parameters
600
+ ----------
601
+ values : float or array_like
602
+ Positive values.
603
+ lam : float
604
+ The exponent, in [-5, 5].
605
+
606
+ Returns
607
+ -------
608
+ float or numpy.ndarray
609
+ The derivatives, shaped like ``values``; NaN for a non-positive value or ``|lam| > 5``.
610
+ """
611
+ member, exponent = _stats().BoxCox.Derivative, float(lam)
612
+ return loop(values, lambda x: member(x, exponent))
613
+
614
+
615
+ def box_cox_fit_lambda(values: Any) -> tuple[bool, float]:
616
+ """Fit the Box-Cox exponent by maximum likelihood through ``BoxCox.TryFitLambda``.
617
+
618
+ Parameters
619
+ ----------
620
+ values : array_like
621
+ Positive values, not all equal.
622
+
623
+ Returns
624
+ -------
625
+ success : bool
626
+ Whether a finite exponent was found.
627
+ lam : float
628
+ The exponent in [-5, 5], or NaN when the sample cannot support a fit or the search fails.
629
+ """
630
+ success, lam = _stats().BoxCox.TryFitLambda(to_net_doubles(values), 0.0)
631
+ return bool(success), float(lam)
632
+
633
+
634
+ def plotting_positions(n: int, kind: PlottingPosition | str | float = PlottingPosition.WEIBULL) -> np.ndarray:
635
+ """Return plotting-position probabilities through ``PlottingPositions.Function``.
636
+
637
+ Parameters
638
+ ----------
639
+ n : int
640
+ Sample size.
641
+ kind : PlottingPosition, str or float, optional
642
+ A named formula, or the plotting-position parameter α of (i − α)/(n + 1 − 2α).
643
+
644
+ Returns
645
+ -------
646
+ numpy.ndarray
647
+ The n ascending probabilities.
648
+ """
649
+ pp = _stats().PlottingPositions
650
+ if isinstance(kind, (int, float)) and not isinstance(kind, bool):
651
+ return to_numpy_doubles(pp.Function(int(n), float(kind)))
652
+ return to_numpy_doubles(pp.Function(int(n), net_enum(pp.PlottingPostionType, kind)))
653
+
654
+
655
+ def weighted_plotting_positions(weights: Any, alpha: float | PlottingPosition | str) -> np.ndarray:
656
+ """Return weighted plotting positions through ``PlottingPositions.Function(IList, ...)``.
657
+
658
+ ``F_i = (c_i - alpha w_i) / (W + (1 - 2 alpha) W / n)`` with ``c_i`` the sequential cumulative
659
+ weight and ``W`` the total; unit weights give bitwise the positions of
660
+ :func:`plotting_positions`. A named formula selects ``Function(IList, PlottingPostionType)``:
661
+ the alpha formulas at :func:`plotting_position_alpha`, and Kaplan-Meier as ``c_i / W``.
662
+
663
+ Parameters
664
+ ----------
665
+ weights : array_like
666
+ Non-negative weights of the observations in ascending order of their values.
667
+ alpha : float, PlottingPosition or str
668
+ The plotting-position parameter in [0, 0.5] (0 Weibull, 0.44 Gringorten, 0.5 Hazen; above
669
+ one half a small last weight would give a position above one), or a named formula.
670
+
671
+ Returns
672
+ -------
673
+ numpy.ndarray
674
+ The non-decreasing positions, one per weight.
675
+ """
676
+ pp = _stats().PlottingPositions
677
+ if isinstance(alpha, (int, float)) and not isinstance(alpha, bool):
678
+ return to_numpy_doubles(pp.Function(to_net_doubles(weights), float(alpha)))
679
+ return to_numpy_doubles(pp.Function(to_net_doubles(weights), net_enum(pp.PlottingPostionType, alpha)))
680
+
681
+
682
+ def plotting_position_alpha(kind: PlottingPosition | str) -> float:
683
+ """Return the alpha coefficient of a named plotting-position formula (``PlottingPositions.Alpha``).
684
+
685
+ Parameters
686
+ ----------
687
+ kind : PlottingPosition or str
688
+ An alpha formula (Weibull, Median, Blom, Cunnane, Gringorten or Hazen).
689
+
690
+ Returns
691
+ -------
692
+ float
693
+ The coefficient of (i - alpha) / (n + 1 - 2 alpha).
694
+ """
695
+ pp = _stats().PlottingPositions
696
+ return float(pp.Alpha(net_enum(pp.PlottingPostionType, kind)))
697
+
698
+
699
+ class RunningStatistics:
700
+ """Streaming moments (``Numerics.Data.Statistics.RunningStatistics``).
701
+
702
+ Parameters
703
+ ----------
704
+ values : array_like, optional
705
+ Initial values to push.
706
+
707
+ Attributes
708
+ ----------
709
+ net : Numerics.Data.Statistics.RunningStatistics
710
+ The wrapped .NET object.
711
+ """
712
+
713
+ def __init__(self, values: Any = None) -> None:
714
+ stats = _stats()
715
+ self.net = stats.RunningStatistics() if values is None else stats.RunningStatistics(to_net_doubles(values))
716
+
717
+ def push(self, values: Any) -> None:
718
+ """Push one value or an array (``Push(double)`` / ``Push(IList<double>)``).
719
+
720
+ Parameters
721
+ ----------
722
+ values : float or array_like
723
+ Values to add.
724
+ """
725
+ array = np.asarray(values, dtype=np.float64)
726
+ if array.ndim == 0:
727
+ self.net.Push(float(array))
728
+ else:
729
+ self.net.Push(to_net_doubles(array.ravel()))
730
+
731
+ def clear(self) -> None:
732
+ """Reset to the empty state in place (``RunningStatistics.Clear``).
733
+
734
+ Pushing the same values afterwards gives bitwise the statistics of a new accumulator.
735
+ """
736
+ self.net.Clear()
737
+
738
+ @classmethod
739
+ def combine(cls, a: RunningStatistics, b: RunningStatistics) -> RunningStatistics:
740
+ """Merge two accumulators (``RunningStatistics.Combine``).
741
+
742
+ Parameters
743
+ ----------
744
+ a, b : RunningStatistics
745
+ Accumulators.
746
+
747
+ Returns
748
+ -------
749
+ RunningStatistics
750
+ The combined accumulator.
751
+ """
752
+ out = cls.__new__(cls)
753
+ out.net = _stats().RunningStatistics.Combine(a.net, b.net)
754
+ return out
755
+
756
+ @property
757
+ def count(self) -> int:
758
+ """Number of values pushed."""
759
+ return int(self.net.Count)
760
+
761
+ @property
762
+ def minimum(self) -> float:
763
+ """Smallest value."""
764
+ return float(self.net.Minimum)
765
+
766
+ @property
767
+ def maximum(self) -> float:
768
+ """Largest value."""
769
+ return float(self.net.Maximum)
770
+
771
+ @property
772
+ def mean(self) -> float:
773
+ """Mean."""
774
+ return float(self.net.Mean)
775
+
776
+ @property
777
+ def variance(self) -> float:
778
+ """Sample variance."""
779
+ return float(self.net.Variance)
780
+
781
+ @property
782
+ def standard_deviation(self) -> float:
783
+ """Sample standard deviation."""
784
+ return float(self.net.StandardDeviation)
785
+
786
+ @property
787
+ def skewness(self) -> float:
788
+ """Sample skewness."""
789
+ return float(self.net.Skewness)
790
+
791
+ @property
792
+ def kurtosis(self) -> float:
793
+ """Sample kurtosis."""
794
+ return float(self.net.Kurtosis)
795
+
796
+ @property
797
+ def population_variance(self) -> float:
798
+ """Population variance (divisor n); 0 for a single value."""
799
+ return float(self.net.PopulationVariance)
800
+
801
+ @property
802
+ def population_standard_deviation(self) -> float:
803
+ """Population standard deviation (divisor n); 0 for a single value."""
804
+ return float(self.net.PopulationStandardDeviation)
805
+
806
+ @property
807
+ def coefficient_of_variation(self) -> float:
808
+ """Coefficient of variation, the sample standard deviation over the mean."""
809
+ return float(self.net.CoefficientOfVariation)
810
+
811
+ @property
812
+ def population_skewness(self) -> float:
813
+ """Population skewness (type 1, no bias correction)."""
814
+ return float(self.net.PopulationSkewness)
815
+
816
+ @property
817
+ def population_kurtosis(self) -> float:
818
+ """Population kurtosis (type 1, no bias correction)."""
819
+ return float(self.net.PopulationKurtosis)
820
+
821
+ def clone(self) -> RunningStatistics:
822
+ """Return an independent copy of the accumulator (``RunningStatistics.Clone``).
823
+
824
+ Returns
825
+ -------
826
+ RunningStatistics
827
+ A new accumulator with the same state.
828
+ """
829
+ out = type(self).__new__(type(self))
830
+ out.net = self.net.Clone()
831
+ return out
832
+
833
+
834
+ def autocorrelation(data: Any, max_lag: int | None = None,
835
+ kind: AutocorrelationType | str = AutocorrelationType.CORRELATION,
836
+ centered: bool = True) -> tuple[np.ndarray, np.ndarray]:
837
+ """Return a sample autocorrelation function through ``Autocorrelation.Function``.
838
+
839
+ ``COVARIANCE`` is ``R(k) = n^-1 sum_{t=k}^{n-1} (x_t - xbar)(x_{t-k} - xbar)`` (divisor ``n`` at
840
+ every lag, the mean by ``Statistics.Mean``), ``CORRELATION`` is ``R(k) / R(0)`` and ``PARTIAL``
841
+ is the Durbin-Levinson partial autocorrelation from lag 1. With ``centered=False`` the mean is
842
+ taken as zero, for a process whose mean is known to be zero: ``R(k) = n^-1 sum x_t x_{t-k}`` and
843
+ the correlation is ``sum x_t x_{t-k} / sum x_t^2``.
844
+
845
+ Parameters
846
+ ----------
847
+ data : array_like
848
+ The series, in time order: one-dimensional, finite, with at least two values.
849
+ max_lag : int, optional
850
+ The largest lag, an integer in [1, n - 1]; default Numerics' ``floor(min(10 log10(n), n - 1))``.
851
+ kind : AutocorrelationType or str, optional
852
+ ``CORRELATION`` (default), ``COVARIANCE`` or ``PARTIAL``.
853
+ centered : bool, optional
854
+ Remove the sample mean (default True); False evaluates about zero.
855
+
856
+ Returns
857
+ -------
858
+ lags : numpy.ndarray
859
+ The lags (``0..max_lag``; ``1..max_lag`` for ``PARTIAL``), as integers.
860
+ values : numpy.ndarray
861
+ The function at those lags.
862
+
863
+ Raises
864
+ ------
865
+ ValueError
866
+ If ``data`` is not one-dimensional, holds a non-finite value or has fewer than two values;
867
+ if ``max_lag`` is not an integer in [1, n - 1]; or if Numerics returns no function (a
868
+ zero-variance series for ``CORRELATION``). Each message names the argument at fault.
869
+ """
870
+ series = np.asarray(data, dtype=np.float64)
871
+ if series.ndim != 1:
872
+ raise ValueError(f"data must be one-dimensional; got shape {series.shape}.")
873
+ if series.size < 2:
874
+ raise ValueError(f"data needs at least two values; got {series.size}.")
875
+ if not np.all(np.isfinite(series)):
876
+ raise ValueError("data must be finite; it holds NaN or infinite values.")
877
+ if max_lag is None:
878
+ lag = -1
879
+ else:
880
+ if isinstance(max_lag, (bool, np.bool_)) or not isinstance(max_lag, (int, np.integer)):
881
+ raise ValueError(f"max_lag must be an integer; got {max_lag!r}.")
882
+ lag = int(max_lag)
883
+ if not 1 <= lag <= series.size - 1:
884
+ raise ValueError(f"max_lag must lie in [1, {series.size - 1}] for {series.size} values; got {lag}.")
885
+ stats = _stats()
886
+ net_kind = net_enum(stats.Autocorrelation.Type, kind)
887
+ if centered:
888
+ table = stats.Autocorrelation.Function(to_net_doubles(series), lag, net_kind)
889
+ else:
890
+ table = stats.Autocorrelation.Function(to_net_doubles(series), lag, net_kind, False)
891
+ if table is None:
892
+ raise ValueError("Autocorrelation.Function returned no function: the series needs at least two values, "
893
+ "a maximum lag of at least 1 and, for the correlation, a nonzero variance.")
894
+ result = to_numpy_matrix(table)
895
+ return result[:, 0].astype(np.int64), np.ascontiguousarray(result[:, 1])
896
+
897
+
898
+ def _one(name: str, data: Any) -> float:
899
+ """Call a one-sample ``Statistics`` member through its bound ``IList<double>`` overload."""
900
+ return float(_statistics_member(name, "list")(to_net_doubles(data)))
901
+
902
+
903
+ def minimum(data: Any) -> float:
904
+ """Return the smallest value through ``Statistics.Minimum``.
905
+
906
+ Parameters
907
+ ----------
908
+ data : array_like
909
+ Values, in any order.
910
+
911
+ Returns
912
+ -------
913
+ float
914
+ The minimum; NaN for an empty sample or any NaN.
915
+ """
916
+ return _one("Minimum", data)
917
+
918
+
919
+ def maximum(data: Any) -> float:
920
+ """Return the largest value through ``Statistics.Maximum``.
921
+
922
+ Parameters
923
+ ----------
924
+ data : array_like
925
+ Values, in any order.
926
+
927
+ Returns
928
+ -------
929
+ float
930
+ The maximum; NaN for an empty sample or any NaN.
931
+ """
932
+ return _one("Maximum", data)
933
+
934
+
935
+ def sum(data: Any) -> float:
936
+ """Return the sequential sum through ``Statistics.Sum``.
937
+
938
+ Parameters
939
+ ----------
940
+ data : array_like
941
+ Values, in any order.
942
+
943
+ Returns
944
+ -------
945
+ float
946
+ The sum in index order; NaN for an empty sample or any NaN.
947
+ """
948
+ return _one("Sum", data)
949
+
950
+
951
+ def parallel_mean(data: Any) -> float:
952
+ """Return the mean through ``Statistics.ParallelMean`` (deterministic chunked reduction).
953
+
954
+ The chunk boundaries depend only on the sample length, so the result is reproducible across
955
+ runs and machines; it can differ from :func:`mean` in the last bits.
956
+
957
+ Parameters
958
+ ----------
959
+ data : array_like
960
+ Values, in any order.
961
+
962
+ Returns
963
+ -------
964
+ float
965
+ The mean; NaN for an empty sample or any NaN.
966
+ """
967
+ return _one("ParallelMean", data)
968
+
969
+
970
+ def geometric_mean(data: Any) -> float:
971
+ """Return the geometric mean through ``Statistics.GeometricMean``.
972
+
973
+ Parameters
974
+ ----------
975
+ data : array_like
976
+ Positive values.
977
+
978
+ Returns
979
+ -------
980
+ float
981
+ The geometric mean; NaN for an empty sample or any NaN.
982
+ """
983
+ return _one("GeometricMean", data)
984
+
985
+
986
+ def harmonic_mean(data: Any) -> float:
987
+ """Return the harmonic mean through ``Statistics.HarmonicMean``.
988
+
989
+ Parameters
990
+ ----------
991
+ data : array_like
992
+ Nonzero values.
993
+
994
+ Returns
995
+ -------
996
+ float
997
+ The harmonic mean; NaN for an empty sample or any NaN.
998
+ """
999
+ return _one("HarmonicMean", data)
1000
+
1001
+
1002
+ def population_variance(data: Any) -> float:
1003
+ """Return the population variance (divisor n) through ``Statistics.PopulationVariance``.
1004
+
1005
+ Parameters
1006
+ ----------
1007
+ data : array_like
1008
+ Values.
1009
+
1010
+ Returns
1011
+ -------
1012
+ float
1013
+ The variance with divisor n; NaN for an empty sample or any NaN.
1014
+ """
1015
+ return _one("PopulationVariance", data)
1016
+
1017
+
1018
+ def population_standard_deviation(data: Any) -> float:
1019
+ """Return the population standard deviation (divisor n) through ``Statistics.PopulationStandardDeviation``.
1020
+
1021
+ Parameters
1022
+ ----------
1023
+ data : array_like
1024
+ Values.
1025
+
1026
+ Returns
1027
+ -------
1028
+ float
1029
+ The standard deviation with divisor n; NaN for an empty sample or any NaN.
1030
+ """
1031
+ return _one("PopulationStandardDeviation", data)
1032
+
1033
+
1034
+ def _pair(result: Any) -> tuple[float, float]:
1035
+ """Return a .NET ``ValueTuple<double, double>`` as a Python pair."""
1036
+ return float(result.Item1), float(result.Item2)
1037
+
1038
+
1039
+ def mean_variance(data: Any) -> tuple[float, float]:
1040
+ """Return the mean and sample variance in one pass through ``Statistics.MeanVariance``.
1041
+
1042
+ Parameters
1043
+ ----------
1044
+ data : array_like
1045
+ Values.
1046
+
1047
+ Returns
1048
+ -------
1049
+ tuple of float
1050
+ The mean and the sample variance (divisor n - 1).
1051
+ """
1052
+ return _pair(_stats().Statistics.MeanVariance(to_net_doubles(data)))
1053
+
1054
+
1055
+ def mean_standard_deviation(data: Any) -> tuple[float, float]:
1056
+ """Return the mean and sample standard deviation through ``Statistics.MeanStandardDeviation``.
1057
+
1058
+ Parameters
1059
+ ----------
1060
+ data : array_like
1061
+ Values.
1062
+
1063
+ Returns
1064
+ -------
1065
+ tuple of float
1066
+ The mean and the sample standard deviation (divisor n - 1).
1067
+ """
1068
+ return _pair(_stats().Statistics.MeanStandardDeviation(to_net_doubles(data)))
1069
+
1070
+
1071
+ def coefficient_of_variation(data: Any) -> float:
1072
+ """Return the coefficient of variation through ``Statistics.CoefficientOfVariation``.
1073
+
1074
+ Parameters
1075
+ ----------
1076
+ data : array_like
1077
+ Values.
1078
+
1079
+ Returns
1080
+ -------
1081
+ float
1082
+ The sample standard deviation over the mean.
1083
+ """
1084
+ return _one("CoefficientOfVariation", data)
1085
+
1086
+
1087
+ def _weighted_moment(name: str, data: Any, weights: Any, weight_type: WeightType | str) -> float:
1088
+ """Call ``Statistics.Skewness`` or ``Kurtosis``, unweighted or with weights."""
1089
+ if weights is None:
1090
+ return _one(name, data)
1091
+ member = _statistics_member(name, "list", "list", "weight")
1092
+ return float(member(to_net_doubles(data), to_net_doubles(weights), net_enum(_stats().WeightType, weight_type)))
1093
+
1094
+
1095
+ def skewness(data: Any, weights: Any = None, weight_type: WeightType | str = WeightType.FREQUENCY) -> float:
1096
+ """Return the bias-corrected sample skewness through ``Statistics.Skewness``.
1097
+
1098
+ The adjusted Fisher-Pearson coefficient ``G1``, as ``scipy.stats.skew(bias=False)``. With
1099
+ weights the correction uses the effective sample size of ``weight_type``.
1100
+
1101
+ Parameters
1102
+ ----------
1103
+ data : array_like
1104
+ Values.
1105
+ weights : array_like, optional
1106
+ Non-negative, finite weights, one per value.
1107
+ weight_type : WeightType or str, optional
1108
+ Weight interpretation; default frequency.
1109
+
1110
+ Returns
1111
+ -------
1112
+ float
1113
+ The skewness; NaN for an empty sample, any NaN, or an effective sample size of two or less.
1114
+ """
1115
+ return _weighted_moment("Skewness", data, weights, weight_type)
1116
+
1117
+
1118
+ def kurtosis(data: Any, weights: Any = None, weight_type: WeightType | str = WeightType.FREQUENCY) -> float:
1119
+ """Return the bias-corrected sample excess kurtosis through ``Statistics.Kurtosis``.
1120
+
1121
+ The small-sample estimator ``G2``, as ``scipy.stats.kurtosis(bias=False)``. With weights the
1122
+ correction uses the effective sample size of ``weight_type``.
1123
+
1124
+ Parameters
1125
+ ----------
1126
+ data : array_like
1127
+ Values.
1128
+ weights : array_like, optional
1129
+ Non-negative, finite weights, one per value.
1130
+ weight_type : WeightType or str, optional
1131
+ Weight interpretation; default frequency.
1132
+
1133
+ Returns
1134
+ -------
1135
+ float
1136
+ The excess kurtosis; NaN for an empty sample, any NaN, or an effective sample size of three
1137
+ or less.
1138
+ """
1139
+ return _weighted_moment("Kurtosis", data, weights, weight_type)
1140
+
1141
+
1142
+ def covariance(data1: Any, data2: Any) -> float:
1143
+ """Return the sample covariance (divisor n - 1) through ``Statistics.Covariance``.
1144
+
1145
+ Parameters
1146
+ ----------
1147
+ data1, data2 : array_like
1148
+ Samples of the same length.
1149
+
1150
+ Returns
1151
+ -------
1152
+ float
1153
+ The covariance; NaN for fewer than two pairs or any NaN.
1154
+ """
1155
+ return float(_stats().Statistics.Covariance(to_net_doubles(data1), to_net_doubles(data2)))
1156
+
1157
+
1158
+ def population_covariance(data1: Any, data2: Any) -> float:
1159
+ """Return the population covariance (divisor n) through ``Statistics.PopulationCovariance``.
1160
+
1161
+ Parameters
1162
+ ----------
1163
+ data1, data2 : array_like
1164
+ Samples of the same length.
1165
+
1166
+ Returns
1167
+ -------
1168
+ float
1169
+ The covariance; NaN for an empty sample or any NaN.
1170
+ """
1171
+ return float(_stats().Statistics.PopulationCovariance(to_net_doubles(data1), to_net_doubles(data2)))
1172
+
1173
+
1174
+ def product_moments(data: Any) -> np.ndarray:
1175
+ """Return the first four product moments through ``Statistics.ProductMoments``.
1176
+
1177
+ Parameters
1178
+ ----------
1179
+ data : array_like
1180
+ Values.
1181
+
1182
+ Returns
1183
+ -------
1184
+ numpy.ndarray
1185
+ ``[mean, standard deviation, skewness, excess kurtosis]`` with the bias-corrected skewness
1186
+ and kurtosis; four NaN values for fewer than four values or any NaN.
1187
+ """
1188
+ return to_numpy_doubles(_stats().Statistics.ProductMoments(to_net_doubles(data)))
1189
+
1190
+
1191
+ def five_number_summary(data: Any) -> np.ndarray:
1192
+ """Return the five-number summary through ``Statistics.FiveNumberSummary``.
1193
+
1194
+ Parameters
1195
+ ----------
1196
+ data : array_like
1197
+ Values, in any order.
1198
+
1199
+ Returns
1200
+ -------
1201
+ numpy.ndarray
1202
+ ``[min, 25th, 50th, 75th percentile, max]``, the percentiles Type 7.
1203
+ """
1204
+ return to_numpy_doubles(_stats().Statistics.FiveNumberSummary(to_net_doubles(data)))
1205
+
1206
+
1207
+ def seven_number_summary(data: Any) -> np.ndarray:
1208
+ """Return the seven-number summary through ``Statistics.SevenNumberSummary``.
1209
+
1210
+ Parameters
1211
+ ----------
1212
+ data : array_like
1213
+ Values, in any order.
1214
+
1215
+ Returns
1216
+ -------
1217
+ numpy.ndarray
1218
+ ``[min, 5th, 25th, 50th, 75th, 95th percentile, max]``, the percentiles Type 7.
1219
+ """
1220
+ return to_numpy_doubles(_stats().Statistics.SevenNumberSummary(to_net_doubles(data)))
1221
+
1222
+
1223
+ def jackknife_standard_error(data: Any, statistic: Any) -> float:
1224
+ """Return the jackknife standard error of a statistic through ``Statistics.JackKnifeStandardError``.
1225
+
1226
+ Numerics evaluates the leave-one-out samples in parallel. A Python callable serializes on the
1227
+ GIL; pass C# source (compiled with :func:`pynumerics.callbacks.compile_csharp`) for a
1228
+ statistic that runs without crossing into Python.
1229
+
1230
+ Parameters
1231
+ ----------
1232
+ data : array_like
1233
+ Values.
1234
+ statistic : callable, str or System.Delegate
1235
+ The statistic of one sample, a ``Func<IList<double>, double>``: a Python callable taking a
1236
+ NumPy array, C# source, or a .NET delegate.
1237
+
1238
+ Returns
1239
+ -------
1240
+ float
1241
+ The standard error; 0 for a single value.
1242
+ """
1243
+ fn = callbacks.to_delegate(statistic, "Func<IList<double>,double>")
1244
+ return float(_stats().Statistics.JackKnifeStandardError(to_net_doubles(data), fn))
1245
+
1246
+
1247
+ def jackknife_sample(data: Any, statistic: Any) -> np.ndarray | None:
1248
+ """Return the statistic of every leave-one-out sample through ``Statistics.JackKnifeSample``.
1249
+
1250
+ Parameters
1251
+ ----------
1252
+ data : array_like
1253
+ Values.
1254
+ statistic : callable, str or System.Delegate
1255
+ The statistic of one sample, as for :func:`jackknife_standard_error`.
1256
+
1257
+ Returns
1258
+ -------
1259
+ numpy.ndarray or None
1260
+ One value per left-out index, or None for an empty sample.
1261
+ """
1262
+ fn = callbacks.to_delegate(statistic, "Func<IList<double>,double>")
1263
+ result = _stats().Statistics.JackKnifeSample(to_net_doubles(data), fn)
1264
+ return None if result is None else to_numpy_doubles(result)
1265
+
1266
+
1267
+ def entropy(data: Any, pdf: Any) -> float:
1268
+ """Return ``-sum p log p`` over the density values at the data through ``Statistics.Entropy``.
1269
+
1270
+ Values where the density is zero or negative are skipped.
1271
+
1272
+ Parameters
1273
+ ----------
1274
+ data : array_like
1275
+ Points at which the density is evaluated.
1276
+ pdf : callable, str or System.Delegate
1277
+ The density, a ``Func<double, double>``: a Python callable (for example a distribution's
1278
+ ``pdf``), C# source, or a .NET delegate.
1279
+
1280
+ Returns
1281
+ -------
1282
+ float
1283
+ The entropy sum.
1284
+ """
1285
+ return float(_stats().Statistics.Entropy(to_net_doubles(data), callbacks.scalar_function(pdf)))
1286
+
1287
+
1288
+ def effective_sample_size(weights: Any) -> float:
1289
+ """Return the Kish effective sample size ``(sum w)^2 / sum w^2`` through ``Statistics.EffectiveSampleSize``.
1290
+
1291
+ The total and the sum of squares are accumulated sequentially in index order, the order the
1292
+ reliability-weighted statistics use. This is the effective size of a weighted sample, not of
1293
+ a correlated series: for the effective size of MCMC draws use
1294
+ :func:`pynumerics.mcmc.effective_sample_size`.
1295
+
1296
+ Parameters
1297
+ ----------
1298
+ weights : array_like
1299
+ Non-negative, finite weights.
1300
+
1301
+ Returns
1302
+ -------
1303
+ float
1304
+ The effective sample size, between one and the number of positive weights; NaN for an
1305
+ empty input.
1306
+
1307
+ Raises
1308
+ ------
1309
+ ValueError
1310
+ If a weight is negative or not finite.
1311
+ """
1312
+ return float(_stats().Statistics.EffectiveSampleSize(to_net_doubles(weights)))
1313
+
1314
+
1315
+ def box_cox_log_likelihood(values: Any, lam: float) -> float:
1316
+ """Return the Box-Cox profile log-likelihood through ``BoxCox.LogLikelihood``.
1317
+
1318
+ Parameters
1319
+ ----------
1320
+ values : array_like
1321
+ Positive values.
1322
+ lam : float
1323
+ The exponent, in [-5, 5].
1324
+
1325
+ Returns
1326
+ -------
1327
+ float
1328
+ The normal log-likelihood of the transformed values plus the log-Jacobian.
1329
+ """
1330
+ return float(_stats().BoxCox.LogLikelihood(to_net_doubles(values), float(lam)))
1331
+
1332
+
1333
+ def box_cox_log_jacobian(values: Any, lam: float) -> float:
1334
+ """Return the log-Jacobian ``(lam - 1) sum ln x`` of the Box-Cox transformation (``BoxCox.LogJacobian``).
1335
+
1336
+ Parameters
1337
+ ----------
1338
+ values : array_like
1339
+ Positive values.
1340
+ lam : float
1341
+ The exponent, in [-5, 5].
1342
+
1343
+ Returns
1344
+ -------
1345
+ float
1346
+ The log-Jacobian.
1347
+ """
1348
+ return float(_stats().BoxCox.LogJacobian(to_net_doubles(values), float(lam)))
1349
+
1350
+
1351
+ def yeo_johnson_transform(values: Any, lam: float) -> Any:
1352
+ """Return the Yeo-Johnson transformation through ``YeoJohnson.Transform``.
1353
+
1354
+ Parameters
1355
+ ----------
1356
+ values : float or array_like
1357
+ Values of any sign.
1358
+ lam : float
1359
+ The exponent, in [-5, 5].
1360
+
1361
+ Returns
1362
+ -------
1363
+ float or numpy.ndarray
1364
+ The transformed values, shaped like ``values``.
1365
+ """
1366
+ yeo_johnson = _stats().YeoJohnson
1367
+ exponent = float(lam)
1368
+ scalar = cached_overload("YeoJohnson.Transform(double, double)", lambda: bind(yeo_johnson.Transform, 2))
1369
+ return evaluate(values, lambda x: scalar(x, exponent), lambda v: yeo_johnson.Transform(v, exponent))
1370
+
1371
+
1372
+ def yeo_johnson_inverse_transform(values: Any, lam: float) -> Any:
1373
+ """Return the inverse Yeo-Johnson transformation through ``YeoJohnson.InverseTransform``.
1374
+
1375
+ Parameters
1376
+ ----------
1377
+ values : float or array_like
1378
+ Transformed values.
1379
+ lam : float
1380
+ The exponent, in [-5, 5].
1381
+
1382
+ Returns
1383
+ -------
1384
+ float or numpy.ndarray
1385
+ The original-scale values, shaped like ``values``.
1386
+ """
1387
+ yeo_johnson = _stats().YeoJohnson
1388
+ exponent = float(lam)
1389
+ scalar = cached_overload("YeoJohnson.InverseTransform(double, double)",
1390
+ lambda: bind(yeo_johnson.InverseTransform, 2))
1391
+ return evaluate(values, lambda y: scalar(y, exponent), lambda v: yeo_johnson.InverseTransform(v, exponent))
1392
+
1393
+
1394
+ def yeo_johnson_derivative(values: Any, lam: float) -> Any:
1395
+ """Return the derivative of the Yeo-Johnson transformation through ``YeoJohnson.Derivative``.
1396
+
1397
+ Numerics has no array overload, so an array is evaluated element by element.
1398
+
1399
+ Parameters
1400
+ ----------
1401
+ values : float or array_like
1402
+ Values of any sign.
1403
+ lam : float
1404
+ The exponent, in [-5, 5].
1405
+
1406
+ Returns
1407
+ -------
1408
+ float or numpy.ndarray
1409
+ The derivatives, shaped like ``values``.
1410
+ """
1411
+ member, exponent = _stats().YeoJohnson.Derivative, float(lam)
1412
+ return loop(values, lambda x: member(x, exponent))
1413
+
1414
+
1415
+ def yeo_johnson_fit_lambda(values: Any) -> float:
1416
+ """Fit the Yeo-Johnson exponent by maximum likelihood through ``YeoJohnson.FitLambda``.
1417
+
1418
+ Parameters
1419
+ ----------
1420
+ values : array_like
1421
+ At least two values.
1422
+
1423
+ Returns
1424
+ -------
1425
+ float
1426
+ The exponent in [-5, 5], or NaN when the search fails.
1427
+
1428
+ Raises
1429
+ ------
1430
+ ValueError
1431
+ If fewer than two values are given.
1432
+ """
1433
+ member = cached_overload("YeoJohnson.FitLambda(IList)", lambda: bind_list(_stats().YeoJohnson.FitLambda))
1434
+ return float(member(to_net_doubles(values)))
1435
+
1436
+
1437
+ def yeo_johnson_log_likelihood(values: Any, lam: float) -> float:
1438
+ """Return the Yeo-Johnson profile log-likelihood through ``YeoJohnson.LogLikelihood``.
1439
+
1440
+ Parameters
1441
+ ----------
1442
+ values : array_like
1443
+ Values of any sign.
1444
+ lam : float
1445
+ The exponent, in [-5, 5].
1446
+
1447
+ Returns
1448
+ -------
1449
+ float
1450
+ The normal log-likelihood of the transformed values plus the log-Jacobian.
1451
+ """
1452
+ return float(_stats().YeoJohnson.LogLikelihood(to_net_doubles(values), float(lam)))
1453
+
1454
+
1455
+ def yeo_johnson_log_jacobian(values: Any, lam: float) -> float:
1456
+ """Return the log-Jacobian of the Yeo-Johnson transformation through ``YeoJohnson.LogJacobian``.
1457
+
1458
+ Parameters
1459
+ ----------
1460
+ values : array_like
1461
+ Values of any sign.
1462
+ lam : float
1463
+ The exponent, in [-5, 5].
1464
+
1465
+ Returns
1466
+ -------
1467
+ float
1468
+ ``(lam - 1) sum sign(x) ln(1 + |x|)``.
1469
+ """
1470
+ return float(_stats().YeoJohnson.LogJacobian(to_net_doubles(values), float(lam)))
1471
+
1472
+
1473
+ def autocorrelation_confidence_interval(sample_size: int, interval: float = 0.95) -> tuple[float, float]:
1474
+ """Return the white-noise band of the sample autocorrelation (``Autocorrelation.CorrelationConfidenceInterval``).
1475
+
1476
+ Parameters
1477
+ ----------
1478
+ sample_size : int
1479
+ The series length.
1480
+ interval : float, optional
1481
+ The two-sided level; default 0.95.
1482
+
1483
+ Returns
1484
+ -------
1485
+ tuple of float
1486
+ The lower and upper bounds ``-+ z / sqrt(n)``.
1487
+ """
1488
+ bounds = _stats().Autocorrelation.CorrelationConfidenceInterval(int(sample_size), float(interval))
1489
+ return float(bounds[0]), float(bounds[1])
1490
+
1491
+
1492
+ def _sorted(values: Any, data_is_sorted: bool) -> Any:
1493
+ """Return the values as a ``System.Double[]``, sorted ascending unless already sorted."""
1494
+ array = np.asarray(values, dtype=np.float64)
1495
+ return to_net_doubles(array if data_is_sorted else np.sort(array))
1496
+
1497
+
1498
+ def first_order_sobol(x: Any, y: Any, bins: int = 20) -> float:
1499
+ """Estimate the first-order Sobol index of an input from given data (``GlobalSensitivity.FirstOrderSobol``).
1500
+
1501
+ The variance of the conditional output mean over equal-frequency input bins, divided by the
1502
+ output variance (Plischke, Borgonovo and Smith, 2013).
1503
+
1504
+ Parameters
1505
+ ----------
1506
+ x : array_like
1507
+ The input sample.
1508
+ y : array_like
1509
+ The output sample, aligned with ``x``.
1510
+ bins : int, optional
1511
+ The number of equal-frequency input bins; default 20.
1512
+
1513
+ Returns
1514
+ -------
1515
+ float
1516
+ The index, clamped to [0, 1]; 0 for a constant output.
1517
+
1518
+ Raises
1519
+ ------
1520
+ ValueError
1521
+ If the samples differ in length, are shorter than ``bins``, ``bins`` is below two, or a
1522
+ value is not finite.
1523
+ """
1524
+ return float(_stats().GlobalSensitivity.FirstOrderSobol(to_net_doubles(x), to_net_doubles(y), int(bins)))
1525
+
1526
+
1527
+ def pawn(x: Any, y: Any, bins: int = 20) -> np.ndarray:
1528
+ """Return the per-bin PAWN statistics of an input from given data (``GlobalSensitivity.Pawn``).
1529
+
1530
+ For each equal-frequency input bin, the two-sample Kolmogorov-Smirnov statistic between the
1531
+ conditional and the unconditional output (Pianosi and Wagener, 2015).
1532
+
1533
+ Parameters
1534
+ ----------
1535
+ x : array_like
1536
+ The input sample.
1537
+ y : array_like
1538
+ The output sample, aligned with ``x``.
1539
+ bins : int, optional
1540
+ The number of equal-frequency input bins; default 20.
1541
+
1542
+ Returns
1543
+ -------
1544
+ numpy.ndarray
1545
+ One statistic per bin, in bin order.
1546
+
1547
+ Raises
1548
+ ------
1549
+ ValueError
1550
+ As for :func:`first_order_sobol`.
1551
+ """
1552
+ return to_numpy_doubles(_stats().GlobalSensitivity.Pawn(to_net_doubles(x), to_net_doubles(y), int(bins)))
1553
+
1554
+
1555
+ def pawn_median(x: Any, y: Any, bins: int = 20) -> float:
1556
+ """Return the PAWN index, the median of :func:`pawn` (``GlobalSensitivity.PawnMedian``).
1557
+
1558
+ Parameters
1559
+ ----------
1560
+ x : array_like
1561
+ The input sample.
1562
+ y : array_like
1563
+ The output sample, aligned with ``x``.
1564
+ bins : int, optional
1565
+ The number of equal-frequency input bins; default 20.
1566
+
1567
+ Returns
1568
+ -------
1569
+ float
1570
+ The median per-bin statistic.
1571
+
1572
+ Raises
1573
+ ------
1574
+ ValueError
1575
+ As for :func:`first_order_sobol`.
1576
+ """
1577
+ return float(_stats().GlobalSensitivity.PawnMedian(to_net_doubles(x), to_net_doubles(y), int(bins)))
1578
+
1579
+
1580
+ def borgonovo_delta(x: Any, y: Any, x_bins: int = 20, y_bins: int = 20) -> float:
1581
+ """Estimate the Borgonovo moment-independent delta from given data (``GlobalSensitivity.BorgonovoDelta``).
1582
+
1583
+ Half the bin-weighted total variation between conditional and unconditional frequencies of
1584
+ equal-frequency output classes; invariant to increasing transforms of the output.
1585
+
1586
+ Parameters
1587
+ ----------
1588
+ x : array_like
1589
+ The input sample.
1590
+ y : array_like
1591
+ The output sample, aligned with ``x``.
1592
+ x_bins : int, optional
1593
+ The number of equal-frequency input bins; default 20.
1594
+ y_bins : int, optional
1595
+ The number of equal-frequency output classes; default 20.
1596
+
1597
+ Returns
1598
+ -------
1599
+ float
1600
+ The delta, in [0, 1 - 1 / y_bins].
1601
+
1602
+ Raises
1603
+ ------
1604
+ ValueError
1605
+ As for :func:`first_order_sobol`, or if ``y_bins`` is below two or above the sample size.
1606
+ """
1607
+ return float(_stats().GlobalSensitivity.BorgonovoDelta(to_net_doubles(x), to_net_doubles(y), int(x_bins),
1608
+ int(y_bins)))
1609
+
1610
+
1611
+ class HistogramBin:
1612
+ """One histogram bin (``Numerics.Data.Statistics.Histogram.Bin``).
1613
+
1614
+ Parameters
1615
+ ----------
1616
+ lower_bound : float
1617
+ The lower bound.
1618
+ upper_bound : float
1619
+ The upper bound.
1620
+ frequency : int, optional
1621
+ The count; default 0.
1622
+
1623
+ Attributes
1624
+ ----------
1625
+ net : Numerics.Data.Statistics.Histogram.Bin
1626
+ The wrapped .NET object.
1627
+ """
1628
+
1629
+ __hash__ = None # mutable: compared by value, never hashed
1630
+
1631
+ def __init__(self, lower_bound: float, upper_bound: float, frequency: int = 0) -> None:
1632
+ self.net = _stats().Histogram.Bin(float(lower_bound), float(upper_bound), int(frequency))
1633
+
1634
+ @classmethod
1635
+ def _from_net(cls, net: Any) -> HistogramBin:
1636
+ out = cls.__new__(cls)
1637
+ out.net = net
1638
+ return out
1639
+
1640
+ @property
1641
+ def lower_bound(self) -> float:
1642
+ """Lower bound."""
1643
+ return float(self.net.LowerBound)
1644
+
1645
+ @lower_bound.setter
1646
+ def lower_bound(self, value: float) -> None:
1647
+ self.net.LowerBound = float(value)
1648
+
1649
+ @property
1650
+ def upper_bound(self) -> float:
1651
+ """Upper bound."""
1652
+ return float(self.net.UpperBound)
1653
+
1654
+ @upper_bound.setter
1655
+ def upper_bound(self, value: float) -> None:
1656
+ self.net.UpperBound = float(value)
1657
+
1658
+ @property
1659
+ def midpoint(self) -> float:
1660
+ """Midpoint of the bounds."""
1661
+ return float(self.net.Midpoint)
1662
+
1663
+ @property
1664
+ def frequency(self) -> int:
1665
+ """Count of values in the bin."""
1666
+ return int(self.net.Frequency)
1667
+
1668
+ @frequency.setter
1669
+ def frequency(self, value: int) -> None:
1670
+ self.net.Frequency = int(value)
1671
+
1672
+ def clone(self) -> HistogramBin:
1673
+ """Return an independent copy (``Bin.Clone``).
1674
+
1675
+ Returns
1676
+ -------
1677
+ HistogramBin
1678
+ The copy.
1679
+ """
1680
+ return HistogramBin._from_net(self.net.Clone())
1681
+
1682
+ def compare_to(self, other: HistogramBin | None) -> int:
1683
+ """Order two non-overlapping bins (``Bin.CompareTo``).
1684
+
1685
+ Parameters
1686
+ ----------
1687
+ other : HistogramBin or None
1688
+ The bin to compare with.
1689
+
1690
+ Returns
1691
+ -------
1692
+ int
1693
+ 0 when both bounds are bitwise equal, +1 when this bin is higher (or ``other`` is
1694
+ None), -1 otherwise.
1695
+ """
1696
+ return int(self.net.CompareTo(None if other is None else other.net))
1697
+
1698
+ def __eq__(self, other: object) -> bool:
1699
+ return isinstance(other, HistogramBin) and bool(self.net.Equals(other.net))
1700
+
1701
+ def __repr__(self) -> str:
1702
+ return f"HistogramBin({self.lower_bound!r}, {self.upper_bound!r}, {self.frequency!r})"
1703
+
1704
+
1705
+ class Histogram:
1706
+ """An adaptive histogram (``Numerics.Data.Statistics.Histogram``).
1707
+
1708
+ Equal-width bins span the data range; values added later outside the range widen the first or
1709
+ last bin. A value on an interior edge counts in the bin above it.
1710
+
1711
+ Parameters
1712
+ ----------
1713
+ data : array_like
1714
+ The initial values.
1715
+ number_of_bins : int, optional
1716
+ The bin count; default Numerics' ``ceil(2 n^(1/3)) + 1``.
1717
+
1718
+ Attributes
1719
+ ----------
1720
+ net : Numerics.Data.Statistics.Histogram
1721
+ The wrapped .NET object.
1722
+ """
1723
+
1724
+ def __init__(self, data: Any, number_of_bins: int | None = None) -> None:
1725
+ values = to_net_doubles(data)
1726
+ h = _stats().Histogram
1727
+ self.net = h(values) if number_of_bins is None else h(values, int(number_of_bins))
1728
+
1729
+ @property
1730
+ def lower_bound(self) -> float:
1731
+ """Lower edge of the first bin."""
1732
+ return float(self.net.LowerBound)
1733
+
1734
+ @property
1735
+ def upper_bound(self) -> float:
1736
+ """Upper edge of the last bin."""
1737
+ return float(self.net.UpperBound)
1738
+
1739
+ @property
1740
+ def number_of_bins(self) -> int:
1741
+ """Number of bins."""
1742
+ return int(self.net.NumberOfBins)
1743
+
1744
+ @property
1745
+ def bin_width(self) -> float:
1746
+ """Initial bin width."""
1747
+ return float(self.net.BinWidth)
1748
+
1749
+ @property
1750
+ def data_count(self) -> int:
1751
+ """Total count over the bins."""
1752
+ return int(self.net.DataCount)
1753
+
1754
+ @property
1755
+ def mean(self) -> float:
1756
+ """Frequency-weighted mean of the bin midpoints; 0 when empty."""
1757
+ return float(self.net.Mean)
1758
+
1759
+ @property
1760
+ def median(self) -> float:
1761
+ """Midpoint of the bin holding the median count; NaN when empty."""
1762
+ return float(self.net.Median)
1763
+
1764
+ @property
1765
+ def mode(self) -> float:
1766
+ """Midpoint of the most frequent bin."""
1767
+ return float(self.net.Mode)
1768
+
1769
+ @property
1770
+ def standard_deviation(self) -> float:
1771
+ """Frequency-weighted standard deviation of the bin midpoints (divisor n); 0 when empty."""
1772
+ return float(self.net.StandardDeviation)
1773
+
1774
+ def __len__(self) -> int:
1775
+ return int(self.net.NumberOfBins)
1776
+
1777
+ def __getitem__(self, index: int) -> HistogramBin:
1778
+ """Return a copy of the bin at an index, in ascending order (``Histogram[index]``).
1779
+
1780
+ Parameters
1781
+ ----------
1782
+ index : int
1783
+ The bin index; negative indices count from the end.
1784
+
1785
+ Returns
1786
+ -------
1787
+ HistogramBin
1788
+ A copy of the bin.
1789
+
1790
+ Raises
1791
+ ------
1792
+ IndexError
1793
+ If the index is out of range.
1794
+ """
1795
+ count = len(self)
1796
+ position = int(index) + count if int(index) < 0 else int(index)
1797
+ if not 0 <= position < count:
1798
+ raise IndexError(f"Bin index {index} is out of range for {count} bins.")
1799
+ return HistogramBin._from_net(self.net[position])
1800
+
1801
+ def bins(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
1802
+ """Return the lower bounds, upper bounds and frequencies of every bin, in ascending order.
1803
+
1804
+ Returns
1805
+ -------
1806
+ lower : numpy.ndarray
1807
+ The lower bounds.
1808
+ upper : numpy.ndarray
1809
+ The upper bounds.
1810
+ frequency : numpy.ndarray
1811
+ The int64 counts.
1812
+ """
1813
+ items = [self.net[i] for i in range(len(self))]
1814
+ return (np.array([float(b.LowerBound) for b in items]), np.array([float(b.UpperBound) for b in items]),
1815
+ np.array([int(b.Frequency) for b in items], dtype=np.int64))
1816
+
1817
+ def add_bin(self, bin: HistogramBin) -> None:
1818
+ """Append a bin (``Histogram.AddBin``).
1819
+
1820
+ Parameters
1821
+ ----------
1822
+ bin : HistogramBin
1823
+ The bin; it must not overlap an existing one.
1824
+ """
1825
+ self.net.AddBin(bin.net)
1826
+
1827
+ def add_data(self, values: Any) -> None:
1828
+ """Count one value or an array in one call (``Histogram.AddData``).
1829
+
1830
+ Parameters
1831
+ ----------
1832
+ values : float or array_like
1833
+ The values; one outside the range widens the first or last bin.
1834
+ """
1835
+ array = np.asarray(values, dtype=np.float64)
1836
+ if array.ndim == 0:
1837
+ self.net.AddData(float(array))
1838
+ else:
1839
+ self.net.AddData(to_net_doubles(array.ravel()))
1840
+
1841
+ def bin_index(self, value: float) -> int:
1842
+ """Return the index of the bin containing a value (``Histogram.GetBinIndexOf``).
1843
+
1844
+ Parameters
1845
+ ----------
1846
+ value : float
1847
+ A value inside the histogram limits.
1848
+
1849
+ Returns
1850
+ -------
1851
+ int
1852
+ The bin index.
1853
+
1854
+ Raises
1855
+ ------
1856
+ ValueError
1857
+ If the value lies outside the histogram limits.
1858
+ """
1859
+ return int(self.net.GetBinIndexOf(float(value)))
1860
+
1861
+
1862
+ class RunningCovarianceMatrix:
1863
+ """A streaming, identity-regularized covariance matrix (``Numerics.Data.Statistics.RunningCovarianceMatrix``).
1864
+
1865
+ The co-moment matrix starts at the identity, so after N vectors ``covariance`` is ``M2 + I``,
1866
+ ``sample_covariance`` is ``(M2 + I) / (N - 1)`` and ``population_covariance`` is
1867
+ ``(M2 + I) / N``: the sample statistics shrunk toward the identity with a weight that fades as
1868
+ 1 / N. Subtract the identity from ``covariance`` for the plain co-moment matrix.
1869
+
1870
+ Parameters
1871
+ ----------
1872
+ size : int
1873
+ The vector length.
1874
+
1875
+ Attributes
1876
+ ----------
1877
+ net : Numerics.Data.Statistics.RunningCovarianceMatrix
1878
+ The wrapped .NET object.
1879
+ """
1880
+
1881
+ def __init__(self, size: int) -> None:
1882
+ self.net = _stats().RunningCovarianceMatrix(int(size))
1883
+
1884
+ def push(self, values: Any) -> None:
1885
+ """Add one vector, or each row of a 2-D array, with a Welford update (``Push``).
1886
+
1887
+ Numerics takes one vector per call, so a 2-D array crosses once per row.
1888
+
1889
+ Parameters
1890
+ ----------
1891
+ values : array_like
1892
+ A vector of length ``size``, or an array of shape ``(m, size)``.
1893
+
1894
+ Raises
1895
+ ------
1896
+ ValueError
1897
+ If a vector length differs from ``size``.
1898
+ """
1899
+ array = np.asarray(values, dtype=np.float64)
1900
+ push = self.net.Push
1901
+ for row in (array,) if array.ndim == 1 else array:
1902
+ push(to_net_doubles(row))
1903
+
1904
+ @property
1905
+ def n(self) -> int:
1906
+ """Number of vectors pushed."""
1907
+ return int(self.net.N)
1908
+
1909
+ @property
1910
+ def mean(self) -> np.ndarray:
1911
+ """Mean vector."""
1912
+ return to_numpy_matrix(self.net.Mean.ToArray()).ravel()
1913
+
1914
+ @property
1915
+ def covariance(self) -> np.ndarray:
1916
+ """Co-moment matrix ``M2 + I``, not divided by the sample size."""
1917
+ return to_numpy_matrix(self.net.Covariance.ToArray())
1918
+
1919
+ @property
1920
+ def sample_covariance(self) -> np.ndarray:
1921
+ """Regularized sample covariance ``(M2 + I) / (N - 1)``."""
1922
+ return to_numpy_matrix(self.net.SampleCovariance.ToArray())
1923
+
1924
+ @property
1925
+ def population_covariance(self) -> np.ndarray:
1926
+ """Regularized population covariance ``(M2 + I) / N``."""
1927
+ return to_numpy_matrix(self.net.PopulationCovariance.ToArray())
1928
+
1929
+ @property
1930
+ def sample_correlation(self) -> np.ndarray:
1931
+ """Correlation matrix of :attr:`sample_covariance`."""
1932
+ return to_numpy_matrix(self.net.SampleCorrelation.ToArray())
1933
+
1934
+ @property
1935
+ def population_correlation(self) -> np.ndarray:
1936
+ """Correlation matrix of :attr:`population_covariance`."""
1937
+ return to_numpy_matrix(self.net.PopulationCorrelation.ToArray())
1938
+
1939
+
1940
+ __all__ = [
1941
+ "Histogram", "HistogramBin", "RunningCovarianceMatrix", "RunningStatistics", "autocorrelation",
1942
+ "autocorrelation_confidence_interval", "borgonovo_delta", "box_cox_derivative", "box_cox_fit_lambda",
1943
+ "box_cox_inverse_transform", "box_cox_log_jacobian", "box_cox_log_likelihood", "box_cox_transform",
1944
+ "coefficient_of_variation", "column_linear_moments", "column_means", "column_percentiles",
1945
+ "column_standard_deviations", "column_variances", "covariance", "effective_sample_size",
1946
+ "entropy", "first_order_sobol", "five_number_summary", "geometric_mean", "harmonic_mean", "jackknife_sample",
1947
+ "jackknife_standard_error", "kendalls_tau", "kurtosis", "linear_coefficient_of_variation", "linear_moments",
1948
+ "maximum", "mean", "mean_standard_deviation", "mean_variance", "median", "minimum", "parallel_mean", "pawn",
1949
+ "pawn_median",
1950
+ "pearson", "percentile", "plotting_position_alpha", "plotting_position_percentile", "plotting_positions",
1951
+ "population_covariance", "population_standard_deviation", "population_variance", "product_moments", "ranks",
1952
+ "seven_number_summary", "skewness", "spearman", "standard_deviation", "sum",
1953
+ "variance", "weighted_plotting_positions", "wilson_score_interval", "yeo_johnson_derivative",
1954
+ "yeo_johnson_fit_lambda", "yeo_johnson_inverse_transform", "yeo_johnson_log_jacobian",
1955
+ "yeo_johnson_log_likelihood", "yeo_johnson_transform",
1956
+ ]
1957
+
1958
+ translate_public(globals())