@@ -183,6 +183,13 @@ class IsotonicRegression(BaseEstimator, TransformerMixin, RegressorMixin):
183
183
increase or decrease based on the Spearman correlation estimate's
184
184
sign.
185
185
186
+ out_of_bounds : string, optional, default: "nan"
187
+ The ``out_of_bounds`` parameter handles how x-values outside of the
188
+ training domain are handled. When set to "nan", predicted y-values
189
+ will be NaN. When set to "clip", predicted y-values will be
190
+ set to the value corresponding to the nearest train interval endpoint.
191
+ When set to "raise", allow ``interp1d`` to throw ValueError.
192
+
186
193
187
194
Attributes
188
195
----------
@@ -192,17 +199,25 @@ class IsotonicRegression(BaseEstimator, TransformerMixin, RegressorMixin):
192
199
`y_` : ndarray (n_samples, )
193
200
Isotonic fit of y.
194
201
202
+ `X_min_` : float
203
+ Minimum value of input array X_ for left bound.
204
+
205
+ `X_max_` : float
206
+ Maximum value of input array X_ for right bound.
207
+
195
208
References
196
209
----------
197
210
Isotonic Median Regression: A Linear Programming Approach
198
211
Nilotpal Chakravarti
199
212
Mathematics of Operations Research
200
213
Vol. 14, No. 2 (May, 1989), pp. 303-308
201
214
"""
202
- def __init__ (self , y_min = None , y_max = None , increasing = True ):
215
+ def __init__ (self , y_min = None , y_max = None , increasing = True ,
216
+ out_of_bounds = 'nan' ):
203
217
self .y_min = y_min
204
218
self .y_max = y_max
205
219
self .increasing = increasing
220
+ self .out_of_bounds = out_of_bounds
206
221
207
222
def _check_fit_data (self , X , y , sample_weight = None ):
208
223
if len (X .shape ) != 1 :
@@ -254,6 +269,11 @@ def fit(self, X, y, sample_weight=None, weight=None):
254
269
self .X_ = as_float_array (X [order ], copy = False )
255
270
self .y_ = isotonic_regression (y [order ], sample_weight , self .y_min ,
256
271
self .y_max , increasing = self .increasing_ )
272
+
273
+ # Handle the left and right bounds on X
274
+ self .X_min_ = np .min (self .X_ )
275
+ self .X_max_ = np .max (self .X_ )
276
+
257
277
return self
258
278
259
279
def transform (self , T ):
@@ -273,9 +293,21 @@ def transform(self, T):
273
293
if len (T .shape ) != 1 :
274
294
raise ValueError ("X should be a vector" )
275
295
276
- f = interpolate .interp1d (self .X_ , self .y_ , kind = 'linear' ,
277
- bounds_error = True )
278
- return f (T )
296
+ # Only raise exception on out-of-bounds data if requested.
297
+ if self .out_of_bounds == "raise" :
298
+ f = interpolate .interp1d (self .X_ , self .y_ , kind = 'linear' ,
299
+ bounds_error = True )
300
+ else :
301
+ f = interpolate .interp1d (self .X_ , self .y_ , kind = 'linear' ,
302
+ bounds_error = False )
303
+
304
+ # Clip out-of-bounds values if requested.
305
+ if self .out_of_bounds == "clip" :
306
+ T_final = np .clip (T , self .X_min_ , self .X_max_ )
307
+ else :
308
+ T_final = T
309
+
310
+ return f (T_final )
279
311
280
312
def fit_transform (self , X , y , sample_weight = None , weight = None ):
281
313
"""Fit model and transform y by linear interpolation.
@@ -325,6 +357,11 @@ def fit_transform(self, X, y, sample_weight=None, weight=None):
325
357
self .X_ = as_float_array (X [order ], copy = False )
326
358
self .y_ = isotonic_regression (y [order ], sample_weight , self .y_min ,
327
359
self .y_max , increasing = self .increasing_ )
360
+
361
+ # Handle the left and right bounds on X
362
+ self .X_min_ = np .min (self .X_ )
363
+ self .X_max_ = np .max (self .X_ )
364
+
328
365
return self .y_ [order_inv ]
329
366
330
367
def predict (self , T ):
0 commit comments