From a0df21b3c5f890ce088968b8cdb18452aa34c8f7 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:20:11 +0200 Subject: [PATCH 01/14] test(public-api): pin pretab.transformers facade to 24 classes --- tests/integration/test_public_api.py | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/integration/test_public_api.py b/tests/integration/test_public_api.py index 5b3b93e..16cc7b6 100644 --- a/tests/integration/test_public_api.py +++ b/tests/integration/test_public_api.py @@ -10,6 +10,39 @@ import pretab +# The stable public transformer facade. Every name here must stay importable from +# ``pretab.transformers`` no matter how the internal modules are relocated during the +# 1.0.0 layout refactor. Pinning the exact set makes an accidental drop or rename fail +# loudly instead of silently shrinking ``__all__``. +FACADE_TRANSFORMERS = frozenset( + { + "BSplineTransformer", + "ContinuousOrdinalTransformer", + "CubicRegressionSplineTransformer", + "FourierFeatureTransformer", + "ISplineTransformer", + "LanguageEmbeddingTransformer", + "MSplineTransformer", + "MissingStateIndicator", + "NaturalCubicSplineTransformer", + "NoTransformer", + "NumericBinningTransformer", + "NystroemFeaturesTransformer", + "OneHotFromOrdinalTransformer", + "PLETransformer", + "PSplineTransformer", + "PeriodicEncodingTransformer", + "RBFExpansionTransformer", + "RandomFourierFeaturesTransformer", + "ReLUExpansionTransformer", + "SigmoidExpansionTransformer", + "TanhExpansionTransformer", + "TensorProductSplineTransformer", + "ThinPlateSplineTransformer", + "ToFloatTransformer", + } +) + def test_public_names_are_exported(): for name in ("Preprocessor", "PretabWarning", "configure_logging", "set_verbosity", "__version__"): @@ -33,6 +66,17 @@ def test_transformers_public_surface_is_resolvable(): assert hasattr(transformers, name) +def test_transformers_facade_is_frozen(): + transformers = importlib.import_module("pretab.transformers") + assert set(transformers.__all__) == FACADE_TRANSFORMERS + + +@pytest.mark.parametrize("name", sorted(FACADE_TRANSFORMERS)) +def test_facade_transformer_is_an_importable_class(name): + transformers = importlib.import_module("pretab.transformers") + assert isinstance(getattr(transformers, name), type) + + def test_legacy_pipeline_package_is_removed(): with pytest.raises(ModuleNotFoundError): importlib.import_module("pretab.pipeline") From 9f43e0a387a06ea4ba040756d08a61c9ff45fe10 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:36:18 +0200 Subject: [PATCH 02/14] refactor(expansion): move spline transformers to pretab.expansion.spline --- pretab/compose/registry.py | 24 +++++++++---------- pretab/expansion/__init__.py | 13 ++++++++++ .../splines => expansion/spline}/__init__.py | 2 +- .../splines => expansion/spline}/b_spline.py | 4 ++-- .../spline/base.py} | 0 .../spline}/cubic_regression.py | 0 .../splines => expansion/spline}/i_spline.py | 4 ++-- .../splines => expansion/spline}/m_spline.py | 4 ++-- .../splines => expansion/spline}/mixins.py | 2 +- .../spline}/multivariate/__init__.py | 0 .../spline}/multivariate/tensor_product.py | 2 +- .../spline}/multivariate/thin_plate.py | 0 .../spline}/natural_cubic.py | 0 .../splines => expansion/spline}/p_spline.py | 0 pretab/transformers/__init__.py | 20 ++++++++-------- tests/integration/test_adaptive_output_dim.py | 6 ++--- 16 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 pretab/expansion/__init__.py rename pretab/{transformers/splines => expansion/spline}/__init__.py (93%) rename pretab/{transformers/splines => expansion/spline}/b_spline.py (95%) rename pretab/{transformers/splines/base_spline.py => expansion/spline/base.py} (100%) rename pretab/{transformers/splines => expansion/spline}/cubic_regression.py (100%) rename pretab/{transformers/splines => expansion/spline}/i_spline.py (96%) rename pretab/{transformers/splines => expansion/spline}/m_spline.py (96%) rename pretab/{transformers/splines => expansion/spline}/mixins.py (99%) rename pretab/{transformers/splines => expansion/spline}/multivariate/__init__.py (100%) rename pretab/{transformers/splines => expansion/spline}/multivariate/tensor_product.py (98%) rename pretab/{transformers/splines => expansion/spline}/multivariate/thin_plate.py (100%) rename pretab/{transformers/splines => expansion/spline}/natural_cubic.py (100%) rename pretab/{transformers/splines => expansion/spline}/p_spline.py (100%) diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index 30fde8e..b06d665 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -27,6 +27,18 @@ StandardScaler, ) +from ..expansion.spline.b_spline import BSplineTransformer +from ..expansion.spline.cubic_regression import CubicRegressionSplineTransformer +from ..expansion.spline.i_spline import ISplineTransformer +from ..expansion.spline.m_spline import MSplineTransformer +from ..expansion.spline.multivariate.tensor_product import ( + TensorProductSplineTransformer, +) +from ..expansion.spline.multivariate.thin_plate import ( + ThinPlateSplineTransformer, +) +from ..expansion.spline.natural_cubic import NaturalCubicSplineTransformer +from ..expansion.spline.p_spline import PSplineTransformer from ..transformers.categorical.language_embedding import ( LanguageEmbeddingTransformer, ) @@ -44,18 +56,6 @@ from ..transformers.feature_maps.tanh import TanhExpansionTransformer from ..transformers.numerical.binning import NumericBinningTransformer from ..transformers.numerical.piecewise import PLETransformer -from ..transformers.splines.b_spline import BSplineTransformer -from ..transformers.splines.cubic_regression import CubicRegressionSplineTransformer -from ..transformers.splines.i_spline import ISplineTransformer -from ..transformers.splines.m_spline import MSplineTransformer -from ..transformers.splines.multivariate.tensor_product import ( - TensorProductSplineTransformer, -) -from ..transformers.splines.multivariate.thin_plate import ( - ThinPlateSplineTransformer, -) -from ..transformers.splines.natural_cubic import NaturalCubicSplineTransformer -from ..transformers.splines.p_spline import PSplineTransformer __all__ = [ "CATEGORICAL_ALIASES", diff --git a/pretab/expansion/__init__.py b/pretab/expansion/__init__.py new file mode 100644 index 0000000..22461c7 --- /dev/null +++ b/pretab/expansion/__init__.py @@ -0,0 +1,13 @@ +"""Basis-expansion representations. + +Expansions map each numeric feature into a richer set of columns so that a linear +model can capture nonlinear structure. PreTab groups them into two families: + +- :mod:`pretab.expansion.spline` for spline basis expansions such as B-spline, + P-spline, natural cubic, and the multivariate tensor-product and thin-plate bases. +- :mod:`pretab.expansion.functional` for explicit nonlinear basis functions such as + radial basis functions, ReLU, sigmoid, tanh, and Fourier features. + +Every class here is also re-exported from :mod:`pretab.transformers`, which stays the +stable, flat public import surface. +""" diff --git a/pretab/transformers/splines/__init__.py b/pretab/expansion/spline/__init__.py similarity index 93% rename from pretab/transformers/splines/__init__.py rename to pretab/expansion/spline/__init__.py index 8339755..72976f6 100644 --- a/pretab/transformers/splines/__init__.py +++ b/pretab/expansion/spline/__init__.py @@ -1,5 +1,5 @@ from .b_spline import BSplineTransformer -from .base_spline import BaseSplineTransformer +from .base import BaseSplineTransformer from .cubic_regression import CubicRegressionSplineTransformer from .i_spline import ISplineTransformer from .m_spline import MSplineTransformer diff --git a/pretab/transformers/splines/b_spline.py b/pretab/expansion/spline/b_spline.py similarity index 95% rename from pretab/transformers/splines/b_spline.py rename to pretab/expansion/spline/b_spline.py index c09d307..8c0e70f 100644 --- a/pretab/transformers/splines/b_spline.py +++ b/pretab/expansion/spline/b_spline.py @@ -11,7 +11,7 @@ from scipy.interpolate import BSpline from ...core.parameters import UNSET -from .base_spline import BaseSplineTransformer +from .base import BaseSplineTransformer class BSplineTransformer(BaseSplineTransformer): @@ -23,7 +23,7 @@ class BSplineTransformer(BaseSplineTransformer): automatic (``output_dim`` with ``placement_strategy``). Multi-column input is expanded column by column and the results are stacked horizontally. - See :class:`~pretab.transformers.splines.base_spline.BaseSplineTransformer` + See :class:`~pretab.expansion.spline.base.BaseSplineTransformer` for the full parameter description. ``include_bias`` defaults to False: a B-spline basis over a clamped knot vector is a partition of unity (every row sums to 1), so prepending a bias column makes it an exact linear combination diff --git a/pretab/transformers/splines/base_spline.py b/pretab/expansion/spline/base.py similarity index 100% rename from pretab/transformers/splines/base_spline.py rename to pretab/expansion/spline/base.py diff --git a/pretab/transformers/splines/cubic_regression.py b/pretab/expansion/spline/cubic_regression.py similarity index 100% rename from pretab/transformers/splines/cubic_regression.py rename to pretab/expansion/spline/cubic_regression.py diff --git a/pretab/transformers/splines/i_spline.py b/pretab/expansion/spline/i_spline.py similarity index 96% rename from pretab/transformers/splines/i_spline.py rename to pretab/expansion/spline/i_spline.py index 6c32265..cc1130e 100644 --- a/pretab/transformers/splines/i_spline.py +++ b/pretab/expansion/spline/i_spline.py @@ -12,7 +12,7 @@ from scipy.interpolate import BSpline from ...core.parameters import UNSET -from .base_spline import BaseSplineTransformer +from .base import BaseSplineTransformer class ISplineTransformer(BaseSplineTransformer): @@ -25,7 +25,7 @@ class ISplineTransformer(BaseSplineTransformer): (``knot_locations``) > target-aware (``placement_strategy``) > automatic (``output_dim`` with ``placement_strategy``). - See :class:`~pretab.transformers.splines.base_spline.BaseSplineTransformer` + See :class:`~pretab.expansion.spline.base.BaseSplineTransformer` for the full parameter description. ``include_bias`` defaults to False here. Because I-splines start at zero, a bias term may be useful for a non-zero intercept. diff --git a/pretab/transformers/splines/m_spline.py b/pretab/expansion/spline/m_spline.py similarity index 96% rename from pretab/transformers/splines/m_spline.py rename to pretab/expansion/spline/m_spline.py index 0198860..ed8725f 100644 --- a/pretab/transformers/splines/m_spline.py +++ b/pretab/expansion/spline/m_spline.py @@ -12,7 +12,7 @@ from scipy.interpolate import BSpline from ...core.parameters import UNSET -from .base_spline import BaseSplineTransformer +from .base import BaseSplineTransformer class MSplineTransformer(BaseSplineTransformer): @@ -25,7 +25,7 @@ class MSplineTransformer(BaseSplineTransformer): (``knot_locations``) > target-aware (``placement_strategy``) > automatic (``output_dim`` with ``placement_strategy``). - See :class:`~pretab.transformers.splines.base_spline.BaseSplineTransformer` + See :class:`~pretab.expansion.spline.base.BaseSplineTransformer` for the full parameter description. ``include_bias`` defaults to False here. Examples diff --git a/pretab/transformers/splines/mixins.py b/pretab/expansion/spline/mixins.py similarity index 99% rename from pretab/transformers/splines/mixins.py rename to pretab/expansion/spline/mixins.py index 17fe52e..b325181 100644 --- a/pretab/transformers/splines/mixins.py +++ b/pretab/expansion/spline/mixins.py @@ -175,7 +175,7 @@ def _place_bspline_knots( Places ``output_dim - degree - 1`` interior knots (via :meth:`_place_interior_knots`) and brackets them with ``degree + 1`` repeated boundary knots on each side -- the B/M/I convention used by - :class:`~pretab.transformers.splines.base_spline.BaseSplineTransformer`. + :class:`~pretab.expansion.spline.base.BaseSplineTransformer`. The resulting marginal B-spline basis then has exactly ``output_dim`` (non-bias) columns: ``len(knots) - degree - 1 == output_dim``. On the adaptive selector path ``min_interior`` / ``max_interior`` clamp the diff --git a/pretab/transformers/splines/multivariate/__init__.py b/pretab/expansion/spline/multivariate/__init__.py similarity index 100% rename from pretab/transformers/splines/multivariate/__init__.py rename to pretab/expansion/spline/multivariate/__init__.py diff --git a/pretab/transformers/splines/multivariate/tensor_product.py b/pretab/expansion/spline/multivariate/tensor_product.py similarity index 98% rename from pretab/transformers/splines/multivariate/tensor_product.py rename to pretab/expansion/spline/multivariate/tensor_product.py index c7e3ae5..7aed557 100644 --- a/pretab/transformers/splines/multivariate/tensor_product.py +++ b/pretab/expansion/spline/multivariate/tensor_product.py @@ -53,7 +53,7 @@ class TensorProductSplineTransformer(SplineBasisMixin, TransformerMixin, BaseEst .. note:: The tensor-product spline is a penalized (difference-penalty) spline - per marginal, exactly like :class:`~pretab.transformers.splines.p_spline.PSplineTransformer`, + per marginal, exactly like :class:`~pretab.expansion.spline.p_spline.PSplineTransformer`, so it assumes **equally-spaced** knots and is *unsupervised-only*: target-aware placement does not apply and only ``"uniform"`` / ``"quantile"`` spacing is accepted. diff --git a/pretab/transformers/splines/multivariate/thin_plate.py b/pretab/expansion/spline/multivariate/thin_plate.py similarity index 100% rename from pretab/transformers/splines/multivariate/thin_plate.py rename to pretab/expansion/spline/multivariate/thin_plate.py diff --git a/pretab/transformers/splines/natural_cubic.py b/pretab/expansion/spline/natural_cubic.py similarity index 100% rename from pretab/transformers/splines/natural_cubic.py rename to pretab/expansion/spline/natural_cubic.py diff --git a/pretab/transformers/splines/p_spline.py b/pretab/expansion/spline/p_spline.py similarity index 100% rename from pretab/transformers/splines/p_spline.py rename to pretab/expansion/spline/p_spline.py diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index c68964c..17cc6c0 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -1,3 +1,13 @@ +from ..expansion.spline import ( + BSplineTransformer, + CubicRegressionSplineTransformer, + ISplineTransformer, + MSplineTransformer, + NaturalCubicSplineTransformer, + PSplineTransformer, + TensorProductSplineTransformer, + ThinPlateSplineTransformer, +) from .categorical import ( ContinuousOrdinalTransformer, LanguageEmbeddingTransformer, @@ -18,16 +28,6 @@ PeriodicEncodingTransformer, PLETransformer, ) -from .splines import ( - BSplineTransformer, - CubicRegressionSplineTransformer, - ISplineTransformer, - MSplineTransformer, - NaturalCubicSplineTransformer, - PSplineTransformer, - TensorProductSplineTransformer, - ThinPlateSplineTransformer, -) __all__ = [ "BSplineTransformer", diff --git a/tests/integration/test_adaptive_output_dim.py b/tests/integration/test_adaptive_output_dim.py index e8eb57e..6d9529b 100644 --- a/tests/integration/test_adaptive_output_dim.py +++ b/tests/integration/test_adaptive_output_dim.py @@ -21,9 +21,9 @@ from pretab.exceptions import InvalidParamError from pretab.preprocessor import Preprocessor -from pretab.transformers.splines.b_spline import BSplineTransformer -from pretab.transformers.splines.i_spline import ISplineTransformer -from pretab.transformers.splines.m_spline import MSplineTransformer +from pretab.expansion.spline.b_spline import BSplineTransformer +from pretab.expansion.spline.i_spline import ISplineTransformer +from pretab.expansion.spline.m_spline import MSplineTransformer OUTPUT_DIM = 6 From aae47fbabaf3ea5edbbf940bd35dbd48477548cc Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:42:59 +0200 Subject: [PATCH 03/14] refactor(expansion): move functional expansions to pretab.expansion.functional --- pretab/compose/registry.py | 10 +++---- pretab/expansion/functional/__init__.py | 26 +++++++++++++++++++ .../functional}/base.py | 0 .../functional}/fourier.py | 0 .../functional}/rbf.py | 0 .../functional}/relu.py | 0 .../functional}/sigmoid.py | 0 .../functional}/tanh.py | 0 pretab/transformers/__init__.py | 12 +++++---- pretab/transformers/feature_maps/__init__.py | 10 ------- 10 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 pretab/expansion/functional/__init__.py rename pretab/{transformers/feature_maps => expansion/functional}/base.py (100%) rename pretab/{transformers/feature_maps => expansion/functional}/fourier.py (100%) rename pretab/{transformers/feature_maps => expansion/functional}/rbf.py (100%) rename pretab/{transformers/feature_maps => expansion/functional}/relu.py (100%) rename pretab/{transformers/feature_maps => expansion/functional}/sigmoid.py (100%) rename pretab/{transformers/feature_maps => expansion/functional}/tanh.py (100%) diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index b06d665..85767d9 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -27,6 +27,11 @@ StandardScaler, ) +from ..expansion.functional.fourier import FourierFeatureTransformer +from ..expansion.functional.rbf import RBFExpansionTransformer +from ..expansion.functional.relu import ReLUExpansionTransformer +from ..expansion.functional.sigmoid import SigmoidExpansionTransformer +from ..expansion.functional.tanh import TanhExpansionTransformer from ..expansion.spline.b_spline import BSplineTransformer from ..expansion.spline.cubic_regression import CubicRegressionSplineTransformer from ..expansion.spline.i_spline import ISplineTransformer @@ -45,15 +50,10 @@ from ..transformers.categorical.legacy import OneHotFromOrdinalTransformer from ..transformers.categorical.ordinal import ContinuousOrdinalTransformer from ..transformers.encoders.floats import NoTransformer -from ..transformers.feature_maps.fourier import FourierFeatureTransformer from ..transformers.feature_maps.kernel_approx import ( NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, ) -from ..transformers.feature_maps.rbf import RBFExpansionTransformer -from ..transformers.feature_maps.relu import ReLUExpansionTransformer -from ..transformers.feature_maps.sigmoid import SigmoidExpansionTransformer -from ..transformers.feature_maps.tanh import TanhExpansionTransformer from ..transformers.numerical.binning import NumericBinningTransformer from ..transformers.numerical.piecewise import PLETransformer diff --git a/pretab/expansion/functional/__init__.py b/pretab/expansion/functional/__init__.py new file mode 100644 index 0000000..5af85f6 --- /dev/null +++ b/pretab/expansion/functional/__init__.py @@ -0,0 +1,26 @@ +"""Explicit nonlinear basis-function expansions. + +Each transformer maps a numeric feature through a fixed nonlinear function (radial +basis, ReLU, sigmoid, tanh, or a sine/cosine pair) evaluated at a set of centers or +frequencies, producing one output column per basis unit. This is the "functional" +half of :mod:`pretab.expansion`, distinct from spline bases which live in +:mod:`pretab.expansion.spline`. + +Every class here is also re-exported from :mod:`pretab.transformers`. +""" + +from .base import BaseCenterExpansion +from .fourier import FourierFeatureTransformer +from .rbf import RBFExpansionTransformer +from .relu import ReLUExpansionTransformer +from .sigmoid import SigmoidExpansionTransformer +from .tanh import TanhExpansionTransformer + +__all__ = [ + "BaseCenterExpansion", + "FourierFeatureTransformer", + "RBFExpansionTransformer", + "ReLUExpansionTransformer", + "SigmoidExpansionTransformer", + "TanhExpansionTransformer", +] diff --git a/pretab/transformers/feature_maps/base.py b/pretab/expansion/functional/base.py similarity index 100% rename from pretab/transformers/feature_maps/base.py rename to pretab/expansion/functional/base.py diff --git a/pretab/transformers/feature_maps/fourier.py b/pretab/expansion/functional/fourier.py similarity index 100% rename from pretab/transformers/feature_maps/fourier.py rename to pretab/expansion/functional/fourier.py diff --git a/pretab/transformers/feature_maps/rbf.py b/pretab/expansion/functional/rbf.py similarity index 100% rename from pretab/transformers/feature_maps/rbf.py rename to pretab/expansion/functional/rbf.py diff --git a/pretab/transformers/feature_maps/relu.py b/pretab/expansion/functional/relu.py similarity index 100% rename from pretab/transformers/feature_maps/relu.py rename to pretab/expansion/functional/relu.py diff --git a/pretab/transformers/feature_maps/sigmoid.py b/pretab/expansion/functional/sigmoid.py similarity index 100% rename from pretab/transformers/feature_maps/sigmoid.py rename to pretab/expansion/functional/sigmoid.py diff --git a/pretab/transformers/feature_maps/tanh.py b/pretab/expansion/functional/tanh.py similarity index 100% rename from pretab/transformers/feature_maps/tanh.py rename to pretab/expansion/functional/tanh.py diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index 17cc6c0..724f43b 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -1,3 +1,10 @@ +from ..expansion.functional import ( + FourierFeatureTransformer, + RBFExpansionTransformer, + ReLUExpansionTransformer, + SigmoidExpansionTransformer, + TanhExpansionTransformer, +) from ..expansion.spline import ( BSplineTransformer, CubicRegressionSplineTransformer, @@ -15,13 +22,8 @@ ) from .encoders import MissingStateIndicator, NoTransformer, ToFloatTransformer from .feature_maps import ( - FourierFeatureTransformer, NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, - RBFExpansionTransformer, - ReLUExpansionTransformer, - SigmoidExpansionTransformer, - TanhExpansionTransformer, ) from .numerical import ( NumericBinningTransformer, diff --git a/pretab/transformers/feature_maps/__init__.py b/pretab/transformers/feature_maps/__init__.py index 41105fd..ef4e36c 100644 --- a/pretab/transformers/feature_maps/__init__.py +++ b/pretab/transformers/feature_maps/__init__.py @@ -1,16 +1,6 @@ -from .fourier import FourierFeatureTransformer from .kernel_approx import NystroemFeaturesTransformer, RandomFourierFeaturesTransformer -from .rbf import RBFExpansionTransformer -from .relu import ReLUExpansionTransformer -from .sigmoid import SigmoidExpansionTransformer -from .tanh import TanhExpansionTransformer __all__ = [ - "FourierFeatureTransformer", "NystroemFeaturesTransformer", - "RBFExpansionTransformer", "RandomFourierFeaturesTransformer", - "ReLUExpansionTransformer", - "SigmoidExpansionTransformer", - "TanhExpansionTransformer", ] From 571d444f52e1b50823df4e197d405b91d98dbc1d Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:45:44 +0200 Subject: [PATCH 04/14] refactor(encoding): move numerical encoders to pretab.encoding.numerical --- pretab/compose/registry.py | 4 ++-- pretab/encoding/__init__.py | 11 +++++++++++ .../{transformers => encoding}/numerical/__init__.py | 6 +++--- .../{transformers => encoding}/numerical/binning.py | 0 .../{transformers => encoding}/numerical/periodic.py | 0 .../piecewise.py => encoding/numerical/ple.py} | 0 pretab/transformers/__init__.py | 10 +++++----- 7 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 pretab/encoding/__init__.py rename pretab/{transformers => encoding}/numerical/__init__.py (55%) rename pretab/{transformers => encoding}/numerical/binning.py (100%) rename pretab/{transformers => encoding}/numerical/periodic.py (100%) rename pretab/{transformers/numerical/piecewise.py => encoding/numerical/ple.py} (100%) diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index 85767d9..e7b7cea 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -27,6 +27,8 @@ StandardScaler, ) +from ..encoding.numerical.binning import NumericBinningTransformer +from ..encoding.numerical.ple import PLETransformer from ..expansion.functional.fourier import FourierFeatureTransformer from ..expansion.functional.rbf import RBFExpansionTransformer from ..expansion.functional.relu import ReLUExpansionTransformer @@ -54,8 +56,6 @@ NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, ) -from ..transformers.numerical.binning import NumericBinningTransformer -from ..transformers.numerical.piecewise import PLETransformer __all__ = [ "CATEGORICAL_ALIASES", diff --git a/pretab/encoding/__init__.py b/pretab/encoding/__init__.py new file mode 100644 index 0000000..ea83302 --- /dev/null +++ b/pretab/encoding/__init__.py @@ -0,0 +1,11 @@ +"""Feature encoding representations. + +Encoding recodes a raw column into a form a model can use directly, as opposed to +:mod:`pretab.expansion`, which expands a column into a richer basis. PreTab splits +encoding by input kind: + +- :mod:`pretab.encoding.numerical` recodes numeric values (binning, PLE, periodic). +- :mod:`pretab.encoding.categorical` maps categories to codes or indicators. + +Every class here is also re-exported from :mod:`pretab.transformers`. +""" diff --git a/pretab/transformers/numerical/__init__.py b/pretab/encoding/numerical/__init__.py similarity index 55% rename from pretab/transformers/numerical/__init__.py rename to pretab/encoding/numerical/__init__.py index 2406d5c..630e3da 100644 --- a/pretab/transformers/numerical/__init__.py +++ b/pretab/encoding/numerical/__init__.py @@ -1,10 +1,10 @@ -"""Numerical single-column transformers: binning, piecewise-linear encoding (PLE) -and periodic encoding. +"""Numerical encoding: recode numeric values into bins, target-aware piecewise +linear encodings, or cyclic (sin/cos) representations. """ from .binning import NumericBinningTransformer from .periodic import PeriodicEncodingTransformer -from .piecewise import PLETransformer +from .ple import PLETransformer __all__ = [ "NumericBinningTransformer", diff --git a/pretab/transformers/numerical/binning.py b/pretab/encoding/numerical/binning.py similarity index 100% rename from pretab/transformers/numerical/binning.py rename to pretab/encoding/numerical/binning.py diff --git a/pretab/transformers/numerical/periodic.py b/pretab/encoding/numerical/periodic.py similarity index 100% rename from pretab/transformers/numerical/periodic.py rename to pretab/encoding/numerical/periodic.py diff --git a/pretab/transformers/numerical/piecewise.py b/pretab/encoding/numerical/ple.py similarity index 100% rename from pretab/transformers/numerical/piecewise.py rename to pretab/encoding/numerical/ple.py diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index 724f43b..d55ff47 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -1,3 +1,8 @@ +from ..encoding.numerical import ( + NumericBinningTransformer, + PeriodicEncodingTransformer, + PLETransformer, +) from ..expansion.functional import ( FourierFeatureTransformer, RBFExpansionTransformer, @@ -25,11 +30,6 @@ NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, ) -from .numerical import ( - NumericBinningTransformer, - PeriodicEncodingTransformer, - PLETransformer, -) __all__ = [ "BSplineTransformer", From 53c9aaa0f8d56dfb6c78954c92c3a3b90d8e84ed Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:49:15 +0200 Subject: [PATCH 05/14] refactor(encoding): move categorical encoders to pretab.encoding.categorical --- pretab/compose/registry.py | 4 ++-- pretab/encoding/categorical/__init__.py | 11 +++++++++++ .../legacy.py => encoding/categorical/one_hot.py} | 0 .../{transformers => encoding}/categorical/ordinal.py | 0 pretab/transformers/__init__.py | 10 +++++----- pretab/transformers/categorical/__init__.py | 9 +++------ 6 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 pretab/encoding/categorical/__init__.py rename pretab/{transformers/categorical/legacy.py => encoding/categorical/one_hot.py} (100%) rename pretab/{transformers => encoding}/categorical/ordinal.py (100%) diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index e7b7cea..b713599 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -27,6 +27,8 @@ StandardScaler, ) +from ..encoding.categorical.one_hot import OneHotFromOrdinalTransformer +from ..encoding.categorical.ordinal import ContinuousOrdinalTransformer from ..encoding.numerical.binning import NumericBinningTransformer from ..encoding.numerical.ple import PLETransformer from ..expansion.functional.fourier import FourierFeatureTransformer @@ -49,8 +51,6 @@ from ..transformers.categorical.language_embedding import ( LanguageEmbeddingTransformer, ) -from ..transformers.categorical.legacy import OneHotFromOrdinalTransformer -from ..transformers.categorical.ordinal import ContinuousOrdinalTransformer from ..transformers.encoders.floats import NoTransformer from ..transformers.feature_maps.kernel_approx import ( NystroemFeaturesTransformer, diff --git a/pretab/encoding/categorical/__init__.py b/pretab/encoding/categorical/__init__.py new file mode 100644 index 0000000..22c994b --- /dev/null +++ b/pretab/encoding/categorical/__init__.py @@ -0,0 +1,11 @@ +"""Categorical encoding: map categories to ordinal codes or one-hot indicators +from an already ordinal-encoded input. +""" + +from .one_hot import OneHotFromOrdinalTransformer +from .ordinal import ContinuousOrdinalTransformer + +__all__ = [ + "ContinuousOrdinalTransformer", + "OneHotFromOrdinalTransformer", +] diff --git a/pretab/transformers/categorical/legacy.py b/pretab/encoding/categorical/one_hot.py similarity index 100% rename from pretab/transformers/categorical/legacy.py rename to pretab/encoding/categorical/one_hot.py diff --git a/pretab/transformers/categorical/ordinal.py b/pretab/encoding/categorical/ordinal.py similarity index 100% rename from pretab/transformers/categorical/ordinal.py rename to pretab/encoding/categorical/ordinal.py diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index d55ff47..7665a59 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -1,3 +1,7 @@ +from ..encoding.categorical import ( + ContinuousOrdinalTransformer, + OneHotFromOrdinalTransformer, +) from ..encoding.numerical import ( NumericBinningTransformer, PeriodicEncodingTransformer, @@ -20,11 +24,7 @@ TensorProductSplineTransformer, ThinPlateSplineTransformer, ) -from .categorical import ( - ContinuousOrdinalTransformer, - LanguageEmbeddingTransformer, - OneHotFromOrdinalTransformer, -) +from .categorical import LanguageEmbeddingTransformer from .encoders import MissingStateIndicator, NoTransformer, ToFloatTransformer from .feature_maps import ( NystroemFeaturesTransformer, diff --git a/pretab/transformers/categorical/__init__.py b/pretab/transformers/categorical/__init__.py index b4ceec4..cef7030 100644 --- a/pretab/transformers/categorical/__init__.py +++ b/pretab/transformers/categorical/__init__.py @@ -1,13 +1,10 @@ -"""Categorical transformers: ordinal encoding, language embeddings and the -time-boxed legacy one-hot-from-ordinal encoder. +"""Categorical transformers: language embeddings. Ordinal encoding and the +time-boxed legacy one-hot-from-ordinal encoder live in +:mod:`pretab.encoding.categorical`. """ from .language_embedding import LanguageEmbeddingTransformer -from .legacy import OneHotFromOrdinalTransformer -from .ordinal import ContinuousOrdinalTransformer __all__ = [ - "ContinuousOrdinalTransformer", "LanguageEmbeddingTransformer", - "OneHotFromOrdinalTransformer", ] From 2c6265f29749abeebd7560a1a1efaf499247f916 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:54:05 +0200 Subject: [PATCH 06/14] refactor(kernel-approximation): split kernel approximations into pretab.kernel_approximation --- pretab/compose/registry.py | 6 +- pretab/kernel_approximation/__init__.py | 17 +++++ .../nystroem.py} | 73 +------------------ pretab/kernel_approximation/random_fourier.py | 73 +++++++++++++++++++ pretab/transformers/__init__.py | 6 +- pretab/transformers/feature_maps/__init__.py | 6 -- 6 files changed, 98 insertions(+), 83 deletions(-) create mode 100644 pretab/kernel_approximation/__init__.py rename pretab/{transformers/feature_maps/kernel_approx.py => kernel_approximation/nystroem.py} (57%) create mode 100644 pretab/kernel_approximation/random_fourier.py delete mode 100644 pretab/transformers/feature_maps/__init__.py diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index b713599..f47e40c 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -48,14 +48,12 @@ ) from ..expansion.spline.natural_cubic import NaturalCubicSplineTransformer from ..expansion.spline.p_spline import PSplineTransformer +from ..kernel_approximation.nystroem import NystroemFeaturesTransformer +from ..kernel_approximation.random_fourier import RandomFourierFeaturesTransformer from ..transformers.categorical.language_embedding import ( LanguageEmbeddingTransformer, ) from ..transformers.encoders.floats import NoTransformer -from ..transformers.feature_maps.kernel_approx import ( - NystroemFeaturesTransformer, - RandomFourierFeaturesTransformer, -) __all__ = [ "CATEGORICAL_ALIASES", diff --git a/pretab/kernel_approximation/__init__.py b/pretab/kernel_approximation/__init__.py new file mode 100644 index 0000000..0636e18 --- /dev/null +++ b/pretab/kernel_approximation/__init__.py @@ -0,0 +1,17 @@ +"""Kernel approximation representations. + +Each transformer builds an explicit, low-dimensional feature map whose inner +products approximate an implicit kernel, so a linear model downstream can behave +like a kernel method without materializing the full kernel matrix. This mirrors +:mod:`sklearn.kernel_approximation`, which PreTab wraps directly. + +Every class here is also re-exported from :mod:`pretab.transformers`. +""" + +from .nystroem import NystroemFeaturesTransformer +from .random_fourier import RandomFourierFeaturesTransformer + +__all__ = [ + "NystroemFeaturesTransformer", + "RandomFourierFeaturesTransformer", +] diff --git a/pretab/transformers/feature_maps/kernel_approx.py b/pretab/kernel_approximation/nystroem.py similarity index 57% rename from pretab/transformers/feature_maps/kernel_approx.py rename to pretab/kernel_approximation/nystroem.py index fd693c7..49fa24a 100644 --- a/pretab/transformers/feature_maps/kernel_approx.py +++ b/pretab/kernel_approximation/nystroem.py @@ -1,80 +1,13 @@ import numpy as np -from sklearn.kernel_approximation import Nystroem, RBFSampler +from sklearn.kernel_approximation import Nystroem from sklearn.utils.validation import check_is_fitted -from ...core.base import BasePreTabTransformer -from ...exceptions import InvalidParamError +from ..core.base import BasePreTabTransformer +from ..exceptions import InvalidParamError _NYSTROEM_KERNELS = ("rbf", "poly", "polynomial", "sigmoid", "laplacian", "cosine", "linear", "chi2", "additive_chi2") -class RandomFourierFeaturesTransformer(BasePreTabTransformer): - r"""Random Fourier features approximating an RBF kernel map (multivariate). - - Thin wrapper around :class:`sklearn.kernel_approximation.RBFSampler` that - jointly maps all input features into a randomized low-dimensional feature - space whose inner products approximate a Gaussian (RBF) kernel. This is a - **standalone, multivariate** transformer: it models the feature block as a - whole and is therefore not selectable per column through - :class:`~pretab.preprocessor.Preprocessor`. - - Parameters - ---------- - n_components : int, default=100 - Number of Monte-Carlo random features (output columns). - gamma : float, default=1.0 - Bandwidth of the approximated RBF kernel ``exp(-gamma * ||x - y||^2)``. - random_state : int, RandomState instance or None, default=None - Seeds the random projection for reproducibility. - - Attributes - ---------- - sampler_ : RBFSampler - The fitted underlying scikit-learn sampler. - n_features_in_ : int - Number of input features seen during ``fit``. - total_output_dim_ : int - Total number of output columns (equals ``n_components``). - - Examples - -------- - >>> import numpy as np - >>> from pretab.transformers import RandomFourierFeaturesTransformer - >>> X = np.random.default_rng(0).uniform(size=(40, 3)) - >>> RandomFourierFeaturesTransformer(n_components=20, random_state=0).fit_transform(X).shape - (40, 20) - """ - - _allow_nan = False - _feature_suffix_value = "rff" - _representation_family = "random_fourier" - _representation_scope = "multivariate" - - def __init__(self, n_components: int = 100, gamma: float = 1.0, random_state: int | None = None): - self.n_components = n_components - self.gamma = gamma - self.random_state = random_state - - def fit(self, X, y=None): - X = self._validate(X, reset=True) - if not isinstance(self.n_components, (int, np.integer)) or self.n_components < 1: - raise InvalidParamError(f"n_components must be a positive integer; got {self.n_components!r}.") - self.sampler_ = RBFSampler( - n_components=self.n_components, - gamma=self.gamma, - random_state=self.random_state, - ).fit(X) - return self - - def transform(self, X): - check_is_fitted(self, "sampler_") - X = self._validate(X, reset=False) - return np.asarray(self.sampler_.transform(X)) - - def _output_sizes(self) -> list[int]: - return [self.n_components] - - class NystroemFeaturesTransformer(BasePreTabTransformer): r"""Nystroem kernel-map approximation over the full feature block (multivariate). diff --git a/pretab/kernel_approximation/random_fourier.py b/pretab/kernel_approximation/random_fourier.py new file mode 100644 index 0000000..ccb0b8e --- /dev/null +++ b/pretab/kernel_approximation/random_fourier.py @@ -0,0 +1,73 @@ +import numpy as np +from sklearn.kernel_approximation import RBFSampler +from sklearn.utils.validation import check_is_fitted + +from ..core.base import BasePreTabTransformer +from ..exceptions import InvalidParamError + + +class RandomFourierFeaturesTransformer(BasePreTabTransformer): + r"""Random Fourier features approximating an RBF kernel map (multivariate). + + Thin wrapper around :class:`sklearn.kernel_approximation.RBFSampler` that + jointly maps all input features into a randomized low-dimensional feature + space whose inner products approximate a Gaussian (RBF) kernel. This is a + **standalone, multivariate** transformer: it models the feature block as a + whole and is therefore not selectable per column through + :class:`~pretab.preprocessor.Preprocessor`. + + Parameters + ---------- + n_components : int, default=100 + Number of Monte-Carlo random features (output columns). + gamma : float, default=1.0 + Bandwidth of the approximated RBF kernel ``exp(-gamma * ||x - y||^2)``. + random_state : int, RandomState instance or None, default=None + Seeds the random projection for reproducibility. + + Attributes + ---------- + sampler_ : RBFSampler + The fitted underlying scikit-learn sampler. + n_features_in_ : int + Number of input features seen during ``fit``. + total_output_dim_ : int + Total number of output columns (equals ``n_components``). + + Examples + -------- + >>> import numpy as np + >>> from pretab.transformers import RandomFourierFeaturesTransformer + >>> X = np.random.default_rng(0).uniform(size=(40, 3)) + >>> RandomFourierFeaturesTransformer(n_components=20, random_state=0).fit_transform(X).shape + (40, 20) + """ + + _allow_nan = False + _feature_suffix_value = "rff" + _representation_family = "random_fourier" + _representation_scope = "multivariate" + + def __init__(self, n_components: int = 100, gamma: float = 1.0, random_state: int | None = None): + self.n_components = n_components + self.gamma = gamma + self.random_state = random_state + + def fit(self, X, y=None): + X = self._validate(X, reset=True) + if not isinstance(self.n_components, (int, np.integer)) or self.n_components < 1: + raise InvalidParamError(f"n_components must be a positive integer; got {self.n_components!r}.") + self.sampler_ = RBFSampler( + n_components=self.n_components, + gamma=self.gamma, + random_state=self.random_state, + ).fit(X) + return self + + def transform(self, X): + check_is_fitted(self, "sampler_") + X = self._validate(X, reset=False) + return np.asarray(self.sampler_.transform(X)) + + def _output_sizes(self) -> list[int]: + return [self.n_components] diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index 7665a59..9b3860b 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -24,12 +24,12 @@ TensorProductSplineTransformer, ThinPlateSplineTransformer, ) -from .categorical import LanguageEmbeddingTransformer -from .encoders import MissingStateIndicator, NoTransformer, ToFloatTransformer -from .feature_maps import ( +from ..kernel_approximation import ( NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, ) +from .categorical import LanguageEmbeddingTransformer +from .encoders import MissingStateIndicator, NoTransformer, ToFloatTransformer __all__ = [ "BSplineTransformer", diff --git a/pretab/transformers/feature_maps/__init__.py b/pretab/transformers/feature_maps/__init__.py deleted file mode 100644 index ef4e36c..0000000 --- a/pretab/transformers/feature_maps/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .kernel_approx import NystroemFeaturesTransformer, RandomFourierFeaturesTransformer - -__all__ = [ - "NystroemFeaturesTransformer", - "RandomFourierFeaturesTransformer", -] From d069f9cab765cb352b7d03c87064b00dd970a3ad Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 08:56:30 +0200 Subject: [PATCH 07/14] refactor(embedding): move language embedding to pretab.embedding --- pretab/compose/registry.py | 4 +--- pretab/embedding/__init__.py | 14 ++++++++++++++ .../language.py} | 2 +- pretab/transformers/__init__.py | 2 +- pretab/transformers/categorical/__init__.py | 10 ---------- 5 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 pretab/embedding/__init__.py rename pretab/{transformers/categorical/language_embedding.py => embedding/language.py} (98%) delete mode 100644 pretab/transformers/categorical/__init__.py diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index f47e40c..83ca9a0 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -27,6 +27,7 @@ StandardScaler, ) +from ..embedding.language import LanguageEmbeddingTransformer from ..encoding.categorical.one_hot import OneHotFromOrdinalTransformer from ..encoding.categorical.ordinal import ContinuousOrdinalTransformer from ..encoding.numerical.binning import NumericBinningTransformer @@ -50,9 +51,6 @@ from ..expansion.spline.p_spline import PSplineTransformer from ..kernel_approximation.nystroem import NystroemFeaturesTransformer from ..kernel_approximation.random_fourier import RandomFourierFeaturesTransformer -from ..transformers.categorical.language_embedding import ( - LanguageEmbeddingTransformer, -) from ..transformers.encoders.floats import NoTransformer __all__ = [ diff --git a/pretab/embedding/__init__.py b/pretab/embedding/__init__.py new file mode 100644 index 0000000..9eed69f --- /dev/null +++ b/pretab/embedding/__init__.py @@ -0,0 +1,14 @@ +"""Embedding representations. + +Maps a categorical or text column to a dense vector produced by a pretrained +model, as opposed to :mod:`pretab.encoding`, which recodes categories into small +discrete representations (codes or indicators). + +Every class here is also re-exported from :mod:`pretab.transformers`. +""" + +from .language import LanguageEmbeddingTransformer + +__all__ = [ + "LanguageEmbeddingTransformer", +] diff --git a/pretab/transformers/categorical/language_embedding.py b/pretab/embedding/language.py similarity index 98% rename from pretab/transformers/categorical/language_embedding.py rename to pretab/embedding/language.py index f164816..847ce28 100644 --- a/pretab/transformers/categorical/language_embedding.py +++ b/pretab/embedding/language.py @@ -2,7 +2,7 @@ from sklearn.base import BaseEstimator, TransformerMixin from sklearn.utils.validation import check_is_fitted -from ...exceptions import OptionalDependencyError, PretabConfigError, PretabDataError +from ..exceptions import OptionalDependencyError, PretabConfigError, PretabDataError class LanguageEmbeddingTransformer(TransformerMixin, BaseEstimator): diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index 9b3860b..8d5fe8e 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -1,3 +1,4 @@ +from ..embedding import LanguageEmbeddingTransformer from ..encoding.categorical import ( ContinuousOrdinalTransformer, OneHotFromOrdinalTransformer, @@ -28,7 +29,6 @@ NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, ) -from .categorical import LanguageEmbeddingTransformer from .encoders import MissingStateIndicator, NoTransformer, ToFloatTransformer __all__ = [ diff --git a/pretab/transformers/categorical/__init__.py b/pretab/transformers/categorical/__init__.py deleted file mode 100644 index cef7030..0000000 --- a/pretab/transformers/categorical/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Categorical transformers: language embeddings. Ordinal encoding and the -time-boxed legacy one-hot-from-ordinal encoder live in -:mod:`pretab.encoding.categorical`. -""" - -from .language_embedding import LanguageEmbeddingTransformer - -__all__ = [ - "LanguageEmbeddingTransformer", -] From 4188a647349c54d5c4eccb1ae24385f5f3d419d4 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 09:03:21 +0200 Subject: [PATCH 08/14] refactor(preprocessing): move floats and missing encoders to pretab.preprocessing --- pretab/compose/factory.py | 4 ++-- pretab/compose/registry.py | 2 +- pretab/preprocessing/__init__.py | 19 +++++++++++++++++++ .../encoders => preprocessing}/floats.py | 0 .../encoders => preprocessing}/missing.py | 0 pretab/transformers/__init__.py | 2 +- pretab/transformers/encoders/__init__.py | 14 -------------- 7 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 pretab/preprocessing/__init__.py rename pretab/{transformers/encoders => preprocessing}/floats.py (100%) rename pretab/{transformers/encoders => preprocessing}/missing.py (100%) delete mode 100644 pretab/transformers/encoders/__init__.py diff --git a/pretab/compose/factory.py b/pretab/compose/factory.py index 347605d..f272a6d 100644 --- a/pretab/compose/factory.py +++ b/pretab/compose/factory.py @@ -16,8 +16,8 @@ from sklearn.preprocessing import MinMaxScaler, StandardScaler from ..exceptions import ConfigWarning, IncompatibleParamsError, invalid_param_error -from ..transformers.encoders.floats import ToFloatTransformer -from ..transformers.encoders.missing import MissingStateIndicator +from ..preprocessing.floats import ToFloatTransformer +from ..preprocessing.missing import MissingStateIndicator from .config import PreprocessorConfig from .registry import ( CATEGORICAL_ALIASES, diff --git a/pretab/compose/registry.py b/pretab/compose/registry.py index 83ca9a0..22d168b 100644 --- a/pretab/compose/registry.py +++ b/pretab/compose/registry.py @@ -51,7 +51,7 @@ from ..expansion.spline.p_spline import PSplineTransformer from ..kernel_approximation.nystroem import NystroemFeaturesTransformer from ..kernel_approximation.random_fourier import RandomFourierFeaturesTransformer -from ..transformers.encoders.floats import NoTransformer +from ..preprocessing.floats import NoTransformer __all__ = [ "CATEGORICAL_ALIASES", diff --git a/pretab/preprocessing/__init__.py b/pretab/preprocessing/__init__.py new file mode 100644 index 0000000..1473271 --- /dev/null +++ b/pretab/preprocessing/__init__.py @@ -0,0 +1,19 @@ +"""Supporting data-preparation utilities. + +These transformers don't expand or recode a feature; they prepare it for the rest +of the pipeline, converting types or flagging missingness before it reaches the +transformer that actually does the work. Distinct from +:mod:`pretab.preprocessor`, which holds the top-level :class:`~pretab.preprocessor.Preprocessor` +facade. + +Every class here is also re-exported from :mod:`pretab.transformers`. +""" + +from .floats import NoTransformer, ToFloatTransformer +from .missing import MissingStateIndicator + +__all__ = [ + "MissingStateIndicator", + "NoTransformer", + "ToFloatTransformer", +] diff --git a/pretab/transformers/encoders/floats.py b/pretab/preprocessing/floats.py similarity index 100% rename from pretab/transformers/encoders/floats.py rename to pretab/preprocessing/floats.py diff --git a/pretab/transformers/encoders/missing.py b/pretab/preprocessing/missing.py similarity index 100% rename from pretab/transformers/encoders/missing.py rename to pretab/preprocessing/missing.py diff --git a/pretab/transformers/__init__.py b/pretab/transformers/__init__.py index 8d5fe8e..6a1d593 100644 --- a/pretab/transformers/__init__.py +++ b/pretab/transformers/__init__.py @@ -29,7 +29,7 @@ NystroemFeaturesTransformer, RandomFourierFeaturesTransformer, ) -from .encoders import MissingStateIndicator, NoTransformer, ToFloatTransformer +from ..preprocessing import MissingStateIndicator, NoTransformer, ToFloatTransformer __all__ = [ "BSplineTransformer", diff --git a/pretab/transformers/encoders/__init__.py b/pretab/transformers/encoders/__init__.py deleted file mode 100644 index 1d729b2..0000000 --- a/pretab/transformers/encoders/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Numeric helper transformers for tabular preprocessing. - -These transformers turn raw column values into numeric arrays that downstream -models can consume: a float cast and a pass-through. -""" - -from .floats import NoTransformer, ToFloatTransformer -from .missing import MissingStateIndicator - -__all__ = [ - "MissingStateIndicator", - "NoTransformer", - "ToFloatTransformer", -] From f1b6a34ed930bb3646125c03e8d13c83f0b365db Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 09:17:59 +0200 Subject: [PATCH 09/14] docs: restructure representations by expansion/encoding/embedding taxonomy --- docs/api/representations.rst | 63 +++++++-- docs/index.rst | 11 +- ...categorical.md => categorical_encoding.md} | 43 ++---- docs/representations/choosing_a_method.md | 6 +- docs/representations/comparison_table.md | 42 ++++-- docs/representations/embeddings.md | 36 +++++ docs/representations/feature_maps.md | 128 ------------------ docs/representations/functional_expansions.md | 82 +++++++++++ docs/representations/kernel_approximation.md | 65 +++++++++ ...nning_and_ple.md => numerical_encoding.md} | 38 +++++- docs/representations/overview.md | 52 +++++-- .../preprocessing_utilities.md | 62 +++++++++ docs/representations/references.md | 4 +- .../{splines.md => spline_expansions.md} | 4 +- docs/tutorials/multivariate_features.md | 6 +- 15 files changed, 427 insertions(+), 215 deletions(-) rename docs/representations/{categorical.md => categorical_encoding.md} (51%) create mode 100644 docs/representations/embeddings.md delete mode 100644 docs/representations/feature_maps.md create mode 100644 docs/representations/functional_expansions.md create mode 100644 docs/representations/kernel_approximation.md rename docs/representations/{binning_and_ple.md => numerical_encoding.md} (65%) create mode 100644 docs/representations/preprocessing_utilities.md rename docs/representations/{splines.md => spline_expansions.md} (98%) diff --git a/docs/api/representations.rst b/docs/api/representations.rst index f6e1427..3ed9fc9 100644 --- a/docs/api/representations.rst +++ b/docs/api/representations.rst @@ -7,8 +7,8 @@ view, see the :doc:`comparison table <../representations/comparison_table>`. .. currentmodule:: pretab.transformers -Splines -------- +Spline expansions +------------------ .. autosummary:: :toctree: _autosummary @@ -20,11 +20,23 @@ Splines CubicRegressionSplineTransformer NaturalCubicSplineTransformer PSplineTransformer + +Canonical import: ``pretab.expansion.spline``. + +Multivariate splines +--------------------- + +.. autosummary:: + :toctree: _autosummary + :nosignatures: + TensorProductSplineTransformer ThinPlateSplineTransformer -Feature maps ------------- +Canonical import: ``pretab.expansion.spline.multivariate``. + +Functional expansions +---------------------- .. autosummary:: :toctree: _autosummary @@ -35,12 +47,23 @@ Feature maps SigmoidExpansionTransformer TanhExpansionTransformer FourierFeatureTransformer - PeriodicEncodingTransformer + +Canonical import: ``pretab.expansion.functional``. + +Kernel approximation +---------------------- + +.. autosummary:: + :toctree: _autosummary + :nosignatures: + RandomFourierFeaturesTransformer NystroemFeaturesTransformer -Binning and PLE ---------------- +Canonical import: ``pretab.kernel_approximation``. + +Numerical encoding +-------------------- .. autosummary:: :toctree: _autosummary @@ -48,9 +71,12 @@ Binning and PLE NumericBinningTransformer PLETransformer + PeriodicEncodingTransformer + +Canonical import: ``pretab.encoding.numerical``. -Categorical ------------ +Categorical encoding +----------------------- .. autosummary:: :toctree: _autosummary @@ -58,10 +84,22 @@ Categorical ContinuousOrdinalTransformer OneHotFromOrdinalTransformer + +Canonical import: ``pretab.encoding.categorical``. + +Embeddings +------------ + +.. autosummary:: + :toctree: _autosummary + :nosignatures: + LanguageEmbeddingTransformer -Utility transformers --------------------- +Canonical import: ``pretab.embedding``. + +Preprocessing utilities +-------------------------- .. autosummary:: :toctree: _autosummary @@ -70,3 +108,6 @@ Utility transformers MissingStateIndicator NoTransformer ToFloatTransformer + +Canonical import: ``pretab.preprocessing``. + diff --git a/docs/index.rst b/docs/index.rst index def872c..994bdfb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,10 +33,13 @@ representations/overview representations/comparison_table representations/choosing_a_method - representations/splines - representations/feature_maps - representations/binning_and_ple - representations/categorical + representations/spline_expansions + representations/functional_expansions + representations/kernel_approximation + representations/numerical_encoding + representations/categorical_encoding + representations/embeddings + representations/preprocessing_utilities representations/references .. toctree:: diff --git a/docs/representations/categorical.md b/docs/representations/categorical_encoding.md similarity index 51% rename from docs/representations/categorical.md rename to docs/representations/categorical_encoding.md index c1eaa88..ed6d26d 100644 --- a/docs/representations/categorical.md +++ b/docs/representations/categorical_encoding.md @@ -1,9 +1,9 @@ -# Categorical +# Categorical encoding -Categorical features range from a handful of labels to free text with thousands of distinct -values. PreTab covers the spectrum: compact integer encoding, explicit one-hot, and pretrained -language embeddings for high-cardinality text. All of them handle unseen categories without -raising. +Categorical encoding maps a category to codes or indicators a model can consume directly. +PreTab covers compact integer encoding and explicit one-hot encoding, both of which handle +unseen categories without raising. For high-cardinality text where the labels themselves carry +meaning, see [Embeddings](embeddings.md) instead. ## Integer (ordinal) encoding @@ -41,34 +41,7 @@ encodes an already integer-coded column. ```{warning} One-hot width grows with cardinality. A column with thousands of categories produces thousands of columns. Use the [output budget](../core_concepts/outputs_and_inspection.md) to cap it, or -prefer integer encoding or embeddings for high-cardinality columns. -``` - -## Language embeddings - -For high-cardinality text categories (product titles, free-text tags, descriptions), a -pretrained sentence embedding captures semantic similarity that integer or one-hot encoding -cannot. Similar labels land near each other in the embedding space. - -```python -from pretab.transformers import LanguageEmbeddingTransformer - -t = LanguageEmbeddingTransformer(model_name="paraphrase-MiniLM-L3-v2") -X2 = t.fit_transform(x) -``` - -Constructor highlights: `model_name="paraphrase-MiniLM-L3-v2"`, or pass a preloaded `model`. -The registry key is `pretrained`. - -```{important} -Language embeddings require the optional `embeddings` extra, which pulls in -`sentence-transformers`. Install it with `pip install "pretab[embeddings]"`. Without it, -requesting `pretrained` raises a clear `OptionalDependencyError`. -``` - -```{tip} -Embeddings shine when category labels carry meaning as text. If the labels are opaque codes -with no semantic content, integer encoding is simpler and just as effective. +prefer integer encoding or [embeddings](embeddings.md) for high-cardinality columns. ``` ## Choosing a categorical method @@ -77,10 +50,10 @@ with no semantic content, integer encoding is simpler and just as effective. | --- | --- | | Low cardinality, unordered | One-hot | | Fed to a tree or embedding layer | Integer | -| High-cardinality meaningful text | Language embedding | +| High-cardinality meaningful text | [Language embedding](embeddings.md) | ## Where to go next +- [Embeddings](embeddings.md) for high-cardinality text categories. - [Missing values](../core_concepts/missing_values.md) for categorical imputation. - [Configuration](../core_concepts/configuration.md) to set categorical methods per column. -- [Installation](../getting_started/installation.md) for the `embeddings` extra. diff --git a/docs/representations/choosing_a_method.md b/docs/representations/choosing_a_method.md index 7f8834d..9b8a55f 100644 --- a/docs/representations/choosing_a_method.md +++ b/docs/representations/choosing_a_method.md @@ -111,6 +111,8 @@ To set expectations, PreTab deliberately does not do the following. ## Where to go next - [Comparison table](comparison_table.md) to filter by capability. -- [Splines](splines.md), [Feature maps](feature_maps.md), - [Binning and PLE](binning_and_ple.md), [Categorical](categorical.md) for the details. +- [Spline expansions](spline_expansions.md), [Functional expansions](functional_expansions.md), + [Kernel approximation](kernel_approximation.md), [Numerical encoding](numerical_encoding.md), + [Categorical encoding](categorical_encoding.md), and [Embeddings](embeddings.md) for the + details. - [Comparing representations](../tutorials/comparing_representations.md) to measure the choice. diff --git a/docs/representations/comparison_table.md b/docs/representations/comparison_table.md index 4e93bbf..73e8591 100644 --- a/docs/representations/comparison_table.md +++ b/docs/representations/comparison_table.md @@ -37,7 +37,7 @@ source of truth, and these tables mirror it. | Yeo-Johnson | `yeo-johnson` | univariate | forbidden | yes | | Passthrough | `none` | univariate | forbidden | yes | -## Numerical: splines +## Spline expansions | Method | Key | Scope | Target | Adaptive | Penalty | Selectable | | --- | --- | --- | --- | --- | --- | --- | @@ -56,7 +56,7 @@ standalone, not selected per column through `Preprocessor`. The alias `thinplate `tprs`. ``` -## Numerical: feature maps +## Functional expansions | Method | Key | Scope | Target | Adaptive | Selectable | | --- | --- | --- | --- | --- | --- | @@ -65,15 +65,26 @@ standalone, not selected per column through `Preprocessor`. The alias `thinplate | Sigmoid expansion | `sigmoid` | univariate | optional | yes | yes | | Tanh expansion | `tanh` | univariate | optional | yes | yes | | Fourier features | `fourier` | univariate | forbidden | no | yes | + +## Kernel approximation + +| Method | Key | Scope | Target | Adaptive | Selectable | +| --- | --- | --- | --- | --- | --- | | Random Fourier features | `rff` | multivariate | forbidden | no | no | | Nyström kernel map | `nystroem` | multivariate | forbidden | no | no | -## Numerical: discretization +```{note} +Random Fourier features and Nyström model the whole input matrix jointly and are used +standalone, not selected per column through `Preprocessor`. +``` + +## Numerical encoding | Method | Key | Scope | Target | Adaptive | Selectable | | --- | --- | --- | --- | --- | --- | | Numeric binning | `custombin` | univariate | forbidden | no | yes | | Piecewise-linear encoding (PLE) | `ple` | univariate | required | yes | yes | +| Periodic encoding | n/a | univariate | forbidden | no | no | ```{important} PLE is the only numerical method that **requires** the target. It always places its bins @@ -81,22 +92,37 @@ against `y`, so it must be fit with a target and is best used with cross-fitting [Target awareness](../core_concepts/target_awareness.md). ``` -## Categorical +```{note} +Periodic encoding has no registry key: it takes a required per-feature `period`, so it is not +selectable through `Preprocessor`. Instantiate `PeriodicEncodingTransformer` directly. +``` + +## Categorical encoding | Method | Key | Scope | Target | Selectable | | --- | --- | --- | --- | --- | | Ordinal (integer) encoding | `int` | univariate | forbidden | yes | | One-hot encoding | `one-hot` | univariate | forbidden | yes | | One-hot from ordinal | `onehot_from_ordinal` | univariate | forbidden | yes | -| Pretrained language embedding | `pretrained` | univariate | forbidden | yes | | Passthrough | `none` | univariate | forbidden | yes | ```{note} -`pretrained` requires the optional `embeddings` extra. The alias `ohe` resolves to `one-hot`. +The alias `ohe` resolves to `one-hot`. +``` + +## Embeddings + +| Method | Key | Scope | Target | Selectable | +| --- | --- | --- | --- | --- | +| Pretrained language embedding | `pretrained` | univariate | forbidden | yes | + +```{note} +`pretrained` requires the optional `embeddings` extra. ``` ## Where to go next - [Choosing a method](choosing_a_method.md) for guidance on which of these to reach for. -- [Splines](splines.md), [Feature maps](feature_maps.md), - [Binning and PLE](binning_and_ple.md), [Categorical](categorical.md) for the details. +- [Spline expansions](spline_expansions.md), [Functional expansions](functional_expansions.md), + [Kernel approximation](kernel_approximation.md), [Numerical encoding](numerical_encoding.md), + [Categorical encoding](categorical_encoding.md), [Embeddings](embeddings.md) for the details. diff --git a/docs/representations/embeddings.md b/docs/representations/embeddings.md new file mode 100644 index 0000000..538a05a --- /dev/null +++ b/docs/representations/embeddings.md @@ -0,0 +1,36 @@ +# Embeddings + +Embeddings map a categorical or text column to a dense vector produced by a pretrained model, +rather than recoding it into a small discrete representation the way +[categorical encoding](categorical_encoding.md) does. For high-cardinality text categories +(product titles, free-text tags, descriptions), a pretrained sentence embedding captures +semantic similarity that integer or one-hot encoding cannot. Similar labels land near each +other in the embedding space. + +```python +from pretab.transformers import LanguageEmbeddingTransformer + +t = LanguageEmbeddingTransformer(model_name="paraphrase-MiniLM-L3-v2") +X2 = t.fit_transform(x) +``` + +Constructor highlights: `model_name="paraphrase-MiniLM-L3-v2"`, or pass a preloaded `model`. +The registry key is `pretrained`. + +```{important} +Language embeddings require the optional `embeddings` extra, which pulls in +`sentence-transformers`. Install it with `pip install "pretab[embeddings]"`. Without it, +requesting `pretrained` raises a clear `OptionalDependencyError`. +``` + +```{tip} +Embeddings shine when category labels carry meaning as text. If the labels are opaque codes +with no semantic content, [integer encoding](categorical_encoding.md#integer-ordinal-encoding) +is simpler and just as effective. +``` + +## Where to go next + +- [Categorical encoding](categorical_encoding.md) for compact integer and one-hot alternatives. +- [Installation](../getting_started/installation.md) for the `embeddings` extra. +- [Missing values](../core_concepts/missing_values.md) for categorical imputation. diff --git a/docs/representations/feature_maps.md b/docs/representations/feature_maps.md deleted file mode 100644 index 8179ca1..0000000 --- a/docs/representations/feature_maps.md +++ /dev/null @@ -1,128 +0,0 @@ -# Feature maps - -Feature maps are basis functions borrowed from machine learning rather than classical -statistics. They spread a feature across a set of activation functions (radial bumps, ReLU -ramps, sigmoids) or project it onto a Fourier basis, and they include the two standard -kernel approximations. Together they cover local, threshold, and periodic structure. - -## Radial basis functions - -The RBF expansion places centers along the feature range and measures Gaussian similarity to -each, - -$$ -\phi_k(x) = \exp\!\big(-\gamma\,(x - c_k)^2\big). -$$ - -Each output is a smooth bump around a center, so a linear model on top can build up a curve -from local pieces. - -```python -from pretab.transformers import RBFExpansionTransformer - -t = RBFExpansionTransformer(output_dim=10, gamma=1.0) -``` - -Constructor highlights: `output_dim`, `gamma=1.0` (bump width; larger is narrower), -`target_aware=False`, `placement_strategy`, `adaptive`, `random_state`. - -```{tip} -`gamma` trades locality for coverage. Large `gamma` gives narrow, sharply local bumps; small -`gamma` gives broad, overlapping ones. Tune it alongside `output_dim`. -``` - -## ReLU, sigmoid, and tanh expansions - -These place a set of thresholds along the range and apply an activation at each, mirroring a -single hidden layer. - -ReLU -: Piecewise-linear ramps. Excellent for sharp, threshold-like effects. - -Sigmoid and Tanh -: Smooth saturating steps. `scale` controls the steepness of the transition. - -```python -from pretab.transformers import ReLUExpansionTransformer, TanhExpansionTransformer - -relu = ReLUExpansionTransformer(output_dim=10) -tanh = TanhExpansionTransformer(output_dim=10, scale=1.0) -``` - -```{note} -ReLU expansions are a natural fit when the effect of a feature turns on past a threshold, for -example a fee that applies only above a limit. -``` - -## Fourier features - -The Fourier map represents a feature with sines and cosines at a set of frequencies, ideal for -signals with cyclical structure. - -```python -from pretab.transformers import FourierFeatureTransformer - -t = FourierFeatureTransformer(n_frequencies=5, frequency_strategy="harmonic") -``` - -Constructor highlights: `n_frequencies=5`, `frequency_strategy="harmonic"`, -`include_original=False`, `random_state`. - -### Periodic encoding - -When you know the period, the periodic encoder is the direct choice. It maps a value onto its -position in a cycle of known length, so December and January sit next to each other. - -```python -from pretab.transformers import PeriodicEncodingTransformer - -t = PeriodicEncodingTransformer(period=12, harmonics=2) # e.g. month of year -``` - -```{tip} -Use `PeriodicEncodingTransformer` when the period is known (hour of day, month of year). Use -`FourierFeatureTransformer` when you want the model to work across a set of frequencies. -``` - -## Kernel approximations - -Two multivariate maps approximate a kernel machine without forming the full kernel matrix. -They are standalone transformers, not per-column methods. - -### Random Fourier features - -Approximates a shift-invariant kernel (by default the RBF kernel) with random projections, -following Rahimi and Recht. This makes kernel-style models scale to large datasets. - -```python -from pretab.transformers import RandomFourierFeaturesTransformer - -t = RandomFourierFeaturesTransformer(n_components=100, gamma=1.0) -X2 = t.fit_transform(X) -``` - -### Nyström - -Approximates a kernel by sampling landmark points and projecting onto them, following Williams -and Seeger. It supports several kernels through `kernel`. - -```python -from pretab.transformers import NystroemFeaturesTransformer - -t = NystroemFeaturesTransformer(n_components=100, kernel="rbf") -X2 = t.fit_transform(X) -``` - -Constructor highlights: `n_components=100`, `kernel="rbf"`, `gamma=None`, `degree=3`, -`coef0=1`, `random_state`. - -```{warning} -Random Fourier features and Nyström are multivariate and operate on the whole input matrix. -They are not available as a per-column `numerical_method`; fit them standalone. -``` - -## Where to go next - -- [Splines](splines.md) for smooth statistical bases. -- [Binning and PLE](binning_and_ple.md) for discretization. -- [References](references.md) for the kernel-approximation literature. diff --git a/docs/representations/functional_expansions.md b/docs/representations/functional_expansions.md new file mode 100644 index 0000000..33c495d --- /dev/null +++ b/docs/representations/functional_expansions.md @@ -0,0 +1,82 @@ +# Functional expansions + +Functional expansions are basis functions borrowed from machine learning rather than classical +statistics. They spread a feature across a set of activation functions (radial bumps, ReLU +ramps, sigmoids) or project it onto a deterministic Fourier basis. Together they cover local, +threshold, and periodic structure with a per-column, `Preprocessor`-selectable transformer. + +## Radial basis functions + +The RBF expansion places centers along the feature range and measures Gaussian similarity to +each, + +$$ +\phi_k(x) = \exp\!\big(-\gamma\,(x - c_k)^2\big). +$$ + +Each output is a smooth bump around a center, so a linear model on top can build up a curve +from local pieces. + +```python +from pretab.transformers import RBFExpansionTransformer + +t = RBFExpansionTransformer(output_dim=10, gamma=1.0) +``` + +Constructor highlights: `output_dim`, `gamma=1.0` (bump width; larger is narrower), +`target_aware=False`, `placement_strategy`, `adaptive`, `random_state`. + +```{tip} +`gamma` trades locality for coverage. Large `gamma` gives narrow, sharply local bumps; small +`gamma` gives broad, overlapping ones. Tune it alongside `output_dim`. +``` + +## ReLU, sigmoid, and tanh expansions + +These place a set of thresholds along the range and apply an activation at each, mirroring a +single hidden layer. + +ReLU +: Piecewise-linear ramps. Excellent for sharp, threshold-like effects. + +Sigmoid and Tanh +: Smooth saturating steps. `scale` controls the steepness of the transition. + +```python +from pretab.transformers import ReLUExpansionTransformer, TanhExpansionTransformer + +relu = ReLUExpansionTransformer(output_dim=10) +tanh = TanhExpansionTransformer(output_dim=10, scale=1.0) +``` + +```{note} +ReLU expansions are a natural fit when the effect of a feature turns on past a threshold, for +example a fee that applies only above a limit. +``` + +## Fourier features + +The Fourier map represents a feature with sines and cosines at a set of frequencies, ideal for +signals with cyclical structure. + +```python +from pretab.transformers import FourierFeatureTransformer + +t = FourierFeatureTransformer(n_frequencies=5, frequency_strategy="harmonic") +``` + +Constructor highlights: `n_frequencies=5`, `frequency_strategy="harmonic"`, +`include_original=False`, `random_state`. + +```{tip} +Use `FourierFeatureTransformer` when you want the model to work across a set of frequencies +without committing to a single known period. If the period is known (hour of day, month of +year), the direct [periodic encoder](numerical_encoding.md#periodic-encoding) is usually simpler. +``` + +## Where to go next + +- [Spline expansions](spline_expansions.md) for smooth statistical bases. +- [Kernel approximation](kernel_approximation.md) for the multivariate RFF and Nyström maps. +- [Numerical encoding](numerical_encoding.md) for binning, PLE, and periodic encoding. +- [References](references.md) for the underlying literature. diff --git a/docs/representations/kernel_approximation.md b/docs/representations/kernel_approximation.md new file mode 100644 index 0000000..92f2946 --- /dev/null +++ b/docs/representations/kernel_approximation.md @@ -0,0 +1,65 @@ +# Kernel approximation + +Kernel approximation builds an explicit, low-dimensional feature map whose inner products +approximate an implicit kernel, so a linear model downstream can behave like a kernel machine +without ever forming the full kernel matrix. PreTab wraps the two standard approaches. Both are +**multivariate, standalone transformers**: they operate on the whole input matrix and are not +selectable per column through `Preprocessor`. + +## Random Fourier features + +Approximates a shift-invariant kernel (by default the RBF kernel) with random projections, +following Rahimi and Recht. This makes kernel-style models scale to large datasets, since the +cost of the approximation does not grow with the number of training points the way an exact +kernel method's does. + +```python +from pretab.transformers import RandomFourierFeaturesTransformer + +t = RandomFourierFeaturesTransformer(n_components=100, gamma=1.0) +X2 = t.fit_transform(X) +``` + +Constructor highlights: `n_components=100`, `gamma=1.0`, `random_state`. + +```{tip} +`n_components` trades approximation quality for cost. More components track the true kernel +more closely at the price of a wider output; start around 100 and increase if validation +performance is still improving. +``` + +## Nyström + +Approximates a kernel by sampling landmark points from the training data and projecting onto +them, following Williams and Seeger. It supports several kernels through `kernel`, and is often +more accurate than random Fourier features at a given output width because the landmarks adapt +to the data rather than being drawn at random. + +```python +from pretab.transformers import NystroemFeaturesTransformer + +t = NystroemFeaturesTransformer(n_components=100, kernel="rbf") +X2 = t.fit_transform(X) +``` + +Constructor highlights: `n_components=100`, `kernel="rbf"`, `gamma=None`, `degree=3`, +`coef0=1`, `random_state`. + +```{note} +Both methods approximate the same idea from different angles: random Fourier features draw a +random basis independent of the data, while Nyström samples landmarks from the data itself. +When in doubt, try both and compare with cross-validation. +``` + +```{warning} +Random Fourier features and Nyström are multivariate and operate on the whole input matrix. +They are not available as a per-column `numerical_method`; fit them standalone or combine them +with per-column methods through a `ColumnTransformer`. +``` + +## Where to go next + +- [Functional expansions](functional_expansions.md) for the per-column basis functions. +- [Spline expansions](spline_expansions.md) for the multivariate tensor-product and thin-plate + splines, another way to model several inputs jointly. +- [References](references.md) for the kernel-approximation literature. diff --git a/docs/representations/binning_and_ple.md b/docs/representations/numerical_encoding.md similarity index 65% rename from docs/representations/binning_and_ple.md rename to docs/representations/numerical_encoding.md index 2296f45..bb7930e 100644 --- a/docs/representations/binning_and_ple.md +++ b/docs/representations/numerical_encoding.md @@ -1,9 +1,10 @@ -# Binning and PLE +# Numerical encoding -Discretization turns a continuous feature into regions. It captures sharp, threshold-like -effects that smooth bases blur, and it is the natural representation when a feature acts in -steps. PreTab offers unsupervised numeric binning and supervised piecewise-linear encoding -(PLE). +Encoding recodes a numeric value rather than expanding it into a smooth basis. PreTab covers +three flavors: unsupervised discretization (numeric binning), supervised piecewise-linear +encoding (PLE), and periodic encoding for values that wrap around a known cycle. Discretization +captures sharp, threshold-like effects that smooth bases blur, and is the natural choice when a +feature acts in steps. ## Numeric binning @@ -73,6 +74,29 @@ PLE is a strong default for numerical features, and it is the default `numerical to follow the target. ``` +## Periodic encoding + +When a feature wraps around a known cycle, such as hour of day or month of year, the periodic +encoder maps each value onto its position on that cycle using sine and cosine harmonics. This +keeps the boundary continuous, so December and January sit next to each other instead of at +opposite ends of a number line. + +```python +from pretab.transformers import PeriodicEncodingTransformer + +t = PeriodicEncodingTransformer(period=12, harmonics=2) # e.g. month of year +``` + +Constructor highlights: `period` (required, the cycle length), `harmonics=1`, +`include_original=False`. + +```{note} +Periodic encoding is a standalone time-series utility. It is not wired into `Preprocessor` +because it requires a per-feature `period`, so apply it directly to the relevant cyclical +column. Use [Fourier features](functional_expansions.md#fourier-features) instead when you want +the model to search across a set of frequencies rather than commit to one known period. +``` + ## Binning versus PLE | | Numeric binning | PLE | @@ -85,5 +109,7 @@ to follow the target. ## Where to go next - [Target awareness](../core_concepts/target_awareness.md) for fitting PLE safely. -- [Splines](splines.md) for smooth alternatives to binning. +- [Spline expansions](spline_expansions.md) for smooth alternatives to binning. +- [Functional expansions](functional_expansions.md) for Fourier features, the deterministic + alternative to periodic encoding. - [References](references.md) for the PLE source. diff --git a/docs/representations/overview.md b/docs/representations/overview.md index aed5c1e..7cfdc5b 100644 --- a/docs/representations/overview.md +++ b/docs/representations/overview.md @@ -10,31 +10,51 @@ data. ::::{grid} 1 1 2 2 :gutter: 3 -:::{grid-item-card} Splines -:link: splines +:::{grid-item-card} Spline expansions +:link: spline_expansions :link-type: doc Smooth, locally-supported bases: B, M, I, cubic regression, natural cubic, penalized (P-spline), and the multivariate tensor-product and thin-plate splines. ::: -:::{grid-item-card} Feature maps -:link: feature_maps +:::{grid-item-card} Functional expansions +:link: functional_expansions :link-type: doc -Basis functions from machine learning: radial (RBF), ReLU, sigmoid, tanh, deterministic -Fourier, and the kernel approximations (random Fourier features, Nyström). +Basis functions from machine learning: radial (RBF), ReLU, sigmoid, tanh, and deterministic +Fourier features. ::: -:::{grid-item-card} Binning and PLE -:link: binning_and_ple +:::{grid-item-card} Kernel approximation +:link: kernel_approximation :link-type: doc -Discretization: numeric binning with several encodings, and supervised piecewise-linear -encoding (PLE). +Multivariate kernel machines without the full kernel matrix: random Fourier features and +Nyström. ::: -:::{grid-item-card} Categorical -:link: categorical +:::{grid-item-card} Numerical encoding +:link: numerical_encoding :link-type: doc -Ordinal and one-hot encoding, plus pretrained language embeddings for high-cardinality text. +Discretization and recoding: numeric binning, supervised piecewise-linear encoding (PLE), and +periodic encoding. +::: + +:::{grid-item-card} Categorical encoding +:link: categorical_encoding +:link-type: doc +Ordinal and one-hot encoding for categories, handling unseen values without raising. +::: + +:::{grid-item-card} Embeddings +:link: embeddings +:link-type: doc +Pretrained language embeddings for high-cardinality text categories. +::: + +:::{grid-item-card} Preprocessing utilities +:link: preprocessing_utilities +:link-type: doc +Supporting transformers `Preprocessor` wires in automatically: pass-through, type conversion, +and missing-value flagging. ::: :::: @@ -85,7 +105,9 @@ the primary sources for each, so the representations are traceable to their lite ## Where to go next -- [Splines](splines.md), [Feature maps](feature_maps.md), [Binning and PLE](binning_and_ple.md), - [Categorical](categorical.md) for the families. +- [Spline expansions](spline_expansions.md), [Functional expansions](functional_expansions.md), + [Kernel approximation](kernel_approximation.md), [Numerical encoding](numerical_encoding.md), + [Categorical encoding](categorical_encoding.md), [Embeddings](embeddings.md), and + [Preprocessing utilities](preprocessing_utilities.md) for the families. - [Comparison table](comparison_table.md) to filter by capability. - [Choosing a method](choosing_a_method.md) for guidance and failure modes. diff --git a/docs/representations/preprocessing_utilities.md b/docs/representations/preprocessing_utilities.md new file mode 100644 index 0000000..8fed666 --- /dev/null +++ b/docs/representations/preprocessing_utilities.md @@ -0,0 +1,62 @@ +# Preprocessing utilities + +Preprocessing utilities don't expand or recode a feature. They prepare it for the rest of the +pipeline, converting types or flagging missingness before it reaches the transformer that does +the actual representation work. `Preprocessor` wires these in automatically; most users never +instantiate them directly, but they are part of the public API for anyone building a custom +`ColumnTransformer` or pipeline by hand. + +```{note} +This page is distinct from [`pretab.preprocessor`](../api/preprocessor.rst), the module that +holds the top-level `Preprocessor` facade. `pretab.preprocessing` is the package for these +smaller supporting transformers. +``` + +## Pass-through and type conversion + +`NoTransformer` returns its input unchanged. It backs the `"none"` categorical and numerical +methods, letting a column skip representation entirely while still satisfying the +scikit-learn transformer API. + +```python +from pretab.transformers import NoTransformer + +t = NoTransformer() +X2 = t.fit_transform(X) # X2 is X, unmodified +``` + +`ToFloatTransformer` casts its input to floating point. `Preprocessor` appends it after +one-hot encoding so the categorical block has the same dtype as the rest of the design matrix. + +```python +from pretab.transformers import ToFloatTransformer + +t = ToFloatTransformer() +t.fit_transform(X).dtype # dtype('float64') +``` + +## Missing-value flagging + +`MissingStateIndicator` emits a binary column marking where the input was missing, computed on +the raw data before imputation. `Preprocessor` uses it when `missing_policy="separate_state"`: +the indicator is kept apart from the imputed representation basis, so a downstream model can +learn a dedicated response to missingness instead of confusing it with an imputed value. + +```python +import numpy as np +from pretab.transformers import MissingStateIndicator + +X = np.array([[1.0], [np.nan], [3.0]]) +MissingStateIndicator().fit_transform(X) +# array([[0.], [1.], [0.]]) +``` + +```{tip} +Unlike `sklearn.impute.MissingIndicator`, `MissingStateIndicator` works on both numeric and +object (categorical) columns and always emits one column per input feature. +``` + +## Where to go next + +- [Missing values](../core_concepts/missing_values.md) for the full `missing_policy` behavior. +- [Configuration](../core_concepts/configuration.md) for how `Preprocessor` builds its pipelines. diff --git a/docs/representations/references.md b/docs/representations/references.md index 7f92170..ef6cce3 100644 --- a/docs/representations/references.md +++ b/docs/representations/references.md @@ -53,5 +53,5 @@ basis for `PLETransformer`. ## Where to go next - [Representations overview](overview.md) to return to the catalogue. -- [Splines](splines.md), [Feature maps](feature_maps.md), - [Binning and PLE](binning_and_ple.md) for the methods these sources describe. +- [Spline expansions](spline_expansions.md), [Kernel approximation](kernel_approximation.md), + [Numerical encoding](numerical_encoding.md) for the methods these sources describe. diff --git a/docs/representations/splines.md b/docs/representations/spline_expansions.md similarity index 98% rename from docs/representations/splines.md rename to docs/representations/spline_expansions.md index 45caef2..7b20cbb 100644 --- a/docs/representations/splines.md +++ b/docs/representations/spline_expansions.md @@ -1,4 +1,4 @@ -# Splines +# Spline expansions Splines are piecewise-polynomial bases with local support. They turn a single numerical column into a set of smooth, overlapping basis functions, so a linear model on top can bend to follow @@ -150,7 +150,7 @@ want to model jointly. ## Where to go next -- [Feature maps](feature_maps.md) for non-spline bases. +- [Functional expansions](functional_expansions.md) for non-spline bases. - [Multivariate features tutorial](../tutorials/multivariate_features.md) for a worked joint model. - [References](references.md) for the primary spline literature. diff --git a/docs/tutorials/multivariate_features.md b/docs/tutorials/multivariate_features.md index 036ef2f..74948b0 100644 --- a/docs/tutorials/multivariate_features.md +++ b/docs/tutorials/multivariate_features.md @@ -112,6 +112,8 @@ The thin-plate spline handles the geographic interaction while PLE handles the s ## Where to go next -- [Splines](../representations/splines.md) for the tensor-product and thin-plate details. -- [Feature maps](../representations/feature_maps.md) for the kernel approximations. +- [Spline expansions](../representations/spline_expansions.md) for the tensor-product and + thin-plate details. +- [Kernel approximation](../representations/kernel_approximation.md) for random Fourier + features and Nyström. - [References](../representations/references.md) for the underlying theory. From d18000d787cf879cff7bcc2834ebecdfdedd86e7 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 09:45:26 +0200 Subject: [PATCH 10/14] test: reorganize transformer tests to mirror the package taxonomy --- CHANGELOG.md | 14 ++++++++++++++ .../test_language_embedding_transformer.py | 0 .../test_onehot_from_ordinal_transformer.py | 0 .../numerical}/test_custombin_transformer.py | 0 .../numerical}/test_periodic.py | 0 .../numerical}/test_ple_transformer.py | 0 .../functional}/test_fourier_transformer.py | 0 .../functional}/test_rbfexpansion_transformer.py | 0 .../functional}/test_reluexpansion_transformer.py | 0 .../test_sigmoidexpansion_transformer.py | 0 .../functional}/test_tanh_transformer.py | 0 .../spline}/test_cubic_transformer.py | 0 .../spline}/test_naturalcubic_transformer.py | 0 .../spline}/test_pspline_transformer.py | 0 .../spline}/test_spline_api_parity.py | 0 .../spline}/test_spline_expansions.py | 0 .../spline}/test_tensorproduct_transformer.py | 0 .../spline}/test_thinplate_transformer.py | 0 .../test_kernel_approx_transformer.py | 0 19 files changed, 14 insertions(+) rename tests/{transformers => embedding}/test_language_embedding_transformer.py (100%) rename tests/{transformers => encoding/categorical}/test_onehot_from_ordinal_transformer.py (100%) rename tests/{transformers => encoding/numerical}/test_custombin_transformer.py (100%) rename tests/{transformers => encoding/numerical}/test_periodic.py (100%) rename tests/{transformers => encoding/numerical}/test_ple_transformer.py (100%) rename tests/{transformers => expansion/functional}/test_fourier_transformer.py (100%) rename tests/{transformers => expansion/functional}/test_rbfexpansion_transformer.py (100%) rename tests/{transformers => expansion/functional}/test_reluexpansion_transformer.py (100%) rename tests/{transformers => expansion/functional}/test_sigmoidexpansion_transformer.py (100%) rename tests/{transformers => expansion/functional}/test_tanh_transformer.py (100%) rename tests/{transformers => expansion/spline}/test_cubic_transformer.py (100%) rename tests/{transformers => expansion/spline}/test_naturalcubic_transformer.py (100%) rename tests/{transformers => expansion/spline}/test_pspline_transformer.py (100%) rename tests/{transformers => expansion/spline}/test_spline_api_parity.py (100%) rename tests/{transformers => expansion/spline}/test_spline_expansions.py (100%) rename tests/{transformers => expansion/spline}/test_tensorproduct_transformer.py (100%) rename tests/{transformers => expansion/spline}/test_thinplate_transformer.py (100%) rename tests/{transformers => kernel_approximation}/test_kernel_approx_transformer.py (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9fb03..c9f0c2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ This project adheres to [Semantic Versioning](https://semver.org/) and uses Going forward, this file is updated automatically by `cz bump` on each release. +## Unreleased + +### Refactor + +- Reorganized the `pretab` package by representation taxonomy: spline and functional + expansions now live under `pretab.expansion`, numeric and categorical encoders under + `pretab.encoding`, kernel approximations under `pretab.kernel_approximation`, language + embeddings under `pretab.embedding`, and supporting utilities under `pretab.preprocessing`. + `pretab.transformers` remains the stable, flat public import for every transformer class; + no class was renamed and no public behavior changed. +- Restructured the representations documentation and API reference to match the new + taxonomy, adding dedicated pages for kernel approximation, embeddings, and preprocessing + utilities. + ## v1.0.0rc2 (2026-08-21) ### Fix diff --git a/tests/transformers/test_language_embedding_transformer.py b/tests/embedding/test_language_embedding_transformer.py similarity index 100% rename from tests/transformers/test_language_embedding_transformer.py rename to tests/embedding/test_language_embedding_transformer.py diff --git a/tests/transformers/test_onehot_from_ordinal_transformer.py b/tests/encoding/categorical/test_onehot_from_ordinal_transformer.py similarity index 100% rename from tests/transformers/test_onehot_from_ordinal_transformer.py rename to tests/encoding/categorical/test_onehot_from_ordinal_transformer.py diff --git a/tests/transformers/test_custombin_transformer.py b/tests/encoding/numerical/test_custombin_transformer.py similarity index 100% rename from tests/transformers/test_custombin_transformer.py rename to tests/encoding/numerical/test_custombin_transformer.py diff --git a/tests/transformers/test_periodic.py b/tests/encoding/numerical/test_periodic.py similarity index 100% rename from tests/transformers/test_periodic.py rename to tests/encoding/numerical/test_periodic.py diff --git a/tests/transformers/test_ple_transformer.py b/tests/encoding/numerical/test_ple_transformer.py similarity index 100% rename from tests/transformers/test_ple_transformer.py rename to tests/encoding/numerical/test_ple_transformer.py diff --git a/tests/transformers/test_fourier_transformer.py b/tests/expansion/functional/test_fourier_transformer.py similarity index 100% rename from tests/transformers/test_fourier_transformer.py rename to tests/expansion/functional/test_fourier_transformer.py diff --git a/tests/transformers/test_rbfexpansion_transformer.py b/tests/expansion/functional/test_rbfexpansion_transformer.py similarity index 100% rename from tests/transformers/test_rbfexpansion_transformer.py rename to tests/expansion/functional/test_rbfexpansion_transformer.py diff --git a/tests/transformers/test_reluexpansion_transformer.py b/tests/expansion/functional/test_reluexpansion_transformer.py similarity index 100% rename from tests/transformers/test_reluexpansion_transformer.py rename to tests/expansion/functional/test_reluexpansion_transformer.py diff --git a/tests/transformers/test_sigmoidexpansion_transformer.py b/tests/expansion/functional/test_sigmoidexpansion_transformer.py similarity index 100% rename from tests/transformers/test_sigmoidexpansion_transformer.py rename to tests/expansion/functional/test_sigmoidexpansion_transformer.py diff --git a/tests/transformers/test_tanh_transformer.py b/tests/expansion/functional/test_tanh_transformer.py similarity index 100% rename from tests/transformers/test_tanh_transformer.py rename to tests/expansion/functional/test_tanh_transformer.py diff --git a/tests/transformers/test_cubic_transformer.py b/tests/expansion/spline/test_cubic_transformer.py similarity index 100% rename from tests/transformers/test_cubic_transformer.py rename to tests/expansion/spline/test_cubic_transformer.py diff --git a/tests/transformers/test_naturalcubic_transformer.py b/tests/expansion/spline/test_naturalcubic_transformer.py similarity index 100% rename from tests/transformers/test_naturalcubic_transformer.py rename to tests/expansion/spline/test_naturalcubic_transformer.py diff --git a/tests/transformers/test_pspline_transformer.py b/tests/expansion/spline/test_pspline_transformer.py similarity index 100% rename from tests/transformers/test_pspline_transformer.py rename to tests/expansion/spline/test_pspline_transformer.py diff --git a/tests/transformers/test_spline_api_parity.py b/tests/expansion/spline/test_spline_api_parity.py similarity index 100% rename from tests/transformers/test_spline_api_parity.py rename to tests/expansion/spline/test_spline_api_parity.py diff --git a/tests/transformers/test_spline_expansions.py b/tests/expansion/spline/test_spline_expansions.py similarity index 100% rename from tests/transformers/test_spline_expansions.py rename to tests/expansion/spline/test_spline_expansions.py diff --git a/tests/transformers/test_tensorproduct_transformer.py b/tests/expansion/spline/test_tensorproduct_transformer.py similarity index 100% rename from tests/transformers/test_tensorproduct_transformer.py rename to tests/expansion/spline/test_tensorproduct_transformer.py diff --git a/tests/transformers/test_thinplate_transformer.py b/tests/expansion/spline/test_thinplate_transformer.py similarity index 100% rename from tests/transformers/test_thinplate_transformer.py rename to tests/expansion/spline/test_thinplate_transformer.py diff --git a/tests/transformers/test_kernel_approx_transformer.py b/tests/kernel_approximation/test_kernel_approx_transformer.py similarity index 100% rename from tests/transformers/test_kernel_approx_transformer.py rename to tests/kernel_approximation/test_kernel_approx_transformer.py From 9a1b8c1cd806050a1da766fd0d5155498488d198 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 11:12:56 +0200 Subject: [PATCH 11/14] docs: ground representation examples in verified shapes and add parameter/warning notes --- README.md | 27 ++++-- docs/homepage.md | 2 +- docs/representations/categorical_encoding.md | 25 +++-- docs/representations/comparison_table.md | 6 +- docs/representations/embeddings.md | 17 +++- docs/representations/functional_expansions.md | 34 ++++++- docs/representations/kernel_approximation.md | 19 +++- docs/representations/numerical_encoding.md | 57 ++++++++++-- docs/representations/overview.md | 7 -- .../preprocessing_utilities.md | 17 +++- docs/representations/spline_expansions.md | 93 +++++++++++++++++-- 11 files changed, 255 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 1af32fc..96facae 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,12 @@ print({k: v.shape for k, v in X.items()}) ## Available Transformers -PreTab groups its transformers into three families. Each one follows the standard `fit` / -`transform` API and is importable from `pretab.transformers`. +PreTab groups its transformers by representation taxonomy. Each one follows the standard +`fit` / `transform` API and is importable from `pretab.transformers` (the stable, flat public +import); advanced users can also reach them through the namespace shown per table +(`pretab.expansion.spline`, `pretab.expansion.functional`, and so on). -### Splines +### Spline expansions | Transformer | Basis | Best for | | ----------------------------------- | -------------------------------------- | ---------------------------------------- | @@ -104,7 +106,7 @@ PreTab groups its transformers into three families. Each one follows the standar | `TensorProductSplineTransformer` | Tensor-product spline (multivariate) | Smooth interactions across 2+ features | | `ThinPlateSplineTransformer` | Thin-plate spline (multivariate) | Smooth surfaces across 2+ features | -### Feature maps +### Functional expansions | Transformer | Basis | Best for | | ------------------------------------ | ------------------------------------------ | ---------------------------------------- | @@ -113,15 +115,26 @@ PreTab groups its transformers into three families. Each one follows the standar | `SigmoidExpansionTransformer` | Sigmoid basis | Smooth saturating features | | `TanhExpansionTransformer` | Tanh basis | Zero-centered saturating features | | `FourierFeatureTransformer` | Sine/cosine basis | Periodic or cyclic numerical effects | + +### Kernel approximation + +| Transformer | Basis | Best for | +| ------------------------------------ | ------------------------------------------ | ---------------------------------------- | | `RandomFourierFeaturesTransformer` | Random Fourier features (multivariate) | Scalable RBF-kernel approximation | | `NystroemFeaturesTransformer` | Nystroem kernel map (multivariate) | Landmark-based kernel approximation | -### Encoding and binning +### Numerical encoding | Transformer | Method | Best for | | ------------------------------- | ------------------------------------------ | ---------------------------------------- | | `PLETransformer` | Piecewise-linear encoding (supervised) | Strong numerical encoding for models | | `NumericBinningTransformer` | Uniform/quantile binning, tree-driven | Discretizing numerical columns | +| `PeriodicEncodingTransformer` | Sine/cosine cyclic encoding | Values that wrap around a known period | + +### Categorical encoding and embeddings + +| Transformer | Method | Best for | +| ------------------------------- | ------------------------------------------ | ---------------------------------------- | | `ContinuousOrdinalTransformer` | Integer (ordinal) encoding | Compact codes for categoricals | | `LanguageEmbeddingTransformer` | Pretrained language embeddings | High-cardinality, semantic columns | @@ -131,7 +144,9 @@ PreTab groups its transformers into three families. Each one follows the standar > **Note:** Inside the `Preprocessor` you select these by short name, for example `"ple"`, > `"rbf"`, `"one-hot"`, `"pretrained"`. See > [Representations](https://pretab.readthedocs.io/en/latest/representations/overview.html) for -> the full catalogue and [comparison table](https://pretab.readthedocs.io/en/latest/representations/comparison_table.html). +> the full catalogue, including exact input/output shapes and per-parameter effects, and the +> [comparison table](https://pretab.readthedocs.io/en/latest/representations/comparison_table.html) +> to filter by capability. ## 📚 Documentation diff --git a/docs/homepage.md b/docs/homepage.md index de01e60..b175009 100644 --- a/docs/homepage.md +++ b/docs/homepage.md @@ -120,7 +120,7 @@ See PreTab lift a linear model, baseline vs. PreTab. :::{grid-item-card} Representations :link: representations/overview :link-type: doc -The full catalogue of splines, feature maps, and encoders. +The full catalogue of spline and functional expansions, kernel approximations, and encoders. ::: :::{grid-item-card} API reference diff --git a/docs/representations/categorical_encoding.md b/docs/representations/categorical_encoding.md index ed6d26d..7ccb8bd 100644 --- a/docs/representations/categorical_encoding.md +++ b/docs/representations/categorical_encoding.md @@ -11,14 +11,19 @@ The default categorical method maps each category to an integer. It is compact a as an input to models that consume category indices, such as embedding layers. ```python +import numpy as np from pretab.transformers import ContinuousOrdinalTransformer +X = np.array([["a"], ["b"], ["a"], ["c"]]) # (4, 1) t = ContinuousOrdinalTransformer() -X2 = t.fit_transform(x) +t.fit_transform(X).ravel() +# array([1, 2, 1, 3]) codes start at 1; output shape stays (4, 1) +t.transform(np.array([["unseen"]])).ravel() +# array([0]) unseen categories map to the reserved 0 code ``` -Unseen categories at transform time map to a reserved slot rather than raising, so a model in -production never crashes on a new label. +Unseen categories at transform time map to a reserved slot (code `0`) rather than raising, so a +model in production never crashes on a new label. ```{note} Integer encoding imposes an order on the codes. Feed it to models that treat the code as an @@ -32,16 +37,24 @@ One-hot encoding produces one indicator column per category, the right choice wh downstream model should treat categories as unordered. ```python +import pandas as pd +from pretab import Preprocessor + +df = pd.DataFrame({"color": ["red", "blue", "green", "red"]}) pre = Preprocessor(categorical_method="one-hot") +pre.fit(df) +pre.get_feature_names_out() +# array(['cat_color_blue', 'cat_color_green', 'cat_color_red'], dtype=object) ``` The alias `ohe` resolves to `one-hot`. There is also `onehot_from_ordinal`, which one-hot encodes an already integer-coded column. ```{warning} -One-hot width grows with cardinality. A column with thousands of categories produces thousands -of columns. Use the [output budget](../core_concepts/outputs_and_inspection.md) to cap it, or -prefer integer encoding or [embeddings](embeddings.md) for high-cardinality columns. +One-hot width grows with cardinality: a column with `k` distinct categories produces `k` +output columns (3 in the example above). A column with thousands of categories produces +thousands of columns. Use the [output budget](../core_concepts/outputs_and_inspection.md) to +cap it, or prefer integer encoding or [embeddings](embeddings.md) for high-cardinality columns. ``` ## Choosing a categorical method diff --git a/docs/representations/comparison_table.md b/docs/representations/comparison_table.md index 73e8591..8b4f485 100644 --- a/docs/representations/comparison_table.md +++ b/docs/representations/comparison_table.md @@ -41,9 +41,9 @@ source of truth, and these tables mirror it. | Method | Key | Scope | Target | Adaptive | Penalty | Selectable | | --- | --- | --- | --- | --- | --- | --- | -| B-spline | `bspline` | univariate | optional | yes | no | yes | -| M-spline | `mspline` | univariate | optional | yes | no | yes | -| I-spline | `ispline` | univariate | optional | yes | no | yes | +| B-spline | `bspline` | univariate | optional | yes | yes | yes | +| M-spline | `mspline` | univariate | optional | yes | yes | yes | +| I-spline | `ispline` | univariate | optional | yes | yes | yes | | Cubic regression spline | `cubicspline` | univariate | optional | yes | yes | yes | | Natural cubic spline | `naturalspline` | univariate | optional | yes | yes | yes | | Penalized spline (P-spline) | `pspline` | univariate | forbidden | yes | yes | yes | diff --git a/docs/representations/embeddings.md b/docs/representations/embeddings.md index 538a05a..bd56f69 100644 --- a/docs/representations/embeddings.md +++ b/docs/representations/embeddings.md @@ -11,11 +11,15 @@ other in the embedding space. from pretab.transformers import LanguageEmbeddingTransformer t = LanguageEmbeddingTransformer(model_name="paraphrase-MiniLM-L3-v2") -X2 = t.fit_transform(x) +X = [["red running shoes"], ["blue jacket"], ["red running shoes"]] # 3 rows, 1 column +t.fit_transform(X).shape +# (3, embedding_dim_): one row per input; embedding_dim_ is set from the loaded +# model's own dimensionality once fitted, e.g. via t.embedding_dim_ ``` -Constructor highlights: `model_name="paraphrase-MiniLM-L3-v2"`, or pass a preloaded `model`. -The registry key is `pretrained`. +Constructor highlights: `model_name="paraphrase-MiniLM-L3-v2"`, or pass a preloaded `model` +(any object exposing an `encode(X)` method, useful for tests or a custom embedding backend +without pulling in `sentence-transformers`). The registry key is `pretrained`. ```{important} Language embeddings require the optional `embeddings` extra, which pulls in @@ -23,6 +27,13 @@ Language embeddings require the optional `embeddings` extra, which pulls in requesting `pretrained` raises a clear `OptionalDependencyError`. ``` +```{note} +The output width is fixed by the underlying model, not by any PreTab parameter, and is exposed +after fitting as `embedding_dim_`. It does not depend on `n_samples` or on how many distinct +categories are present. Swapping `model_name` for a different model changes the output width +accordingly. +``` + ```{tip} Embeddings shine when category labels carry meaning as text. If the labels are opaque codes with no semantic content, [integer encoding](categorical_encoding.md#integer-ordinal-encoding) diff --git a/docs/representations/functional_expansions.md b/docs/representations/functional_expansions.md index 33c495d..6757768 100644 --- a/docs/representations/functional_expansions.md +++ b/docs/representations/functional_expansions.md @@ -5,6 +5,12 @@ statistics. They spread a feature across a set of activation functions (radial b ramps, sigmoids) or project it onto a deterministic Fourier basis. Together they cover local, threshold, and periodic structure with a per-column, `Preprocessor`-selectable transformer. +```{important} +As with splines, `output_dim` (or `n_frequencies` for the Fourier map) is the number of output +columns **per input feature**. A `(n_samples, 3)` input with `output_dim=10` produces +`(n_samples, 30)` output. +``` + ## Radial basis functions The RBF expansion places centers along the feature range and measures Gaussian similarity to @@ -18,9 +24,13 @@ Each output is a smooth bump around a center, so a linear model on top can build from local pieces. ```python +import numpy as np from pretab.transformers import RBFExpansionTransformer +X = np.linspace(0, 1, 50).reshape(-1, 1) # (50, 1) t = RBFExpansionTransformer(output_dim=10, gamma=1.0) +t.fit_transform(X).shape +# (50, 10) ``` Constructor highlights: `output_dim`, `gamma=1.0` (bump width; larger is narrower), @@ -31,6 +41,13 @@ Constructor highlights: `output_dim`, `gamma=1.0` (bump width; larger is narrowe `gamma` gives broad, overlapping ones. Tune it alongside `output_dim`. ``` +```{warning} +`target_aware=True` places centers using a supervised tree over `(X, y)`. Fitting it directly +on your full training set (outside a `Pipeline` or `pretab.CrossFittedTransformer`) raises a +`LeakageWarning`, because the center placement has already seen the labels you would then train +on. The same applies to ReLU, sigmoid, and tanh below whenever `target_aware=True`. +``` + ## ReLU, sigmoid, and tanh expansions These place a set of thresholds along the range and apply an activation at each, mirroring a @@ -40,13 +57,18 @@ ReLU : Piecewise-linear ramps. Excellent for sharp, threshold-like effects. Sigmoid and Tanh -: Smooth saturating steps. `scale` controls the steepness of the transition. +: Smooth saturating steps. `scale` controls the steepness of the transition: **smaller** values + give a sharper, more step-like transition; **larger** values spread it out. ```python +import numpy as np from pretab.transformers import ReLUExpansionTransformer, TanhExpansionTransformer +X = np.linspace(0, 1, 50).reshape(-1, 1) relu = ReLUExpansionTransformer(output_dim=10) tanh = TanhExpansionTransformer(output_dim=10, scale=1.0) +relu.fit_transform(X).shape # (50, 10) +tanh.fit_transform(X).shape # (50, 10) ``` ```{note} @@ -60,14 +82,24 @@ The Fourier map represents a feature with sines and cosines at a set of frequenc signals with cyclical structure. ```python +import numpy as np from pretab.transformers import FourierFeatureTransformer +X = np.linspace(0, 1, 50).reshape(-1, 1) t = FourierFeatureTransformer(n_frequencies=5, frequency_strategy="harmonic") +t.fit_transform(X).shape +# (50, 10): 2 columns (sin, cos) per frequency ``` Constructor highlights: `n_frequencies=5`, `frequency_strategy="harmonic"`, `include_original=False`, `random_state`. +**Parameter impact.** `n_frequencies` sets the output width to `2 * n_frequencies` (one sine +and one cosine column per frequency); `include_original=True` adds one more column for the raw +value, giving `2 * n_frequencies + 1`. `frequency_strategy="harmonic"` uses integer multiples of +the base frequency (1x, 2x, 3x, ...); the alternative spacing is useful when the signal is not +a clean harmonic series. + ```{tip} Use `FourierFeatureTransformer` when you want the model to work across a set of frequencies without committing to a single known period. If the period is known (hour of day, month of diff --git a/docs/representations/kernel_approximation.md b/docs/representations/kernel_approximation.md index 92f2946..52b08ad 100644 --- a/docs/representations/kernel_approximation.md +++ b/docs/representations/kernel_approximation.md @@ -14,10 +14,13 @@ cost of the approximation does not grow with the number of training points the w kernel method's does. ```python +import numpy as np from pretab.transformers import RandomFourierFeaturesTransformer +X = np.random.default_rng(0).uniform(size=(200, 3)) # (200, 3): the whole feature block t = RandomFourierFeaturesTransformer(n_components=100, gamma=1.0) -X2 = t.fit_transform(X) +t.fit_transform(X).shape +# (200, 100): n_components columns, independent of the number of input features ``` Constructor highlights: `n_components=100`, `gamma=1.0`, `random_state`. @@ -36,15 +39,27 @@ more accurate than random Fourier features at a given output width because the l to the data rather than being drawn at random. ```python +import numpy as np from pretab.transformers import NystroemFeaturesTransformer +X = np.random.default_rng(0).uniform(size=(200, 3)) t = NystroemFeaturesTransformer(n_components=100, kernel="rbf") -X2 = t.fit_transform(X) +t.fit_transform(X).shape +# (200, 100) ``` Constructor highlights: `n_components=100`, `kernel="rbf"`, `gamma=None`, `degree=3`, `coef0=1`, `random_state`. +```{warning} +Nyström samples its landmarks from the training rows, so `n_components` cannot exceed +`n_samples`. If you fit on fewer rows than `n_components` (for example a small +cross-validation fold), scikit-learn silently clamps `n_components` down to `n_samples` and +emits a `UserWarning` rather than raising: the fitted output width is `min(n_components, +n_samples_seen_in_fit)`. Random Fourier features have no such limit, since they draw a random +basis instead of sampling training rows. +``` + ```{note} Both methods approximate the same idea from different angles: random Fourier features draw a random basis independent of the data, while Nyström samples landmarks from the data itself. diff --git a/docs/representations/numerical_encoding.md b/docs/representations/numerical_encoding.md index bb7930e..f8b2a72 100644 --- a/docs/representations/numerical_encoding.md +++ b/docs/representations/numerical_encoding.md @@ -12,22 +12,34 @@ Numeric binning splits a feature into intervals and encodes which interval each into. You choose how the edges are placed and how the result is encoded. ```python +import numpy as np from pretab.transformers import NumericBinningTransformer -t = NumericBinningTransformer(output_dim=8, encode="onehot", placement_strategy="quantile") +X = np.random.default_rng(0).uniform(size=(100, 1)) # (100, 1) +onehot = NumericBinningTransformer(output_dim=8, encode="onehot", placement_strategy="quantile") +onehot.fit_transform(X).shape +# (100, 8): one column per bin + +ordinal = NumericBinningTransformer(output_dim=8, encode="ordinal", placement_strategy="quantile") +ordinal.fit_transform(X).shape +# (100, 1): a single integer column, regardless of output_dim ``` -The `encode` parameter selects the output form. +The `encode` parameter selects the output form, and it changes the output **width**, not just +the values: `"onehot"` produces `output_dim` columns, while `"ordinal"` and `"soft"` behave +differently from each other despite both accepting the same `output_dim`. `"ordinal"` -: A single integer column giving the bin index. +: A single integer column giving the bin index (output width is always 1, independent of + `output_dim`). `"onehot"` -: One indicator column per bin. +: One indicator column per bin (output width equals `output_dim`). `"soft"` : A soft assignment that spreads each value across neighbouring bins, so the boundaries are not - hard. This keeps a little of the smoothness that hard binning discards. + hard (output width equals `output_dim`, same shape as `"onehot"` but with fractional + membership instead of a single 1). Edge placement follows `placement_strategy`: `"uniform"` for equal-width bins, `"quantile"` for equal-frequency bins. See @@ -46,10 +58,16 @@ position within its bin**. The result is a piecewise-linear function that bends the target changes, following the tabular deep-learning work of Gorishniy and colleagues. ```python +import numpy as np from pretab.transformers import PLETransformer -t = PLETransformer(output_dim=12, task="regression") -X2 = t.fit_transform(x, y) # y is required +X = np.random.default_rng(0).uniform(size=(100, 1)) +y = np.random.default_rng(0).integers(0, 2, size=100) +t = PLETransformer(output_dim=12, task="classification") +t.fit_transform(X, y).shape +# (100, 12): output_dim is exact here (no adaptive clamping) +t.total_output_dim_ +# 12 ``` Constructor highlights: `output_dim`, `placement_strategy="cart"`, `task="regression"`, @@ -61,6 +79,13 @@ and should be fit leakage-safely, ideally with cross-fitting. See [Target awareness](../core_concepts/target_awareness.md). ``` +```{warning} +Because PLE always reads `y` to place its bins, fitting it directly on data you will also train +on emits a `LeakageWarning`. Fit it inside a scikit-learn `Pipeline` or wrap it in +`pretab.CrossFittedTransformer` so the bin edges never see the rows they will later transform +for training. +``` + ### Why piecewise-linear rather than one-hot Plain binning throws away where a value sits inside its bin; two values in the same interval @@ -82,14 +107,30 @@ keeps the boundary continuous, so December and January sit next to each other in opposite ends of a number line. ```python +import numpy as np from pretab.transformers import PeriodicEncodingTransformer -t = PeriodicEncodingTransformer(period=12, harmonics=2) # e.g. month of year +X = np.array([[0], [6], [12], [18], [24]]) # hour-of-day style values +t = PeriodicEncodingTransformer(period=24, harmonics=2) # e.g. hour of day +t.fit_transform(X).shape +# (5, 4): 2 columns (sin, cos) per harmonic ``` Constructor highlights: `period` (required, the cycle length), `harmonics=1`, `include_original=False`. +**Parameter impact.** Output width is `2 * harmonics` (one sine/cosine pair per harmonic), plus +one extra column when `include_original=True`. Higher `harmonics` lets the encoding represent +finer-grained sub-cycles (for example distinguishing morning from afternoon within a day), at +the cost of a wider output. + +```{important} +Valid input is the **closed interval** `[0, period]`: both endpoints are accepted, and by +construction they map to the identical `(sin, cos)` pair, since `x=0` and `x=period` are the +same point on the cycle. Values outside `[0, period]` raise a `PretabDataError` at fit and +transform, there is no silent wrap-around or clamping. +``` + ```{note} Periodic encoding is a standalone time-series utility. It is not wired into `Preprocessor` because it requires a per-feature `period`, so apply it directly to the relevant cyclical diff --git a/docs/representations/overview.md b/docs/representations/overview.md index 7cfdc5b..a588b65 100644 --- a/docs/representations/overview.md +++ b/docs/representations/overview.md @@ -50,13 +50,6 @@ Ordinal and one-hot encoding for categories, handling unseen values without rais Pretrained language embeddings for high-cardinality text categories. ::: -:::{grid-item-card} Preprocessing utilities -:link: preprocessing_utilities -:link-type: doc -Supporting transformers `Preprocessor` wires in automatically: pass-through, type conversion, -and missing-value flagging. -::: - :::: ## Shared terminology diff --git a/docs/representations/preprocessing_utilities.md b/docs/representations/preprocessing_utilities.md index 8fed666..affa43f 100644 --- a/docs/representations/preprocessing_utilities.md +++ b/docs/representations/preprocessing_utilities.md @@ -19,20 +19,33 @@ methods, letting a column skip representation entirely while still satisfying th scikit-learn transformer API. ```python +import numpy as np from pretab.transformers import NoTransformer +X = np.zeros((5, 3)) # (5, 3) t = NoTransformer() -X2 = t.fit_transform(X) # X2 is X, unmodified +t.fit_transform(X).shape +# (5, 3): identical to the input, values and width both unchanged ``` `ToFloatTransformer` casts its input to floating point. `Preprocessor` appends it after one-hot encoding so the categorical block has the same dtype as the rest of the design matrix. ```python +import numpy as np from pretab.transformers import ToFloatTransformer +X = np.array([[1], [2], [3]]) # (3, 1), integer dtype t = ToFloatTransformer() -t.fit_transform(X).dtype # dtype('float64') +out = t.fit_transform(X) +out.shape, out.dtype +# ((3, 1), dtype('float64')): width unchanged, only the dtype changes +``` + +```{note} +Neither utility has an `output_dim`-style width parameter: unlike every expansion or encoding +family elsewhere in this section, the output always has the exact same number of columns as +the input. ``` ## Missing-value flagging diff --git a/docs/representations/spline_expansions.md b/docs/representations/spline_expansions.md index 7b20cbb..dc0e3b1 100644 --- a/docs/representations/spline_expansions.md +++ b/docs/representations/spline_expansions.md @@ -19,26 +19,63 @@ a point in one region does not disturb the fit in another. Width is set by `outp knot positions by `placement_strategy` (see [Resolution and placement](../core_concepts/resolution_and_placement.md)). +```{important} +For every univariate spline in this section, `output_dim` is the number of output columns +**per input feature**, not the total. A `(n_samples, 3)` input produces +`(n_samples, 3 * output_dim)` output (plus one extra column per feature if +`include_bias=True`). Feature names are suffixed per input, for example `x0_bs0, x0_bs1, ...` +for a B-spline on column `x0`. +``` + ## B-spline The B-spline is the default general-purpose smooth basis. Its functions are non-negative, sum to one, and each spans only `degree + 1` knot intervals. ```python +import numpy as np from pretab.transformers import BSplineTransformer -t = BSplineTransformer(output_dim=13, degree=3, placement_strategy="quantile") +X = np.linspace(0, 1, 50).reshape(-1, 1) # (50, 1) +t = BSplineTransformer(output_dim=8, degree=3, placement_strategy="quantile") +t.fit_transform(X).shape +# (50, 8) ``` Constructor highlights: `output_dim`, `degree=3`, `include_bias=False`, `knot_locations=None` (pass explicit knots to override placement), `target_aware=False`, `placement_strategy="quantile"`, `adaptive`, `random_state`. +**Parameter impact.** + +`degree` +: Sets the minimum usable `output_dim`: PreTab requires `output_dim >= degree + 1` (a cubic, + `degree=3`, needs at least 4 columns) and raises a typed error otherwise. Higher degree gives + smoother, wider-support basis functions at the same `output_dim`; `degree=1` recovers + piecewise-linear segments. + +`output_dim` +: The exact per-feature output width (unlike the cubic/natural/tensor families below, no + conversion is applied). More columns track finer local detail and increase overfitting risk. + +`include_bias` +: Defaults to `False`. A B-spline basis over a clamped knot vector already sums to 1 in every + row (a partition of unity), so prepending a bias column makes the design exactly + rank-deficient. Set `include_bias=True` only if a downstream model specifically needs an + explicit intercept column; it adds one extra output column. + ```{tip} Cubic (`degree=3`) B-splines with quantile knots are a strong default for smooth regression. Increase `output_dim` for more wiggle, decrease it to regularize. ``` +```{note} +Every spline in this family also exposes `get_penalty_matrix(feature_index=0, diff_order=2)`, +a `D^T D` second-difference penalty matrix of shape `(output_dim, output_dim)` (plus the bias +row/column left unpenalized when `include_bias=True`). It is not limited to the "penalized" +splines further down this page. +``` + ## M-spline and I-spline These two share the B-spline machinery but target special shapes. @@ -53,11 +90,18 @@ I-spline domain knowledge says a relationship cannot reverse. ```python +import numpy as np from pretab.transformers import ISplineTransformer +X = np.linspace(0, 1, 50).reshape(-1, 1) t = ISplineTransformer(output_dim=10, degree=3) # monotone basis +t.fit_transform(X).shape +# (50, 10) ``` +Both share the same `degree`/`output_dim` constraint and parameter set as the B-spline above +(`output_dim >= degree + 1`, `include_bias=False` by default). + ```{note} I-splines only guarantee monotonicity when the downstream coefficients are constrained to be non-negative. Pair them with a non-negative linear model. @@ -70,17 +114,23 @@ smoothing penalty through `get_penalty_matrix()`. Cubic regression spline : A cubic basis parameterized at the knots (`cubicspline`), convenient for GAM-style additive - models. + models. Requires `output_dim >= 3`. Natural cubic spline : A cubic spline constrained to be **linear beyond the boundary knots** (`naturalspline`). The linear tails reduce the wild behaviour ordinary cubics show near the edges of the data. + Requires `output_dim >= 2`. ```python +import numpy as np from pretab.transformers import NaturalCubicSplineTransformer -t = NaturalCubicSplineTransformer(output_dim=12) -penalty = t.get_penalty_matrix() # for smoothing penalties +X = np.linspace(0, 1, 50).reshape(-1, 1) +t = NaturalCubicSplineTransformer(output_dim=8) +X2 = t.fit_transform(X) +X2.shape # (50, 8): output width equals output_dim exactly +t.n_knots_ # [7]: fitted interior-knot count per feature (output_dim - 1) +t.get_penalty_matrix().shape # (8, 8) ``` ```{tip} @@ -95,16 +145,24 @@ following Eilers and Marx. Instead of controlling smoothness only through the nu it uses many knots and a penalty of order `diff_order` to keep the fit smooth. ```python +import numpy as np from pretab.transformers import PSplineTransformer -t = PSplineTransformer(output_dim=20, degree=3, diff_order=2) -penalty = t.get_penalty_matrix() +X = np.linspace(0, 1, 50).reshape(-1, 1) +t = PSplineTransformer(output_dim=8, degree=3, diff_order=2) +t.fit_transform(X).shape # (50, 8) +t.get_penalty_matrix().shape # (8, 8) ``` Constructor highlights: `output_dim`, `degree=3`, `diff_order=2`, `include_bias=False`, `placement_strategy="uniform"`, `adaptive`. The P-spline is unsupervised; it does not read the target. +**Parameter impact.** `diff_order` sets the order of the penalty: `diff_order=1` penalizes +changes in level between adjacent coefficients (favors flat fits), `diff_order=2` (the default) +penalizes changes in slope (favors locally-linear fits), and higher orders favor progressively +smoother curves. Requires `output_dim >= degree + 1`, the same floor as B-spline. + ```{note} The P-spline decouples smoothness from knot count. Use a generous `output_dim` and let the penalty do the regularizing. Its penalty matrix plugs directly into penalized linear models. @@ -118,13 +176,25 @@ through `Preprocessor`. ### Tensor-product spline Builds a joint basis over multiple inputs as the tensor product of per-axis bases, capturing -interactions on a smooth grid. It exposes an anisotropic penalty. +interactions on a smooth grid. It exposes an anisotropic penalty per marginal via +`get_penalty_matrix(feature_index=...)`. ```python +import numpy as np from pretab.transformers import TensorProductSplineTransformer -t = TensorProductSplineTransformer(output_dim=8, degree=3, diff_order=2) -X2 = t.fit_transform(X[["lat", "lon"]]) +rng = np.random.default_rng(0) +X2 = rng.uniform(-3, 3, size=(200, 2)) # two input columns, e.g. lat/lon +t = TensorProductSplineTransformer(output_dim=5, degree=3, diff_order=2) +t.fit_transform(X2).shape # (200, 25) +t.get_penalty_matrix(feature_index=0).shape # (5, 5), one marginal +``` + +```{warning} +`output_dim` here is **per input dimension**, and the total width is `output_dim ** n_dims`. +With two columns and `output_dim=5` the result has `5 ** 2 = 25` columns; with three columns it +would be `125`. Keep `output_dim` small as the number of joint inputs grows, or the output width +explodes. ``` ### Thin-plate spline @@ -133,10 +203,13 @@ A thin-plate regression spline, the smooth-surface method from generalized addit places landmarks (by default with k-means) and forms a low-rank basis. ```python +import numpy as np from pretab.transformers import ThinPlateSplineTransformer +rng = np.random.default_rng(0) +X2 = rng.uniform(-3, 3, size=(200, 2)) t = ThinPlateSplineTransformer(n_components=10, landmark_strategy="kmeans") -X2 = t.fit_transform(X[["lat", "lon"]]) +t.fit_transform(X2).shape # (200, 10): output width is n_components, not input-dependent ``` Constructor highlights: `n_components=10`, `landmark_strategy="kmeans"`, `rank_strategy="eigen"`, From ea506e26bd90d6e39456d1fc0b8b336b3ff647a2 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 11:17:57 +0200 Subject: [PATCH 12/14] docs: clarify periodic encoding has no automatic period detection --- docs/representations/numerical_encoding.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/representations/numerical_encoding.md b/docs/representations/numerical_encoding.md index f8b2a72..01bc6f8 100644 --- a/docs/representations/numerical_encoding.md +++ b/docs/representations/numerical_encoding.md @@ -124,6 +124,13 @@ one extra column when `include_original=True`. Higher `harmonics` lets the encod finer-grained sub-cycles (for example distinguishing morning from afternoon within a day), at the cost of a wider output. +```{warning} +PreTab has no mechanism to detect the period automatically from the data. `period` is a +required constructor argument with no default, and `fit` only validates that values fall +within `[0, period]`, it never infers the cycle length. You must know and supply the period +yourself (24 for hour of day, 7 for day of week, 12 for month of year, and so on). +``` + ```{important} Valid input is the **closed interval** `[0, period]`: both endpoints are accepted, and by construction they map to the identical `(sin, cos)` pair, since `x=0` and `x=period` are the From f1365afb083140b21160737a7fac5e001907659d Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 11:22:29 +0200 Subject: [PATCH 13/14] docs: add math formulas for functional expansions and kernel approximation --- docs/representations/functional_expansions.md | 21 +++++++++++++++-- docs/representations/kernel_approximation.md | 23 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/representations/functional_expansions.md b/docs/representations/functional_expansions.md index 6757768..42e845c 100644 --- a/docs/representations/functional_expansions.md +++ b/docs/representations/functional_expansions.md @@ -51,7 +51,13 @@ on. The same applies to ReLU, sigmoid, and tanh below whenever `target_aware=Tru ## ReLU, sigmoid, and tanh expansions These place a set of thresholds along the range and apply an activation at each, mirroring a -single hidden layer. +single hidden layer. For a feature $x$ and center $c_k$ (with `scale` $s$ for sigmoid and tanh), + +$$ +\text{ReLU: } \phi_k(x) = \max(0,\ x - c_k), \qquad +\text{Sigmoid: } \phi_k(x) = \frac{1}{1 + \exp\!\big(-(x - c_k)/s\big)}, \qquad +\text{Tanh: } \phi_k(x) = \tanh\!\big((x - c_k)/s\big). +$$ ReLU : Piecewise-linear ramps. Excellent for sharp, threshold-like effects. @@ -79,7 +85,18 @@ example a fee that applies only above a limit. ## Fourier features The Fourier map represents a feature with sines and cosines at a set of frequencies, ideal for -signals with cyclical structure. +signals with cyclical structure. For a feature $x$ with fitted origin $x_0$ (the observed +minimum) and angular frequency $\omega_k$, + +$$ +\phi_k(x) = \big(\sin(\omega_k (x - x_0)),\ \cos(\omega_k (x - x_0))\big). +$$ + +The fundamental frequency is set from the feature's observed range at fit time +($2\pi / \text{range}$), and `frequency_strategy` controls how the $\omega_k$ are spread above +it: `"harmonic"` uses integer multiples $k \cdot \omega_1$; `"log_spaced"` uses octaves +$2^{k-1} \cdot \omega_1$; `"random"` draws frequencies from a half-normal distribution scaled by +$\omega_1$. ```python import numpy as np diff --git a/docs/representations/kernel_approximation.md b/docs/representations/kernel_approximation.md index 52b08ad..af660a0 100644 --- a/docs/representations/kernel_approximation.md +++ b/docs/representations/kernel_approximation.md @@ -11,7 +11,16 @@ selectable per column through `Preprocessor`. Approximates a shift-invariant kernel (by default the RBF kernel) with random projections, following Rahimi and Recht. This makes kernel-style models scale to large datasets, since the cost of the approximation does not grow with the number of training points the way an exact -kernel method's does. +kernel method's does. For an input vector $x$, each output column draws a random weight vector +$w_k \sim \mathcal{N}(0,\ 2\gamma I)$ and offset $b_k \sim \mathrm{Uniform}(0, 2\pi)$ at fit time, +then computes + +$$ +\phi_k(x) = \sqrt{\frac{2}{n_{\text{components}}}}\ \cos\!\big(w_k^\top x + b_k\big). +$$ + +The inner product $\phi(x)^\top \phi(x')$ approximates the RBF kernel +$\exp(-\gamma \lVert x - x' \rVert^2)$ in expectation over the random draw. ```python import numpy as np @@ -36,7 +45,17 @@ performance is still improving. Approximates a kernel by sampling landmark points from the training data and projecting onto them, following Williams and Seeger. It supports several kernels through `kernel`, and is often more accurate than random Fourier features at a given output width because the landmarks adapt -to the data rather than being drawn at random. +to the data rather than being drawn at random. For landmarks $z_1, \dots, z_m$ (the sampled +training rows) and kernel function $K$, let $k_m(x) = \big(K(x, z_1), \dots, K(x, z_m)\big)$ be +the vector of kernel evaluations between $x$ and every landmark. The output is + +$$ +\phi(x) = K_{mm}^{-1/2}\ k_m(x), +$$ + +where $K_{mm}$ is the $m \times m$ kernel matrix between the landmarks themselves, and +$K_{mm}^{-1/2}$ is computed once at fit time via its eigendecomposition. The inner product +$\phi(x)^\top \phi(x')$ approximates $K(x, x')$. ```python import numpy as np From 640f6c3ebdfdd5e478df6c202c1bf060e0e59d64 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Fri, 28 Aug 2026 11:33:46 +0200 Subject: [PATCH 14/14] docs: add per-family math formulas to spline expansions --- docs/representations/spline_expansions.md | 67 ++++++++++++++++++++--- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/docs/representations/spline_expansions.md b/docs/representations/spline_expansions.md index dc0e3b1..ce2e28b 100644 --- a/docs/representations/spline_expansions.md +++ b/docs/representations/spline_expansions.md @@ -30,7 +30,13 @@ for a B-spline on column `x0`. ## B-spline The B-spline is the default general-purpose smooth basis. Its functions are non-negative, -sum to one, and each spans only `degree + 1` knot intervals. +sum to one, and each spans only `degree + 1` knot intervals. For knots $\tau$ and degree $p$, +the basis follows the standard Cox-de Boor recursion, + +$$ +B_{i,0}(x) = \begin{cases} 1 & \tau_i \le x < \tau_{i+1} \\ 0 & \text{otherwise} \end{cases}, \qquad +B_{i,p}(x) = \frac{x - \tau_i}{\tau_{i+p} - \tau_i} B_{i,p-1}(x) + \frac{\tau_{i+p+1} - x}{\tau_{i+p+1} - \tau_{i+1}} B_{i+1,p-1}(x). +$$ ```python import numpy as np @@ -82,13 +88,22 @@ These two share the B-spline machinery but target special shapes. M-spline : A non-negative spline basis (`include_bias=False`). Useful when the components themselves - should be non-negative, for example as a density-like basis. + should be non-negative, for example as a density-like basis. Built by rescaling each B-spline + basis function so it integrates to one over its support, + + $$ + M_k(x) = \frac{p + 1}{\tau_{k+p+1} - \tau_k}\, B_k(x). + $$ I-spline : The integral of an M-spline, giving a **monotone** basis. A model with non-negative coefficients on an I-spline basis is guaranteed monotone in the input, which is valuable when domain knowledge says a relationship cannot reverse. + $$ + I_k(x) = \int_{\tau_k}^{x} M_k(t)\, dt. + $$ + ```python import numpy as np from pretab.transformers import ISplineTransformer @@ -114,12 +129,23 @@ smoothing penalty through `get_penalty_matrix()`. Cubic regression spline : A cubic basis parameterized at the knots (`cubicspline`), convenient for GAM-style additive - models. Requires `output_dim >= 3`. + models. Requires `output_dim >= 3`. The basis stacks the polynomial terms with one truncated + cubic term per interior knot $\kappa_j$, + + $$ + \big(x,\ x^2,\ x^3,\ (x - \kappa_1)_+^3,\ \dots,\ (x - \kappa_K)_+^3\big), \qquad (z)_+ = \max(0, z). + $$ Natural cubic spline : A cubic spline constrained to be **linear beyond the boundary knots** (`naturalspline`). The linear tails reduce the wild behaviour ordinary cubics show near the edges of the data. - Requires `output_dim >= 2`. + Requires `output_dim >= 2`. For knots $\xi_1, \dots, \xi_T$ ($\xi_1$, $\xi_T$ the boundary + knots), the basis stacks $x$ with one constrained term per interior knot $\xi_k$, + + $$ + d_k(x) = \frac{(x - \xi_k)_+^3 - (x - \xi_T)_+^3}{\xi_T - \xi_k}, \qquad + N_k(x) = d_k(x) - \frac{\xi_T - \xi_k}{\xi_T - \xi_1} d_1(x) - \frac{\xi_k - \xi_1}{\xi_T - \xi_1} d_T(x). + $$ ```python import numpy as np @@ -142,7 +168,17 @@ tails behave far better than an unconstrained cubic there. The P-spline combines a B-spline basis with a difference penalty on adjacent coefficients, following Eilers and Marx. Instead of controlling smoothness only through the number of knots, -it uses many knots and a penalty of order `diff_order` to keep the fit smooth. +it uses many knots and a penalty of order `diff_order` to keep the fit smooth. The basis +functions $B_k$ are exactly the B-spline basis above; fitting a linear model with coefficients +$\beta$ on top penalizes the loss with + +$$ +\lambda\, \beta^\top D^\top D\, \beta, +$$ + +where $D$ is the `diff_order`-th order difference operator (the same matrix returned by +`get_penalty_matrix()`) and $\lambda$ is chosen by the downstream penalized model, not by +`PSplineTransformer` itself. ```python import numpy as np @@ -176,7 +212,15 @@ through `Preprocessor`. ### Tensor-product spline Builds a joint basis over multiple inputs as the tensor product of per-axis bases, capturing -interactions on a smooth grid. It exposes an anisotropic penalty per marginal via +interactions on a smooth grid. Each per-axis marginal is a B-spline basis (above), and the +joint basis function for multi-index $(k_1, \dots, k_d)$ over $d$ input columns $x_1, \dots, x_d$ +is their product, + +$$ +\Phi_{k_1, \dots, k_d}(x_1, \dots, x_d) = \prod_{j=1}^{d} B_{k_j}(x_j). +$$ + +It exposes an anisotropic penalty per marginal via `get_penalty_matrix(feature_index=...)`. ```python @@ -200,7 +244,16 @@ explodes. ### Thin-plate spline A thin-plate regression spline, the smooth-surface method from generalized additive models. It -places landmarks (by default with k-means) and forms a low-rank basis. +places landmarks (by default with k-means) and forms a low-rank basis from the leading +eigenvectors of a projected radial-kernel matrix between the data and the landmarks. The radial +kernel $\eta(r)$ depends on the input dimension $d$: + +$$ +\eta(r) = \begin{cases} r^3 & d = 1 \\ r^2 \log r & d = 2 \\ r & d \ge 3 \end{cases} +$$ + +where $r = \lVert x - z_j \rVert$ is the distance from $x$ to landmark $z_j$. This follows the +low-rank thin-plate regression spline of Wood (2003). ```python import numpy as np