tinyconformal 0.1.2__tar.gz → 0.2.0__tar.gz

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 (24) hide show
  1. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/PKG-INFO +67 -27
  2. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/README.md +61 -20
  3. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/pyproject.toml +14 -4
  4. tinyconformal-0.2.0/tests/test_classifiers.py +200 -0
  5. tinyconformal-0.2.0/tests/test_regressors.py +249 -0
  6. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/classifier/class_conditional.py +3 -3
  7. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal.egg-info/PKG-INFO +67 -27
  8. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal.egg-info/SOURCES.txt +1 -2
  9. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal.egg-info/requires.txt +5 -6
  10. tinyconformal-0.1.2/tests/test_classifiers.py +0 -144
  11. tinyconformal-0.1.2/tinyconformal/plot/__init__.py +0 -6
  12. tinyconformal-0.1.2/tinyconformal/plot/plot.py +0 -347
  13. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/LICENSE +0 -0
  14. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/setup.cfg +0 -0
  15. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/classifier/__init__.py +0 -0
  16. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/classifier/base.py +0 -0
  17. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/classifier/marginal.py +0 -0
  18. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/regressor/__init__.py +0 -0
  19. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/regressor/base.py +0 -0
  20. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/regressor/cqr.py +0 -0
  21. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/regressor/exactness_bound.py +0 -0
  22. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal/regressor/icp.py +0 -0
  23. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal.egg-info/dependency_links.txt +0 -0
  24. {tinyconformal-0.1.2 → tinyconformal-0.2.0}/tinyconformal.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyconformal
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: A small toolbox for conformal prediction
5
5
  Author-email: Lucas Leão <heylucasleao@gmail.com>
6
6
  License-Expression: MIT
@@ -14,18 +14,21 @@ Requires-Dist: venn-abers>=1.4.6
14
14
  Requires-Dist: scikit-learn>=1.3.0
15
15
  Requires-Dist: quantile-forest>=1.4.2
16
16
  Provides-Extra: plot
17
- Requires-Dist: plotly>=5.22.0; extra == "plot"
18
- Requires-Dist: kaleido<=0.2.1; extra == "plot"
17
+ Requires-Dist: tinyshift[plot]; extra == "plot"
19
18
  Provides-Extra: notebook
20
19
  Requires-Dist: nbformat>=5.10.4; extra == "notebook"
21
- Requires-Dist: ipykernel>=6.7.0; extra == "notebook"
22
- Provides-Extra: all
23
- Requires-Dist: plot; extra == "all"
24
- Requires-Dist: notebook; extra == "all"
20
+ Requires-Dist: ipykernel>6.7.0; extra == "notebook"
21
+ Provides-Extra: dev
22
+ Requires-Dist: tinyconformal[notebook,plot]; extra == "dev"
23
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
25
24
  Dynamic: license-file
26
25
 
27
26
  # TinyConformal
