Skip to content

Feature Pipelines

Scikit-Learn's Pipeline and FeatureUnion are the standard tools for transformer composition in the Python ML ecosystem. Yohou provides its own counterparts (FeaturePipeline, FeatureUnion, ColumnTransformer) because time series transformers carry obligations that Scikit-Learn's containers do not enforce. The most important is the observation_horizon: a stateful transformer needs a certain number of past observations to produce valid output. When transformers are composed, the container must propagate that requirement so that forecasters know how much history to retain. Scikit-Learn's Pipeline has no concept of temporal lookback and would discard this information. The second obligation is the "time" column: time series DataFrames carry a timestamp column that must pass through every transformer step without being treated as a regular feature. The third is statefulness: each transformer may maintain an internal memory buffer through observe and rewind, and the composition classes propagate those calls to every component. Yohou's composition classes handle all three automatically.

Three patterns cover the common cases. Sequential composition (FeaturePipeline) chains steps in order. Parallel composition (FeatureUnion, ColumnTransformer) runs steps independently on the same data and concatenates the results.

For composing forecasters rather than transformers, see Forecaster Composition.

FeaturePipeline

FeaturePipeline chains transformers sequentially: each transformer's output feeds into the next. This is the time series equivalent of sklearn's Pipeline, but it respects the temporal contract.

Use FeaturePipeline when preprocessing steps must execute in order, such as imputation followed by scaling followed by lag feature extraction.

from yohou.compose import FeaturePipeline
from yohou.preprocessing import SimpleTimeImputer, StandardScaler, LagTransformer

transformer = FeaturePipeline(steps=[
    ("impute", SimpleTimeImputer()),
    ("scale", StandardScaler()),
    ("lags", LagTransformer(lag=[1, 2, 3])),
])

Order matters here. Imputing after scaling would leave NaN gaps in the scaled data; computing lag features before scaling would mix raw and scaled values. FeaturePipeline enforces this ordering while propagating observation_horizon and state requirements from each step.

FeaturePipeline also supports inverse_transform: it applies each step's inverse in reverse order, which is needed when a forecaster must map predictions back through a target_transformer. This requires every step in the pipeline to be invertible.

The optional memory parameter accepts a joblib.Memory instance (or a path string) to cache fitted transformers, which can speed up repeated fits during hyperparameter search.

FeatureUnion

FeatureUnion runs multiple transformers in parallel and concatenates their outputs column-wise. This is useful when you want features from different sources: lag features alongside rolling statistics alongside calendar features.

from yohou.compose import FeatureUnion
from yohou.preprocessing import LagTransformer, RollingStatisticsTransformer

transformer = FeatureUnion(transformer_list=[
    ("lags", LagTransformer(lag=[1, 7, 14])),
    ("rolling", RollingStatisticsTransformer(window_size=7)),
])

Each transformer receives the same input independently, so there is no ordering dependency. The transformer_weights parameter allows scaling each transformer's output by a factor, and n_jobs enables parallel execution across transformers.

When verbose_feature_names_out=True (the default), output columns are prefixed with the transformer name using a single underscore separator (for example, lags_sales). For panel data the prefix is inserted after the group separator, so store_1__sales becomes store_1__lags_sales rather than lags_store_1__sales. This preserves the panel column convention.

ColumnTransformer

ColumnTransformer applies different transformers to different column subsets. This is the time series analogue of sklearn's ColumnTransformer, adapted for polars DataFrames with a time column. It is useful when different features need different preprocessing: numeric columns might need scaling while categorical columns need encoding.

from yohou.compose import ColumnTransformer
from yohou.preprocessing import FunctionTransformer, StandardScaler

transformer = ColumnTransformer(transformers=[
    ("num", StandardScaler(), ["temperature", "humidity"]),
    ("cat", FunctionTransformer(), ["weather_type"]),
], remainder="passthrough")

Columns not matched by any transformer are handled by the remainder parameter: "drop" (the default) discards them, "passthrough" keeps them unchanged, or a transformer instance applies a default transformation.

