nestkit 0.1.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 (43) hide show
  1. nestkit/__init__.py +49 -0
  2. nestkit/_base.py +398 -0
  3. nestkit/_validation.py +113 -0
  4. nestkit/calibration/__init__.py +12 -0
  5. nestkit/calibration/calibrators.py +162 -0
  6. nestkit/calibration/diagnostics.py +513 -0
  7. nestkit/callbacks.py +235 -0
  8. nestkit/classifier.py +559 -0
  9. nestkit/comparison/__init__.py +10 -0
  10. nestkit/comparison/comparator.py +391 -0
  11. nestkit/comparison/statistical_tests.py +327 -0
  12. nestkit/diagnostics/__init__.py +9 -0
  13. nestkit/diagnostics/stability.py +201 -0
  14. nestkit/importance/__init__.py +11 -0
  15. nestkit/importance/aggregator.py +319 -0
  16. nestkit/importance/extractors.py +180 -0
  17. nestkit/importance/stability.py +106 -0
  18. nestkit/inner/__init__.py +9 -0
  19. nestkit/inner/search.py +151 -0
  20. nestkit/inner/tuning_report.py +209 -0
  21. nestkit/plotting/__init__.py +66 -0
  22. nestkit/plotting/_style.py +38 -0
  23. nestkit/plotting/calibration.py +220 -0
  24. nestkit/plotting/comparison.py +302 -0
  25. nestkit/plotting/folds.py +199 -0
  26. nestkit/plotting/importance.py +242 -0
  27. nestkit/plotting/summary.py +479 -0
  28. nestkit/plotting/threshold.py +186 -0
  29. nestkit/plotting/tuning.py +121 -0
  30. nestkit/regressor.py +251 -0
  31. nestkit/results/__init__.py +19 -0
  32. nestkit/results/_base.py +283 -0
  33. nestkit/results/classifier_results.py +279 -0
  34. nestkit/results/regressor_results.py +146 -0
  35. nestkit/thresholding/__init__.py +28 -0
  36. nestkit/thresholding/criteria.py +288 -0
  37. nestkit/thresholding/results.py +73 -0
  38. nestkit/thresholding/strategies.py +300 -0
  39. nestkit-0.1.0.dist-info/METADATA +207 -0
  40. nestkit-0.1.0.dist-info/RECORD +43 -0
  41. nestkit-0.1.0.dist-info/WHEEL +5 -0
  42. nestkit-0.1.0.dist-info/licenses/LICENSE +21 -0
  43. nestkit-0.1.0.dist-info/top_level.txt +1 -0