28
- TinyConformal is an experimental Python library for conformal predictions, providing tools to generate valid prediction sets with a specified significance level (alpha). This project aims to facilitate the implementation of personal and future projects on the topic.
27
+
28
+ Related project: [tinyshift](https://github.com/HeyLucasLeao/tinyshift)
29
+
30
+ TinyConformal is a Python library for conformal prediction in classification and regression.
31
+ It provides tools to build valid prediction sets and prediction intervals with a target significance level (`alpha`).
29
32
 
30
33
  For more information on a previous project related to Out-of-Bag (OOB) solutions, visit [this link](https://github.com/HeyLucasLeao/cp-study).
31
34
 
@@ -40,36 +43,68 @@ Currently, TinyConformal supports Out-of-Bag (OOB) solutions for `RandomForestCl
40
43
 
41
44
  ## Installation
42
45
 
43
- Install TinyConformal using pip:
46
+ ### Using pip
44
47
 
45
48
  ```bash
46
49
  pip install tinyconformal
47
50
  ```
48
51
 
49
- > **Note:** If you want to enable plotting capabilities, you need to install the extras using Poetry:
52
+ Optional extras:
53
+
54
+ ```bash
55
+ pip install "tinyconformal[plot]"
56
+ pip install "tinyconformal[notebook]"
57
+ pip install "tinyconformal[dev]"
58
+ ```
59
+
60
+ ### Using uv
61
+
62
+ Install in the current environment:
50
63
 
51
64
  ```bash
52
- poetry install --E plot
65
+ uv pip install tinyconformal
53
66
  ```
54
67
 
55
- ## Usage
68
+ Add as a dependency in a project:
56
69
 
57
- ### Importing Classifiers
70
+ ```bash
71
+ uv add tinyconformal
72
+ ```
73
+
74
+ Optional extras with uv:
75
+
76
+ ```bash
77
+ uv pip install "tinyconformal[plot]"
78
+ uv pip install "tinyconformal[notebook]"
79
+ uv pip install "tinyconformal[dev]"
80
+ ```
58
81
 
59
- Import the conformal classifiers from the `tinyconformal.classifier` module:
82
+ ## Submodules and usage
83
+
84
+ TinyConformal is organized into two main submodules:
85
+
86
+ - `tinyconformal.classifier`: conformal classifiers for binary classification.
87
+ - `tinyconformal.regressor`: conformal regressors and exactness-bound utilities.
88
+
89
+ ### Classifier submodule
90
+
91
+ Import from `tinyconformal.classifier`:
60
92
 
61
93
  ```python
62
- from tinyconformal.classifier import BinaryClassConditionalConformalClassifier
63
94
  from tinyconformal.classifier import BinaryMarginalConformalClassifier
95
+ from tinyconformal.classifier import BinaryClassConditionalConformalClassifier
64
96
  ```
65
- ### Importing Regressors
66
97
 
67
- Import the conformal regressors from the `tinyconformal.regressor` module:
98
+ ### Regressor submodule
99
+
100
+ Import from `tinyconformal.regressor`:
68
101
 
69
102
  ```python
70
103
  from tinyconformal.regressor import ConformalizedRegressor
71
104
  from tinyconformal.regressor import ConformalizedQuantileRegressor
105
+ from tinyconformal.regressor import ExactnessBound
72
106
  ```
107
+
73
108
  ### Example
74
109
 
75
110
  Example usage of `BinaryClassConditionalConformalClassifier`:
@@ -85,7 +120,7 @@ learner.fit(X_train, y_train)
85
120
 
86
121
  # Create and fit the conformal classifier
87
122
  conformal_classifier = BinaryClassConditionalConformalClassifier(learner)
88
- conformal_classifier.fit(X=X_train, y=y_train, oob=True)
123
+ conformal_classifier.fit(y=y_train, oob=True)
89
124
 
90
125
  # Make predictions
91
126
  X_test = ... # your test data
@@ -113,13 +148,18 @@ For regressors, you can combine an exactness bound estimate with unlabeled calib
113
148
 
114
149
  ```python
115
150
  from sklearn.ensemble import RandomForestRegressor
116
- from tinyconformal import ConformalizedRegressor, ExactnessBound
151
+ from tinyconformal.regressor import ConformalizedRegressor, ExactnessBound
117
152
 
118
153
  learner = RandomForestRegressor(random_state=42)
119
- tilde_beta = ExactnessBound.estimate_icp_bound(learner, X_train, y_train, p=0.95, cv=5)
154
+ tilde_beta, beta = ExactnessBound.estimate_icp_bound(
155
+ learner, X_train, y_train, p=0.95, cv=5
156
+ )
157
+
158
+ # Fit learner before using conformal regressor
159
+ learner.fit(X_train, y_train)
120
160
 
121
161
  regressor = ConformalizedRegressor(learner, alpha=0.05)
122
- regressor.unlabeled_fit(X_unlabeled, tilde_beta=tilde_beta)
162
+ regressor.unlabeled_fit(X_unlabeled, tilde_beta=tilde_beta, beta=beta)
123
163
 
124
164
  intervals = regressor.predict_interval(X_test)
125
165
  ```
@@ -140,16 +180,16 @@ print(results)
140
180
  `BinaryMarginalConformalClassifier` is a marginal-coverage conformal classifier that uses a classifier as the underlying learner.
141
181
 
142
182
  - Training via labeled calibration: `fit(X, y)`
143
- - Training via OOB calibration: `fit(X, y, oob=True)`
144
- - Training via unlabeled calibration: `unlabeled_fit(X)`
183
+ - Training via OOB calibration: `fit(y=y_train, oob=True)`
184
+ - Training via unlabeled calibration: `unlabeled_fit(X, beta=...)`
145
185
 
146
186
  ### BinaryClassConditionalConformalClassifier
147
187
 
148
188
  `BinaryClassConditionalConformalClassifier` is a class-conditional conformal classifier that uses a classifier as the underlying learner.
149
189
 
150
190
  - Training via labeled calibration: `fit(X, y)`
151
- - Training via OOB calibration: `fit(X, y, oob=True)`
152
- - Training via unlabeled calibration: `unlabeled_fit(X)` using pseudo-labels derived from the model probabilities
191
+ - Training via OOB calibration: `fit(y=y_train, oob=True)`
192
+ - Training via unlabeled calibration: `unlabeled_fit(X, beta=...)` using pseudo-labels derived from the model probabilities
153
193
 
154
194
  ### ConformalizedRegressor
155
195
 
@@ -157,7 +197,7 @@ print(results)
157
197
 
158
198
  - Training via labeled calibration: `fit(X, y)`
159
199
  - Training via OOB calibration: `fit(X, y, oob=True)`
160
- - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=...)` using an exactness bound
200
+ - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=..., beta=...)` using an exactness bound
161
201
 
162
202
  ### ConformalizedQuantileRegressor
163
203
 
@@ -165,7 +205,7 @@ print(results)
165
205
 
166
206
  - Training via labeled calibration: `fit(X, y)`
167
207
  - Training via OOB calibration: `fit(X, y, oob=True)`
168
- - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=...)` using an exactness bound
208
+ - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=..., beta=...)` using an exactness bound
169
209
 
170
210
  ### ExactnessBound
171
211
 
@@ -1,5 +1,9 @@
1
1
  # TinyConformal
2
- TinyConformal is an experimental Python library for conformal predictions, providing tools to generate valid prediction sets with a specified significance level (alpha). This project aims to facilitate the implementation of personal and future projects on the topic.
2
+
3
+ Related project: [tinyshift](https://github.com/HeyLucasLeao/tinyshift)
4
+
5
+ TinyConformal is a Python library for conformal prediction in classification and regression.
6
+ It provides tools to build valid prediction sets and prediction intervals with a target significance level (`alpha`).
3
7
 
4
8
  For more information on a previous project related to Out-of-Bag (OOB) solutions, visit [this link](https://github.com/HeyLucasLeao/cp-study).
5
9
 
@@ -14,36 +18,68 @@ Currently, TinyConformal supports Out-of-Bag (OOB) solutions for `RandomForestCl
14
18
 
