7  Building a tabular pipeline

7.1 Introduction

Up until now we have covered how to clean data with the Cleaner, extract features from different column types, and handle categorical features with specialized encoders. In this section we will show how we can combine all these preprocessing techniques into a complete machine learning pipeline.

A pipeline ensures that:

  • Data transformations are applied consistently across training and test sets
  • Data leakage is avoided by fitting transformers only on training data
  • The workflow is reproducible and deployable
  • Preprocessing steps are properly chained together

In this chapter, we explore two approaches: building custom pipelines with TableVectorizer, and using the tabular_pipeline function for quick, well-tuned baselines.

7.2 Manual pipeline construction with TableVectorizer

The TableVectorizer can be the foundation of a custom scikit-learn pipeline, where cleaning and feature engineering are dealt with by a single object. Scaling and imputation are not required by all models, so they are not in the TableVectorizer’s scope.

We combine it with other preprocessing steps and a final estimator:

from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from skrub import TableVectorizer

model = make_pipeline(
    TableVectorizer(),           # Feature engineering
    SimpleImputer(),             # Handle missing values
    StandardScaler(),            # Normalize features
    LogisticRegression()         # Final estimator
)

This approach gives complete control over which preprocessing steps to use and in what order. We can customize the TableVectorizer parameters (cardinality threshold, custom encoders, etc.) and add additional preprocessing steps as needed.

In the case of the example we used LogisticRegression as our estimator, but if we used a different estimator, such as the HistogramGradientBoostingClassifier, the scaling and imputation steps could have been avoided.

7.3 The tabular_pipeline

For many common use cases, we can skip the manual pipeline construction and use the tabular_pipeline function. This function automatically creates an appropriate pipeline based on the estimator we provide:

from skrub import tabular_pipeline
from sklearn.linear_model import LogisticRegression

# Create a complete pipeline for a specific estimator
model = tabular_pipeline(LogisticRegression())

Or, we can use a string to get a pre-configured pipeline with a default estimator:

# Classification with HistGradientBoostingClassifier
model = tabular_pipeline('classification')

# Regression with HistGradientBoostingRegressor
model = tabular_pipeline('regression')

7.4 How tabular_pipeline adapts to different estimators

The tabular_pipeline function configures the preprocessing pipeline based on the estimator type:

7.4.1 For linear models (e.g., LogisticRegression, Ridge)

  • TableVectorizer: Uses the default configuration, except for the addition of spline-encoded datetime features by the DatetimeEncoder
  • SimpleImputer: Added because linear models cannot handle missing values
  • SquashingScaler: Normalizes numeric features to improve convergence and performance
  • Estimator: The provided linear model

This configuration ensures numeric features are properly scaled and missing values are handled appropriately.

7.4.2 For tree-based ensemble models (RandomForest, HistGradientBoosting)

  • TableVectorizer: Configured specifically for tree models
    • Low-cardinality categorical features: Either kept as categorical (HistGradientBoosting) or ordinal encoded (RandomForest)
    • High-cardinality features: StringEncoder for robust feature extraction
    • Datetime features: No spline encoding (unnecessary for trees)
  • Scaler: Not added (unnecessary for tree-based models)
  • Estimator: The provided tree-based estimator

This configuration leverages the native capabilities of tree models while still providing effective feature engineering through the StringEncoder.

7.5 Customizing the pipeline

Tip

This is unnecessary most of the time!

from sklearn.decomposition import PCA
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from skrub import tabular_pipeline
# Create a pipeline that includes two steps
model_pipeline = make_pipeline(PCA(n_components=20), Ridge())

# Pass the new model to tabular_pipeline
full_pipeline = tabular_pipeline(model_pipeline)
[name for name, _ in full_pipeline.steps]
['tablevectorizer', 'simpleimputer', 'squashingscaler', 'pipeline']

7.6 When to Use Which Approach

Use tabular_pipeline if: - You need a quick baseline for benchmarking/checking if there is a signal - You want a starting point for experimentation - You don’t need custom preprocessing/feature engineering

Embed a pipeline in tabular_pipeline if : - You need to modify only the end of the pipeline

Use a manual pipeline if : - You need fine-grained control over most of the transformations - Your training process includes custom preprocessing steps - You want to experiment with different transformers

Or… use the skrub Data Ops! (more on that later, maybe)

7.7 Example: Complete Workflow

from sklearn.model_selection import cross_val_score

# Create pipeline
model = tabular_pipeline(LogisticRegression())

# Evaluate with cross-validation
scores = cross_val_score(model, X, y, cv=5)
print(f"Score: {scores.mean():.4f}")

That’s it!

7.8 What we have seen in this chapter

  • Always use pipelines to prevent data leakage
  • TableVectorizer handles most of the feature engineering
  • Manual pipelines give full control
  • tabular_pipeline provides a good baseline with good defaults
  • Provide a model, or "classification" or "regression"