nestkit/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ """nestkit - A rigorous nested cross-validation toolkit for scikit-learn.
2
+
3
+ Core estimators
4
+ ---------------
5
+ - :class:`NestedCVClassifier` - Classification with optional calibration
6
+ and threshold optimization.
7
+ - :class:`NestedCVRegressor` - Regression with optional residual-based
8
+ prediction intervals.
9
+
10
+ Results
11
+ -------
12
+ - :class:`ClassifierResults` / :class:`RegressorResults` - Rich result
13
+ containers with summary statistics, export methods, and plots.
14
+
15
+ Submodules
16
+ ----------
17
+ - :mod:`nestkit.calibration` - Post-hoc probability calibration and
18
+ calibration diagnostics.
19
+ - :mod:`nestkit.callbacks` - Fold-level monitoring (progress, logging,
20
+ checkpointing).
21
+ - :mod:`nestkit.comparison` - Statistical model comparison.
22
+ - :mod:`nestkit.diagnostics` - Hyperparameter stability analysis.
23
+ - :mod:`nestkit.importance` - Cross-fold feature importance aggregation.
24
+ - :mod:`nestkit.inner` - Inner CV tuning reports.
25
+ - :mod:`nestkit.plotting` - 25+ plotting functions for nested CV results.
26
+ - :mod:`nestkit.thresholding` - Decision-threshold optimization criteria.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import logging
32
+
33
+ from nestkit.classifier import NestedCVClassifier
34
+ from nestkit.regressor import NestedCVRegressor
35
+ from nestkit.results.classifier_results import ClassifierResults
36
+ from nestkit.results.regressor_results import RegressorResults
37
+
38
+ __version__ = "0.1.0"
39
+
40
+ logger = logging.getLogger("nestkit")
41
+ logger.addHandler(logging.NullHandler())
42
+
43
+ __all__ = [
44
+ "ClassifierResults",
45
+ "NestedCVClassifier",
46
+ "NestedCVRegressor",
47
+ "RegressorResults",
48
+ "__version__",
49
+ ]
nestkit/_base.py ADDED
@@ -0,0 +1,398 @@
1
+ """Base class for nested cross-validation.
2
+
3
+ Provides the shared infrastructure for :class:`~nestkit.NestedCVClassifier`
4
+ and :class:`~nestkit.NestedCVRegressor`, including the outer-loop parallelism,
5
+ inner hyperparameter search dispatch, callback orchestration, and result
6
+ aggregation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import time
13
+ from abc import ABCMeta, abstractmethod
14
+ from typing import Any
15
+
16
+ from joblib import Parallel, delayed
17
+ from sklearn.base import BaseEstimator, clone, is_classifier
18
+ from sklearn.model_selection import check_cv
19
+ from sklearn.utils.validation import check_X_y
20
+
21
+ from nestkit.inner.search import build_search
22
+
23
+ logger = logging.getLogger("nestkit")
24
+
25
+
26
+ class _BaseNestedCV(BaseEstimator, metaclass=ABCMeta):
27
+ """Abstract base class for nested cross-validation estimators.
28
+
29
+ This class is not intended to be instantiated directly. Use
30
+ :class:`~nestkit.NestedCVClassifier` or :class:`~nestkit.NestedCVRegressor`
31
+ instead.
32
+
33
+ The nested CV procedure consists of four phases per outer fold:
34
+
35
+ 1. **Inner CV search** - hyperparameter tuning via grid or randomized
36
+ search on the outer training set.
37
+ 2. **Post-inner processing** - task-specific operations such as
38
+ probability calibration or prediction interval estimation
39
+ (implemented by subclasses).
40
+ 3. **Refit** - the best hyperparameters are used to refit the estimator
41
+ on the full outer training set.
42
+ 4. **Outer evaluation** - the refitted model is scored on the held-out
43
+ outer test fold.
44
+
45
+ Parameters
46
+ ----------
47
+ estimator : estimator object
48
+ A scikit-learn compatible estimator that implements ``fit``.
49
+ The estimator is cloned for each outer fold.
50
+ param_grid : dict or list of dict
51
+ Dictionary with parameter names (``str``) as keys and lists of
52
+ parameter settings to try as values, or a list of such
53
+ dictionaries. Passed directly to the inner search strategy.
54
+ search_strategy : {'grid', 'random', 'bayesian'}, default='grid'
55
+ Hyperparameter search strategy. ``'grid'`` uses
56
+ :class:`~sklearn.model_selection.GridSearchCV`; ``'random'`` uses
57
+ :class:`~sklearn.model_selection.RandomizedSearchCV`;
58
+ ``'bayesian'`` uses :class:`skopt.BayesSearchCV` (requires
59
+ *scikit-optimize*).
60
+ outer_cv : int, cross-validation generator, or iterable, default=5
61
+ Determines the outer cross-validation splitting strategy.
62
+ See :func:`sklearn.model_selection.check_cv` for accepted formats.
63
+ inner_cv : int, cross-validation generator, or iterable, default=5
64
+ Determines the inner cross-validation splitting strategy used
65
+ for hyperparameter search.
66
+ scoring : str, callable, list, tuple, or dict, default=None
67
+ Scoring metric(s) for the inner search. If ``None``, the
68
+ estimator's default scorer is used. See
69
+ :func:`sklearn.metrics.get_scorer` for valid string values.
70
+ refit : bool or str, default=True
71
+ Whether to refit the best estimator on the full outer training
72
+ set. If a string, it must match one of the scoring keys when
73
+ multi-metric scoring is used.
74
+ return_train_score : bool, default=False
75
+ Whether to include training scores in the inner CV results.
76
+ return_estimator : bool, default=True
77
+ Whether to store the fitted estimator for each outer fold in
78
+ the results.
79
+ error_score : 'raise' or numeric, default='raise'
80
+ Value to assign to the score if an error occurs during inner CV
81
+ fitting. If ``'raise'``, the error is raised.
82
+ n_jobs_outer : int or None, default=None
83
+ Number of jobs for parallelizing the outer folds. ``None`` means
84
+ sequential execution; ``-1`` uses all processors.
85
+ n_jobs_inner : int or None, default=None
86
+ Number of jobs for parallelizing the inner search within each
87
+ outer fold.
88
+ verbose : int, default=0
89
+ Verbosity level. Values > 0 enable progressively more output
90
+ from the inner search.
91
+ random_state : int, RandomState instance, or None, default=None
92
+ Controls randomness in the inner search (e.g., for
93
+ ``RandomizedSearchCV``). Pass an int for reproducible results.
94
+ callbacks : list of callback objects or None, default=None
95
+ List of :class:`~nestkit.FoldCallback` objects that are notified
96
+ at key points during the nested CV procedure.
97
+ pre_dispatch : int or str, default='2*n_jobs'
98
+ Controls the number of jobs dispatched during parallel outer
99
+ fold execution. See :class:`joblib.Parallel` for details.
100
+
101
+ Notes
102
+ -----
103
+ Setting ``n_jobs_outer > 1`` together with ``n_jobs_inner > 1`` may
104
+ cause thread oversubscription and degrade performance. As a rule of
105
+ thumb, parallelize at one level only.
106
+
107
+ See Also
108
+ --------
109
+ nestkit.NestedCVClassifier : Classification-specific nested CV.
110
+ nestkit.NestedCVRegressor : Regression-specific nested CV.
111
+ sklearn.model_selection.GridSearchCV : Inner search backend (grid).
112
+ sklearn.model_selection.RandomizedSearchCV : Inner search backend (random).
113
+ """
114
+
115
+ def __init__(
116
+ self,
117
+ estimator,
118
+ param_grid,
119
+ *,
120
+ search_strategy="grid",
121
+ outer_cv=5,
122
+ inner_cv=5,
123
+ scoring=None,
124
+ refit=True,
125
+ return_train_score=False,
126
+ return_estimator=True,
127
+ error_score="raise",
128
+ n_jobs_outer=None,
129
+ n_jobs_inner=None,
130
+ verbose=0,
131
+ random_state=None,
132
+ callbacks=None,
133
+ pre_dispatch="2*n_jobs",
134
+ ):
135
+ self.estimator = estimator
136
+ self.param_grid = param_grid
137
+ self.search_strategy = search_strategy
138
+ self.outer_cv = outer_cv
139
+ self.inner_cv = inner_cv
140
+ self.scoring = scoring
141
+ self.refit = refit
142
+ self.return_train_score = return_train_score
143
+ self.return_estimator = return_estimator
144
+ self.error_score = error_score
145
+ self.n_jobs_outer = n_jobs_outer
146
+ self.n_jobs_inner = n_jobs_inner
147
+ self.verbose = verbose
148
+ self.random_state = random_state
149
+ self.callbacks = callbacks
150
+ self.pre_dispatch = pre_dispatch
151
+
152
+ @abstractmethod
153
+ def _post_inner_processing(self, search, X_train, y_train, groups_train, **fit_params) -> dict:
154
+ """Called after inner search. Returns task-specific artifacts."""
155
+ ...
156
+
157
+ @abstractmethod
158
+ def _evaluate_outer_fold(self, estimator, X_test, y_test, post_inner_artifacts) -> dict:
159
+ """Evaluate best estimator on outer test fold."""
160
+ ...
161
+
162
+ @abstractmethod
163
+ def _build_results_container(self) -> Any:
164
+ """Return the appropriate results container class."""
165
+ ...
166
+
167
+ def fit(self, X, y, groups=None, **fit_params):
168
+ """Run the full nested cross-validation procedure.
169
+
170
+ For each outer fold the method performs: (1) inner hyperparameter
171
+ search, (2) task-specific post-processing, (3) refit with the best
172
+ parameters on the full outer training set, and (4) evaluation on
173
+ the held-out outer test set.
174
+
175
+ Parameters
176
+ ----------
177
+ X : array-like of shape (n_samples, n_features)
178
+ Training data. If a pandas DataFrame is passed, feature names
179
+ and the original index are preserved in the results.
180
+ y : array-like of shape (n_samples,)
181
+ Target values.
182
+ groups : array-like of shape (n_samples,) or None, default=None
183
+ Group labels for the samples, used by group-aware CV
184
+ splitters (e.g., :class:`~sklearn.model_selection.GroupKFold`).
185
+ **fit_params : dict
186
+ Additional keyword arguments forwarded to the estimator's
187
+ ``fit`` method in both the inner search and the final refit.
188
+
189
+ Returns
190
+ -------
191
+ self
192
+ The fitted nested CV estimator. Results are accessible via
193
+ the :attr:`results_` attribute.
194
+
195
+ Raises
196
+ ------
197
+ ValueError
198
+ If ``X`` and ``y`` have incompatible shapes.
199
+ """
200
+ # DataFrame handling
201
+ if hasattr(X, "columns"):
202
+ self.feature_names_in_ = list(X.columns)
203
+ self._original_index = X.index.copy()
204
+ X = X.to_numpy()
205
+ else:
206
+ self.feature_names_in_ = [f"feature_{i}" for i in range(X.shape[1])]
207
+ self._original_index = None
208
+
209
+ X, y = check_X_y(X, y, multi_output=True, allow_nd=True)
210
+
211
+ outer_cv = check_cv(self.outer_cv, y, classifier=is_classifier(self.estimator))
212
+ splits = list(outer_cv.split(X, y, groups))
213
+ n_outer_folds = len(splits)
214
+
215
+ # Initialize results container
216
+ results_cls = self._build_results_container()
217
+ self.results_ = results_cls(
218
+ n_outer_folds=n_outer_folds,
219
+ feature_names=self.feature_names_in_,
220
+ original_index=self._original_index,
221
+ )
222
+
223
+ # Outer loop
224
+ parallel = Parallel(
225
+ n_jobs=self.n_jobs_outer,
226
+ verbose=max(0, self.verbose - 1),
227
+ pre_dispatch=self.pre_dispatch,
228
+ )
229
+
230
+ fold_results = parallel(
231
+ delayed(self._fit_outer_fold)(
232
+ clone(self.estimator),
233
+ X,
234
+ y,
235
+ train_idx,
236
+ test_idx,
237
+ fold_idx,
238
+ groups,
239
+ **fit_params,
240
+ )
241
+ for fold_idx, (train_idx, test_idx) in enumerate(splits)
242
+ )
243
+
244
+ # Aggregate
245
+ for fr in fold_results:
246
+ self.results_.add_fold(fr)
247
+ self.results_.finalize()
248
+
249
+ # Callbacks
250
+ if self.callbacks:
251
+ for cb in self.callbacks:
252
+ if hasattr(cb, "on_nested_cv_complete"):
253
+ cb.on_nested_cv_complete(self.results_)
254
+
255
+ self.is_fitted_ = True
256
+ return self
257
+
258
+ def _fit_outer_fold(
259
+ self, estimator, X, y, train_idx, test_idx, fold_idx, groups, **fit_params
260
+ ):
261
+ """Execute all four phases of a single outer fold.
262
+
263
+ Parameters
264
+ ----------
265
+ estimator : estimator object
266
+ Cloned base estimator for this fold.
267
+ X : ndarray of shape (n_samples, n_features)
268
+ Full feature matrix.
269
+ y : ndarray of shape (n_samples,)
270
+ Full target vector.
271
+ train_idx : ndarray of shape (n_train,)
272
+ Indices of the outer training set.
273
+ test_idx : ndarray of shape (n_test,)
274
+ Indices of the outer test set.
275
+ fold_idx : int
276
+ Zero-based index of the current outer fold.
277
+ groups : ndarray or None
278
+ Group labels for the full dataset.
279
+ **fit_params : dict
280
+ Additional keyword arguments forwarded to ``fit``.
281
+
282
+ Returns
283
+ -------
284
+ fold_result : dataclass instance
285
+ Task-specific fold result (e.g.,
286
+ :class:`~nestkit.results.ClassifierOuterFoldResult`).
287
+ """
288
+ logger.info("Outer fold %d: starting", fold_idx)
289
+
290
+ # Callbacks
291
+ if self.callbacks:
292
+ for cb in self.callbacks:
293
+ if hasattr(cb, "on_outer_fold_start"):
294
+ cb.on_outer_fold_start(fold_idx, train_idx, test_idx)
295
+
296
+ X_train, y_train = X[train_idx], y[train_idx]
297
+ X_test, y_test = X[test_idx], y[test_idx]
298
+ groups_train = groups[train_idx] if groups is not None else None
299
+
300
+ # Phase 1: Inner CV search
301
+ t_start = time.time()
302
+ search = build_search(
303
+ strategy=self.search_strategy,
304
+ estimator=estimator,
305
+ param_grid=self.param_grid,
306
+ cv=self.inner_cv,
307
+ scoring=self.scoring,
308
+ refit=self.refit,
309
+ n_jobs=self.n_jobs_inner,
310
+ return_train_score=self.return_train_score,
311
+ error_score=self.error_score,
312
+ verbose=self.verbose,
313
+ random_state=self.random_state,
314
+ )
315
+
316
+ fit_kw = {}
317
+ if groups_train is not None:
318
+ fit_kw["groups"] = groups_train
319
+ search.fit(X_train, y_train, **fit_kw, **fit_params)
320
+
321
+ fit_time = time.time() - t_start
322
+ logger.info(
323
+ "Outer fold %d: inner search complete (%.1fs), best_score=%.4f",
324
+ fold_idx,
325
+ fit_time,
326
+ search.best_score_,
327
+ )
328
+
329
+ # Callbacks
330
+ if self.callbacks:
331
+ for cb in self.callbacks:
332
+ if hasattr(cb, "on_inner_search_complete"):
333
+ cb.on_inner_search_complete(fold_idx, search)
334
+
335
+ # Post-inner processing (calibration, thresholding, etc.)
336
+ artifacts = self._post_inner_processing(
337
+ search, X_train, y_train, groups_train, **fit_params
338
+ )
339
+
340
+ if self.callbacks:
341
+ for cb in self.callbacks:
342
+ if hasattr(cb, "on_post_processing_complete"):
343
+ cb.on_post_processing_complete(fold_idx, artifacts)
344
+
345
+ # Phase 4: Refit on full outer train
346
+ best_params = search.best_params_
347
+ final_estimator = clone(self.estimator).set_params(**best_params)
348
+ final_estimator.fit(X_train, y_train, **fit_params)
349
+
350
+ # Evaluate on outer test
351
+ t_score = time.time()
352
+ eval_result = self._evaluate_outer_fold(final_estimator, X_test, y_test, artifacts)
353
+ score_time = time.time() - t_score
354
+
355
+ # Build fold result
356
+ fold_result = self._build_fold_result(
357
+ fold_idx=fold_idx,
358
+ train_idx=train_idx,
359
+ test_idx=test_idx,
360
+ best_params=best_params,
361
+ best_inner_score=float(search.best_score_),
362
+ inner_cv_results=search.cv_results_,
363
+ fit_time=fit_time,
364
+ score_time=score_time,
365
+ estimator=final_estimator if self.return_estimator else None,
366
+ artifacts=artifacts,
367
+ eval_result=eval_result,
368
+ )
369
+
370
+ if self.callbacks:
371
+ for cb in self.callbacks:
372
+ if hasattr(cb, "on_outer_fold_complete"):
373
+ cb.on_outer_fold_complete(fold_idx, fold_result)
374
+
375
+ logger.info("Outer fold %d: complete", fold_idx)
376
+ return fold_result
377
+
378
+ @abstractmethod
379
+ def _build_fold_result(self, **kwargs) -> Any:
380
+ """Build the task-specific fold result dataclass."""
381
+ ...
382
+
383
+ # sklearn compatibility
384
+
385
+ def __sklearn_tags__(self):
386
+ try:
387
+ from sklearn.utils._tags import Tags, TargetTags
388
+
389
+ return Tags(
390
+ estimator_type=None,
391
+ target_tags=TargetTags(required=True),
392
+ no_validation=False,
393
+ )
394
+ except ImportError:
395
+ return {"estimator_type": None, "no_validation": False}
396
+
397
+ def __sklearn_is_fitted__(self) -> bool:
398
+ return hasattr(self, "is_fitted_") and self.is_fitted_
nestkit/_validation.py ADDED
@@ -0,0 +1,113 @@
1
+ """Input validation helpers for nestkit parameters.
2
+
3
+ These functions are used internally by :class:`~nestkit.NestedCVClassifier`
4
+ and :class:`~nestkit.NestedCVRegressor` to validate user-provided
5
+ configuration before the nested CV procedure begins.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import numpy as np
13
+
14
+
15
+ def validate_threshold_params(
16
+ threshold_strategy: str | None,
17
+ threshold_criterion: str | Any,
18
+ cost_matrix: Any | None,
19
+ min_recall: float | None,
20
+ ) -> None:
21
+ """Validate threshold optimization parameters.
22
+
23
+ Parameters
24
+ ----------
25
+ threshold_strategy : str or None
26
+ One of ``'fold_specific'``, ``'pooled'``, or ``None``.
27
+ threshold_criterion : str or callable
28
+ Threshold selection criterion name or callable.
29
+ cost_matrix : array-like or None
30
+ Cost matrix required for ``'cost'`` criterion.
31
+ min_recall : float or None
32
+ Minimum recall required for ``'precision_at_recall'`` criterion.
33
+
34
+ Raises
35
+ ------
36
+ ValueError
37
+ If any parameter combination is invalid.
38
+ """
39
+ valid_strategies = {None, "fold_specific", "pooled"}
40
+ if threshold_strategy not in valid_strategies:
41
+ raise ValueError(
42
+ f"threshold_strategy must be one of {valid_strategies}, got '{threshold_strategy}'"
43
+ )
44
+
45
+ valid_criteria = {"youden", "f_beta", "cost", "balanced_accuracy", "precision_at_recall"}
46
+ if isinstance(threshold_criterion, str) and threshold_criterion not in valid_criteria:
47
+ raise ValueError(
48
+ f"threshold_criterion must be one of {valid_criteria} or callable, "
49
+ f"got '{threshold_criterion}'"
50
+ )
51
+
52
+ if threshold_criterion == "cost" and cost_matrix is None:
53
+ raise ValueError("cost_matrix is required when threshold_criterion='cost'")
54
+
55
+ if threshold_criterion == "precision_at_recall" and min_recall is None:
56
+ raise ValueError("min_recall is required when threshold_criterion='precision_at_recall'")
57
+
58
+
59
+ def validate_calibration_method(method: str | None) -> None:
60
+ """Validate the calibration method parameter.
61
+
62
+ Parameters
63
+ ----------
64
+ method : str or None
65
+ One of ``'sigmoid'``, ``'isotonic'``, ``'beta'``,
66
+ ``'venn_abers'``, or ``None``.
67
+
68
+ Raises
69
+ ------
70
+ ValueError
71
+ If ``method`` is not a recognized calibration method.
72
+ """
73
+ valid = {None, "sigmoid", "isotonic", "beta", "venn_abers"}
74
+ if method not in valid:
75
+ raise ValueError(f"calibration_method must be one of {valid}, got '{method}'")
76
+
77
+
78
+ def ensure_2d_proba(y_proba: np.ndarray) -> np.ndarray:
79
+ """Ensure probability array is 2D ``(n_samples, n_classes)``.
80
+
81
+ Parameters
82
+ ----------
83
+ y_proba : ndarray of shape (n_samples,) or (n_samples, n_classes)
84
+ Probability predictions. If 1-D, interpreted as positive-class
85
+ probabilities for a binary problem.
86
+
87
+ Returns
88
+ -------
89
+ ndarray of shape (n_samples, 2) or (n_samples, n_classes)
90
+ Two-dimensional probability array.
91
+ """
92
+ if y_proba.ndim == 1:
93
+ return np.column_stack([1 - y_proba, y_proba])
94
+ return y_proba
95
+
96
+
97
+ def extract_positive_proba(y_proba: np.ndarray) -> np.ndarray:
98
+ """Extract positive-class probabilities from a probability array.
99
+
100
+ Parameters
101
+ ----------
102
+ y_proba : ndarray of shape (n_samples,) or (n_samples, n_classes)
103
+ Probability predictions. If 2-D, the second column (index 1) is
104
+ returned.
105
+
106
+ Returns
107
+ -------
108
+ ndarray of shape (n_samples,)
109
+ Positive-class probabilities.
110
+ """
111
+ if y_proba.ndim == 2:
112
+ return y_proba[:, 1]
113
+ return y_proba
@@ -0,0 +1,12 @@
1
+ """Post-hoc probability calibration for classification models.
2
+
3
+ Provides :class:`PostHocCalibrator` supporting Platt scaling (sigmoid),
4
+ isotonic regression, beta calibration, and Venn-ABERS prediction, as well
5
+ as :class:`CalibrationDiagnostics` for evaluating calibration quality
6
+ via ECE, MCE, Brier score, and reliability diagrams.
7
+ """
8
+
9
+ from nestkit.calibration.calibrators import PostHocCalibrator
10
+ from nestkit.calibration.diagnostics import CalibrationDiagnostics
11
+
12
+ __all__ = ["CalibrationDiagnostics", "PostHocCalibrator"]