15
19
  ## Installation
16
20
 
17
- Install TinyConformal using pip:
21
+ ### Using pip
18
22
 
19
23
  ```bash
20
24
  pip install tinyconformal
21
25
  ```
22
26
 
23
- > **Note:** If you want to enable plotting capabilities, you need to install the extras using Poetry:
27
+ Optional extras:
28
+
29
+ ```bash
30
+ pip install "tinyconformal[plot]"
31
+ pip install "tinyconformal[notebook]"
32
+ pip install "tinyconformal[dev]"
33
+ ```
34
+
35
+ ### Using uv
36
+
37
+ Install in the current environment:
24
38
 
25
39
  ```bash
26
- poetry install --E plot
40
+ uv pip install tinyconformal
27
41
  ```
28
42
 
29
- ## Usage
43
+ Add as a dependency in a project:
30
44
 
31
- ### Importing Classifiers
45
+ ```bash
46
+ uv add tinyconformal
47
+ ```
48
+
49
+ Optional extras with uv:
50
+
51
+ ```bash
52
+ uv pip install "tinyconformal[plot]"
53
+ uv pip install "tinyconformal[notebook]"
54
+ uv pip install "tinyconformal[dev]"
55
+ ```
32
56
 
33
- Import the conformal classifiers from the `tinyconformal.classifier` module:
57
+ ## Submodules and usage
58
+
59
+ TinyConformal is organized into two main submodules:
60
+
61
+ - `tinyconformal.classifier`: conformal classifiers for binary classification.
62
+ - `tinyconformal.regressor`: conformal regressors and exactness-bound utilities.
63
+
64
+ ### Classifier submodule
65
+
66
+ Import from `tinyconformal.classifier`:
34
67
 
