from typing import cast, Union
import numpy as np
from numpy.typing import ArrayLike, NDArray
from sklearn.utils import column_or_1d
from mapie.utils import (
_check_arrays_length,
_check_array_nan,
_check_array_inf,
_check_array_shape_classification, _check_nb_sets_sizes, _check_number_bins,
)
[docs]def classification_mean_width_score(y_pred_set: ArrayLike) -> float:
"""
Mean width of prediction set output by
:class:`~mapie.classification._MapieClassifier`.
Parameters
----------
y_pred_set: NDArray of shape (n_samples, n_class, n_confidence_level)
Prediction sets given by booleans of labels.
Returns
-------
NDArray of shape (n_confidence_level,)
Mean width of the prediction sets for each confidence level.
Examples
--------
>>> import numpy as np
>>> from mapie.metrics.classification import classification_mean_width_score
>>> y_pred_set = np.array([
... [[False, False], [False, True], [True, True]],
... [[False, True], [True, False], [True, True]],
... [[True, False], [True, True], [True, False]],
... [[False, False], [True, True], [True, True]],
... [[True, True], [False, True], [True, False]]
... ])
>>> print(classification_mean_width_score(y_pred_set))
[2. 1.8]
"""
y_pred_set = np.asarray(y_pred_set, dtype=bool)
_check_array_nan(y_pred_set)
_check_array_inf(y_pred_set)
width = y_pred_set.sum(axis=1)
mean_width = width.mean(axis=0)
return mean_width
[docs]def classification_coverage_score(
y_true: NDArray,
y_pred_set: NDArray
) -> NDArray:
"""
Effective coverage score obtained by the prediction sets.
The effective coverage is obtained by estimating the fraction
of true labels that lie within the prediction sets.
Prediction sets obtained by the ``predict`` method can be passed directly to the
``y_pred_set`` argument (see example below).
Beside this intended use, this function also works with:
- ``y_true`` of shape (n_sample,) and ``y_pred_set`` of shape (n_sample, n_class)
- ``y_true`` of shape (n_sample, n) and ``y_pred_set`` of shape
(n_sample, n_class, n)
Parameters
----------
y_true: NDArray of shape (n_samples,)
True labels.
y_pred_set: NDArray of shape (n_samples, n_class, n_confidence_level)
Prediction sets with different confidence levels, given by booleans of labels
with the ``predict`` method.
Returns
-------
NDArray of shape (n_confidence_level,)
Effective coverage obtained by the prediction sets for each confidence level.
Examples
--------
>>> from mapie.metrics.classification import classification_coverage_score
>>> from mapie.classification import SplitConformalClassifier
>>> from mapie.utils import train_conformalize_test_split
>>> from sklearn.datasets import make_classification
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.neighbors import KNeighborsClassifier
>>> X, y = make_classification(n_samples=500)
>>> (
... X_train, X_conformalize, X_test,
... y_train, y_conformalize, y_test
... ) = train_conformalize_test_split(
... X, y, train_size=0.6, conformalize_size=0.2, test_size=0.2, random_state=1
... )
>>> mapie_classifier = SplitConformalClassifier(
... estimator=KNeighborsClassifier(),
... confidence_level=[0.9, 0.95, 0.99],
... prefit=False,
... ).fit(X_train, y_train).conformalize(X_conformalize, y_conformalize)
>>> predicted_points, predicted_sets = mapie_classifier.predict_set(X_test)
>>> coverage = classification_coverage_score(y_test, predicted_sets)[0]
"""
_check_arrays_length(y_true, y_pred_set)
_check_array_nan(y_true)
_check_array_inf(y_true)
_check_array_nan(y_pred_set)
_check_array_inf(y_pred_set)
y_pred_set = _check_array_shape_classification(y_true, y_pred_set)
if len(y_true.shape) != 2:
y_true = cast(NDArray, column_or_1d(y_true))
y_true = np.expand_dims(y_true, axis=1)
y_true = np.expand_dims(y_true, axis=1)
coverage = np.nanmean(
np.take_along_axis(y_pred_set, y_true, axis=1),
axis=0
)
return coverage[0]
[docs]def classification_ssc(
y_true: NDArray,
y_pred_set: NDArray,
num_bins: Union[int, None] = None
) -> NDArray:
"""
Compute Size-Stratified Coverage metrics proposed in [3] that is
the conditional coverage conditioned by the size of the predictions sets.
The sets are ranked by their size (ascending) and then divided into
num_bins groups: one value of coverage by groups is computed.
[3] Angelopoulos, A. N., & Bates, S. (2021).
A gentle introduction to conformal prediction and
distribution-free uncertainty quantification.
arXiv preprint arXiv:2107.07511.
Parameters
----------
y_true: NDArray of shape (n_samples,)
True labels.
y_pred_set: NDArray of shape (n_samples, n_class, n_confidence_level)
or (n_samples, n_class)
Prediction sets given by booleans of labels.
num_bins: int or None
Number of groups. If None, one value of coverage by possible
size of sets (n_classes +1) is computed. Should be less than the
number of different set sizes.
Returns
-------
NDArray of shape (n_confidence_level, num_bins)
Examples
--------
>>> from mapie.metrics.classification import classification_ssc
>>> import numpy as np
>>> y_true = y_true_class = np.array([3, 3, 1, 2, 2])
>>> y_pred_set = np.array([
... [True, True, True, True],
... [False, True, False, True],
... [True, True, True, False],
... [False, False, True, True],
... [True, True, False, True]])
>>> print(classification_ssc(y_true, y_pred_set, num_bins=2))
[[1. 0.66666667]]
"""
y_true = cast(NDArray, column_or_1d(y_true))
y_pred_set = _check_array_shape_classification(y_true, y_pred_set)
_check_arrays_length(y_true, y_pred_set)
_check_array_nan(y_true)
_check_array_inf(y_true)
_check_array_nan(y_pred_set)
_check_array_inf(y_pred_set)
sizes = np.sum(y_pred_set, axis=1)
n_classes = y_pred_set.shape[1]
if num_bins is None:
bins = list(range(n_classes + 1))
else:
_check_nb_sets_sizes(sizes, num_bins)
_check_number_bins(num_bins)
bins = [
b[0] for b in np.array_split(range(n_classes + 1), num_bins)
]
digitized_sizes: NDArray = np.digitize(sizes, bins)
coverages = np.zeros((y_pred_set.shape[2], len(bins)))
for alpha in range(y_pred_set.shape[2]):
indexes_bybins = [
np.argwhere(digitized_sizes[:, alpha] == i)
for i in range(1, len(bins)+1)
]
for i, indexes in enumerate(indexes_bybins):
coverages[alpha, i] = classification_coverage_score(
y_true[indexes],
np.take_along_axis(
y_pred_set[:, :, alpha],
indexes,
axis=0
)
).item()
return coverages
[docs]def classification_ssc_score(
y_true: NDArray,
y_pred_set: NDArray,
num_bins: Union[int, None] = None
) -> NDArray:
"""
Aggregate by the minimum for each confidence level the Size-Stratified Coverage [3]:
returns the maximum violation of the conditional coverage
(with the groups defined).
Parameters
----------
y_true: NDArray of shape (n_samples,)
True labels.
y_pred_set: NDArray of shape (n_samples, n_class, n_confidence_level)
or (n_samples, n_class)
Prediction sets given by booleans of labels.
num_bins: int or None
Number of groups. If None, one value of coverage by possible
size of sets (n_classes +1) is computed. Should be less than
the number of different set sizes.
Returns
-------
NDArray of shape (n_confidence_level,)
Examples
--------
>>> from mapie.metrics.classification import classification_ssc_score
>>> import numpy as np
>>> y_true = y_true_class = np.array([3, 3, 1, 2, 2])
>>> y_pred_set = np.array([
... [True, True, True, True],
... [False, True, False, True],
... [True, True, True, False],
... [False, False, True, True],
... [True, True, False, True]])
>>> print(classification_ssc_score(y_true, y_pred_set, num_bins=2))
[0.66666667]
"""
_check_arrays_length(y_true, y_pred_set)
_check_array_nan(y_true)
_check_array_inf(y_true)
_check_array_nan(y_pred_set)
_check_array_inf(y_pred_set)
return np.nanmin(classification_ssc(y_true, y_pred_set, num_bins), axis=1)