scikit-learn Pipelines skill for Codex

Cross-validation scores you can believe. Codex keeps every fitted transformation inside one sklearn Pipeline, picks the splitter that matches how predictions happen (grouped, stratified or forward in time), and beats a dummy baseline by more than fold noise before tuning. It runs a leakage checklist, calibrates probabilities and tunes thresholds when decisions need them, and touches the test set once.

  • AI & LLM Engineering
  • $scikit-learn-pipelines
  • 152 lines
  • 2 files
  • ~1,800 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- scikit-learn-pipelines

Installs to ~/.agents/skills/scikit-learn-pipelines/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $scikit-learn-pipelines, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds leak-free scikit-learn models with Pipeline and ColumnTransformer - preprocessing inside cross-validation, the right splitter (StratifiedKFold, GroupKFold, TimeSeriesSplit), hyperparameter search, HistGradientBoosting defaults, TargetEncoder, class imbalance, probability calibration, decision thresholds and safe model persistence. Use when asked to train a classifier or regressor on tabular data, fix suspiciously high validation scores, set up cross-validation or GridSearchCV, tune a threshold, or package an sklearn model for production.

Use it when

  • Training a classifier or regressor on tabular data
  • Validation scores look too good or collapse in production
  • You need hyperparameter search, calibration or threshold tuning

Use something else when

How to check the result

Cross-validated metrics with standard deviations beat a dummy baseline by more than the fold spread, and the single final test score falls within that CV range.

The mistake it prevents

Fitting a scaler, imputer or target encoder on the full dataset before splitting, which inflates every validation score.

~/.agents/skills/scikit-learn-pipelines/SKILL.md

--- name: scikit-learn-pipelines description: Builds leak-free scikit-learn models with Pipeline and ColumnTransformer - preprocessing inside cross-validation, the right splitter… license: MIT ---

Done well means: every transformation that learns from data lives inside one Pipeline fitted only on training folds, the CV splitter mirrors how the model will meet new data, a dumb baseline is beaten by a margin larger than fold-to-fold noise, and the test set is touched exactly once.

Before modelling

  1. Read AGENTS.md, the data loading code and sklearn.__version__ (APIs below assume 1.4+).
  2. Define the prediction moment: what is known at the time the model is used? Columns computed after that moment (refund flags for a fraud model at checkout) are leakage.
  3. Identify grouping (customer, patient, device) and time ordering. These decide the splitter.
  4. Hold out a test set now; do not look at it again until the final report.

Choose the splitter

DataSplitter
i.i.d. rows, classificationStratifiedKFold(n_splits=5, shuffle=True, random_state=0)
Several rows per entity (user, patient, store)GroupKFold or StratifiedGroupKFold with groups=
Ordered in time, predicting the futureTimeSeriesSplit(n_splits=5, gap=...); or a single forward split
Very small data (< 500 rows)RepeatedStratifiedKFold for stable estimates

The holdout split must follow the same rule (group-aware or time-based), not just CV.

Reference pipeline

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer, make_column_selector as sel
from sklearn.dummy import DummyClassifier
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

X, y = df.drop(columns=["churned"]), df["churned"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)

numeric = Pipeline([("impute", SimpleImputer(strategy="median", add_indicator=True)),
                    ("scale", StandardScaler())])
categorical = Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
                        ("onehot", OneHotEncoder(handle_unknown="infrequent_if_exist", min_frequency=20))])
pre = ColumnTransformer([("num", numeric, sel(dtype_include="number")),
                         ("cat", categorical, sel(dtype_include=["object", "category"]))])

linear = Pipeline([("pre", pre), ("clf", LogisticRegression(max_iter=2000, class_weight="balanced"))])
hgb = HistGradientBoostingClassifier(categorical_features="from_dtype", early_stopping=True, random_state=0)

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
scoring = {"auc": "roc_auc", "ap": "average_precision", "logloss": "neg_log_loss"}
for name, model in {"dummy": DummyClassifier(strategy="prior"), "linear": linear, "hgb": hgb}.items():
    r = cross_validate(model, X_train, y_train, cv=cv, scoring=scoring, n_jobs=-1)
    print(name, {k: f"{r['test_' + k].mean():.3f} +/- {r['test_' + k].std():.3f}" for k in scoring})
  • HistGradientBoosting handles NaN natively and, with categorical_features="from_dtype", pandas category columns without one-hot. It is the strong default for tabular data; cast string columns with X[c].astype("category") first.
  • pipe.set_output(transform="pandas") keeps column names through transforms for debugging.

