Skip to content

Commit aa587c7

Browse files
Merge branch 'master' into 447-mapieregressor-sets-method-to-base-from-aci
2 parents b1b5aad + f98bafc commit aa587c7

File tree

8 files changed

+163
-104
lines changed

8 files changed

+163
-104
lines changed

.bumpversion.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ search = version = "{current_version}"
1616
replace = version = "{new_version}"
1717

1818
[bumpversion:file:CITATION.cff]
19-
search = version = "{current_version}"
20-
replace = version = "{new_version}"
19+
search = version: {current_version}
20+
replace = version: {new_version}
2121

HISTORY.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
History
33
=======
44

5-
0.8.3 (2024-**-**)
5+
0.8.4 (2024-**-**)
66
------------------
7-
* Fixed overloading of the value of the ‘method’ attribute when using MapieRegressor and MapieTimeSeriesRegressor
7+
8+
* Fix the quantile formula to ensure valid coverage for any number of calibration data in `ConformityScore`.
9+
* Fix overloading of the value of the `method` attribute when using `MapieRegressor` and `MapieTimeSeriesRegressor`.
810
* Fix conda versionning.
911
* Reduce precision for test in `MapieCalibrator`.
1012
* Fix invalid certificate when downloading data.

README.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
|GitHubActions| |Codecov| |ReadTheDocs| |License| |PythonVersion| |PyPi| |Conda| |Release| |Commits| |DOI|
44

55
.. |GitHubActions| image:: https://github.com/scikit-learn-contrib/MAPIE/actions/workflows/test.yml/badge.svg
6-
:target: https://github.com/scikit-learn-contrib/MAPIE/actions
6+
:target: https://github.com/scikit-learn-contrib/MAPIE/actions
77

88
.. |Codecov| image:: https://codecov.io/gh/scikit-learn-contrib/MAPIE/branch/master/graph/badge.svg?token=F2S6KYH4V1
99
:target: https://codecov.io/gh/scikit-learn-contrib/MAPIE
@@ -13,25 +13,25 @@
1313
:alt: Documentation Status
1414

1515
.. |License| image:: https://img.shields.io/github/license/scikit-learn-contrib/MAPIE
16-
:target: https://github.com/scikit-learn-contrib/MAPIE/blob/master/LICENSE
16+
:target: https://github.com/scikit-learn-contrib/MAPIE/blob/master/LICENSE
1717

1818
.. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/mapie
19-
:target: https://pypi.org/project/mapie/
19+
:target: https://pypi.org/project/mapie/
2020

2121
.. |PyPi| image:: https://img.shields.io/pypi/v/mapie
22-
:target: https://pypi.org/project/mapie/
22+
:target: https://pypi.org/project/mapie/
2323

2424
.. |Conda| image:: https://img.shields.io/conda/vn/conda-forge/mapie
25-
:target: https://anaconda.org/conda-forge/mapie
25+
:target: https://anaconda.org/conda-forge/mapie
2626

2727
.. |Release| image:: https://img.shields.io/github/v/release/scikit-learn-contrib/mapie
28-
:target: https://github.com/scikit-learn-contrib/MAPIE/releases
28+
:target: https://github.com/scikit-learn-contrib/MAPIE/releases
2929

3030
.. |Commits| image:: https://img.shields.io/github/commits-since/scikit-learn-contrib/mapie/latest/master
31-
:target: https://github.com/scikit-learn-contrib/MAPIE/commits/master
31+
:target: https://github.com/scikit-learn-contrib/MAPIE/commits/master
3232

3333
.. |DOI| image:: https://img.shields.io/badge/10.48550/arXiv.2207.12274-B31B1B.svg
34-
:target: https://arxiv.org/abs/2207.12274
34+
:target: https://arxiv.org/abs/2207.12274
3535

3636
.. image:: https://github.com/scikit-learn-contrib/MAPIE/raw/master/doc/images/mapie_logo_nobg_cut.png
3737
:width: 400