35
68
  ```python
36
- from tinyconformal.classifier import BinaryClassConditionalConformalClassifier
37
69
  from tinyconformal.classifier import BinaryMarginalConformalClassifier
70
+ from tinyconformal.classifier import BinaryClassConditionalConformalClassifier
38
71
  ```
39
- ### Importing Regressors
40
72
 
41
- Import the conformal regressors from the `tinyconformal.regressor` module:
73
+ ### Regressor submodule
74
+
75
+ Import from `tinyconformal.regressor`:
42
76
 
43
77
  ```python
44
78
  from tinyconformal.regressor import ConformalizedRegressor
45
79
  from tinyconformal.regressor import ConformalizedQuantileRegressor
80
+ from tinyconformal.regressor import ExactnessBound
46
81
  ```
82
+
47
83
  ### Example
48
84
 
49
85
  Example usage of `BinaryClassConditionalConformalClassifier`:
@@ -59,7 +95,7 @@ learner.fit(X_train, y_train)
59
95
 
60
96
  # Create and fit the conformal classifier
61
97
  conformal_classifier = BinaryClassConditionalConformalClassifier(learner)
62
- conformal_classifier.fit(X=X_train, y=y_train, oob=True)
98
+ conformal_classifier.fit(y=y_train, oob=True)
63
99
 
64
100
  # Make predictions
65
101
  X_test = ... # your test data
@@ -87,13 +123,18 @@ For regressors, you can combine an exactness bound estimate with unlabeled calib
87
123
 
88
124
  ```python
89
125
  from sklearn.ensemble import RandomForestRegressor
90
- from tinyconformal import ConformalizedRegressor, ExactnessBound
126
+ from tinyconformal.regressor import ConformalizedRegressor, ExactnessBound
91
127
 
92
128
  learner = RandomForestRegressor(random_state=42)
93
- tilde_beta = ExactnessBound.estimate_icp_bound(learner, X_train, y_train, p=0.95, cv=5)
129
+ tilde_beta, beta = ExactnessBound.estimate_icp_bound(
130
+ learner, X_train, y_train, p=0.95, cv=5
131
+ )
132
+
133
+ # Fit learner before using conformal regressor
134
+ learner.fit(X_train, y_train)
94
135
 
95
136
  regressor = ConformalizedRegressor(learner, alpha=0.05)
96
- regressor.unlabeled_fit(X_unlabeled, tilde_beta=tilde_beta)
137
+ regressor.unlabeled_fit(X_unlabeled, tilde_beta=tilde_beta, beta=beta)
97
138
 
98
139
  intervals = regressor.predict_interval(X_test)
99
140
  ```
@@ -114,16 +155,16 @@ print(results)
114
155
  `BinaryMarginalConformalClassifier` is a marginal-coverage conformal classifier that uses a classifier as the underlying learner.
115
156
 
116
157
  - Training via labeled calibration: `fit(X, y)`
