4
4
5
5
import math
6
6
from itertools import combinations_with_replacement
7
- from numbers import Integral , Real
7
+ from numbers import Integral
8
8
9
9
import numpy as np
10
10
from scipy .optimize import least_squares
11
11
from scipy .stats import rankdata
12
12
from sklearn .base import BaseEstimator , RegressorMixin
13
13
from 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
15
15
from sklearn .utils ._param_validation import Interval , StrOptions , validate_params
16
16
from sklearn .utils .validation import check_is_fitted
17
17
@@ -52,7 +52,7 @@ def make_time_shift_features(X, ids):
52
52
[5., 3., 4.],
53
53
[7., 5., 6.]])
54
54
"""
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" )
56
56
ids = check_array (ids , ensure_2d = True , dtype = int )
57
57
n_samples = X .shape [0 ]
58
58
n_outputs = ids .shape [0 ]
@@ -177,7 +177,7 @@ def make_poly_features(X, ids):
177
177
[ 1., 5., 25., 6.],
178
178
[ 1., 7., 49., 8.]])
179
179
"""
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" )
181
181
ids = check_array (ids , ensure_2d = True , dtype = int )
182
182
n_samples = X .shape [0 ]
183
183
n_outputs , degree = ids .shape
@@ -263,6 +263,12 @@ def make_poly_ids(
263
263
return np .delete (ids , const_id , 0 ) # remove the constant featrue
264
264
265
265
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
+
266
272
class Narx (BaseEstimator , RegressorMixin ):
267
273
"""Nonlinear Autoregressive eXogenous model.
268
274
For example, a (polynomial) Narx model is like
@@ -392,6 +398,9 @@ def fit(self, X, y, coef_init=None, **params):
392
398
When `coef_init` is an array, the model will be a Multi-Step-Ahead
393
399
Narx whose coefficients and intercept are initialized by the array.
394
400
401
+ .. note::
402
+ When coef_init is None, missing values (i.e., np.nan) are allowed.
403
+
395
404
**params : dict
396
405
Keyword arguments passed to
397
406
`scipy.optimize.least_squares`.
@@ -401,7 +410,9 @@ def fit(self, X, y, coef_init=None, **params):
401
410
self : object
402
411
Fitted Estimator.
403
412
"""
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 )
405
416
406
417
if self .time_shift_ids is None :
407
418
self .time_shift_ids_ = make_time_shift_ids (
@@ -455,8 +466,10 @@ def fit(self, X, y, coef_init=None, **params):
455
466
osa_narx = LinearRegression ()
456
467
time_shift_vars = make_time_shift_features (xy_hstack , self .time_shift_ids_ )
457
468
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 )
458
471
459
- osa_narx .fit (poly_terms , y )
472
+ osa_narx .fit (poly_terms_masked , y_masked )
460
473
if coef_init is None :
461
474
self .coef_ = osa_narx .coef_
462
475
self .intercept_ = osa_narx .intercept_
@@ -516,9 +529,21 @@ def _expression(self, X, y_hat, coef, intercept, k):
516
529
def _predict (expression , X , y_init , coef , intercept , max_delay ):
517
530
n_samples = X .shape [0 ]
518
531
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 )
522
547
if np .any (y_hat [k ] > 1e20 ):
523
548
y_hat [k :] = np .inf
524
549
return y_hat
@@ -535,9 +560,11 @@ def _residual(
535
560
coef = coef_intercept [:- 1 ]
536
561
intercept = coef_intercept [- 1 ]
537
562
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 ()
541
568
542
569
@validate_params (
543
570
{
@@ -564,11 +591,11 @@ def predict(self, X, y_init=None):
564
591
"""
565
592
check_is_fitted (self )
566
593
567
- X = self ._validate_data (X , reset = False )
594
+ X = self ._validate_data (X , reset = False , force_all_finite = "allow-nan" )
568
595
if y_init is None :
569
596
y_init = np .zeros (self .max_delay_ )
570
597
else :
571
- y_init = check_array (y_init , ensure_2d = False , dtype = np . float64 )
598
+ y_init = column_or_1d (y_init , dtype = float )
572
599
if y_init .shape [0 ] != self .max_delay_ :
573
600
raise ValueError (
574
601
"`y_init` should have the shape of "
@@ -586,6 +613,9 @@ def predict(self, X, y_init=None):
586
613
self .max_delay_ ,
587
614
)
588
615
616
+ def _more_tags (self ):
617
+ return {"allow_nan" : True }
618
+
589
619
590
620
@validate_params (
591
621
{
@@ -721,10 +751,10 @@ def make_narx(
721
751
Parameters
722
752
----------
723
753
X : array-like of shape (n_samples, n_features)
724
- Feature matrix.
754
+ Feature matrix.
725
755
726
756
y : array-like of shape (n_samples,)
727
- Target matrix .
757
+ Target vector .
728
758
729
759
n_features_to_select : int
730
760
The parameter is the absolute number of features to select.
@@ -800,14 +830,15 @@ def make_narx(
800
830
| X[k-1,0]*X[k-3,0] | 1.999 |
801
831
| X[k-2,0]*X[k-0,1] | 1.527 |
802
832
"""
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 )
804
836
805
837
xy_hstack = np .c_ [X , y ]
806
838
n_features = X .shape [1 ]
807
- n_outputs = xy_hstack .shape [1 ] - n_features
808
839
809
840
if include_zero_delay is None :
810
- include_zero_delay = [True ] * n_features + [False ] * n_outputs
841
+ include_zero_delay = [True ] * n_features + [False ]
811
842
812
843
time_shift_ids_all = make_time_shift_ids (
813
844
n_features = xy_hstack .shape [1 ],
@@ -831,11 +862,14 @@ def make_narx(
831
862
)
832
863
poly_terms = make_poly_features (time_shift_vars , poly_ids_all )
833
864
865
+ # Remove missing values
866
+ poly_terms_masked , y_masked = _mask_missing_value (poly_terms , y )
867
+
834
868
csf = FastCan (
835
869
n_features_to_select ,
836
870
eta = eta ,
837
871
verbose = 0 ,
838
- ).fit (poly_terms , y )
872
+ ).fit (poly_terms_masked , y_masked )
839
873
if drop is not None :
840
874
indices , _ = refine (csf , drop = drop , max_iter = max_iter , verbose = verbose )
841
875
support = np .zeros (shape = csf .n_features_in_ , dtype = bool )
0 commit comments