@@ -289,14 +289,17 @@ def report(self, *, environment, mode, **full_report_kwargs):
289289 self ._set_is_fitted (mode )
290290 return result
291291
292- def _score (self , environment ):
292+ def _score (self , environment , return_predictions = False ):
293293 score_node = find_scoring_node (self .data_op )
294294 if score_node is None :
295- return self ._eval_in_mode ("score" , environment )
295+ result = self ._eval_in_mode ("score" , environment )
296+ return (result , {}) if return_predictions else result
296297 estimator = self .__skrub_to_Xy_pipeline__ (environment )
297298 cv_data = _compute_X_y_and_cv (self .data_op , environment )
298299 X , y = cv_data ["X" ], cv_data .get ("y" )
299- return estimator ._score (X , y , cast_to_float = False )
300+ return estimator ._score (
301+ X , y , cast_to_float = False , return_predictions = return_predictions
302+ )
300303
301304 def __getattr__ (self , name ):
302305 if name not in supported_modes (self .data_op ):
@@ -899,38 +902,51 @@ def _process_scores(scorer_info, scorer_output):
899902 name = "score"
900903 return [(name , scorer_output )]
901904
902- def _score (self , X , y = None , cast_to_float = True ):
905+ def _score (self , X , y = None , cast_to_float = True , return_predictions = False ):
903906 score_node = find_scoring_node (self .data_op )
904907 if score_node is None :
905- return self ._eval_in_mode ("score" , X , y )
908+ result = self ._eval_in_mode ("score" , X , y )
909+ return (result , {}) if return_predictions else result
906910 env = self ._get_env (X , y )
907911 scorers = evaluate (
908912 score_node ._skrub_impl .scorers , mode = "fit_transform" , environment = env
909913 )
910914 all_scores = []
911- caching_estimator = _CachingXyPipeline (self .data_op , self .environment )
915+ predictions = env .get ("_skrub_predictions" , {})
916+ cache = {(k , id (X )): v for k , v in predictions .items ()}
917+ caching_estimator = _CachingXyPipeline (
918+ self .data_op , self .environment , cache = cache
919+ )
912920 _copy_attr (self , caching_estimator , ["_is_fitted" ])
913921 for scorer_info in scorers :
914922 scorer = self ._prepare_scorer (scorer_info ["scoring" ], scorer_info ["kwargs" ])
915923 scorer_output = scorer (caching_estimator , X , y )
916924 all_scores .extend (self ._process_scores (scorer_info , scorer_output ))
925+ predictions .update ({k : v for ((k , X_id ), v ) in cache .items () if X_id == id (X )})
917926 rename = unique_renaming ()
918927 result = {rename (name ): score for name , score in all_scores }
919- if cast_to_float and len (result ) == 1 :
928+ if cast_to_float and len (result ) == 1 and not return_predictions :
920929 # If there is a single score stick to scikit-learn interface which
921930 # returns a number.
922931 return next (iter (result .values ()))
923- return result
932+ return ( result , predictions ) if return_predictions else result
924933
925934
926935class _CachingXyPipeline (_XyPipeline ):
936+ def __init__ (self , data_op , environment , cache ):
937+ super ().__init__ (data_op , environment )
938+ self .cache = cache
939+
927940 def _eval_in_mode (self , mode , X , y = None ):
928- if not hasattr (self , "_cache" ):
929- self ._cache = {}
930- key = (mode , id (X ), id (y ))
931- if key not in self ._cache :
932- self ._cache [key ] = super ()._eval_in_mode (mode , X , y = y )
933- return self ._cache [key ]
941+ if y is not None :
942+ return super ()._eval_in_mode (mode , X , y = y )
943+ key = (mode , id (X ))
944+ if key not in self .cache :
945+ self .cache [key ] = super ()._eval_in_mode (mode , X , y = y )
946+ return self .cache [key ]
947+
948+ def _score (self , X , y = None ):
949+ return super ()._eval_in_mode ("score" , X , y = y )
934950
935951
936952def _compute_X_y_and_cv (data_op , environment ):
0 commit comments