Leakage checklist

- [ ] No fit/fit_transform on the full dataset before splitting (scalers, imputers, encoders, PCA, feature selection, SMOTE)
- [ ] Target encoding inside the pipeline (TargetEncoder cross-fits during fit_transform)
- [ ] No feature derived from the label or from post-outcome events
- [ ] Duplicates and near-duplicates do not straddle train and validation
- [ ] Group or time structure respected by every split
- [ ] Hyperparameters chosen on CV folds, never on the test set

Smell test: validation AUC above 0.95 on a messy business problem, or one feature with enormous importance, means investigate leakage before celebrating. Check permutation importance on validation data, then read how the top feature is produced.

from scipy.stats import loguniform, randint
from sklearn.model_selection import RandomizedSearchCV

search = RandomizedSearchCV(
    hgb,
    {"learning_rate": loguniform(0.01, 0.3), "max_leaf_nodes": randint(15, 127),
     "min_samples_leaf": randint(10, 200), "l2_regularization": loguniform(1e-4, 10)},
    n_iter=40, cv=cv, scoring="average_precision", n_jobs=-1, random_state=0, refit=True,
)
search.fit(X_train, y_train)

Name pipeline params with double underscores (clf__C). Prefer randomized or HalvingRandomSearchCV over exhaustive grids above three parameters. If tuning and model selection both use the same CV, the best score is optimistic; nested CV gives an honest estimate.

Imbalance, probabilities, thresholds

NeedTool
Rare positive classScore with average_precision; class_weight="balanced" before any resampling
Calibrated probabilities (risk scores, expected cost)CalibratedClassifierCV(model, method="isotonic", cv=5) (sigmoid when data is small)
A decision threshold other than 0.5TunedThresholdClassifierCV(model, scoring="f1") or a cost-based scorer (1.5+)
Resampling (SMOTE)Only inside an imblearn.pipeline.Pipeline so it runs on training folds only

Check calibration with CalibrationDisplay.from_estimator and Brier score on validation.

Final evaluation and persistence

from sklearn.metrics import classification_report

final = search.best_estimator_
print(classification_report(y_test, final.predict(X_test)))   # once, at the end

import joblib, sklearn
joblib.dump({"model": final, "sklearn": sklearn.__version__, "features": list(X.columns)}, "model.joblib")
  • Pickle/joblib files execute code on load: load only files you created. For sharing across trust boundaries, use skops.io or ONNX (skl2onnx), see $ml-model-deployment.
  • Load with the same scikit-learn version; cross-version unpickling is unsupported.
  • Validate the input schema at inference (column names, dtypes) before predict.

Gotchas

  • cross_val_score on an unfitted pipeline is correct; on pre-transformed X it leaks.
  • OneHotEncoder(handle_unknown="error") crashes in production on a new category.
  • LabelEncoder is for targets, not features; use OrdinalEncoder for features.
  • StandardScaler on sparse one-hot output needs with_mean=False.
  • Reporting accuracy on a 95/5 dataset hides a useless model; always show the dummy baseline.

Report template

**Task:** churn in 90 days | **Rows:** 48k train / 12k test | **Split:** StratifiedGroupKFold by account
**Baseline:** dummy AP 0.08 | linear AP 0.41 +/- 0.02 | HGB tuned AP 0.53 +/- 0.02
**Test (once):** AP 0.52, AUC 0.86; threshold 0.31 tuned for F1 on CV
**Leakage review:** dropped `cancellation_reason` (post-outcome); top features plausible
**Artifact:** model.joblib (sklearn 1.x), input schema checked at load

For building and versioning the features themselves, use $feature-engineering.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "scikit-learn Pipelines"
  short_description: "Cross-validation scores you can believe"
  default_prompt: "$scikit-learn-pipelines Build a churn classifier on this CSV with proper cross-validation"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem