Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions skrub/_apply_to_cols.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,30 +123,6 @@ def __init_subclass__(subclass, **kwargs):
wrapped = _wrap_add_check_single_column(getattr(subclass, method))
setattr(subclass, method, wrapped)

def get_feature_names_out(self, input_features=None):
"""Return a list of features generated by the transformer.

Each feature has format ``{input_name}_{n_component}`` where ``input_name``
is the name of the input column, or a default name for the encoder, and
``n_component`` is the idx of the specific feature.

Parameters
----------
input_features : None
The input features. Ignored, only here for compatibility.

Returns
-------
list of str
The list of feature names.
"""
check_is_fitted(self, "n_components_")
num_digits = len(str(self.n_components_ - 1))
return [
f"{self.input_name_}_{str(i).zfill(num_digits)}"
for i in range(self.n_components_)
]


def _wrap_add_check_single_column(f):
# as we have only a few predefined functions to handle, using their exact
Expand Down
84 changes: 51 additions & 33 deletions skrub/_datetime_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,42 @@ def get_feature_names_out(self, input_features=None):
return self.all_outputs_


class _SplineEncoder(SingleColumnTransformer):
class _BasePeriodicEncoder(SingleColumnTransformer):
"""Base class for periodic encoders."""

def __init__(self, period=24):
self.period = period

def _post_process(self, X, new_features):
result = sbd.make_dataframe_like(X, dict(zip(self.all_outputs_, new_features)))
return sbd.copy_index(X, result)

def get_feature_names_out(self, input_features=None):
"""Return a list of features generated by the transformer.

Each feature has format ``{input_name}_{n_component}`` where ``input_name``
is the name of the input column, or a default name for the encoder, and
``n_component`` is the idx of the specific feature.

Parameters
----------
input_features : None
The input features. Ignored, only here for compatibility.

Returns
-------
list of str
The list of feature names.
"""
check_is_fitted(self, "n_components_")
num_digits = len(str(self.n_components_ - 1))
return [
f"{self.input_name_}_{str(i).zfill(num_digits)}"
for i in range(self.n_components_)
]


class _SplineEncoder(_BasePeriodicEncoder):
"""Generate univariate B-spline bases for features.

This encoder will apply the scikit-learn SplineTransformer to the given
Expand All @@ -533,10 +568,21 @@ class _SplineEncoder(SingleColumnTransformer):
"""

def __init__(self, period=24, n_splines=None, degree=3):
self.period = period
super().__init__(period=period)
self.n_splines = n_splines
self.degree = degree

def _periodic_spline_transformer(self):
n_splines = self.period if self.n_splines is None else self.n_splines
n_knots = n_splines + 1 # periodic and include_bias is True
return SplineTransformer(
degree=self.degree,
n_knots=n_knots,
knots=np.linspace(0, self.period, n_knots).reshape(n_knots, 1),
extrapolation="periodic",
include_bias=True,
)

def fit_transform(self, X, y=None):
"""Fit the encoder and transform a column.

Expand All @@ -560,7 +606,6 @@ def fit_transform(self, X, y=None):

X_out = self.transformer_.fit_transform(sbd.to_numpy(X).reshape(-1, 1))

self.is_fitted = True
self.n_components_ = X_out.shape[1]

# TODO: this will raise an error if X is None, but it should not happen
Expand All @@ -570,7 +615,7 @@ def fit_transform(self, X, y=None):
self.input_name_ = sbd.name(X) + "_spline"
self.all_outputs_ = self.get_feature_names_out()

return self._post_process(X, X_out)
return self._post_process(X, X_out.T)

def transform(self, X):
"""Transform a column.
Expand All @@ -588,28 +633,10 @@ def transform(self, X):

X_out = self.transformer_.transform(sbd.to_numpy(X).reshape(-1, 1))

return self._post_process(X, X_out)

def _post_process(self, X, result):
result = sbd.make_dataframe_like(X, dict(zip(self.all_outputs_, result.T)))
result = sbd.copy_index(X, result)
return self._post_process(X, X_out.T)

return result

def _periodic_spline_transformer(self):
if self.n_splines is None:
self.n_splines = self.period
n_knots = self.n_splines + 1 # periodic and include_bias is True
return SplineTransformer(
degree=self.degree,
n_knots=n_knots,
knots=np.linspace(0, self.period, n_knots).reshape(n_knots, 1),
extrapolation="periodic",
include_bias=True,
)