Like FeatureUnion, ColumnTransformer supports n_jobs for parallel execution, transformer_weights for scaling outputs, and verbose_feature_names_out for panel-aware column prefixing.

Internally, ColumnTransformer strips the "time" column before routing data to individual transformers, then reattaches it in the final output. This prevents column index mismatches that would occur if sklearn's internal bookkeeping tried to track the time column.

Observation Horizon Propagation

Each transformer declares its observation_horizon: the number of past rows it needs to produce valid output. Stateless transformers have observation_horizon == 0. A LagTransformer(lag=[1, 3]) has observation_horizon == 3 (the maximum lag). A RollingStatisticsTransformer(window_size=7) has observation_horizon == 7.

When you compose transformers, the combined observation_horizon depends on the composition pattern:

FeaturePipeline: the combined observation_horizon is the sum across all steps. Each step's transform drops its first observation_horizon rows from the output, so the next step receives fewer rows. The pipeline must receive enough input for every step to produce at least one valid row after all the dropoffs accumulate. A pipeline of StandardScaler (0) followed by LagTransformer(lag=[1, 2]) (2) has a combined observation_horizon of 2. A pipeline of LagTransformer(lag=[1, 2]) (2) followed by RollingStatisticsTransformer(window_size=3) (3) has a combined horizon of 5.

FeatureUnion: the combined observation_horizon is the maximum across all transformers. Each branch receives the same input, so the bottleneck is the branch that needs the most history.

ColumnTransformer: the combined observation_horizon is the maximum across all transformers applied to their respective columns, following the same logic as FeatureUnion.

Forecasters read observation_horizon from their feature_transformer and target_transformer to determine how much history to retain in _y_observed and _X_observed. Nesting deeply pipelined transformers increases the memory footprint because the pipeline's summed horizon grows with each step. Keeping individual horizons short is advisable for memory-constrained deployments.

State Propagation

Stateful transformers maintain an internal buffer of recent observations so that observe_transform can produce valid output on new data without reprocessing the entire history. The composition classes propagate observe, rewind, observe_transform, and rewind_transform calls to every component:

FeaturePipeline forwards these calls sequentially through each step. When observe_transform is called, the first step observes and transforms the new data, then its output is passed to the next step, and so on. rewind_transform replays the full input from scratch, discards the warmup rows, and resets each step's buffer.

FeatureUnion and ColumnTransformer forward these calls in parallel to all child transformers. Each transformer manages its own buffer independently. The results are aligned by the "time" column before horizontal concatenation, so transformers with different observation_horizon values still produce a consistent output.

observe also validates temporal continuity: new data must follow directly from the last observed timestamp. This prevents silent gaps that would corrupt stateful features.

Composability

Sequential and parallel patterns compose freely. A FeatureUnion can be a step inside a FeaturePipeline, and the combined transformer can serve as the feature_transformer or target_transformer for any forecaster:

from yohou.compose import FeaturePipeline, FeatureUnion
from yohou.preprocessing import StandardScaler, LagTransformer, RollingStatisticsTransformer

transformer = FeaturePipeline(steps=[
    ("scale", StandardScaler()),
    ("features", FeatureUnion(transformer_list=[
        ("lags", LagTransformer(lag=[1, 2, 3, 7])),
        ("rolling", RollingStatisticsTransformer(window_size=7, statistics=["mean", "std"])),
    ])),
])

The observation horizon of this nested structure is the sum of the pipeline's steps: 0 (StandardScaler) + max(7, 7) (FeatureUnion) = 7. State propagation, feature naming, and panel-aware prefixing all carry through the nesting without any additional configuration.

Connections

Preprocessing covers the individual transformers used inside pipelines, including how observe and rewind state works on each transformer. Forecaster Composition discusses composing forecasters rather than transformers. Stationarity transforms that can serve as steps inside a FeaturePipeline are described in Stationarity.

For practical recipes, see How to Compose Feature Pipelines. The compose API is documented in the yohou.compose reference.