44
55import math
66from itertools import combinations_with_replacement
7- from numbers import Integral , Real
7+ from numbers import Integral
88
99import numpy as np
1010from scipy .optimize import least_squares
1111from scipy .stats import rankdata
1212from sklearn .base import BaseEstimator , RegressorMixin
1313from sklearn .linear_model import LinearRegression
14- from sklearn .utils import check_array , check_X_y
14+ from sklearn .utils import check_array , check_consistent_length , column_or_1d
1515from sklearn .utils ._param_validation import Interval , StrOptions , validate_params
1616from sklearn .utils .validation import check_is_fitted
1717
@@ -52,7 +52,7 @@ def make_time_shift_features(X, ids):
5252 [5., 3., 4.],
5353 [7., 5., 6.]])
5454 """
55- X = check_array (X , ensure_2d = True , dtype = float )
55+ X = check_array (X , ensure_2d = True , dtype = float , force_all_finite = "allow-nan" )
5656 ids = check_array (ids , ensure_2d = True , dtype = int )
5757 n_samples = X .shape [0 ]
5858 n_outputs = ids .shape [0 ]
@@ -177,7 +177,7 @@ def make_poly_features(X, ids):
177177 [ 1., 5., 25., 6.],
178178 [ 1., 7., 49., 8.]])
179179 """
180- X = check_array (X , ensure_2d = True , dtype = float )
180+ X = check_array (X , ensure_2d = True , dtype = float , force_all_finite = "allow-nan" )
181181 ids = check_array (ids , ensure_2d = True , dtype = int )
182182 n_samples = X .shape [0 ]
183183 n_outputs , degree = ids .shape
@@ -263,6 +263,12 @@ def make_poly_ids(
263263 return np .delete (ids , const_id , 0 ) # remove the constant featrue
264264
265265
266+ def _mask_missing_value (* arr ):
267+ """Remove missing value for all arrays."""
268+ mask_nomissing = np .all (np .isfinite (np .c_ [arr ]), axis = 1 )
269+ return tuple ([x [mask_nomissing ] for x in arr ])
270+
271+
266272class Narx (BaseEstimator , RegressorMixin ):
267273 """Nonlinear Autoregressive eXogenous model.
268274 For example, a (polynomial) Narx model is like
@@ -392,6 +398,9 @@ def fit(self, X, y, coef_init=None, **params):
392398 When `coef_init` is an array, the model will be a Multi-Step-Ahead
393399 Narx whose coefficients and intercept are initialized by the array.
394400
401+ .. note::
402+ When coef_init is None, missing values (i.e., np.nan) are allowed.
403+
395404 **params : dict
396405 Keyword arguments passed to
397406 `scipy.optimize.least_squares`.
@@ -401,7 +410,9 @@ def fit(self, X, y, coef_init=None, **params):
401410 self : object
402411 Fitted Estimator.
403412 """
404- X , y = self ._validate_data (X , y , y_numeric = True , multi_output = False )
413+ X = self ._validate_data (X , dtype = float , force_all_finite = "allow-nan" )
414+ y = column_or_1d (y , dtype = float , warn = True )
415+ check_consistent_length (X , y )
405416
406417 if self .time_shift_ids is None :
407418 self .time_shift_ids_ = make_time_shift_ids (
@@ -455,8 +466,10 @@ def fit(self, X, y, coef_init=None, **params):
455466 osa_narx = LinearRegression ()
456467 time_shift_vars = make_time_shift_features (xy_hstack , self .time_shift_ids_ )
457468 poly_terms = make_poly_features (time_shift_vars , self .poly_ids_ )
469+ # Remove missing values
470+ poly_terms_masked , y_masked = _mask_missing_value (poly_terms , y )
458471
459- osa_narx .fit (poly_terms , y )
472+ osa_narx .fit (poly_terms_masked , y_masked )
460473 if coef_init is None :
461474 self .coef_ = osa_narx .coef_
462475 self .intercept_ = osa_narx .intercept_
@@ -516,9 +529,21 @@ def _expression(self, X, y_hat, coef, intercept, k):
516529 def _predict (expression , X , y_init , coef , intercept , max_delay ):
517530 n_samples = X .shape [0 ]
518531 y_hat = np .zeros (n_samples )
519- y_hat [:max_delay ] = y_init
520- for k in range (max_delay , n_samples ):
521- y_hat [k ] = expression (X , y_hat , coef , intercept , k )
532+ at_init = True
533+ init_k = 0
534+ for k in range (n_samples ):
535+ if ~ np .all (np .isfinite (X [k ])):
536+ at_init = True
537+ init_k = k + 1
538+ y_hat [k ] = np .nan
539+ continue
540+ if k - init_k == max_delay :
541+ at_init = False
542+
543+ if at_init :
544+ y_hat [k ] = y_init [k - init_k ]
545+ else :
546+ y_hat [k ] = expression (X , y_hat , coef , intercept , k )
522547 if np .any (y_hat [k ] > 1e20 ):
523548 y_hat [k :] = np .inf
524549 return y_hat
@@ -535,9 +560,11 @@ def _residual(
535560 coef = coef_intercept [:- 1 ]
536561 intercept = coef_intercept [- 1 ]
537562
538- return (
539- y - Narx ._predict (expression , X , y [:max_delay ], coef , intercept , max_delay )
540- ).flatten ()
563+ y_hat = Narx ._predict (expression , X , y [:max_delay ], coef , intercept , max_delay )
564+
565+ y_masked , y_hat_masked = _mask_missing_value (y , y_hat )
566+
567+ return (y_masked - y_hat_masked ).flatten ()
541568
542569 @validate_params (
543570 {
@@ -564,11 +591,11 @@ def predict(self, X, y_init=None):
564591 """
565592 check_is_fitted (self )
566593
567- X = self ._validate_data (X , reset = False )
594+ X = self ._validate_data (X , reset = False , force_all_finite = "allow-nan" )
568595 if y_init is None :
569596 y_init = np .zeros (self .max_delay_ )
570597 else :
571- y_init = check_array (y_init , ensure_2d = False , dtype = np . float64 )
598+ y_init = column_or_1d (y_init , dtype = float )
572599 if y_init .shape [0 ] != self .max_delay_ :
573600 raise ValueError (
574601 "`y_init` should have the shape of "
@@ -586,6 +613,9 @@ def predict(self, X, y_init=None):
586613 self .max_delay_ ,
587614 )
588615
616+ def _more_tags (self ):
617+ return {"allow_nan" : True }
618+
589619
590620@validate_params (
591621 {
@@ -721,10 +751,10 @@ def make_narx(
721751 Parameters
722752 ----------
723753 X : array-like of shape (n_samples, n_features)
724- Feature matrix.
754+ Feature matrix.
725755
726756 y : array-like of shape (n_samples,)
727- Target matrix .
757+ Target vector .
728758
729759 n_features_to_select : int
730760 The parameter is the absolute number of features to select.
@@ -800,14 +830,15 @@ def make_narx(
800830 | X[k-1,0]*X[k-3,0] | 1.999 |
801831 | X[k-2,0]*X[k-0,1] | 1.527 |
802832 """
803- X , y = check_X_y (X , y , dtype = Real , multi_output = False )
833+ X = check_array (X , dtype = float , ensure_2d = True , force_all_finite = "allow-nan" )
834+ y = column_or_1d (y , dtype = float )
835+ check_consistent_length (X , y )
804836
805837 xy_hstack = np .c_ [X , y ]
806838 n_features = X .shape [1 ]
807- n_outputs = xy_hstack .shape [1 ] - n_features
808839
809840 if include_zero_delay is None :
810- include_zero_delay = [True ] * n_features + [False ] * n_outputs
841+ include_zero_delay = [True ] * n_features + [False ]
811842
812843 time_shift_ids_all = make_time_shift_ids (
813844 n_features = xy_hstack .shape [1 ],
@@ -831,11 +862,14 @@ def make_narx(
831862 )
832863 poly_terms = make_poly_features (time_shift_vars , poly_ids_all )
833864
865+ # Remove missing values
866+ poly_terms_masked , y_masked = _mask_missing_value (poly_terms , y )
867+
834868 csf = FastCan (
835869 n_features_to_select ,
836870 eta = eta ,
837871 verbose = 0 ,
838- ).fit (poly_terms , y )
872+ ).fit (poly_terms_masked , y_masked )
839873 if drop is not None :
840874 indices , _ = refine (csf , drop = drop , max_iter = max_iter , verbose = verbose )
841875 support = np .zeros (shape = csf .n_features_in_ , dtype = bool )
0 commit comments