117
- - Training via OOB calibration: `fit(X, y, oob=True)`
118
- - Training via unlabeled calibration: `unlabeled_fit(X)`
158
+ - Training via OOB calibration: `fit(y=y_train, oob=True)`
159
+ - Training via unlabeled calibration: `unlabeled_fit(X, beta=...)`
119
160
 
120
161
  ### BinaryClassConditionalConformalClassifier
121
162
 
122
163
  `BinaryClassConditionalConformalClassifier` is a class-conditional conformal classifier that uses a classifier as the underlying learner.
123
164
 
124
165
  - Training via labeled calibration: `fit(X, y)`
125
- - Training via OOB calibration: `fit(X, y, oob=True)`
126
- - Training via unlabeled calibration: `unlabeled_fit(X)` using pseudo-labels derived from the model probabilities
166
+ - Training via OOB calibration: `fit(y=y_train, oob=True)`
167
+ - Training via unlabeled calibration: `unlabeled_fit(X, beta=...)` using pseudo-labels derived from the model probabilities
127
168
 
128
169
  ### ConformalizedRegressor
129
170
 
@@ -131,7 +172,7 @@ print(results)
131
172
 
132
173
  - Training via labeled calibration: `fit(X, y)`
133
174
  - Training via OOB calibration: `fit(X, y, oob=True)`
134
- - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=...)` using an exactness bound
175
+ - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=..., beta=...)` using an exactness bound
135
176
 
136
177
  ### ConformalizedQuantileRegressor
137
178
 
@@ -139,7 +180,7 @@ print(results)
139
180
 
140
181
  - Training via labeled calibration: `fit(X, y)`
141
182
  - Training via OOB calibration: `fit(X, y, oob=True)`
