11from __future__ import annotations
2- from typing import Optional , Union , Dict , Tuple
2+ from typing import Optional , Union , Dict , Tuple , Type
33
44import numpy as np
55import pytest
88from sklearn .datasets import make_regression
99from sklearn .linear_model import LinearRegression
1010from sklearn .ensemble import RandomForestRegressor
11+ from sklearn .linear_model import QuantileRegressor
12+ from lightgbm import LGBMRegressor
1113
1214from mapie .subsample import Subsample
1315from mapie ._typing import ArrayLike
1416from mapie .conformity_scores import GammaConformityScore , \
1517 AbsoluteConformityScore
1618from mapie_v1 .regression import SplitConformalRegressor , \
1719 CrossConformalRegressor , \
18- JackknifeAfterBootstrapRegressor
20+ JackknifeAfterBootstrapRegressor , \
21+ ConformalizedQuantileRegressor
1922
2023from mapiev0 .regression import MapieRegressor as MapieRegressorV0 # noqa
24+ from mapiev0 .regression import MapieQuantileRegressor as MapieQuantileRegressorV0 # noqa
2125from mapie_v1 .conformity_scores ._utils import \
2226 check_and_select_regression_conformity_score
2327from mapie_v1 .integration_tests .utils import (filter_params ,
3034
3135
3236X , y_signed = make_regression (
33- n_samples = 50 ,
37+ n_samples = 100 ,
3438 n_features = 10 ,
3539 noise = 1.0 ,
3640 random_state = RANDOM_STATE
3741)
3842y = np .abs (y_signed )
3943sample_weight = RandomState (RANDOM_STATE ).random (len (X ))
40- groups = [0 ] * 10 + [1 ] * 10 + [2 ] * 10 + [3 ] * 10 + [4 ] * 10
44+ groups = [0 ] * 20 + [1 ] * 20 + [2 ] * 20 + [3 ] * 20 + [4 ] * 20
4145positive_predictor = TransformedTargetRegressor (
4246 regressor = LinearRegression (),
4347 func = lambda y_ : np .log (y_ + 1 ),
@@ -105,9 +109,9 @@ def test_intervals_and_predictions_exact_equality_split(
105109 "random_state" : RANDOM_STATE ,
106110 }
107111
108- v0 , v1 = initialize_models (cv , v0_params , v1_params )
109- compare_model_predictions_and_intervals (v0 = v0 ,
110- v1 = v1 ,
112+ v0 , v1 = select_models_by_strategy (cv )
113+ compare_model_predictions_and_intervals (model_v0 = v0 ,
114+ model_v1 = v1 ,
111115 X = X_split ,
112116 y = y_split ,
113117 v0_params = v0_params ,
@@ -184,7 +188,7 @@ def test_intervals_and_predictions_exact_equality_cross(params_cross):
184188 v0_params = params_cross ["v0" ]
185189 v1_params = params_cross ["v1" ]
186190
187- v0 , v1 = initialize_models ("cross" , v0_params , v1_params )
191+ v0 , v1 = select_models_by_strategy ("cross" )
188192 compare_model_predictions_and_intervals (v0 , v1 , X , y , v0_params , v1_params )
189193
190194
@@ -264,58 +268,178 @@ def test_intervals_and_predictions_exact_equality_cross(params_cross):
264268]
265269
266270
271+ split_model = QuantileRegressor (
272+ solver = "highs-ds" ,
273+ alpha = 0.0 ,
274+ )
275+
276+ lgbm_models = []
277+ lgbm_alpha = 0.1
278+ for alpha_ in [lgbm_alpha / 2 , (1 - (lgbm_alpha / 2 )), 0.5 ]:
279+ estimator_ = LGBMRegressor (
280+ objective = 'quantile' ,
281+ alpha = alpha_ ,
282+ )
283+ lgbm_models .append (estimator_ )
284+
285+
267286@pytest .mark .parametrize ("params_jackknife" , params_test_cases_jackknife )
268287def test_intervals_and_predictions_exact_equality_jackknife (params_jackknife ):
269288 v0_params = params_jackknife ["v0" ]
270289 v1_params = params_jackknife ["v1" ]
271290
272- v0 , v1 = initialize_models ("jackknife" , v0_params , v1_params )
291+ v0 , v1 = select_models_by_strategy ("jackknife" )
273292 compare_model_predictions_and_intervals (v0 , v1 , X , y , v0_params , v1_params )
274293
275294
276- def initialize_models (
277- strategy_key : str ,
278- v0_params : Dict ,
279- v1_params : Dict ,
280- ) -> Tuple [MapieRegressorV0 , Union [
281- SplitConformalRegressor ,
282- CrossConformalRegressor ,
283- JackknifeAfterBootstrapRegressor
284- ]]:
295+ params_test_cases_quantile = [
296+ {
297+ "v0" : {
298+ "alpha" : 0.2 ,
299+ "cv" : "split" ,
300+ "method" : "quantile" ,
301+ "calib_size" : 0.3 ,
302+ "sample_weight" : sample_weight ,
303+ "random_state" : RANDOM_STATE ,
304+ },
305+ "v1" : {
306+ "confidence_level" : 0.8 ,
307+ "prefit" : False ,
308+ "test_size" : 0.3 ,
309+ "fit_params" : {"sample_weight" : sample_weight },
310+ "random_state" : RANDOM_STATE ,
311+ },
312+ },
313+ {
314+ "v0" : {
315+ "estimator" : lgbm_models ,
316+ "alpha" : lgbm_alpha ,
317+ "cv" : "prefit" ,
318+ "method" : "quantile" ,
319+ "calib_size" : 0.3 ,
320+ "sample_weight" : sample_weight ,
321+ "optimize_beta" : True ,
322+ "random_state" : RANDOM_STATE ,
323+ },
324+ "v1" : {
325+ "estimator" : lgbm_models ,
326+ "confidence_level" : 1 - lgbm_alpha ,
327+ "prefit" : True ,
328+ "test_size" : 0.3 ,
329+ "fit_params" : {"sample_weight" : sample_weight },
330+ "minimize_interval_width" : True ,
331+ "random_state" : RANDOM_STATE ,
332+ },
333+ },
334+ {
335+ "v0" : {
336+ "estimator" : split_model ,
337+ "alpha" : 0.5 ,
338+ "cv" : "split" ,
339+ "method" : "quantile" ,
340+ "calib_size" : 0.3 ,
341+ "allow_infinite_bounds" : True ,
342+ "random_state" : RANDOM_STATE ,
343+ },
344+ "v1" : {
345+ "estimator" : split_model ,
346+ "confidence_level" : 0.5 ,
347+ "prefit" : False ,
348+ "test_size" : 0.3 ,
349+ "allow_infinite_bounds" : True ,
350+ "random_state" : RANDOM_STATE ,
351+ },
352+ },
353+ {
354+ "v0" : {
355+ "alpha" : 0.1 ,
356+ "cv" : "split" ,
357+ "method" : "quantile" ,
358+ "calib_size" : 0.3 ,
359+ "random_state" : RANDOM_STATE ,
360+ "symmetry" : False
361+ },
362+ "v1" : {
363+ "confidence_level" : 0.9 ,
364+ "prefit" : False ,
365+ "test_size" : 0.3 ,
366+ "random_state" : RANDOM_STATE ,
367+ "symmetric_intervals" : False ,
368+ },
369+ },
370+ ]
285371
286- v1 : Union [SplitConformalRegressor ,
287- CrossConformalRegressor ,
288- JackknifeAfterBootstrapRegressor ]
372+
373+ @pytest .mark .parametrize ("params_quantile" , params_test_cases_quantile )
374+ def test_intervals_and_predictions_exact_equality_quantile (params_quantile ):
375+ v0_params = params_quantile ["v0" ]
376+ v1_params = params_quantile ["v1" ]
377+
378+ test_size = v1_params ["test_size" ] if "test_size" in v1_params else None
379+ prefit = ("prefit" in v1_params ) and v1_params ["prefit" ]
380+
381+ v0 , v1 = select_models_by_strategy ("quantile" )
382+ compare_model_predictions_and_intervals (model_v0 = v0 ,
383+ model_v1 = v1 ,
384+ X = X ,
385+ y = y ,
386+ v0_params = v0_params ,
387+ v1_params = v1_params ,
388+ test_size = test_size ,
389+ prefit = prefit ,
390+ random_state = RANDOM_STATE )
391+
392+
393+ def select_models_by_strategy (
394+ strategy_key : str
395+ ) -> Tuple [
396+ Type [Union [MapieRegressorV0 , MapieQuantileRegressorV0 ]],
397+ Type [Union [
398+ SplitConformalRegressor ,
399+ CrossConformalRegressor ,
400+ JackknifeAfterBootstrapRegressor ,
401+ ConformalizedQuantileRegressor
402+ ]]
403+ ]:
404+
405+ model_v0 : Type [Union [MapieRegressorV0 , MapieQuantileRegressorV0 ]]
406+ model_v1 : Type [Union [
407+ SplitConformalRegressor ,
408+ CrossConformalRegressor ,
409+ JackknifeAfterBootstrapRegressor ,
410+ ConformalizedQuantileRegressor
411+ ]]
289412
290413 if strategy_key in ["split" , "prefit" ]:
291- v1_params = filter_params ( SplitConformalRegressor . __init__ , v1_params )
292- v1 = SplitConformalRegressor ( ** v1_params )
414+ model_v1 = SplitConformalRegressor
415+ model_v0 = MapieRegressorV0
293416
294417 elif strategy_key == "cross" :
295- v1_params = filter_params ( CrossConformalRegressor . __init__ , v1_params )
296- v1 = CrossConformalRegressor ( ** v1_params )
418+ model_v1 = CrossConformalRegressor
419+ model_v0 = MapieRegressorV0
297420
298421 elif strategy_key == "jackknife" :
299- v1_params = filter_params (
300- JackknifeAfterBootstrapRegressor .__init__ ,
301- v1_params
302- )
303- v1 = JackknifeAfterBootstrapRegressor (** v1_params )
422+ model_v1 = JackknifeAfterBootstrapRegressor
423+ model_v0 = MapieRegressorV0
424+
425+ elif strategy_key == "quantile" :
426+ model_v1 = ConformalizedQuantileRegressor
427+ model_v0 = MapieQuantileRegressorV0
304428
305429 else :
306430 raise ValueError (f"Unknown strategy key: { strategy_key } " )
307431
308- v0_params = filter_params (MapieRegressorV0 .__init__ , v0_params )
309- v0 = MapieRegressorV0 (** v0_params )
310-
311- return v0 , v1
432+ return model_v0 , model_v1
312433
313434
314435def compare_model_predictions_and_intervals (
315- v0 : MapieRegressorV0 ,
316- v1 : Union [SplitConformalRegressor ,
317- CrossConformalRegressor ,
318- JackknifeAfterBootstrapRegressor ],
436+ model_v0 : Type [MapieRegressorV0 ],
437+ model_v1 : Type [Union [
438+ SplitConformalRegressor ,
439+ CrossConformalRegressor ,
440+ JackknifeAfterBootstrapRegressor ,
441+ ConformalizedQuantileRegressor
442+ ]],
319443 X : ArrayLike ,
320444 y : ArrayLike ,
321445 v0_params : Dict = {},
@@ -332,16 +456,28 @@ def compare_model_predictions_and_intervals(
332456 else :
333457 X_train , X_conf , y_train , y_conf = X , X , y , y
334458
459+ if prefit :
460+ estimator = v0_params ["estimator" ]
461+ if isinstance (estimator , list ):
462+ for single_estimator in estimator :
463+ single_estimator .fit (X_train , y_train )
464+ else :
465+ estimator .fit (X_train , y_train )
466+
467+ v0_params ["estimator" ] = estimator
468+ v1_params ["estimator" ] = estimator
469+
470+ v0_init_params = filter_params (model_v0 .__init__ , v0_params )
471+ v1_init_params = filter_params (model_v1 .__init__ , v1_params )
472+
473+ v0 = model_v0 (** v0_init_params )
474+ v1 = model_v1 (** v1_init_params )
475+
335476 v0_fit_params = filter_params (v0 .fit , v0_params )
336477 v1_fit_params = filter_params (v1 .fit , v1_params )
337478 v1_conformalize_params = filter_params (v1 .conformalize , v1_params )
338479
339480 if prefit :
340- estimator = v0 .estimator
341- estimator .fit (X_train , y_train )
342- v0 .estimator = estimator
343- v1 ._mapie_regressor .estimator = estimator
344-
345481 v0 .fit (X_conf , y_conf , ** v0_fit_params )
346482 else :
347483 v0 .fit (X , y , ** v0_fit_params )
0 commit comments