11from abc import ABCMeta , abstractmethod
2- from typing import Tuple
32
4- import numpy as np
53from sklearn .utils import deprecated
64
7- from mapie .conformity_scores .interface import BaseConformityScore
8- from mapie .estimator .regressor import EnsembleRegressor
9- from mapie ._compatibility import np_nanquantile
5+ from mapie .conformity_scores .regression import BaseConformityScore
106from mapie ._machine_precision import EPSILON
117from mapie ._typing import NDArray
128
@@ -84,14 +80,19 @@ def get_signed_conformity_scores(
8480 Signed conformity scores.
8581 """
8682
83+ @abstractmethod
8784 def get_conformity_scores (
8885 self ,
8986 y : NDArray ,
9087 y_pred : NDArray ,
9188 ** kwargs
9289 ) -> NDArray :
9390 """
94- Get the conformity score considering the symmetrical property if so.
91+ Placeholder for ``get_conformity_scores``.
92+ Subclasses should implement this method!
93+
94+ Compute the sample conformity scores given the predicted and
95+ observed targets.
9596
9697 Parameters
9798 ----------
@@ -106,63 +107,6 @@ def get_conformity_scores(
106107 NDArray of shape (n_samples,)
107108 Conformity scores.
108109 """
109- conformity_scores = \
110- self .get_signed_conformity_scores (y , y_pred , ** kwargs )
111- if self .consistency_check :
112- self .check_consistency (y , y_pred , conformity_scores , ** kwargs )
113- if self .sym :
114- conformity_scores = np .abs (conformity_scores )
115- return conformity_scores
116-
117- def check_consistency (
118- self ,
119- y : NDArray ,
120- y_pred : NDArray ,
121- conformity_scores : NDArray ,
122- ** kwargs
123- ) -> None :
124- """
125- Check consistency between the following methods:
126- ``get_estimation_distribution`` and ``get_signed_conformity_scores``
127-
128- The following equality should be verified:
129- ``self.get_estimation_distribution(
130- y_pred, self.get_conformity_scores(y, y_pred, **kwargs), **kwargs
131- ) == y``
132-
133- Parameters
134- ----------
135- y: NDArray of shape (n_samples,)
136- Observed target values.
137-
138- y_pred: NDArray of shape (n_samples,)
139- Predicted target values.
140-
141- conformity_scores: NDArray of shape (n_samples,)
142- Conformity scores.
143-
144- Raises
145- ------
146- ValueError
147- If the two methods are not consistent.
148- """
149- score_distribution = self .get_estimation_distribution (
150- y_pred , conformity_scores , ** kwargs
151- )
152- abs_conformity_scores = np .abs (np .subtract (score_distribution , y ))
153- max_conf_score = np .max (abs_conformity_scores )
154- if max_conf_score > self .eps :
155- raise ValueError (
156- "The two functions get_conformity_scores and "
157- "get_estimation_distribution of the BaseRegressionScore class "
158- "are not consistent. "
159- "The following equation must be verified: "
160- "self.get_estimation_distribution(y_pred, "
161- "self.get_conformity_scores(y, y_pred)) == y. "
162- f"The maximum conformity score is { max_conf_score } . "
163- "The eps attribute may need to be increased if you are "
164- "sure that the two methods are consistent."
165- )
166110
167111 @abstractmethod
168112 def get_estimation_distribution (
@@ -192,199 +136,7 @@ def get_estimation_distribution(
192136 Observed values.
193137 """
194138
195- @staticmethod
196- def _beta_optimize (
197- alpha_np : NDArray ,
198- upper_bounds : NDArray ,
199- lower_bounds : NDArray ,
200- ) -> NDArray :
201- """
202- Minimize the width of the PIs, for a given difference of quantiles.
203-
204- Parameters
205- ----------
206- alpha_np: NDArray
207- The quantiles to compute.
208-
209- upper_bounds: NDArray of shape (n_samples,)
210- The array of upper values.
211-
212- lower_bounds: NDArray of shape (n_samples,)
213- The array of lower values.
214-
215- Returns
216- -------
217- NDArray of shape (n_samples,)
218- Array of betas minimizing the differences
219- ``(1-alpha+beta)-quantile - beta-quantile``.
220- """
221- beta_np = np .full (
222- shape = (len (lower_bounds ), len (alpha_np )),
223- fill_value = np .nan ,
224- dtype = float ,
225- )
226-
227- for ind_alpha , _alpha in enumerate (alpha_np ):
228- betas = np .linspace (
229- _alpha / (len (lower_bounds ) + 1 ),
230- _alpha ,
231- num = len (lower_bounds ),
232- endpoint = True ,
233- )
234- one_alpha_beta = np_nanquantile (
235- upper_bounds .astype (float ),
236- 1 - _alpha + betas ,
237- axis = 1 ,
238- method = "higher" ,
239- )
240- beta = np_nanquantile (
241- lower_bounds .astype (float ),
242- betas ,
243- axis = 1 ,
244- method = "lower" ,
245- )
246- beta_np [:, ind_alpha ] = betas [
247- np .argmin (one_alpha_beta - beta , axis = 0 )
248- ]
249-
250- return beta_np
251-
252- def get_bounds (
253- self ,
254- X : NDArray ,
255- alpha_np : NDArray ,
256- estimator : EnsembleRegressor ,
257- conformity_scores : NDArray ,
258- ensemble : bool = False ,
259- method : str = 'base' ,
260- optimize_beta : bool = False ,
261- allow_infinite_bounds : bool = False
262- ) -> Tuple [NDArray , NDArray , NDArray ]:
263- """
264- Compute bounds of the prediction intervals from the observed values,
265- the estimator of type ``EnsembleRegressor`` and the conformity scores.
266-
267- Parameters
268- ----------
269- X: NDArray of shape (n_samples, n_features)
270- Observed feature values.
271-
272- alpha_np: NDArray of shape (n_alpha,)
273- NDArray of floats between ``0`` and ``1``, represents the
274- uncertainty of the confidence interval.
275-
276- estimator: EnsembleRegressor
277- Estimator that is fitted to predict y from X.
278-
279- conformity_scores: NDArray of shape (n_samples,)
280- Conformity scores.
281-
282- ensemble: bool
283- Boolean determining whether the predictions are ensembled or not.
284-
285- By default ``False``.
286-
287- method: str
288- Method to choose for prediction interval estimates.
289- The ``"plus"`` method implies that the quantile is calculated
290- after estimating the bounds, whereas the other methods
291- (among the ``"naive"``, ``"base"`` or ``"minmax"`` methods,
292- for example) do the opposite.
293-
294- By default ``base``.
295-
296- optimize_beta: bool
297- Whether to optimize the PIs' width or not.
298-
299- By default ``False``.
300-
301- allow_infinite_bounds: bool
302- Allow infinite prediction intervals to be produced.
303-
304- By default ``False``.
305-
306- Returns
307- -------
308- Tuple[NDArray, NDArray, NDArray]
309- - The predictions itself. (y_pred) of shape (n_samples,).
310- - The lower bounds of the prediction intervals of shape
311- (n_samples, n_alpha).
312- - The upper bounds of the prediction intervals of shape
313- (n_samples, n_alpha).
314-
315- Raises
316- ------
317- ValueError
318- If beta optimisation with symmetrical conformity score function.
319- """
320- if self .sym and optimize_beta :
321- raise ValueError (
322- "Beta optimisation cannot be used with " +
323- "symmetrical conformity score function."
324- )
325-
326- y_pred , y_pred_low , y_pred_up = estimator .predict (X , ensemble )
327- signed = - 1 if self .sym else 1
328-
329- if optimize_beta :
330- beta_np = self ._beta_optimize (
331- alpha_np ,
332- conformity_scores .reshape (1 , - 1 ),
333- conformity_scores .reshape (1 , - 1 ),
334- )
335- else :
336- beta_np = alpha_np / 2
337-
338- if method == "plus" :
339- alpha_low = alpha_np if self .sym else beta_np
340- alpha_up = 1 - alpha_np if self .sym else 1 - alpha_np + beta_np
341-
342- conformity_scores_low = self .get_estimation_distribution (
343- y_pred_low , signed * conformity_scores , X = X
344- )
345- conformity_scores_up = self .get_estimation_distribution (
346- y_pred_up , conformity_scores , X = X
347- )
348- bound_low = self .get_quantile (
349- conformity_scores_low , alpha_low , axis = 1 , reversed = True ,
350- unbounded = allow_infinite_bounds
351- )
352- bound_up = self .get_quantile (
353- conformity_scores_up , alpha_up , axis = 1 ,
354- unbounded = allow_infinite_bounds
355- )
356-
357- else :
358- if self .sym :
359- alpha_ref = 1 - alpha_np
360- quantile_ref = self .get_quantile (
361- conformity_scores [..., np .newaxis ], alpha_ref , axis = 0
362- )
363- quantile_low , quantile_up = - quantile_ref , quantile_ref
364-
365- else :
366- alpha_low , alpha_up = beta_np , 1 - alpha_np + beta_np
367-
368- quantile_low = self .get_quantile (
369- conformity_scores [..., np .newaxis ],
370- alpha_low , axis = 0 , reversed = True ,
371- unbounded = allow_infinite_bounds
372- )
373- quantile_up = self .get_quantile (
374- conformity_scores [..., np .newaxis ],
375- alpha_up , axis = 0 ,
376- unbounded = allow_infinite_bounds
377- )
378-
379- bound_low = self .get_estimation_distribution (
380- y_pred_low , quantile_low , X = X
381- )
382- bound_up = self .get_estimation_distribution (
383- y_pred_up , quantile_up , X = X
384- )
385-
386- return y_pred , bound_low , bound_up
387-
139+ @abstractmethod
388140 def predict_set (
389141 self ,
390142 X : NDArray ,
@@ -408,7 +160,6 @@ def predict_set(
408160
409161 Returns:
410162 --------
411- The output structure depend on the ``get_bounds`` method .
163+ The output structure depend on the subclass .
412164 The prediction sets for each sample and each alpha level.
413165 """
414- return self .get_bounds (X = X , alpha_np = alpha_np , ** kwargs )
0 commit comments