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,1335 @@
1
+ """Supervised and unsupervised models from ``Numerics.MachineLearning``.
2
+
3
+ Each class wraps one Numerics model and mirrors its members: the constructor takes the training
4
+ data, ``train`` (or ``fit``) runs the Numerics estimator, and ``predict`` and the other queries call
5
+ the Numerics members. Feature matrices cross to .NET once per call as a ``double[,]`` (rows are
6
+ observations, columns are features) and results come back as NumPy arrays. A one-dimensional
7
+ feature array is a single feature column, as in the Numerics ``double[]`` overloads, except where a
8
+ member documents it as one observation. Every estimator, default, seed and tolerance is the Numerics
9
+ one.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Callable
15
+ from typing import Any
16
+
17
+ import numpy as np
18
+
19
+ from pynumerics import callbacks
20
+ from pynumerics._util import cached_overload, loop, namespace, net_enum
21
+ from pynumerics.enums import NetEnum
22
+ from pynumerics.errors import translate_public
23
+ from pynumerics.marshal import (
24
+ _numerics_matrix,
25
+ _numerics_vector,
26
+ _optional_doubles,
27
+ to_net_doubles,
28
+ to_net_ints,
29
+ to_net_matrix,
30
+ to_numpy_doubles,
31
+ to_numpy_ints,
32
+ to_numpy_matrix,
33
+ )
34
+
35
+
36
+ class GaussianProcessKernel(NetEnum):
37
+ """Covariance functions of Gaussian process regression (``Numerics.MachineLearning.GaussianProcessKernel``)."""
38
+
39
+ SQUARED_EXPONENTIAL = "SquaredExponential"
40
+ MATERN32 = "Matern32"
41
+ MATERN52 = "Matern52"
42
+ EXPONENTIAL = "Exponential"
43
+
44
+
45
+ class GaussianProcessMean(NetEnum):
46
+ """Mean functions of Gaussian process regression (``Numerics.MachineLearning.GaussianProcessMean``)."""
47
+
48
+ ZERO = "Zero"
49
+ CONSTANT = "Constant"
50
+ LINEAR = "Linear"
51
+
52
+
53
+ class VariogramModel(NetEnum):
54
+ """Isotropic variogram models for kriging (``Numerics.MachineLearning.VariogramModel``)."""
55
+
56
+ SPHERICAL = "Spherical"
57
+ EXPONENTIAL = "Exponential"
58
+ GAUSSIAN = "Gaussian"
59
+
60
+
61
+ class KrigingType(NetEnum):
62
+ """Kriging variants (``Numerics.MachineLearning.KrigingType``)."""
63
+
64
+ SIMPLE = "Simple"
65
+ ORDINARY = "Ordinary"
66
+ UNIVERSAL = "Universal"
67
+
68
+
69
+ class PrincipalComponentBasis(NetEnum):
70
+ """Bases of principal component analysis (``Numerics.MachineLearning.PrincipalComponentBasis``)."""
71
+
72
+ COVARIANCE = "Covariance"
73
+ CORRELATION = "Correlation"
74
+
75
+
76
+ def _net_type(name: str) -> Any:
77
+ """Return a ``Numerics.MachineLearning`` type by name."""
78
+ return getattr(namespace("Numerics.MachineLearning"), name)
79
+
80
+
81
+ def _columns(x: Any) -> np.ndarray:
82
+ """Return features as a float64 matrix; a 1-D array becomes one feature column."""
83
+ array = np.asarray(x, dtype=np.float64)
84
+ if array.ndim == 1:
85
+ array = array.reshape(-1, 1)
86
+ if array.ndim != 2:
87
+ raise ValueError(f"Expected a 1-D or 2-D feature array; got shape {array.shape}.")
88
+ return array
89
+
90
+
91
+ def _net_columns(x: Any) -> Any:
92
+ """Copy features into a ``System.Double[,]`` (see :func:`_columns`)."""
93
+ return to_net_matrix(_columns(x))
94
+
95
+
96
+ def _doubles(value: Any) -> np.ndarray | None:
97
+ """Copy a .NET ``double[]`` or ``Vector`` to NumPy; a null reference gives None."""
98
+ if value is None:
99
+ return None
100
+ return to_numpy_doubles(value.Array if hasattr(value, "Header") else value)
101
+
102
+
103
+ def _matrix(value: Any) -> np.ndarray | None:
104
+ """Copy a .NET ``double[,]`` or ``Matrix`` to NumPy; a null reference gives None."""
105
+ if value is None:
106
+ return None
107
+ return to_numpy_matrix(value.Array if hasattr(value, "NumberOfRows") else value)
108
+
109
+
110
+ def _ints(value: Any) -> np.ndarray | None:
111
+ """Copy a .NET ``int[]`` to NumPy; a null reference gives None."""
112
+ return None if value is None else to_numpy_ints(value)
113
+
114
+
115
+ def _pair(result: Any) -> tuple[np.ndarray, np.ndarray]:
116
+ """Copy a .NET ``(double[] Mean, double[] Variance)`` tuple to two NumPy arrays."""
117
+ return to_numpy_doubles(result.Item1), to_numpy_doubles(result.Item2)
118
+
119
+
120
+ def _member(name: str, convert: Callable[[Any], Any] | None, doc: str,
121
+ to_net: Callable[[Any], Any] | None = None) -> property:
122
+ """Return a property reading (and, with ``to_net``, writing) the .NET member ``name`` of ``self.net``."""
123
+ def fget(self: Any) -> Any:
124
+ value = getattr(self.net, name)
125
+ return value if convert is None else convert(value)
126
+
127
+ if to_net is None:
128
+ return property(fget, doc=doc)
129
+
130
+ def fset(self: Any, value: Any) -> None:
131
+ setattr(self.net, name, to_net(value))
132
+
133
+ return property(fget, fset, doc=doc)
134
+
135
+
136
+ class _NetModel:
137
+ """Base of the wrappers: holds the .NET object in ``net`` and wraps an existing one."""
138
+
139
+ net: Any
140
+
141
+ @classmethod
142
+ def _wrap(cls, net: Any) -> Any:
143
+ """Return a wrapper around an existing .NET object."""
144
+ out = cls.__new__(cls)
145
+ out.net = net
146
+ return out
147
+
148
+
149
+ class DecisionNode(_NetModel):
150
+ """A node of a decision tree (``Numerics.MachineLearning.DecisionNode``).
151
+
152
+ Attributes
153
+ ----------
154
+ net : Numerics.MachineLearning.DecisionNode
155
+ The wrapped .NET object.
156
+ """
157
+
158
+ def __init__(self) -> None:
159
+ self.net = _net_type("DecisionNode")()
160
+
161
+ feature_index = _member("FeatureIndex", int, "Index of the feature the node splits on; -1 for a leaf.", int)
162
+ threshold = _member("Threshold", float, "Split threshold; NaN for a leaf.", float)
163
+ value = _member("Value", float, "Leaf prediction; NaN for an internal node.", float)
164
+ is_leaf_node = _member("IsLeafNode", bool, "Whether the node is a leaf.", bool)
165
+
166
+ @property
167
+ def left(self) -> DecisionNode | None:
168
+ """Left child, or None."""
169
+ node = self.net.Left
170
+ return None if node is None else DecisionNode._wrap(node)
171
+
172
+ @property
173
+ def right(self) -> DecisionNode | None:
174
+ """Right child, or None."""
175
+ node = self.net.Right
176
+ return None if node is None else DecisionNode._wrap(node)
177
+
178
+
179
+ class DecisionTree(_NetModel):
180
+ """Classification and regression tree (``Numerics.MachineLearning.DecisionTree``).
181
+
182
+ Parameters
183
+ ----------
184
+ x : array_like
185
+ Training features, shape (n, d); a 1-D array is one feature.
186
+ y : array_like
187
+ Training responses (class labels as numbers for classification), length n, at least 10.
188
+ seed : int, optional
189
+ Mersenne Twister seed for the feature subsets; zero or negative seeds from the clock.
190
+
191
+ Attributes
192
+ ----------
193
+ net : Numerics.MachineLearning.DecisionTree
194
+ The wrapped .NET object.
195
+
196
+ Notes
197
+ -----
198
+ Set :attr:`is_regression`, :attr:`max_depth`, :attr:`minimum_split_size` and :attr:`features`
199
+ before :meth:`train`. Each split considers :attr:`features` randomly chosen features (default
200
+ ``max(1, d - 1)``).
201
+ """
202
+
203
+ def __init__(self, x: Any, y: Any, seed: int = -1) -> None:
204
+ self.net = _net_type("DecisionTree")(_net_columns(x), to_net_doubles(y), int(seed))
205
+
206
+ def train(self) -> None:
207
+ """Grow the tree (``Train``)."""
208
+ self.net.Train()
209
+
210
+ def predict(self, x: Any) -> np.ndarray | None:
211
+ """Return the tree prediction for each row (``Predict``).
212
+
213
+ Parameters
214
+ ----------
215
+ x : array_like
216
+ Features, shape (m, d); a 1-D array is one feature.
217
+
218
+ Returns
219
+ -------
220
+ numpy.ndarray or None
221
+ The m predictions, or None when the tree is untrained or ``x`` has a different number of
222
+ columns (the Numerics member returns null).
223
+ """
224
+ return _doubles(self.net.Predict(_net_columns(x)))
225
+
226
+ minimum_split_size = _member("MinimumSplitSize", int, "Minimum number of rows to split a node (default 2).", int)
227
+ max_depth = _member("MaxDepth", int, "Maximum depth of the tree (default 100).", int)
228
+ features = _member("Features", int, "Number of features considered at each split.", int)
229
+ is_regression = _member("IsRegression", bool, "Regression (True, default) or classification.", bool)
230
+ dimensions = _member("Dimensions", int, "Number of feature columns d.")
231
+ is_trained = _member("IsTrained", bool, "Whether the tree has been trained.")
232
+ x = _member("X", _matrix, "Training features, shape (n, d).")
233
+ y = _member("Y", _doubles, "Training responses.")
234
+
235
+ @property
236
+ def root(self) -> DecisionNode:
237
+ """Root node of the tree."""
238
+ return DecisionNode._wrap(self.net.Root)
239
+
240
+
241
+ class RandomForest(_NetModel):
242
+ """Random forest of decision trees on bootstrap samples (``Numerics.MachineLearning.RandomForest``).
243
+
244
+ Parameters
245
+ ----------
246
+ x : array_like
247
+ Training features, shape (n, d); a 1-D array is one feature.
248
+ y : array_like
249
+ Training responses, length n, at least 10.
250
+ seed : int, optional
251
+ Mersenne Twister seed of the forest; zero or negative seeds from the clock.
252
+
253
+ Attributes
254
+ ----------
255
+ net : Numerics.MachineLearning.RandomForest
256
+ The wrapped .NET object.
257
+
258
+ Notes
259
+ -----
260
+ Trees are trained in parallel, each from a seed drawn from the forest generator, so a seeded
261
+ forest is deterministic.
262
+ """
263
+
264
+ def __init__(self, x: Any, y: Any, seed: int = -1) -> None:
265
+ self.net = _net_type("RandomForest")(_net_columns(x), to_net_doubles(y), int(seed))
266
+
267
+ def train(self) -> None:
268
+ """Train every tree (``Train``)."""
269
+ self.net.Train()
270
+
271
+ def predict(self, x: Any, alpha: float = 0.1) -> np.ndarray | None:
272
+ """Return the forest prediction and its interval across trees (``Predict``).
273
+
274
+ Parameters
275
+ ----------
276
+ x : array_like
277
+ Features, shape (m, d); a 1-D array is one feature.
278
+ alpha : float, optional
279
+ One minus the interval coverage; default 0.1 (a 90% interval).
280
+
281
+ Returns
282
+ -------
283
+ numpy.ndarray or None
284
+ Shape (m, 4) with columns lower, median, upper and mean of the tree predictions (floored
285
+ for classification), or None when the forest is untrained or ``x`` has a different number
286
+ of columns.
287
+ """
288
+ return _matrix(self.net.Predict(_net_columns(x), float(alpha)))
289
+
290
+ number_of_trees = _member("NumberOfTrees", int, "Number of trees (default 1000).", int)
291
+ minimum_split_size = _member("MinimumSplitSize", int, "Minimum number of rows to split a node (default 2).", int)
292
+ max_depth = _member("MaxDepth", int, "Maximum depth of each tree (default 100).", int)
293
+ features = _member("Features", int, "Number of features considered at each split.", int)
294
+ is_regression = _member("IsRegression", bool, "Regression (True, default) or classification.", bool)
295
+ dimensions = _member("Dimensions", int, "Number of feature columns d.")
296
+ is_trained = _member("IsTrained", bool, "Whether the forest has been trained.")
297
+ x = _member("X", _matrix, "Training features, shape (n, d).")
298
+ y = _member("Y", _doubles, "Training responses.")
299
+
300
+ @property
301
+ def decision_trees(self) -> list[DecisionTree]:
302
+ """The trained trees; empty before training."""
303
+ trees = self.net.DecisionTrees
304
+ return [] if trees is None else [DecisionTree._wrap(tree) for tree in trees]
305
+
306
+
307
+ class KNearestNeighbors(_NetModel):
308
+ """The k-nearest neighbors regression and classification (``Numerics.MachineLearning.KNearestNeighbors``).
309
+
310
+ Parameters
311
+ ----------
312
+ x : array_like
313
+ Training features, shape (n, d); a 1-D array is one feature.
314
+ y : array_like
315
+ Training responses, length n.
316
+ k : int
317
+ Number of neighbors.
318
+
319
+ Attributes
320
+ ----------
321
+ net : Numerics.MachineLearning.KNearestNeighbors
322
+ The wrapped .NET object.
323
+
324
+ Notes
325
+ -----
326
+ Distances are Euclidean, ties broken by the lower training index. Regression averages the
327
+ neighbors with inverse squared distance weights (exact matches take all the weight);
328
+ classification returns the most common neighbor class.
329
+ """
330
+
331
+ def __init__(self, x: Any, y: Any, k: int) -> None:
332
+ self.net = _net_type("KNearestNeighbors")(_net_columns(x), to_net_doubles(y), int(k))
333
+
334
+ def get_neighbors(self, x: Any) -> np.ndarray | None:
335
+ """Return the training indexes of the k nearest neighbors of each row (``GetNeighbors``).
336
+
337
+ Parameters
338
+ ----------
339
+ x : array_like
340
+ Query features, shape (m, d); a 1-D array is one feature.
341
+
342
+ Returns
343
+ -------
344
+ numpy.ndarray or None
345
+ Shape (m, k) int32, nearest first, or None when ``x`` has a different number of columns.
346
+ """
347
+ rows = _columns(x)
348
+ found = _ints(self.net.GetNeighbors(to_net_matrix(rows)))
349
+ return None if found is None else found.reshape(rows.shape[0], -1)
350
+
351
+ def predict(self, x: Any) -> np.ndarray | None:
352
+ """Return the k-nearest-neighbor prediction of each row (``Predict``).
353
+
354
+ Parameters
355
+ ----------
356
+ x : array_like
357
+ Query features, shape (m, d); a 1-D array is one feature.
358
+
359
+ Returns
360
+ -------
361
+ numpy.ndarray or None
362
+ The m predictions, or None when ``x`` has a different number of columns.
363
+ """
364
+ return _doubles(self.net.Predict(_net_columns(x)))
365
+
366
+ def bootstrap_predict(self, x: Any, seed: int = -1) -> np.ndarray | None:
367
+ """Return the prediction from one bootstrap resample of the training data (``BootstrapPredict``).
368
+
369
+ Parameters
370
+ ----------
371
+ x : array_like
372
+ Query features, shape (m, d); a 1-D array is one feature.
373
+ seed : int, optional
374
+ Mersenne Twister seed of the resample; zero or negative seeds from the clock.
375
+
376
+ Returns
377
+ -------
378
+ numpy.ndarray or None
379
+ The m predictions, or None when ``x`` has a different number of columns.
380
+ """
381
+ return _doubles(self.net.BootstrapPredict(_net_columns(x), int(seed)))
382
+
383
+ def prediction_intervals(self, x: Any, seed: int = -1, realizations: int = 1000,
384
+ alpha: float = 0.1) -> np.ndarray | None:
385
+ """Return bootstrap prediction intervals (``PredictionIntervals``).
386
+
387
+ Parameters
388
+ ----------
389
+ x : array_like
390
+ Query features, shape (m, d); a 1-D array is one feature.
391
+ seed : int, optional
392
+ Mersenne Twister seed; zero or negative seeds from the clock.
393
+ realizations : int, optional
394
+ Number of bootstrap resamples; default 1000.
395
+ alpha : float, optional
396
+ One minus the interval coverage; default 0.1.
397
+
398
+ Returns
399
+ -------
400
+ numpy.ndarray or None
401
+ Shape (m, 4) with columns lower, median, upper and mean.
402
+ """
403
+ return _matrix(self.net.PredictionIntervals(_net_columns(x), int(seed), int(realizations), float(alpha)))
404
+
405
+ k = _member("K", int, "Number of neighbors.")
406
+ is_regression = _member("IsRegression", bool, "Regression (True, default) or classification.", bool)
407
+ number_of_features = _member("NumberOfFeatures", int, "Number of feature columns d.")
408
+ x = _member("X", _matrix, "Training features, shape (n, d).")
409
+ y = _member("Y", _doubles, "Training responses.")
410
+
411
+
412
+ class NaiveBayes(_NetModel):
413
+ """Gaussian naive Bayes classifier (``Numerics.MachineLearning.NaiveBayes``).
414
+
415
+ Parameters
416
+ ----------
417
+ x : array_like
418
+ Training features, shape (n, d); a 1-D array is one feature.
419
+ y : array_like
420
+ Class labels as numbers, length n.
421
+
422
+ Attributes
423
+ ----------
424
+ net : Numerics.MachineLearning.NaiveBayes
425
+ The wrapped .NET object.
426
+
427
+ Notes
428
+ -----
429
+ Class-conditional standard deviations use the n - 1 divisor and are floored at
430
+ ``sqrt(1e-9 * max variance)``; priors are the class frequencies.
431
+ """
432
+
433
+ def __init__(self, x: Any, y: Any) -> None:
434
+ self.net = _net_type("NaiveBayes")(_net_columns(x), to_net_doubles(y))
435
+
436
+ def train(self) -> None:
437
+ """Estimate the priors and class-conditional normals (``Train``)."""
438
+ self.net.Train()
439
+
440
+ def predict(self, x: Any) -> np.ndarray | None:
441
+ """Return the maximum a posteriori class of each row (``Predict``).
442
+
443
+ Parameters
444
+ ----------
445
+ x : array_like
446
+ Features, shape (m, d); a 1-D array is one feature.
447
+
448
+ Returns
449
+ -------
450
+ numpy.ndarray or None
451
+ The m predicted classes, or None when untrained or ``x`` has a different number of columns.
452
+ """
453
+ return _doubles(self.net.Predict(_net_columns(x)))
454
+
455
+ classes = _member("Classes", _doubles, "The distinct class labels.")
456
+ means = _member("Means", _matrix, "Class-conditional feature means, shape (classes, d).")
457
+ standard_deviations = _member("StandardDeviations", _matrix,
458
+ "Class-conditional feature standard deviations, shape (classes, d).")
459
+ priors = _member("Priors", _doubles, "Class prior probabilities.")
460
+ is_trained = _member("IsTrained", bool, "Whether the classifier has been trained.")
461
+ x = _member("X", _matrix, "Training features, shape (n, d).")
462
+ y = _member("Y", _doubles, "Training class labels.")
463
+
464
+
465
+ def _glm_predict(arity: int) -> Any:
466
+ """Return ``GeneralizedLinearModel.Predict(Matrix)`` or ``Predict(Matrix, double)``, bound once."""
467
+ def factory() -> Any:
468
+ import System # noqa: PLC0415 - the runtime is running
469
+
470
+ matrix = namespace("Numerics.Mathematics.LinearAlgebra").Matrix
471
+ signature = (matrix,) if arity == 1 else (matrix, System.Double)
472
+ return _net_type("GeneralizedLinearModel").Predict.Overloads[signature]
473
+
474
+ return cached_overload(f"GeneralizedLinearModel.Predict/{arity}", factory)
475
+
476
+
477
+ class GeneralizedLinearModel(_NetModel):
478
+ """Generalized linear model fitted by maximum likelihood (``Numerics.MachineLearning.GeneralizedLinearModel``).
479
+
480
+ Parameters
481
+ ----------
482
+ x : array_like
483
+ Predictors, shape (n, p); a 1-D array is one predictor.
484
+ y : array_like
485
+ Responses, length n (0/1 for the binary links, counts for the log link).
486
+ has_intercept : bool, optional
487
+ Whether an intercept column is added; default True.
488
+ link : str, optional
489
+ ``LinkFunctionType`` member name: ``"identity"`` (normal, default), ``"log"`` (Poisson),
490
+ ``"logit"``, ``"probit"`` or ``"complementary_log_log"`` (Bernoulli). With ``link_function``
491
+ it selects the family and the optimizer start.
492
+ link_function : Numerics.Functions.ILinkFunction, optional
493
+ A custom .NET link function instance.
494
+ feature_names : sequence of str, optional
495
+ Predictor names for :meth:`summary` (``Matrix.Header``). With an added intercept column the
496
+ coefficient names are ``"Intercept"`` followed by these names; without names they are
497
+ ``β1 ... βp``.
498
+ response_name : str, optional
499
+ Response name for :meth:`summary` (``Vector.Header``).
500
+
501
+ Attributes
502
+ ----------
503
+ net : Numerics.MachineLearning.GeneralizedLinearModel
504
+ The wrapped .NET object.
505
+ """
506
+
507
+ def __init__(self, x: Any, y: Any, has_intercept: bool = True, link: str = "identity",
508
+ link_function: Any = None, feature_names: Any = None, response_name: str | None = None) -> None:
509
+ functions = namespace("Numerics.Functions")
510
+ net_type = _net_type("GeneralizedLinearModel")
511
+ matrix, vector = _numerics_matrix(_columns(x)), _numerics_vector(y)
512
+ if feature_names is not None:
513
+ import System # noqa: PLC0415 - the runtime is running
514
+
515
+ matrix.Header = System.Array[System.String]([str(name) for name in feature_names])
516
+ if response_name is not None:
517
+ vector.Header = str(response_name)
518
+ link_type = net_enum(functions.LinkFunctionType, link)
519
+ if link_function is None:
520
+ self.net = net_type(matrix, vector, bool(has_intercept), link_type)
521
+ else:
522
+ self.net = net_type(matrix, vector, link_function, bool(has_intercept), link_type)
523
+
524
+ def set_optimizer(self, method: str = "NelderMead") -> None:
525
+ """Choose the local optimizer used by :meth:`train` (``SetOptimizer``).
526
+
527
+ Parameters
528
+ ----------
529
+ method : str, optional
530
+ ``LocalMethod`` member name; default ``"NelderMead"``.
531
+ """
532
+ optimization = namespace("Numerics.Mathematics.Optimization")
533
+ self.net.SetOptimizer(net_enum(optimization.LocalMethod, method))
534
+
535
+ def train(self) -> None:
536
+ """Fit the parameters and compute the diagnostics (``Train``).
537
+
538
+ The optimizer result is kept unless the Fisher scoring estimate has a higher log-likelihood.
539
+ """
540
+ self.net.Train()
541
+
542
+ def predict(self, x: Any, alpha: float | None = None) -> np.ndarray:
543
+ """Return the mean response, optionally with a confidence interval (``Predict``).
544
+
545
+ Parameters
546
+ ----------
547
+ x : array_like
548
+ Predictors, shape (m, p), or (m, p + 1) with the intercept column already included.
549
+ alpha : float, optional
550
+ When given, one minus the confidence level of the interval for the mean response.
551
+
552
+ Returns
553
+ -------
554
+ numpy.ndarray
555
+ The m mean responses, or with ``alpha`` shape (m, 3) with columns lower, mean and upper
556
+ (formed on the link scale and mapped through the inverse link).
557
+ """
558
+ matrix = _numerics_matrix(_columns(x))
559
+ if alpha is None:
560
+ return to_numpy_doubles(_glm_predict(1)(self.net, matrix))
561
+ return to_numpy_matrix(_glm_predict(2)(self.net, matrix, float(alpha)))
562
+
563
+ def summary(self) -> list[str]:
564
+ """Return the text summary table (``Summary``).
565
+
566
+ Returns
567
+ -------
568
+ list of str
569
+ The lines of the report.
570
+ """
571
+ return [str(line) for line in self.net.Summary()]
572
+
573
+ has_intercept = _member("HasIntercept", bool, "Whether the model has an intercept.")
574
+ use_robust_se = _member("UseRobustSE", bool, "Use HC0 sandwich standard errors (set before training).", bool)
575
+ parameters = _member("Parameters", _doubles, "Fitted coefficients, intercept first; None before training.")
576
+ parameter_standard_errors = _member("ParameterStandardErrors", _doubles, "Coefficient standard errors.")
577
+ parameter_z_scores = _member("ParameterZScores", _doubles, "Coefficient z statistics.")
578
+ parameter_p_values = _member("ParameterPValues", _doubles, "Two-sided normal p-values of the coefficients.")
579
+ covariance = _member("Covariance", _matrix, "Coefficient covariance matrix.")
580
+ residuals = _member("Residuals", _doubles, "Response residuals y - mu.")
581
+ standard_error = _member("StandardError", float, "Residual standard error sqrt(SSE / (n - p)).")
582
+ sample_size = _member("SampleSize", int, "Number of observations n.")
583
+ degrees_of_freedom = _member("DegreesOfFreedom", int, "Residual degrees of freedom n - p.")
584
+ aic = _member("AIC", float, "Akaike information criterion; the normal family evaluates the likelihood at "
585
+ "the maximum likelihood variance SSE / n, as statsmodels GLM does.")
586
+ aicc = _member("AICc", float, "Small-sample corrected AIC.")
587
+ bic = _member("BIC", float, "Bayesian information criterion.")
588
+ x = _member("X", _matrix, "Design matrix, including the intercept column when present.")
589
+ y = _member("Y", _doubles, "Responses.")
590
+ link_type = _member("LinkType", str, "``LinkFunctionType`` member name of the model.")
591
+ link_function = _member("LinkFunction", None, "The .NET ``ILinkFunction`` instance.")
592
+ optimizer = _member("Optimizer", None, "The .NET ``Optimizer`` used by :meth:`train`.")
593
+
594
+ @property
595
+ def parameter_names(self) -> list[str]:
596
+ """Coefficient names, ``"Intercept"`` first when present."""
597
+ names = self.net.ParameterNames
598
+ return [] if names is None else [str(name) for name in names]
599
+
600
+
601
+ class ElasticNetRegression(_NetModel):
602
+ """Elastic net regression path by coordinate descent (``Numerics.MachineLearning.ElasticNetRegression``).
603
+
604
+ Parameters
605
+ ----------
606
+ x : array_like
607
+ Predictors, shape (n, p); a 1-D array is one predictor.
608
+ y : array_like
609
+ Responses, length n.
610
+
611
+ Attributes
612
+ ----------
613
+ net : Numerics.MachineLearning.ElasticNetRegression
614
+ The wrapped .NET object.
615
+
616
+ Notes
617
+ -----
618
+ Follows glmnet: for each penalty lambda the method minimizes
619
+ ``1/(2n) sum (y - b0 - x b)**2 + lambda sum v_j ((1 - alpha)/2 b_j**2 + alpha |b_j|)`` with the
620
+ predictors and the response standardized. Set the options before :meth:`fit`.
621
+ """
622
+
623
+ def __init__(self, x: Any, y: Any) -> None:
624
+ self.net = _net_type("ElasticNetRegression")(_net_columns(x), to_net_doubles(y))
625
+
626
+ def fit(self) -> None:
627
+ """Fit the coefficient path (``Fit``)."""
628
+ self.net.Fit()
629
+
630
+ def cross_validate(self, folds: int = 10, seed: int = 12345, fold_ids: Any = None) -> None:
631
+ """Run k-fold cross-validation over the path, fitting it first if needed (``CrossValidate``).
632
+
633
+ Parameters
634
+ ----------
635
+ folds : int, optional
636
+ Number of folds, at least 3; default 10. Ignored with ``fold_ids``.
637
+ seed : int, optional
638
+ Seed of the random fold assignment; default 12345. Ignored with ``fold_ids``.
639
+ fold_ids : array_like of int, optional
640
+ The fold label of each observation, at least three distinct labels.
641
+ """
642
+ if fold_ids is None:
643
+ self.net.CrossValidate(int(folds), int(seed))
644
+ else:
645
+ self.net.CrossValidate(to_net_ints(fold_ids))
646
+
647
+ def get_coefficients(self, index: int) -> np.ndarray:
648
+ """Return the coefficients, without the intercept, at a penalty on the path (``GetCoefficients``).
649
+
650
+ Parameters
651
+ ----------
652
+ index : int
653
+ Penalty index.
654
+
655
+ Returns
656
+ -------
657
+ numpy.ndarray
658
+ The p coefficients on the original scale.
659
+ """
660
+ return to_numpy_doubles(self.net.GetCoefficients(int(index)))
661
+
662
+ def selected_variables(self, index: int) -> np.ndarray:
663
+ """Return the indexes of the nonzero coefficients at a penalty (``SelectedVariables``).
664
+
665
+ Parameters
666
+ ----------
667
+ index : int
668
+ Penalty index.
669
+
670
+ Returns
671
+ -------
672
+ numpy.ndarray
673
+ Predictor indexes in increasing order, int32.
674
+ """
675
+ return to_numpy_ints(self.net.SelectedVariables(int(index)))
676
+
677
+ def predict(self, x: Any, index: int) -> np.ndarray | float:
678
+ """Return the prediction at a penalty on the path (``Predict``).
679
+
680
+ Parameters
681
+ ----------
682
+ x : array_like
683
+ Predictors, shape (m, p), or a 1-D array of the p values of one observation.
684
+ index : int
685
+ Penalty index.
686
+
687
+ Returns
688
+ -------
689
+ numpy.ndarray or float
690
+ The m predictions, or one prediction for a 1-D ``x``.
691
+ """
692
+ array = np.asarray(x, dtype=np.float64)
693
+ if array.ndim == 1:
694
+ return float(self.net.Predict(to_net_doubles(array), int(index)))
695
+ return to_numpy_doubles(self.net.Predict(to_net_matrix(array), int(index)))
696
+
697
+ alpha = _member("Alpha", float, "Mixing parameter in [0, 1]: 1 is the lasso (default), 0 ridge.", float)
698
+ number_of_lambdas = _member("NumberOfLambdas", int, "Number of penalties on the default path (100).", int)
699
+ lambda_min_ratio = _member("LambdaMinRatio", float,
700
+ "Smallest penalty as a fraction of lambda_max; NaN selects the glmnet default.", float)
701
+ user_lambdas = _member("UserLambdas", _doubles, "User penalty sequence, or None for the default path.",
702
+ _optional_doubles)
703
+ standardize = _member("Standardize", bool, "Whether the predictors are standardized (default True).", bool)
704
+ has_intercept = _member("HasIntercept", bool, "Whether an intercept is fitted (default True).", bool)
705
+ tolerance = _member("Tolerance", float, "Coordinate descent convergence tolerance (default 1e-7).", float)
706
+ max_iterations = _member("MaxIterations", int, "Maximum number of coordinate passes (default 100000).", int)
707
+ penalty_factors = _member("PenaltyFactors", _doubles, "Per-predictor penalty factors, or None for ones.",
708
+ _optional_doubles)
709
+ sample_size = _member("SampleSize", int, "Number of observations n.")
710
+ number_of_predictors = _member("NumberOfPredictors", int, "Number of predictors p.")
711
+ is_fitted = _member("IsFitted", bool, "Whether the path has been fitted.")
712
+ converged = _member("Converged", bool, "Whether every penalty converged.")
713
+ iterations = _member("Iterations", int, "Total number of coordinate passes.")
714
+ lambdas = _member("Lambdas", _doubles, "Penalties on the fitted path.")
715
+ intercepts = _member("Intercepts", _doubles, "Intercept at each penalty.")
716
+ coefficients = _member("Coefficients", _matrix, "Coefficients on the original scale, shape (penalties, p).")
717
+ deviance_ratio = _member("DevianceRatio", _doubles, "Fraction of deviance explained at each penalty.")
718
+ nonzero_count = _member("NonzeroCount", _ints, "Number of nonzero coefficients at each penalty.")
719
+ is_cross_validated = _member("IsCrossValidated", bool, "Whether cross-validation has run.")
720
+ fold_ids = _member("FoldIds", _ints, "Fold label of each observation used by cross-validation.")
721
+ cross_validation_error = _member("CrossValidationError", _doubles, "Mean squared prediction error per penalty.")
722
+ cross_validation_standard_error = _member("CrossValidationStandardError", _doubles,
723
+ "Standard error of the cross-validation error per penalty.")
724
+ lambda_min_index = _member("LambdaMinIndex", int, "Index of the penalty with the smallest error; -1 before.")
725
+ lambda_one_standard_error_index = _member("LambdaOneStandardErrorIndex", int,
726
+ "Index of the largest penalty within one standard error; -1 before.")
727
+ lambda_min = _member("LambdaMin", float, "Penalty with the smallest cross-validation error.")
728
+ lambda_one_standard_error = _member("LambdaOneStandardError", float,
729
+ "Largest penalty within one standard error of the minimum.")
730
+ x = _member("X", _matrix, "Predictors, shape (n, p).")
731
+ y = _member("Y", _doubles, "Responses.")
732
+
733
+
734
+ def _gpr_predict_interval() -> Any:
735
+ """Return ``GaussianProcessRegression.Predict(Matrix, double, bool)``, bound once."""
736
+ def factory() -> Any:
737
+ import System # noqa: PLC0415 - the runtime is running
738
+
739
+ matrix = namespace("Numerics.Mathematics.LinearAlgebra").Matrix
740
+ signature = (matrix, System.Double, System.Boolean)
741
+ return _net_type("GaussianProcessRegression").Predict.Overloads[signature]
742
+
743
+ return cached_overload("GaussianProcessRegression.Predict(Matrix, double, bool)", factory)
744
+
745
+
746
+ class GaussianProcessRegression(_NetModel):
747
+ """Exact Gaussian process regression (``Numerics.MachineLearning.GaussianProcessRegression``).
748
+
749
+ Parameters
750
+ ----------
751
+ x : array_like
752
+ Training features, shape (n, d); a 1-D array is one feature.
753
+ y : array_like
754
+ Training responses, length n.
755
+ kernel : GaussianProcessKernel or str, optional
756
+ Covariance function; default Matern 5/2.
757
+ mean : GaussianProcessMean or str, optional
758
+ Mean function, estimated by generalized least squares; default constant.
759
+
760
+ Attributes
761
+ ----------
762
+ net : Numerics.MachineLearning.GaussianProcessRegression
763
+ The wrapped .NET object.
764
+
765
+ Notes
766
+ -----
767
+ The covariance is ``signal_variance * rho(r) + noise_variance * I`` with the scaled distance
768
+ ``r`` (one length scale per feature when :attr:`use_ard`). :meth:`train` maximizes the log
769
+ marginal likelihood with L-BFGS-B; :meth:`set_hyperparameters` conditions on fixed values.
770
+ """
771
+
772
+ def __init__(self, x: Any, y: Any, kernel: GaussianProcessKernel | str = GaussianProcessKernel.MATERN52,
773
+ mean: GaussianProcessMean | str = GaussianProcessMean.CONSTANT) -> None:
774
+ net_type = _net_type("GaussianProcessRegression")
775
+ ml = namespace("Numerics.MachineLearning")
776
+ self.net = net_type(_net_columns(x), to_net_doubles(y), net_enum(ml.GaussianProcessKernel, kernel),
777
+ net_enum(ml.GaussianProcessMean, mean))
778
+
779
+ def train(self, noise_variance: float | None = None) -> None:
780
+ """Estimate the hyperparameters by maximum marginal likelihood (``Train``).
781
+
782
+ Parameters
783
+ ----------
784
+ noise_variance : float, optional
785
+ When given, the noise variance is held at this value (zero interpolates); otherwise it is
786
+ estimated with the other hyperparameters.
787
+ """
788
+ if noise_variance is None:
789
+ self.net.Train()
790
+ else:
791
+ self.net.Train(float(noise_variance))
792
+
793
+ def set_hyperparameters(self, signal_variance: float, length_scales: Any, noise_variance: float) -> None:
794
+ """Set the hyperparameters without optimization and condition on the data (``SetHyperparameters``).
795
+
796
+ Parameters
797
+ ----------
798
+ signal_variance : float
799
+ Signal variance, positive.
800
+ length_scales : float or array_like
801
+ One shared length scale, or one per feature (which turns :attr:`use_ard` on).
802
+ noise_variance : float
803
+ Noise variance, non-negative.
804
+ """
805
+ self.net.SetHyperparameters(float(signal_variance), to_net_doubles(length_scales), float(noise_variance))
806
+
807
+ def evaluate_log_marginal_likelihood(self, theta: Any, gradient: bool = False) -> float | tuple[float, np.ndarray]:
808
+ """Return the log marginal likelihood at log hyperparameters (``EvaluateLogMarginalLikelihood``).
809
+
810
+ Parameters
811
+ ----------
812
+ theta : array_like
813
+ ``(ln signal_variance, ln l_1, ..., ln l_q, ln noise_variance)`` with q = d under ARD, else 1.
814
+ gradient : bool, optional
815
+ Whether to also return the gradient.
816
+
817
+ Returns
818
+ -------
819
+ float or tuple of (float, numpy.ndarray)
820
+ The log marginal likelihood (NaN when the covariance cannot be factored), and with
821
+ ``gradient`` its gradient with respect to ``theta``.
822
+ """
823
+ net_theta = to_net_doubles(theta)
824
+ if not gradient:
825
+ return float(self.net.EvaluateLogMarginalLikelihood(net_theta, None))
826
+ import System # noqa: PLC0415 - the runtime is running
827
+
828
+ out = System.Array[System.Double](int(net_theta.Length))
829
+ value = float(self.net.EvaluateLogMarginalLikelihood(net_theta, out))
830
+ return value, to_numpy_doubles(out)
831
+
832
+ def predict(self, x: Any, alpha: float | None = None, include_noise: bool = False) -> np.ndarray:
833
+ """Return the predictive mean, optionally with a central interval (``Predict``).
834
+
835
+ Parameters
836
+ ----------
837
+ x : array_like
838
+ Prediction points, shape (m, d); a 1-D array is one feature.
839
+ alpha : float, optional
840
+ When given, one minus the interval coverage.
841
+ include_noise : bool, optional
842
+ With ``alpha``, whether the interval covers a new noisy observation; default False.
843
+
844
+ Returns
845
+ -------
846
+ numpy.ndarray
847
+ The m means, or with ``alpha`` shape (m, 3) with columns lower, mean and upper.
848
+ """
849
+ if alpha is None:
850
+ return to_numpy_doubles(self.net.Predict(_net_columns(x)))
851
+ matrix = _numerics_matrix(_columns(x))
852
+ return to_numpy_matrix(_gpr_predict_interval()(self.net, matrix, float(alpha), bool(include_noise)))
853
+
854
+ def predict_with_variance(self, x: Any, include_noise: bool = False) -> tuple[np.ndarray, np.ndarray]:
855
+ """Return the predictive mean and variance (``PredictWithVariance``).
856
+
857
+ Parameters
858
+ ----------
859
+ x : array_like
860
+ Prediction points, shape (m, d); a 1-D array is one feature.
861
+ include_noise : bool, optional
862
+ Whether the variance is that of a new observation (True) or of the latent function.
863
+
864
+ Returns
865
+ -------
866
+ tuple of numpy.ndarray
867
+ The m means and the m variances.
868
+ """
869
+ return _pair(self.net.PredictWithVariance(_net_columns(x), bool(include_noise)))
870
+
871
+ def leave_one_out(self) -> tuple[np.ndarray, np.ndarray]:
872
+ """Return the leave-one-out predictive mean and variance of each response (``LeaveOneOut``).
873
+
874
+ Returns
875
+ -------
876
+ tuple of numpy.ndarray
877
+ The n means and the n variances (the variances include the noise).
878
+ """
879
+ return _pair(self.net.LeaveOneOut())
880
+
881
+ use_ard = _member("UseARD", bool, "One length scale per feature (default True).", bool)
882
+ number_of_restarts = _member("NumberOfRestarts", int, "Random optimizer restarts beyond the two data starts.", int)
883
+ prng_seed = _member("PRNGSeed", int, "Seed of the random restarts (default 12345).", int)
884
+ max_iterations = _member("MaxIterations", int, "Maximum optimizer iterations per start (default 200).", int)
885
+ tolerance = _member("Tolerance", float, "Projected gradient tolerance (default 1e-5).", float)
886
+ sample_size = _member("SampleSize", int, "Number of observations n.")
887
+ number_of_features = _member("NumberOfFeatures", int, "Number of features d.")
888
+ signal_variance = _member("SignalVariance", float, "Signal variance.")
889
+ length_scales = _member("LengthScales", _doubles, "Length scales.")
890
+ noise_variance = _member("NoiseVariance", float, "Noise variance.")
891
+ jitter = _member("Jitter", float, "Diagonal jitter added to factor the covariance.")
892
+ mean_coefficients = _member("MeanCoefficients", _doubles, "Generalized least squares mean coefficients.")
893
+ log_marginal_likelihood = _member("LogMarginalLikelihood", float, "Log marginal likelihood after training.")
894
+ status = _member("Status", str, "``OptimizationStatus`` member name of the last training.")
895
+ is_trained = _member("IsTrained", bool, "Whether the model is conditioned on the data.")
896
+
897
+ @property
898
+ def kernel(self) -> GaussianProcessKernel:
899
+ """Covariance function."""
900
+ return GaussianProcessKernel(str(self.net.Kernel))
901
+
902
+ @property
903
+ def mean_function(self) -> GaussianProcessMean:
904
+ """Mean function."""
905
+ return GaussianProcessMean(str(self.net.MeanFunction))
906
+
907
+
908
+ class Kriging(_NetModel):
909
+ """Simple, ordinary and universal kriging with a variogram model (``Numerics.MachineLearning.Kriging``).
910
+
911
+ Parameters
912
+ ----------
913
+ coordinates : array_like
914
+ Observation locations, shape (n, d); a 1-D array is one coordinate.
915
+ values : array_like
916
+ Observed values, length n.
917
+ model : VariogramModel or str
918
+ Variogram model.
919
+ nugget : float
920
+ Nugget c0, non-negative.
921
+ partial_sill : float
922
+ Partial sill c, positive.
923
+ range : float
924
+ Range parameter a (gstat ``vgm`` convention), positive.
925
+ kriging_type : KrigingType or str, optional
926
+ Kriging variant; default ordinary.
927
+ mean : float, optional
928
+ Known mean for simple kriging; default 0.
929
+ distance : callable, str or System.Delegate, optional
930
+ Distance between two locations, ``distance(a, b) -> float``, as a Python callable, C# source
931
+ or a ``Func<double[], double[], double>``; default Euclidean.
932
+
933
+ Attributes
934
+ ----------
935
+ net : Numerics.MachineLearning.Kriging
936
+ The wrapped .NET object.
937
+ """
938
+
939
+ def __init__(self, coordinates: Any, values: Any, model: VariogramModel | str, nugget: float,
940
+ partial_sill: float, range: float, kriging_type: KrigingType | str = KrigingType.ORDINARY,
941
+ mean: float = 0.0, distance: Any = None) -> None:
942
+ net_type = _net_type("Kriging")
943
+ ml = namespace("Numerics.MachineLearning")
944
+ head = (_net_columns(coordinates), to_net_doubles(values), net_enum(ml.VariogramModel, model),
945
+ float(nugget), float(partial_sill), float(range))
946
+ tail = (net_enum(ml.KrigingType, kriging_type), float(mean))
947
+ if distance is None:
948
+ self.net = net_type(*head, *tail)
949
+ else:
950
+ function = callbacks.to_delegate(distance, "Func<double[],double[],double>")
951
+ self.net = net_type(*head, function, *tail)
952
+ callbacks.apply_parallel_policy(self.net, function)
953
+
954
+ def semivariance(self, h: Any) -> Any:
955
+ """Return the variogram at lag distance ``h`` (``Semivariance``).
956
+
957
+ Parameters
958
+ ----------
959
+ h : float or array_like
960
+ Lag distances, non-negative.
961
+
962
+ Returns
963
+ -------
964
+ float or numpy.ndarray
965
+ ``gamma(h)``, zero at h = 0.
966
+ """
967
+ return loop(h, self.net.Semivariance)
968
+
969
+ def covariance(self, h: Any) -> Any:
970
+ """Return the covariance at lag distance ``h`` (``Covariance``).
971
+
972
+ Parameters
973
+ ----------
974
+ h : float or array_like
975
+ Lag distances, non-negative.
976
+
977
+ Returns
978
+ -------
979
+ float or numpy.ndarray
980
+ ``C(h)``, the sill at h = 0.
981
+ """
982
+ return loop(h, self.net.Covariance)
983
+
984
+ def predict(self, x: Any) -> np.ndarray:
985
+ """Return the kriging predictor at each location (``Predict``).
986
+
987
+ Parameters
988
+ ----------
989
+ x : array_like
990
+ Locations, shape (m, d); a 1-D array is one coordinate.
991
+
992
+ Returns
993
+ -------
994
+ numpy.ndarray
995
+ The m predictions.
996
+ """
997
+ return to_numpy_doubles(self.net.Predict(_net_columns(x)))
998
+
999
+ def predict_with_variance(self, x: Any) -> tuple[np.ndarray, np.ndarray] | tuple[float, float]:
1000
+ """Return the kriging predictor and kriging variance (``PredictWithVariance``).
1001
+
1002
+ Parameters
1003
+ ----------
1004
+ x : array_like
1005
+ Locations, shape (m, d), or a 1-D array holding the d coordinates of one location.
1006
+
1007
+ Returns
1008
+ -------
1009
+ tuple
1010
+ The m predictions and m variances, or a (mean, variance) pair of floats for one location.
1011
+ """
1012
+ array = np.asarray(x, dtype=np.float64)
1013
+ if array.ndim == 1:
1014
+ result = self.net.PredictWithVariance(to_net_doubles(array))
1015
+ return float(result.Item1), float(result.Item2)
1016
+ return _pair(self.net.PredictWithVariance(to_net_matrix(array)))
1017
+
1018
+ def cross_validate(self) -> tuple[np.ndarray, np.ndarray]:
1019
+ """Return the leave-one-out prediction and variance at each observation (``CrossValidate``).
1020
+
1021
+ Returns
1022
+ -------
1023
+ tuple of numpy.ndarray
1024
+ The n predictions and the n variances.
1025
+ """
1026
+ return _pair(self.net.CrossValidate())
1027
+
1028
+ nugget = _member("Nugget", float, "Nugget c0.")
1029
+ partial_sill = _member("PartialSill", float, "Partial sill c.")
1030
+ sill = _member("Sill", float, "Sill c0 + c.")
1031
+ range = _member("Range", float, "Range parameter a.")
1032
+ known_mean = _member("KnownMean", float, "Known mean of simple kriging; NaN otherwise.")
1033
+ sample_size = _member("SampleSize", int, "Number of observations n.")
1034
+ number_of_dimensions = _member("NumberOfDimensions", int, "Number of coordinates d.")
1035
+ trend_coefficients = _member("TrendCoefficients", _doubles, "Estimated trend coefficients (empty for simple).")
1036
+
1037
+ @property
1038
+ def model(self) -> VariogramModel:
1039
+ """Variogram model."""
1040
+ return VariogramModel(str(self.net.Model))
1041
+
1042
+ @property
1043
+ def kriging_type(self) -> KrigingType:
1044
+ """Kriging variant."""
1045
+ return KrigingType(str(self.net.Type))
1046
+
1047
+
1048
+ class KMeans(_NetModel):
1049
+ """The k-means clustering by Lloyd iterations (``Numerics.MachineLearning.KMeans``).
1050
+
1051
+ Parameters
1052
+ ----------
1053
+ x : array_like
1054
+ Data, shape (n, d); a 1-D array is one feature.
1055
+ k : int
1056
+ Number of clusters.
1057
+
1058
+ Attributes
1059
+ ----------
1060
+ net : Numerics.MachineLearning.KMeans
1061
+ The wrapped .NET object.
1062
+ """
1063
+
1064
+ def __init__(self, x: Any, k: int) -> None:
1065
+ self.net = _net_type("KMeans")(_net_columns(x), int(k))
1066
+
1067
+ def train(self, seed: int = -1, k_means_plus_plus: bool = True) -> None:
1068
+ """Cluster the data (``Train``); iterations stop when no label changes.
1069
+
1070
+ Parameters
1071
+ ----------
1072
+ seed : int, optional
1073
+ Mersenne Twister seed of the initialization; zero or negative seeds from the clock.
1074
+ k_means_plus_plus : bool, optional
1075
+ The k-means++ seeding (default) or k distinct random rows.
1076
+ """
1077
+ self.net.Train(int(seed), bool(k_means_plus_plus))
1078
+
1079
+ @staticmethod
1080
+ def initialize(x: Any, k: int, seed: int = -1, k_means_plus_plus: bool = True) -> np.ndarray:
1081
+ """Return the initial centroids :meth:`train` starts from (``Initialize``).
1082
+
1083
+ Parameters
1084
+ ----------
1085
+ x : array_like
1086
+ Data, shape (n, d); a 1-D array is one feature.
1087
+ k : int
1088
+ Number of clusters.
1089
+ seed : int, optional
1090
+ Mersenne Twister seed; zero or negative seeds from the clock.
1091
+ k_means_plus_plus : bool, optional
1092
+ The k-means++ seeding (default) or k distinct random rows.
1093
+
1094
+ Returns
1095
+ -------
1096
+ numpy.ndarray
1097
+ Shape (k, d).
1098
+ """
1099
+ net_type = _net_type("KMeans")
1100
+ matrix = _numerics_matrix(_columns(x))
1101
+ return to_numpy_matrix(net_type.Initialize(matrix, int(k), int(seed), bool(k_means_plus_plus)))
1102
+
1103
+ k = _member("K", int, "Number of clusters.")
1104
+ dimension = _member("Dimension", int, "Number of features d.")
1105
+ max_iterations = _member("MaxIterations", int, "Maximum number of iterations (default 1000).", int)
1106
+ iterations = _member("Iterations", int, "Number of iterations of the last training.")
1107
+ means = _member("Means", _matrix, "Cluster centroids, shape (k, d).")
1108
+ labels = _member("Labels", _ints, "Cluster label of each row.")
1109
+ x = _member("X", _matrix, "Data, shape (n, d).")
1110
+
1111
+
1112
+ class GaussianMixtureModel(_NetModel):
1113
+ """Gaussian mixture model fitted by expectation maximization (``Numerics.MachineLearning.GaussianMixtureModel``).
1114
+
1115
+ Parameters
1116
+ ----------
1117
+ x : array_like
1118
+ Data, shape (n, d); a 1-D array is one feature.
1119
+ k : int
1120
+ Number of components.
1121
+
1122
+ Attributes
1123
+ ----------
1124
+ net : Numerics.MachineLearning.GaussianMixtureModel
1125
+ The wrapped .NET object.
1126
+
1127
+ Notes
1128
+ -----
1129
+ The components start from a k-means fit with the same seed, with full covariance matrices.
1130
+ """
1131
+
1132
+ def __init__(self, x: Any, k: int) -> None:
1133
+ self.net = _net_type("GaussianMixtureModel")(_net_columns(x), int(k))
1134
+
1135
+ def train(self, seed: int = -1, k_means_plus_plus: bool = True) -> None:
1136
+ """Fit the mixture (``Train``).
1137
+
1138
+ Parameters
1139
+ ----------
1140
+ seed : int, optional
1141
+ Seed of the k-means initialization; zero or negative seeds from the clock.
1142
+ k_means_plus_plus : bool, optional
1143
+ The k-means++ seeding (default) or random rows.
1144
+ """
1145
+ self.net.Train(int(seed), bool(k_means_plus_plus))
1146
+
1147
+ @property
1148
+ def sigmas(self) -> np.ndarray | None:
1149
+ """Component covariance matrices, shape (k, d, d)."""
1150
+ sigmas = self.net.Sigmas
1151
+ return None if sigmas is None else np.stack([to_numpy_matrix(s.Array) for s in sigmas])
1152
+
1153
+ k = _member("K", int, "Number of components.")
1154
+ dimension = _member("Dimension", int, "Number of features d.")
1155
+ max_iterations = _member("MaxIterations", int, "Maximum number of EM iterations (default 1000).", int)
1156
+ tolerance = _member("Tolerance", float, "Log-likelihood convergence tolerance (default 1e-8).", float)
1157
+ iterations = _member("Iterations", int, "Number of EM iterations of the last training.")
1158
+ means = _member("Means", _matrix, "Component means, shape (k, d).")
1159
+ weights = _member("Weights", _doubles, "Component weights.")
1160
+ labels = _member("Labels", _ints, "Most likely component of each row.")
1161
+ likelihood_matrix = _member("LikelihoodMatrix", _matrix, "Component membership probabilities, shape (n, k).")
1162
+ log_likelihood = _member("LogLikelihood", float, "Log-likelihood of the fitted mixture.")
1163
+ x = _member("X", _matrix, "Data, shape (n, d).")
1164
+
1165
+
1166
+ class JenksCluster(_NetModel):
1167
+ """One class of a Jenks natural breaks classification (``Numerics.MachineLearning.JenksCluster``).
1168
+
1169
+ Parameters
1170
+ ----------
1171
+ data : array_like
1172
+ Sorted data.
1173
+ start_index : int
1174
+ Index of the first value of the cluster.
1175
+ end_index : int
1176
+ Index of the last value of the cluster (inclusive).
1177
+
1178
+ Attributes
1179
+ ----------
1180
+ net : Numerics.MachineLearning.JenksCluster
1181
+ The wrapped .NET object.
1182
+ """
1183
+
1184
+ def __init__(self, data: Any, start_index: int, end_index: int) -> None:
1185
+ self.net = _net_type("JenksCluster")(to_net_doubles(data), int(start_index), int(end_index))
1186
+
1187
+ start_index = _member("StartIndex", int, "Index of the first value.")
1188
+ end_index = _member("EndIndex", int, "Index of the last value (inclusive).")
1189
+ count = _member("Count", int, "Number of values.")
1190
+ min_value = _member("MinValue", float, "Smallest value.")
1191
+ max_value = _member("MaxValue", float, "Largest value.")
1192
+ sum = _member("Sum", float, "Sum of the values.")
1193
+ average = _member("Average", float, "Mean of the values.")
1194
+ variance = _member("Variance", float, "Sample variance (n - 1 divisor; zero for one value).")
1195
+ sum_of_squared_deviations = _member("SumOfSquaredDeviations", float, "Sum of squared deviations from the mean.")
1196
+
1197
+
1198
+ class JenksNaturalBreaks(_NetModel):
1199
+ """Jenks natural breaks optimization (``Numerics.MachineLearning.JenksNaturalBreaks``).
1200
+
1201
+ Parameters
1202
+ ----------
1203
+ data : array_like
1204
+ Values to classify.
1205
+ number_of_clusters : int
1206
+ Number of classes.
1207
+ is_data_sorted : bool, optional
1208
+ Whether ``data`` is already sorted ascending; default False.
1209
+
1210
+ Attributes
1211
+ ----------
1212
+ net : Numerics.MachineLearning.JenksNaturalBreaks
1213
+ The wrapped .NET object.
1214
+
1215
+ Notes
1216
+ -----
1217
+ The classification is computed by the constructor: it minimizes the total within-class sum of
1218
+ squared deviations over contiguous classes of the sorted data.
1219
+ """
1220
+
1221
+ def __init__(self, data: Any, number_of_clusters: int, is_data_sorted: bool = False) -> None:
1222
+ self.net = _net_type("JenksNaturalBreaks")(to_net_doubles(data), int(number_of_clusters), bool(is_data_sorted))
1223
+
1224
+ sorted_data = _member("SortedData", _doubles, "The data sorted ascending.")
1225
+ number_of_clusters = _member("NumberOfClusters", int, "Number of classes.")
1226
+ breaks = _member("Breaks", _doubles, "Break points.")
1227
+ goodness_of_variance_fit = _member("GoodnessOfVarianceFit", float,
1228
+ "Goodness of variance fit, 1 - SDCM / SDAM; closer to 1 is better.")
1229
+
1230
+ @property
1231
+ def clusters(self) -> list[JenksCluster]:
1232
+ """The classes, in ascending order."""
1233
+ clusters = self.net.Clusters
1234
+ return [] if clusters is None else [JenksCluster._wrap(cluster) for cluster in clusters]
1235
+
1236
+
1237
+ class PrincipalComponentAnalysis(_NetModel):
1238
+ """Principal component analysis (``Numerics.MachineLearning.PrincipalComponentAnalysis``).
1239
+
1240
+ Parameters
1241
+ ----------
1242
+ x : array_like
1243
+ Data, shape (n, p), observations in rows.
1244
+ basis : PrincipalComponentBasis or str, optional
1245
+ Covariance (default, centered) or correlation (centered and scaled) basis.
1246
+ number_of_components : int, optional
1247
+ Components to retain; zero or negative (default) retains min(n, p).
1248
+
1249
+ Attributes
1250
+ ----------
1251
+ net : Numerics.MachineLearning.PrincipalComponentAnalysis
1252
+ The wrapped .NET object.
1253
+
1254
+ Notes
1255
+ -----
1256
+ Each principal axis is oriented so that its element of largest magnitude is positive, the
1257
+ scikit-learn convention. Variances use the n - 1 divisor.
1258
+ """
1259
+
1260
+ def __init__(self, x: Any, basis: PrincipalComponentBasis | str = PrincipalComponentBasis.COVARIANCE,
1261
+ number_of_components: int = 0) -> None:
1262
+ net_type = _net_type("PrincipalComponentAnalysis")
1263
+ ml = namespace("Numerics.MachineLearning")
1264
+ self.net = net_type(_net_columns(x), net_enum(ml.PrincipalComponentBasis, basis), int(number_of_components))
1265
+
1266
+ def train(self) -> None:
1267
+ """Compute the principal components (``Train``)."""
1268
+ self.net.Train()
1269
+
1270
+ def transform(self, x: Any) -> np.ndarray:
1271
+ """Project data onto the retained components (``Transform``).
1272
+
1273
+ Parameters
1274
+ ----------
1275
+ x : array_like
1276
+ Data, shape (m, p), or a 1-D array holding the p values of one observation.
1277
+
1278
+ Returns
1279
+ -------
1280
+ numpy.ndarray
1281
+ Scores, shape (m, k), or length k for one observation.
1282
+ """
1283
+ array = np.asarray(x, dtype=np.float64)
1284
+ if array.ndim == 1:
1285
+ return to_numpy_doubles(self.net.Transform(to_net_doubles(array)))
1286
+ return to_numpy_matrix(self.net.Transform(to_net_matrix(array)))
1287
+
1288
+ def inverse_transform(self, scores: Any) -> np.ndarray:
1289
+ """Map component scores back to the data space (``InverseTransform``).
1290
+
1291
+ Parameters
1292
+ ----------
1293
+ scores : array_like
1294
+ Scores, shape (m, k), or a 1-D array holding the k scores of one observation.
1295
+
1296
+ Returns
1297
+ -------
1298
+ numpy.ndarray
1299
+ Reconstructed data, shape (m, p), or length p for one observation.
1300
+ """
1301
+ array = np.asarray(scores, dtype=np.float64)
1302
+ if array.ndim == 1:
1303
+ return to_numpy_doubles(self.net.InverseTransform(to_net_doubles(array)))
1304
+ return to_numpy_matrix(self.net.InverseTransform(to_net_matrix(array)))
1305
+
1306
+ @property
1307
+ def basis(self) -> PrincipalComponentBasis:
1308
+ """Covariance or correlation basis."""
1309
+ return PrincipalComponentBasis(str(self.net.Basis))
1310
+
1311
+ number_of_samples = _member("NumberOfSamples", int, "Number of observations n.")
1312
+ number_of_features = _member("NumberOfFeatures", int, "Number of variables p.")
1313
+ number_of_components = _member("NumberOfComponents", int, "Number of retained components k.")
1314
+ is_trained = _member("IsTrained", bool, "Whether the analysis has been trained.")
1315
+ means = _member("Means", _doubles, "Column means of the training data.")
1316
+ scales = _member("Scales", _doubles, "Column scales: standard deviations (correlation basis) or ones.")
1317
+ components = _member("Components", _matrix, "Principal axes, shape (k, p); row i is component i.")
1318
+ loadings = _member("Loadings", _matrix, "Loadings, shape (p, k): axes times the square root of the variance.")
1319
+ explained_variance = _member("ExplainedVariance", _doubles, "Variance explained by each component.")
1320
+ explained_variance_ratio = _member("ExplainedVarianceRatio", _doubles, "Fraction of the total variance.")
1321
+ cumulative_explained_variance_ratio = _member("CumulativeExplainedVarianceRatio", _doubles,
1322
+ "Cumulative fraction of the total variance.")
1323
+ singular_values = _member("SingularValues", _doubles, "Singular values of the centered (scaled) data.")
1324
+ total_variance = _member("TotalVariance", float, "Trace of the covariance or correlation matrix.")
1325
+ scores = _member("Scores", _matrix, "Component scores of the training data, shape (n, k).")
1326
+
1327
+
1328
+ __all__ = [
1329
+ "DecisionNode", "DecisionTree", "ElasticNetRegression", "GaussianMixtureModel", "GaussianProcessKernel",
1330
+ "GaussianProcessMean", "GaussianProcessRegression", "GeneralizedLinearModel", "JenksCluster",
1331
+ "JenksNaturalBreaks", "KMeans", "KNearestNeighbors", "Kriging", "KrigingType", "NaiveBayes",
1332
+ "PrincipalComponentAnalysis", "PrincipalComponentBasis", "RandomForest", "VariogramModel",
1333
+ ]
1334
+
1335
+ translate_public(globals())