class _CircularEncoder(SingleColumnTransformer):
class _CircularEncoder(_BasePeriodicEncoder):
"""Generate trigonometric features for the given feature.

This encoder will generate two features corresponding to the sine and cosine
Expand All @@ -621,9 +648,6 @@ class _CircularEncoder(SingleColumnTransformer):
Period to be used as basis of the trigonometric function.
"""

def __init__(self, period=24):
self.period = period

def fit_transform(self, X, y=None):
"""Fit the encoder and transform a column.

Expand Down Expand Up @@ -679,9 +703,3 @@ def transform(self, X):
]

return self._post_process(X, new_features)

def _post_process(self, X, result):
result = sbd.make_dataframe_like(X, dict(zip(self.all_outputs_, result)))
result = sbd.copy_index(X, result)

return result
24 changes: 24 additions & 0 deletions skrub/_minhash_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,27 @@ def transform(self, X):
result = sbd.make_dataframe_like(X, dict(zip(names, X_out.T)))
result = sbd.copy_index(X, result)
return result

def get_feature_names_out(self, input_features=None):
"""Return a list of features generated by the transformer.

Each feature has format ``{input_name}_{n_component}`` where ``input_name``
is the name of the input column, or a default name for the encoder, and
``n_component`` is the idx of the specific feature.

Parameters
----------
input_features : None
The input features. Ignored, only here for compatibility.

Returns
-------
list of str
The list of feature names.
"""
check_is_fitted(self, "n_components_")
num_digits = len(str(self.n_components_ - 1))
return [
f"{self.input_name_}_{str(i).zfill(num_digits)}"
for i in range(self.n_components_)
]
25 changes: 25 additions & 0 deletions skrub/_string_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
TfidfVectorizer,
)
from sklearn.pipeline import Pipeline
from sklearn.utils.validation import check_is_fitted

from . import _dataframe as sbd
from ._apply_to_cols import SingleColumnTransformer
Expand Down Expand Up @@ -263,3 +264,27 @@ def _post_process(self, X, result):
result = sbd.copy_index(X, result)

return result

def get_feature_names_out(self, input_features=None):
"""Return a list of features generated by the transformer.

Each feature has format ``{input_name}_{n_component}`` where ``input_name``
is the name of the input column, or a default name for the encoder, and
``n_component`` is the idx of the specific feature.

Parameters
----------
input_features : None
The input features. Ignored, only here for compatibility.

Returns
-------
list of str
The list of feature names.
"""
check_is_fitted(self, "n_components_")
num_digits = len(str(self.n_components_ - 1))
return [
f"{self.input_name_}_{str(i).zfill(num_digits)}"
for i in range(self.n_components_)
]
24 changes: 24 additions & 0 deletions skrub/_text_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,27 @@ def __getstate__(self):
del state[prop]

return state

def get_feature_names_out(self, input_features=None):
"""Return a list of features generated by the transformer.

Each feature has format ``{input_name}_{n_component}`` where ``input_name``
is the name of the input column, or a default name for the encoder, and
``n_component`` is the idx of the specific feature.

Parameters
----------
input_features : None
The input features. Ignored, only here for compatibility.

Returns
-------
list of str
The list of feature names.
"""
check_is_fitted(self, "n_components_")
num_digits = len(str(self.n_components_ - 1))
return [
f"{self.input_name_}_{str(i).zfill(num_digits)}"
for i in range(self.n_components_)
]
8 changes: 8 additions & 0 deletions skrub/tests/test_datetime_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,11 @@ def test_error_checking_periodic_encoder(a_datetime_col):
def test_error_dispatch(func):
with pytest.raises(TypeError, match="Expecting a Pandas or Polars Series"):
func(np.array([1]))


def test_n_splines_default_value(df_module):
"""Check that when `n_splines is None`, it defaults to the `period` value."""
period = 15
enc = _SplineEncoder(period=period)
result = enc.fit_transform(df_module.make_column("when", [20, 20, 20]))
assert sbd.column_names(result) == [f"when_spline_{i:02d}" for i in range(period)]
Loading