tinyconformal 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.
@@ -0,0 +1,7 @@
1
+ # Copyright (c) 2024-2026 Lucas Leão
2
+ # tinyCP - A small toolbox for conformal prediction
3
+ # Licensed under the MIT License
4
+
5
+
6
+ from .marginal import BinaryMarginalConformalClassifier
7
+ from .class_conditional import BinaryClassConditionalConformalClassifier
@@ -0,0 +1,433 @@
1
+ # Copyright (c) 2024-2026 Lucas Leão
2
+ # tinyCP - A small toolbox for conformal prediction
3
+ # Licensed under the MIT License
4
+
5
+
6
+ from venn_abers import VennAbers
7
+ from sklearn.utils.validation import check_is_fitted
8
+ from sklearn.base import BaseEstimator
9
+ import warnings
10
+ import numpy as np
11
+ import sklearn.metrics
12
+ from abc import ABC, abstractmethod
13
+
14
+ warnings.filterwarnings("ignore", category=RuntimeWarning, module="venn_abers")
15
+
16
+
17
+ class BaseConformalClassifier(ABC):
18
+ """
19
+ BaseConformalClassifier
20
+
21
+ A base class for conformal prediction using a model as the learner
22
+ and Venn-Abers calibration for confidence estimation.
23
+ This approach provides valid predictions with a specified significance level (alpha).
24
+
25
+ Conformal classifiers aim to quantify uncertainty in predictions.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ learner: BaseEstimator,
31
+ alpha: float = 0.05,
32
+ ):
33
+ """
34
+
35
+ Initializes the classifier with a specified learner and a Venn-Abers calibration layer.
36
+
37
+ Parameters
38
+ ----------
39
+ learner : BaseEstimator
40
+ The base learner to be used in the classifier.
41
+ alpha : float, default=0.05
42
+ The significance level applied in the classifier.
43
+
44
+ Attributes
45
+ ----------
46
+ learner : BaseEstimator
47
+ The base learner employed in the classifier.
48
+ calibration_layer : VennAbers
49
+ The calibration layer utilized in the classifier.
50
+ decision_function_ : callable or None
51
+ The decision function of the learner.
52
+ hinge : array-like of shape (n_samples,), default=None
53
+ The non-conformity scores of the calibration samples.
54
+ alpha : float, default=0.05
55
+ The significance level applied in the classifier.
56
+ n : int or None
57
+ The number of calibration samples.
58
+ """
59
+
60
+ self.learner = learner
61
+ self.alpha = alpha
62
+ self.calibration_layer = VennAbers()
63
+ self.classes = getattr(self.learner, "classes_", [0, 1])
64
+ self.decision_function_ = None
65
+ self.is_unlabeled = False
66
+ check_is_fitted(learner)
67
+
68
+ if learner.n_classes_ > 2:
69
+ raise ValueError("This classifier supports only binary classification.")
70
+
71
+ self.hinge = None
72
+ self.n = None
73
+
74
+ @abstractmethod
75
+ def fit(self, y):
76
+ """
77
+ Fits the classifier to the training data.
78
+ """
79
+ pass
80
+
81
+ @abstractmethod
82
+ def predict_set(self, X, alpha=None):
83
+ """
84
+ Generate a prediction set for the given input.
85
+ This method must be implemented by subclasses.
86
+ """
87
+ pass
88
+
89
+ @abstractmethod
90
+ def _compute_qhat(self, ncscore, q_level):
91
+ """
92
+ Compute the q-hat value based on the nonconformity scores and the quantile level.
93
+ """
94
+ pass
95
+
96
+ @abstractmethod
97
+ def _compute_set(self, ncscore, qhat):
98
+ """
99
+ Compute a set based on the given ncscore and qhat.
100
+ """
101
+ pass
102
+
103
+ @abstractmethod
104
+ def _compute_q_level(self, n, alpha):
105
+ """
106
+ Compute the quantile level based on the number of samples and significance level.
107
+ """
108
+ pass
109
+
110
+ def _compute_prediction(self, prediction_set):
111
+ """
112
+ Compute the prediction based on the given prediction set.
113
+
114
+ This method evaluates each row in the prediction set and returns 1 if all elements
115
+ in the row match the pattern [0, 1], otherwise returns 0.
116
+ """
117
+ return np.where(np.all(prediction_set == [0, 1], axis=1), 1, 0)
118
+
119
+ def _bookmaker_informedness(self, y, y_pred):
120
+ """
121
+ Calculate the bookmaker informedness score for the given true and predicted labels.
122
+ """
123
+ return sklearn.metrics.balanced_accuracy_score(y, y_pred, adjusted=True)
124
+
125
+ def _select_scoring_function(self, scoring_func):
126
+ """
127
+ Select the scoring function based on the provided string.
128
+ """
129
+
130
+ if scoring_func == "bm":
131
+ func = self._bookmaker_informedness
132
+ elif scoring_func == "mcc":
133
+ func = sklearn.metrics.matthews_corrcoef
134
+ else:
135
+ raise ValueError("Invalid metric function. Please use 'bm' or 'mcc'.")
136
+ return func
137
+
138
+ def _get_alpha(self, alpha):
139
+ """Helper to retrieve the alpha value."""
140
+ return alpha or self.alpha
141
+
142
+ def generate_non_conformity_score(self, y_prob):
143
+ """
144
+ Generates the non-conformity score based on the hinge loss.
145
+
146
+ This function calculates the non-conformity score for conformal prediction
147
+ using the hinge loss approach.
148
+ """
149
+ return 1 - y_prob
150
+
151
+ def generate_conformal_quantile(self, alpha=None):
152
+ """
153
+ Generate the conformal quantile for conformal prediction.
154
+
155
+ This method calculates the conformal quantile based on the nonconformity scores
156
+ of the calibration samples. The quantile serves as a threshold to determine
157
+ the prediction sets in conformal prediction.
158
+
159
+ Parameters:
160
+ -----------
161
+ alpha : float, optional
162
+ The significance level for conformal prediction. If None, the default
163
+ value of self.alpha is used.
164
+
165
+ Returns:
166
+ --------
167
+ float
168
+ The computed conformal quantile.
169
+
170
+ Notes:
171
+ ------
172
+ - The quantile is computed as ceil((n + 1) * (1 - alpha)) / n, where n is the
173
+ number of calibration samples.
174
+ - This method relies on the self.ncscore attribute, which should contain the
175
+ nonconformity scores of the calibration samples.
176
+ """
177
+
178
+ alpha = self._get_alpha(alpha)
179
+
180
+ q_level = self._compute_q_level(self.n, alpha)
181
+
182
+ return self._compute_qhat(self.hinge, q_level)
183
+
184
+ def predict_proba(self, X):
185
+ """
186
+ Returns class probabilities. Uses Venn-Abers if fit() was used,
187
+ or raw learner probabilities if unlabeled_fit() was used.
188
+
189
+ Parameters:
190
+ X: array-like of shape (n_samples, n_features)
191
+ The input samples.
192
+
193
+ Returns:
194
+ p_prime: array-like of shape (n_samples, n_classes)
195
+ The calibrated class probabilities.
196
+ """
197
+ y_score = self.learner.predict_proba(X)
198
+
199
+ if getattr(self, "is_unlabeled", True):
200
+ return y_score
201
+
202
+ p_prime, _ = self.calibration_layer.predict_proba(y_score)
203
+ return p_prime
204
+
205
+ def calibrate(self, X, y, max_alpha=0.2, func="mcc"):
206
+ """
207
+ Calibrates the alpha value to optimize the specified metric.
208
+
209
+ This method evaluates a range of alpha values (from 0.01 to `max_alpha`)
210
+ to determine the optimal significance level based on the provided scoring
211
+ function. The alpha value that maximizes the scoring function is selected.
212
+
213
+ Parameters
214
+ ----------
215
+ X : array-like of shape (n_samples, n_features)
216
+ Input samples used for calibration.
217
+ y : array-like of shape (n_samples,)
218
+ True labels corresponding to the input samples.
219
+ max_alpha : float, optional, default=0.2
220
+ The maximum alpha value to consider during calibration. The range of
221
+ alpha values tested will be from 0.01 to `max_alpha`, inclusive.
222
+ func : str, optional, default="mcc"
223
+ The name of the scoring function to use for optimization. Supported
224
+ functions should be implemented in the `_select_scoring_function` method.
225
+
226
+ Raises
227
+ ------
228
+ If an invalid scoring function name is provided in the `func` parameter.
229
+
230
+ Returns
231
+ The optimal alpha value that maximizes the scoring function.
232
+ """
233
+
234
+ if getattr(self, "is_unlabeled", True):
235
+ raise ValueError(
236
+ "Calibration is not applicable for unlabeled data. Please use labeled data for calibration."
237
+ )
238
+
239
+ scoring_func = self._select_scoring_function(func)
240
+
241
+ alphas = {k: None for k in np.round(np.arange(0.01, max_alpha + 0.01, 0.01), 2)}
242
+
243
+ for alpha in alphas:
244
+ y_pred = self.predict(X, alpha)
245
+ alphas[alpha] = scoring_func(y, y_pred)
246
+
247
+ self.alpha = max(alphas, key=alphas.get)
248
+
249
+ return self.alpha
250
+
251
+ def predict(self, X, alpha=None):
252
+ """
253
+ Predicts the classes for the input samples.
254
+
255
+ Parameters:
256
+ -----------
257
+ X: np.ndarray of shape (n_samples, n_features)
258
+ Input samples.
259
+ alpha: float, optional
260
+ Significance level. If None, defaults to the classifier's alpha value.
261
+
262
+ Returns:
263
+ --------
264
+ np.ndarray of shape (n_samples,)
265
+ Predicted class labels, where 1 indicates the model's certainty.
266
+ """
267
+
268
+ alpha = self._get_alpha(alpha)
269
+
270
+ prediction_set = self.predict_set(X, alpha)
271
+
272
+ return self._compute_prediction(prediction_set)
273
+
274
+ def _expected_calibration_error(self, y, y_prob, M=5):
275
+ """
276
+ Generate the expected calibration error (ECE) of the classifier.
277
+
278
+ Parameters:
279
+ y: array-like of shape (n_samples,)
280
+ The true labels.
281
+ y_prob: array-like of shape (n_samples, n_classes)
282
+ The predicted probabilities.
283
+ M: int, default=5
284
+ The number of bins for the uniform binning approach.
285
+
286
+ Returns:
287
+ ece: float
288
+ The expected calibration error.
289
+
290
+ The function works as follows:
291
+ - It first creates M bins with uniform width over the interval [0, 1].
292
+ - For each sample, it computes the maximum predicted probability and makes a prediction.
293
+ - It then checks whether each prediction is correct or not.
294
+ - For each bin, it calculates the empirical probability of a sample falling into the bin.
295
+ - If the empirical probability is greater than 0, it computes the accuracy and average confidence of the bin.
296
+ - It then calculates the absolute difference between the accuracy and the average confidence, multiplies it by the empirical probability, and adds it to the total ECE.
297
+ """
298
+
299
+ # uniform binning approach with M number of bins
300
+ bin_boundaries = np.linspace(0, 1, M + 1)
301
+ bin_lowers = bin_boundaries[:-1]
302
+ bin_uppers = bin_boundaries[1:]
303
+
304
+ # get max probability per sample i
305
+ confidences = np.max(y_prob, axis=1)
306
+ # get predictions from confidences (positional in this case)
307
+ predicted_label = np.argmax(y_prob, axis=1)
308
+
309
+ # get a boolean list of correct/false predictions
310
+ predictions = predicted_label == y
311
+
312
+ ece = 0.0
313
+ for bin_lower, bin_upper in zip(bin_lowers, bin_uppers):
314
+ # determine if sample is in bin m (between bin lower & upper)
315
+ in_bin = np.logical_and(
316
+ confidences > bin_lower.item(), confidences <= bin_upper.item()
317
+ )
318
+ # can calculate the empirical probability of a sample falling into bin m: (|Bm|/n)
319
+ prob_in_bin = np.mean(in_bin)
320
+
321
+ if prob_in_bin > 0:
322
+ # get the accuracy of bin m: acc(Bm)
323
+ avg_pred = np.mean(predictions[in_bin])
324
+ # get the average confidence of bin m: conf(Bm)
325
+ avg_confidence_in_bin = np.mean(confidences[in_bin])
326
+ # calculate |acc(Bm) - conf(Bm)| * (|Bm|/n) for bin m and add to the total ECE
327
+ ece += np.abs(avg_pred - avg_confidence_in_bin) * prob_in_bin
328
+ return ece
329
+
330
+ def _false_positive_rate(self, y, y_pred):
331
+ """
332
+ Computes the false positive rate (FPR).
333
+ """
334
+ tn, fp, _, _ = sklearn.metrics.confusion_matrix(y, y_pred).ravel()
335
+ return fp / (fp + tn)
336
+
337
+ def _coverage_rate(self, X, y, alpha=None):
338
+ """
339
+ Compute the coverage rate from conformal prediction.
340
+
341
+ Parameters
342
+ ----------
343
+ X : array-like of shape (n_samples, n_features)
344
+ Input features.
345
+ y : array-like of shape (n_samples,)
346
+ True labels.
347
+ alpha : float, optional
348
+ Significance level (1 - desired coverage). If None, the default value of self.alpha is used.
349
+
350
+ Returns
351
+ -------
352
+ float
353
+ The average coverage rate, which represents the proportion of true labels covered by the prediction sets.
354
+ """
355
+
356
+ alpha = self._get_alpha(alpha)
357
+ predict_sets = self.predict_set(X, alpha)
358
+ coverages = predict_sets[np.arange(len(y)), y]
359
+
360
+ return np.mean(coverages)
361
+
362
+ def evaluate(self, X, y, alpha=None):
363
+ """
364
+ Evaluate the classifier on the given dataset.
365
+
366
+ Parameters
367
+ ----------
368
+ X : array-like of shape (n_samples, n_features)
369
+ Input samples.
370
+ y : array-like of shape (n_samples,)
371
+ True labels for the input samples.
372
+ alpha : float, optional
373
+ Significance level for prediction sets. If None, the classifier's default alpha is used.
374
+
375
+ Returns
376
+ -------
377
+ results : dict
378
+ A dictionary containing the following evaluation metrics:
379
+ - "total": Total number of samples.
380
+ - "alpha": Significance level used.
381
+ - "coverage_rate": Coverage rate of the prediction sets.
382
+ - "one_c": Proportion of prediction sets containing exactly one element.
383
+ - "avg_c": Average size of the prediction sets.
384
+ - "empty": Proportion of empty prediction sets.
385
+ - "error": Classification error rate.
386
+ - "log_loss": Log loss of the predictions.
387
+ - "ece": Expected calibration error.
388
+ - "bm": Bookmaker informedness score.
389
+ - "mcc": Matthews correlation coefficient.
390
+ - "f1": F1 score.
391
+ - "fpr": False positive rate.
392
+ """
393
+
394
+ alpha = self._get_alpha(alpha)
395
+
396
+ # Helper function for rounding
397
+ def rounded(value):
398
+ return np.round(value, 3)
399
+
400
+ y_prob = self.predict_proba(X)
401
+ y_pred = self.predict(X, alpha)
402
+ predict_set = self.predict_set(X, alpha)
403
+ total = X.shape[0] if hasattr(X, "shape") else len(X)
404
+ coverage_rate = rounded(self._coverage_rate(X, y, alpha))
405
+ one_c = rounded(np.mean([np.sum(p) == 1 for p in predict_set]))
406
+ avg_c = rounded(np.mean([np.sum(p) for p in predict_set]))
407
+ empty = rounded(np.mean([np.sum(p) == 0 for p in predict_set]))
408
+ error = rounded(1 - np.mean(predict_set[np.arange(len(y)), y]))
409
+ log_loss = rounded(sklearn.metrics.log_loss(y, y_prob[:, 1]))
410
+ ece = rounded(self._expected_calibration_error(y, y_prob))
411
+ fpr = rounded(self._false_positive_rate(y, y_pred))
412
+ bookmaker_informedness = rounded(self._bookmaker_informedness(y, y_pred))
413
+ matthews_corr = rounded(sklearn.metrics.matthews_corrcoef(y, y_pred))
414
+ f1 = rounded(sklearn.metrics.f1_score(y, self.predict(X, alpha)))
415
+
416
+ # Results aggregation
417
+ results = {
418
+ "total": total,
419
+ "alpha": alpha,
420
+ "coverage_rate": coverage_rate,
421
+ "one_c": one_c,
422
+ "avg_c": avg_c,
423
+ "empty": empty,
424
+ "error": error,
425
+ "log_loss": log_loss,
426
+ "ece": ece,
427
+ "bm": bookmaker_informedness,
428
+ "mcc": matthews_corr,
429
+ "f1": f1,
430
+ "fpr": fpr,
431
+ }
432
+
433
+ return results
@@ -0,0 +1,222 @@
1
+ # Copyright (c) 2024-2026 Lucas Leão
2
+ # tinyCP - A small toolbox for conformal prediction
3
+ # Licensed under the MIT License
4
+
5
+
6
+ from sklearn.base import ClassifierMixin, BaseEstimator
7
+ import numpy as np
8
+ import warnings
9
+ from .base import BaseConformalClassifier
10
+
11
+ warnings.filterwarnings("ignore", category=RuntimeWarning, module="venn_abers")
12
+
13
+
14
+ class BinaryClassConditionalConformalClassifier(
15
+ ClassifierMixin, BaseEstimator, BaseConformalClassifier
16
+ ):
17
+ """
18
+ A modrian class conditional conformal classifier methodology utilizing a classifier as the underlying learner.
19
+ This class is inspired by the WrapperClassifier classes from the Crepes library.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ learner: BaseEstimator,
25
+ alpha: float = 0.05,
26
+ ):
27
+ """
28
+ Constructs the classifier with a specified learner and a Venn-Abers calibration layer.
29
+
30
+ Parameters:
31
+ ----------
32
+ learner : BaseEstimator
33
+ The base learner to be used in the classifier.
34
+ alpha : float, default=0.05
35
+ The significance level applied in the classifier.
36
+
37
+ Attributes:
38
+ ----------
39
+ learner : BaseEstimator
40
+ The base learner employed in the classifier.
41
+ calibration_layer : VennAbers
42
+ The calibration layer utilized in the classifier.
43
+ classes : array-like of shape (n_classes,), default=None
44
+ The unique class labels identified during training.
45
+ hinge : list of array-like, default=None
46
+ Nonconformity scores for each class based on the predicted probabilities.
47
+ n : array-like of shape (n_classes,), default=None
48
+ The number of calibration points for each class.
49
+ alpha : float, default=0.05
50
+ The significance level applied in the classifier.
51
+ """
52
+
53
+ super().__init__(learner, alpha)
54
+
55
+ def unlabeled_fit(self, X=None):
56
+ """
57
+ Fits the class-conditional conformal layer using unlabeled data (X) based on
58
+ pseudo-labels derived from the model's predictions (Flechsig & Pilz, 2025).
59
+
60
+ Parameters:
61
+ ----------
62
+ X : array-like of shape (n_samples, n_features)
63
+ Unlabeled calibration features.
64
+
65
+ Returns:
66
+ -------
67
+ self : object
68
+ The fitted classifier.
69
+ """
70
+ if X is None:
71
+ raise ValueError("Unlabeled calibration data (X) must be provided.")
72
+
73
+ self.is_unlabeled = True
74
+ y_prob = self.learner.predict_proba(X)
75
+ idx_max = np.argmax(y_prob, axis=1)
76
+ ncscore = np.min(self.generate_non_conformity_score(y_prob), axis=1)
77
+ self.hinge = [ncscore[idx_max == c] for c in self.classes]
78
+ self.n = [np.sum(idx_max == c) for c in self.classes]
79
+
80
+ return self
81
+
82
+ def fit(self, X=None, y=None, oob=False):
83
+ """
84
+ Fits the classifier to the training data. Calculates the conformity score for each training instance.
85
+
86
+ Parameters:
87
+ ----------
88
+ X : array-like of shape (n_samples, n_features), optional
89
+ The training data. Required if OOB predictions are not used.
90
+ y : array-like of shape (n_samples,)
91
+ The true labels. Required in all cases.
92
+ oob : bool, default=False
93
+ Whether to use Out-of-Bag (OOB) predictions if available.
94
+
95
+ Returns:
96
+ -------
97
+ self : object
98
+ The fitted classifier.
99
+
100
+ Raises:
101
+ ------
102
+ ValueError:
103
+ If OOB is enabled but not supported by the learner,
104
+ or if `X` and `y` are not provided when `oob=False`.
105
+ """
106
+ if y is None:
107
+ raise ValueError("The true labels (y) must be provided.")
108
+
109
+ if oob:
110
+ if (
111
+ not hasattr(self.learner, "oob_decision_function_")
112
+ or self.learner.oob_decision_function_ is None
113
+ ):
114
+ raise ValueError(
115
+ "OOB predictions are not available for the provided learner."
116
+ )
117
+ if X is not None:
118
+ raise ValueError(
119
+ "Training data (X) should not be provided when OOB is used. Ensure that 'y' is the same as the labels used during training."
120
+ )
121
+
122
+ # Use OOB predictions
123
+ self.decision_function_ = self.learner.oob_decision_function_
124
+ else:
125
+
126
+ if X is None:
127
+ raise ValueError(
128
+ "Training data (X) must be provided if OOB is not used."
129
+ )
130
+
131
+ # Use predict_proba for training data
132
+ self.decision_function_ = self.learner.predict_proba(X)
133
+
134
+ self.calibration_layer.fit(self.decision_function_, y)
135
+
136
+ y_prob, _ = self.calibration_layer.predict_proba(self.decision_function_)
137
+
138
+ y_prob = y_prob[np.arange(len(y)), y]
139
+ hinge = self.generate_non_conformity_score(y_prob)
140
+ self.hinge = [hinge[y == c] for c in self.classes]
141
+ self.n = [np.sum(y == c) for c in self.classes]
142
+
143
+ return self
144
+
145
+ def _compute_q_level(self, n, alpha):
146
+ """
147
+ Compute the quantile level for each class based on the number of samples and significance level.
148
+ """
149
+ alpha = self._get_alpha(alpha)
150
+ q_level = np.zeros(len(self.classes))
151
+ for c in self.classes:
152
+ q_level[c] = np.ceil((n[c] + 1) * (1 - alpha)) / n[c]
153
+ return q_level
154
+
155
+ def _compute_qhat(self, ncscore, q_level):
156
+ """
157
+ Compute the q-hat value based on the nonconformity scores and the quantile level.
158
+ """
159
+ qhat = np.zeros(len(self.classes))
160
+ for c in self.classes:
161
+ qhat[c] = np.quantile(ncscore[c], q_level[c], method="higher")
162
+ return qhat
163
+
164
+ def _compute_set(self, ncscore, qhat):
165
+ """
166
+ Compute a predict set based on the given ncscore and qhat.
167
+ """
168
+ prediction_set = np.zeros((len(ncscore), len(self.classes)))
169
+ for c in self.classes:
170
+ prediction_set[:, c] = (ncscore <= qhat[c])[:, c]
171
+ return prediction_set
172
+
173
+ def predict_set(self, X, alpha=None):
174
+ """
175
+ Predicts the possible set of classes for the instances in X based on the predefined significance level.
176
+
177
+ Parameters:
178
+ X: array-like of shape (n_samples, n_features)
179
+ The input samples.
180
+ alpha: float, default=None
181
+ The significance level. If None, the value of self.alpha is used.
182
+
183
+ Returns:
184
+ prediction_set: array-like of shape (n_samples, n_classes)
185
+ The predicted set of classes. A class is included in the set if its non-conformity score is less
186
+ than or equal to the quantile of the hinge loss distribution at the (n+1)*(1-alpha)/n level.
187
+ """
188
+
189
+ alpha = self._get_alpha(alpha)
190
+
191
+ y_prob = self.predict_proba(X)
192
+ ncscore = self.generate_non_conformity_score(y_prob)
193
+ qhat = self.generate_conformal_quantile(alpha)
194
+
195
+ return self._compute_set(ncscore, qhat)
196
+
197
+ def predict_p(self, X):
198
+ """
199
+ Calculate the p-values for each instance in the input data X using a non-conformity score.
200
+
201
+ Parameters:
202
+ -----------
203
+ X : array-like of shape (n_samples, n_features)
204
+ The input data for which the p-values need to be predicted.
205
+
206
+ Returns:
207
+ --------
208
+ p_values : array-like of shape (n_samples, n_classes)
209
+ The p-values for each instance in X for each class.
210
+
211
+ """
212
+ y_prob = self.predict_proba(X)
213
+ ncscore = self.generate_non_conformity_score(y_prob)
214
+ p_values = np.zeros_like(ncscore)
215
+
216
+ for i in range(ncscore.shape[0]):
217
+ for j in range(ncscore.shape[1]):
218
+ numerator = np.sum(self.hinge[j] >= ncscore[i][j]) + 1
219
+ denumerator = self.n[j] + 1
220
+ p_values[i, j] = numerator / denumerator
221
+
222
+ return p_values