142
- - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=...)` using an exactness bound
183
+ - Training via unlabeled calibration: `unlabeled_fit(X, tilde_beta=..., beta=...)` using an exactness bound
143
184
 
144
185
  ### ExactnessBound
145
186
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tinyconformal"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "A small toolbox for conformal prediction"
5
5
  license = "MIT"
6
6
  authors = [{name = "Lucas Leão", email = "heylucasleao@gmail.com"}]
@@ -18,6 +18,16 @@ dependencies = [
18
18
  requires-python = ">=3.10"
19
19
 
20
20
  [project.optional-dependencies]
21
- plot = ["plotly (>=5.22.0)", "kaleido (<=0.2.1)"]
22
- notebook = ["nbformat (>=5.10.4)", "ipykernel (>=6.7.0)"]
23
- all = ["plot", "notebook"]
21
+ plot = [
22
+ "tinyshift[plot]",
23
+ ]
24
+
25
+ notebook = [
26
+ "nbformat>=5.10.4",
27
+ "ipykernel>6.7.0",
28
+ ]
29
+
30
+ dev = [
31
+ "tinyconformal[plot,notebook]",
32
+ "pytest>=7.0.0",
33
+ ]
@@ -0,0 +1,200 @@
1
+ # Copyright (c) 2024-2026 Lucas Leão
2
+ # TinyConformal - A small toolbox for conformal prediction
3
+ # Licensed under the MIT License
4
+
5
+
6
+ import pytest
7
+ import numpy as np
8
+ from sklearn.ensemble import RandomForestClassifier
9
+ from sklearn.model_selection import train_test_split
10
+ from sklearn.datasets import make_classification
11
+ from tinyconformal.classifier.marginal import BinaryMarginalConformalClassifier
12
+ from tinyconformal.classifier.class_conditional import (
13
+ BinaryClassConditionalConformalClassifier,
14
+ )
15
+
16
+
17
+ @pytest.fixture
18
+ def dataset():
19
+ weights = [0.4, 0.6]
20
+ seed = 42
21
+
22
+ X, y = make_classification(
23
+ n_samples=1500,
24
+ n_features=20,
25
+ n_informative=2,
26
+ weights=weights,
27
+ random_state=seed,
28
+ n_redundant=2,
29
+ )
30
+
31
+ X_train, X_test, y_train, y_test = train_test_split(
32
+ X, y, test_size=0.2, random_state=seed, stratify=y
33
+ )
34
+ X_train, X_calib, y_train, y_calib = train_test_split(
35
+ X_train, y_train, test_size=0.25, random_state=seed, stratify=y_train
36
+ )
37
+
38
+ return {
39
+ "X_train": X_train,
40
+ "y_train": y_train,
41
+ "X_calib": X_calib,
42
+ "y_calib": y_calib,
43
+ "X_test": X_test,
44
+ "y_test": y_test,
45
+ }
46
+
47
+
48
+ @pytest.fixture
49
+ def learner(dataset):
50
+ model = RandomForestClassifier(oob_score=True, n_estimators=10)
51
+ model.fit(dataset["X_train"], dataset["y_train"])
52
+ return model
53
+
54
+
55
+ def _assert_classifier_outputs(classifier, dataset):
56
+ classifier.calibrate(dataset["X_calib"], dataset["y_calib"])
57
+ assert 0 < classifier.alpha <= 0.2
58
+
59
+ y_proba = classifier.predict_proba(dataset["X_test"])
60
+ assert y_proba.shape == (dataset["X_test"].shape[0], 2)
61
+
62
+ prediction_set = classifier.predict_set(dataset["X_test"])
63
+ assert prediction_set.shape == (dataset["X_test"].shape[0], 2)
64
+
65
+ p_values = classifier.predict_p(dataset["X_test"])
66
+ assert p_values.shape == (dataset["X_test"].shape[0], 2)
67
+
68
+ y_pred = classifier.predict(dataset["X_test"])
69
+ assert y_pred.shape == (dataset["X_test"].shape[0],)
70
+
71
+ eval_dict = classifier.evaluate(dataset["X_test"], dataset["y_test"])
72
+ assert isinstance(eval_dict, dict)
73
+ expected_keys = {
74
+ "total",
75
+ "alpha",
76
+ "beta",
77
+ "coverage_rate",
78
+ "one_c",
79
+ "avg_c",
80
+ "empty",
81
+ "error",
82
+ "log_loss",
83
+ "ece",
84
+ "bm",
85
+ "mcc",
86
+ "f1",
87
+ "fpr",
88
+ }
89
+ assert set(eval_dict.keys()) == expected_keys
90
+
91
+
92
+ def test_marginal_classifier(dataset, learner):
93
+ classifier = BinaryMarginalConformalClassifier(learner)
94
+ classifier.fit(dataset["X_calib"], dataset["y_calib"], oob=False)
95
+ _assert_classifier_outputs(classifier, dataset)
96
+
97
+
98
+ def test_class_cond_classifier(dataset, learner):
99
+ classifier = BinaryClassConditionalConformalClassifier(learner)
100
+ classifier.fit(dataset["X_calib"], dataset["y_calib"], oob=False)
101
+ _assert_classifier_outputs(classifier, dataset)
102
+
103
+
104
+ def test_oob_marginal_classifier(dataset, learner):
105
+ classifier = BinaryMarginalConformalClassifier(learner)
106
+ classifier.fit(y=dataset["y_train"], oob=True)
107
+ _assert_classifier_outputs(classifier, dataset)
108
+
109
+
110
+ def test_oob_class_conditional_classifier(dataset, learner):
111
+ classifier = BinaryClassConditionalConformalClassifier(learner)
112
+ classifier.fit(y=dataset["y_train"], oob=True)
113
+ _assert_classifier_outputs(classifier, dataset)
114
+
115
+
116
+ @pytest.mark.parametrize(
117
+ "classifier_cls",
118
+ [BinaryMarginalConformalClassifier, BinaryClassConditionalConformalClassifier],
119
+ )
120
+ def test_unlabeled_fit_requires_X(classifier_cls, learner):
121
+ classifier = classifier_cls(learner)
122
+
123
+ with pytest.raises(ValueError, match="Unlabeled calibration data"):
124
+ classifier.unlabeled_fit(X=None)
125
+
126
+
127
+ @pytest.mark.parametrize(
128
+ "classifier_cls",
129
+ [BinaryMarginalConformalClassifier, BinaryClassConditionalConformalClassifier],
130
+ )
131
+ def test_unlabeled_fit_warns_when_beta_missing(classifier_cls, learner, dataset):
132
+ classifier = classifier_cls(learner)
133
+
134
+ with pytest.warns(UserWarning, match="beta"):
135
+ classifier.unlabeled_fit(dataset["X_calib"])
136
+
137
+
138
+ @pytest.mark.parametrize(
139
+ "classifier_cls",
140
+ [BinaryMarginalConformalClassifier, BinaryClassConditionalConformalClassifier],
141
+ )
142
+ def test_unlabeled_fit_disables_calibration(classifier_cls, learner, dataset):
143
+ classifier = classifier_cls(learner)
144
+ classifier.unlabeled_fit(dataset["X_calib"], beta=0.1)
145
+
146
+ with pytest.raises(ValueError, match="Calibration is not applicable"):
147
+ classifier.calibrate(dataset["X_test"], dataset["y_test"])
148
+
149
+
150
+ def test_unlabeled_fit_marginal_predict_p_properties(dataset, learner):
151
+ classifier = BinaryMarginalConformalClassifier(learner)
152
+ classifier.unlabeled_fit(dataset["X_calib"], beta=0.1)
153
+
154
+ assert classifier.is_unlabeled is True
155
+ assert classifier.beta == 0.1
156
+ assert classifier.hinge.shape == (dataset["X_calib"].shape[0],)
157
+ assert classifier.n == dataset["X_calib"].shape[0]
158
+
159
+ p_values = classifier.predict_p(dataset["X_test"])
160
+ assert p_values.shape == (dataset["X_test"].shape[0], 2)
161
+ assert np.all(p_values >= 0)
162
+ assert np.all(p_values <= 1)
163
+
164
+
165
+ def test_unlabeled_fit_class_conditional_predict_p_properties(dataset, learner):
166
+ classifier = BinaryClassConditionalConformalClassifier(learner)
167
+ classifier.unlabeled_fit(dataset["X_calib"], beta=0.1)
168
+
169
+ assert classifier.is_unlabeled is True
170
+ assert classifier.beta == 0.1
171
+ assert len(classifier.hinge) == 2
172
+ assert len(classifier.n) == 2
173
+ assert sum(classifier.n) == dataset["X_calib"].shape[0]
174
+
175
+ p_values = classifier.predict_p(dataset["X_test"])
176
+ assert p_values.shape == (dataset["X_test"].shape[0], 2)
177
+ assert np.all(p_values >= 0)
178
+ assert np.all(p_values <= 1)
179
+
180
+
181
+ @pytest.mark.parametrize(
182
+ "classifier_cls",
183
+ [BinaryMarginalConformalClassifier, BinaryClassConditionalConformalClassifier],
184
+ )
185
+ def test_fit_requires_y(classifier_cls, learner, dataset):
186
+ classifier = classifier_cls(learner)
187
+
188
+ with pytest.raises(ValueError, match="true labels"):
189
+ classifier.fit(dataset["X_calib"], y=None, oob=False)
190
+
191
+
192
+ @pytest.mark.parametrize(
193
+ "classifier_cls",
194
+ [BinaryMarginalConformalClassifier, BinaryClassConditionalConformalClassifier],
195
+ )
196
+ def test_fit_oob_rejects_X(classifier_cls, learner, dataset):
197
+ classifier = classifier_cls(learner)
198
+
199
+ with pytest.raises(ValueError, match="should not be provided"):
200
+ classifier.fit(dataset["X_calib"], y=dataset["y_train"], oob=True)