@@ -511,7 +511,42 @@ def get_feature_names_out(self, input_features=None):
511511 return self .all_outputs_
512512
513513
514- class _SplineEncoder (SingleColumnTransformer ):
514+ class _BasePeriodicEncoder (SingleColumnTransformer ):
515+ """Base class for periodic encoders."""
516+
517+ def __init__ (self , period = 24 ):
518+ self .period = period
519+
520+ def _post_process (self , X , new_features ):
521+ result = sbd .make_dataframe_like (X , dict (zip (self .all_outputs_ , new_features )))
522+ return sbd .copy_index (X , result )
523+
524+ def get_feature_names_out (self , input_features = None ):
525+ """Return a list of features generated by the transformer.
526+
527+ Each feature has format ``{input_name}_{n_component}`` where ``input_name``
528+ is the name of the input column, or a default name for the encoder, and
529+ ``n_component`` is the idx of the specific feature.
530+
531+ Parameters
532+ ----------
533+ input_features : None
534+ The input features. Ignored, only here for compatibility.
535+
536+ Returns
537+ -------
538+ list of str
539+ The list of feature names.
540+ """
541+ check_is_fitted (self , "n_components_" )
542+ num_digits = len (str (self .n_components_ - 1 ))
543+ return [
544+ f"{ self .input_name_ } _{ str (i ).zfill (num_digits )} "
545+ for i in range (self .n_components_ )
546+ ]
547+
548+
549+ class _SplineEncoder (_BasePeriodicEncoder ):
515550 """Generate univariate B-spline bases for features.
516551
517552 This encoder will apply the scikit-learn SplineTransformer to the given
@@ -533,10 +568,21 @@ class _SplineEncoder(SingleColumnTransformer):
533568 """
534569
535570 def __init__ (self , period = 24 , n_splines = None , degree = 3 ):
536- self . period = period
571+ super (). __init__ ( period = period )
537572 self .n_splines = n_splines
538573 self .degree = degree
539574
575+ def _periodic_spline_transformer (self ):
576+ n_splines = self .period if self .n_splines is None else self .n_splines
577+ n_knots = n_splines + 1 # periodic and include_bias is True
578+ return SplineTransformer (
579+ degree = self .degree ,
580+ n_knots = n_knots ,
581+ knots = np .linspace (0 , self .period , n_knots ).reshape (n_knots , 1 ),
582+ extrapolation = "periodic" ,
583+ include_bias = True ,
584+ )
585+
540586 def fit_transform (self , X , y = None ):
541587 """Fit the encoder and transform a column.
542588
@@ -560,7 +606,6 @@ def fit_transform(self, X, y=None):
560606
561607 X_out = self .transformer_ .fit_transform (sbd .to_numpy (X ).reshape (- 1 , 1 ))
562608
563- self .is_fitted = True
564609 self .n_components_ = X_out .shape [1 ]
565610
566611 # TODO: this will raise an error if X is None, but it should not happen
@@ -570,7 +615,7 @@ def fit_transform(self, X, y=None):
570615 self .input_name_ = sbd .name (X ) + "_spline"
571616 self .all_outputs_ = self .get_feature_names_out ()
572617
573- return self ._post_process (X , X_out )
618+ return self ._post_process (X , X_out . T )
574619
575620 def transform (self , X ):
576621 """Transform a column.
@@ -588,28 +633,10 @@ def transform(self, X):
588633
589634 X_out = self .transformer_ .transform (sbd .to_numpy (X ).reshape (- 1 , 1 ))
590635
591- return self ._post_process (X , X_out )
592-
593- def _post_process (self , X , result ):
594- result = sbd .make_dataframe_like (X , dict (zip (self .all_outputs_ , result .T )))
595- result = sbd .copy_index (X , result )
636+ return self ._post_process (X , X_out .T )
596637
597- return result
598638
599- def _periodic_spline_transformer (self ):
600- if self .n_splines is None :
601- self .n_splines = self .period
602- n_knots = self .n_splines + 1 # periodic and include_bias is True
603- return SplineTransformer (
604- degree = self .degree ,
605- n_knots = n_knots ,
606- knots = np .linspace (0 , self .period , n_knots ).reshape (n_knots , 1 ),
607- extrapolation = "periodic" ,
608- include_bias = True ,
609- )
610-
611-
612- class _CircularEncoder (SingleColumnTransformer ):
639+ class _CircularEncoder (_BasePeriodicEncoder ):
613640 """Generate trigonometric features for the given feature.
614641
615642 This encoder will generate two features corresponding to the sine and cosine
@@ -621,9 +648,6 @@ class _CircularEncoder(SingleColumnTransformer):
621648 Period to be used as basis of the trigonometric function.
622649 """
623650
624- def __init__ (self , period = 24 ):
625- self .period = period
626-
627651 def fit_transform (self , X , y = None ):
628652 """Fit the encoder and transform a column.
629653
@@ -679,9 +703,3 @@ def transform(self, X):
679703 ]
680704
681705 return self ._post_process (X , new_features )
682-
683- def _post_process (self , X , result ):
684- result = sbd .make_dataframe_like (X , dict (zip (self .all_outputs_ , result )))
685- result = sbd .copy_index (X , result )
686-
687- return result
0 commit comments