mapie/conformity_scores/conformity_scores.py

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def get_quantile(
214214
conformity_scores: NDArray,
215215
alpha_np: NDArray,
216216
axis: int,
217-
method: str
217+
reversed: bool = False
218218
) -> NDArray:
219219
"""
220220
Compute the alpha quantile of the conformity scores or the conformity
@@ -235,25 +235,32 @@ def get_quantile(
235235
axis: int
236236
The axis from which to compute the quantile.
237237
238-
method: str
239-
``"higher"`` or ``"lower"`` the method to compute the quantile.
238+
reversed: bool
239+
Boolean specifying whether we take the upper or lower quantile,
240+
if False, the alpha quantile, otherwise the (1-alpha) quantile.
240241
241242
Returns
242243
-------
243244
NDArray of shape (1, n_alpha) or (n_samples, n_alpha)
244245
The quantile of the conformity scores.
245246
"""
246-
n_ref = conformity_scores.shape[-1]
247-
quantile = np.column_stack([
247+
n_ref = conformity_scores.shape[1-axis]
248+
n_calib = np.min(np.sum(~np.isnan(conformity_scores), axis=axis))
249+
signed = 1-2*reversed
250+
251+
# Adapt alpha w.r.t upper/lower : alpha vs. 1-alpha
252+
alpha_ref = (1-2*alpha_np)*reversed + alpha_np
253+
254+
# Adjust alpha w.r.t quantile correction
255+
alpha_ref = np.ceil(alpha_ref*(n_calib+1))/n_calib
256+
257+
# Compute the target quantiles
258+
quantile = signed * np.column_stack([
248259
np_nanquantile(
249-
conformity_scores.astype(float),
250-
_alpha,
251-
axis=axis,
252-
method=method
260+
signed * conformity_scores, _alpha, axis=axis, method="lower"
253261
) if 0 < _alpha < 1
254-
else np.inf * np.ones(n_ref) if method == "higher"
255-
else - np.inf * np.ones(n_ref)
256-
for _alpha in alpha_np
262+
else np.inf * np.ones(n_ref)
263+
for _alpha in alpha_ref
257264
])
258265
return quantile
259266

@@ -281,7 +288,7 @@ def _beta_optimize(
281288
-------
282289
NDArray
283290
Array of betas minimizing the differences
284-
``(1-alpa+beta)-quantile - beta-quantile``.
291+
``(1-alpha+beta)-quantile - beta-quantile``.
285292
"""
286293
beta_np = np.full(
287294
shape=(len(lower_bounds), len(alpha_np)),
@@ -405,26 +412,34 @@ def get_bounds(
405412
X, y_pred_up, conformity_scores
406413
)
407414
bound_low = self.get_quantile(
408-
conformity_scores_low, alpha_low, axis=1, method="lower"
415+
conformity_scores_low, alpha_low, axis=1, reversed=True
409416
)
410417
bound_up = self.get_quantile(
411-
conformity_scores_up, alpha_up, axis=1, method="higher"
418+
conformity_scores_up, alpha_up, axis=1
412419
)
420+
413421
else:
414-
quantile_search = "higher" if self.sym else "lower"
415-
alpha_low = 1 - alpha_np if self.sym else beta_np
416-
alpha_up = 1 - alpha_np if self.sym else 1 - alpha_np + beta_np
422+
if self.sym:
423+
alpha_ref = 1-alpha_np
424+
quantile_ref = self.get_quantile(
425+
conformity_scores[..., np.newaxis], alpha_ref, axis=0
426+
)
427+
quantile_low, quantile_up = -quantile_ref, quantile_ref
428+
429+
else:
430+
alpha_low, alpha_up = beta_np, 1 - alpha_np + beta_np
431+
432+
quantile_low = self.get_quantile(
433+
conformity_scores[..., np.newaxis],
434+
alpha_low, axis=0, reversed=True
435+
)
436+
quantile_up = self.get_quantile(
437+
conformity_scores[..., np.newaxis],
438+
alpha_up, axis=0
439+
)
417440

418-
quantile_low = self.get_quantile(
419-
conformity_scores[..., np.newaxis],
420-
alpha_low, axis=0, method=quantile_search
421-
)
422-
quantile_up = self.get_quantile(
423-
conformity_scores[..., np.newaxis],
424-
alpha_up, axis=0, method="higher"
425-
)
426441
bound_low = self.get_estimation_distribution(
427-
X, y_pred_low, signed * quantile_low
442+
X, y_pred_low, quantile_low
428443
)
429444
bound_up = self.get_estimation_distribution(
430445
X, y_pred_up, quantile_up

mapie/regression/regression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ def predict(
629629

630630
alpha_np = cast(NDArray, alpha)
631631
if not allow_infinite_bounds:
632-
n = len(self.conformity_scores_)
632+
n = np.sum(~np.isnan(self.conformity_scores_))
633633
check_alpha_and_n_samples(alpha_np, n)
634634

635635
y_pred, y_pred_low, y_pred_up = \

mapie/tests/test_conformity_scores.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import Any
2-
31
import numpy as np
42
import pytest
53
from sklearn.linear_model import LinearRegression
@@ -382,18 +380,17 @@ def test_residual_normalised_prefit_get_estimation_distribution() -> None:
382380
@pytest.mark.parametrize("score", [AbsoluteConformityScore(),
383381
GammaConformityScore(),
384382
ResidualNormalisedScore()])
385-
@pytest.mark.parametrize("alpha", [[0.3], [0.5, 0.4]])
383+
@pytest.mark.parametrize("alpha", [[0.5], [0.5, 0.6]])
386384
def test_intervals_shape_with_every_score(
387385
score: ConformityScore,
388-
alpha: Any
386+
alpha: NDArray
389387
) -> None:
388+
estim = LinearRegression().fit(X_toy, y_toy)
390389
mapie_reg = MapieRegressor(
391-
method="base", cv="split", conformity_score=score
390+
estimator=estim, method="base", cv="prefit", conformity_score=score
392391
)
393-
X = np.concatenate((X_toy, X_toy))
394-
y = np.concatenate((y_toy, y_toy))
395-
mapie_reg = mapie_reg.fit(X, y)
396-
y_pred, intervals = mapie_reg.predict(X, alpha=alpha)
397-
n_samples = X.shape[0]
392+
mapie_reg = mapie_reg.fit(X_toy, y_toy)
393+
y_pred, intervals = mapie_reg.predict(X_toy, alpha=alpha)
394+
n_samples = X_toy.shape[0]
398395
assert y_pred.shape[0] == n_samples
399396
assert intervals.shape == (n_samples, 2, len(alpha))

0 commit comments

Comments
 (0)