Articles / Machine Learning
Titanic with scikit-learn: Python Pipeline Tutorial
Train a Titanic classifier in Python with fetch_openml, preprocessing, GridSearchCV and a held-out test set. Includes handling unknown categories.

This Titanic classification tutorial keeps a test holdout untouched while preprocessing and tuning happen inside a scikit-learn Pipeline. It uses scikit-learn 1.1 or later; fetch_openml needs network access on its first run.
Load a small, explainable classification problem
Titanic asks whether a passenger survived. We deliberately exclude boat and body: they are recorded after the outcome and would leak the answer into a model intended to make a pre-event prediction.
from sklearn.datasets import fetch_openml
frame = fetch_openml("titanic", version=1, as_frame=True).frame
numeric = ["age", "fare", "sibsp", "parch"]
categorical = ["sex", "embarked", "pclass"]
X = frame[numeric + categorical]
y = frame["survived"].astype(int)
Missing values and category values that appear after training are normal deployment concerns. The transformer below learns medians, modes and categories only when fit is called. unknown_value=-1 is distinct from the non-negative codes learned by OrdinalEncoder, so an unseen port or class will not crash prediction. Ordinal codes imply an arbitrary numeric order for nominal values; for a linear model, prefer OneHotEncoder(handle_unknown="ignore").
Split once; put preprocessing inside the model
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OrdinalEncoder, StandardScaler
from sklearn.tree import DecisionTreeClassifier
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.20, random_state=42, stratify=y
)
prep = ColumnTransformer([
("num", Pipeline([("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler())]), numeric),
("cat", Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
("encode", OrdinalEncoder(handle_unknown="use_encoded_value",
unknown_value=-1))]), categorical),
])
model = Pipeline([("prep", prep), ("tree", DecisionTreeClassifier(random_state=42))])
Do not call prep.fit(X) before the split. That would let values from the test rows influence training. A pipeline is refit separately in every cross-validation fold, which avoids that leak as well.
Tune on training data only
The test set is not a hyperparameter-selection tool. Use cross-validation on X_train, select the setting by mean F1, and reserve X_test for one final estimate. F1 balances precision and recall; whether it is the right metric depends on the cost of each kind of error.
from sklearn.model_selection import GridSearchCV, StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
search = GridSearchCV(
model,
param_grid={"tree__max_depth": [3, 5, 8, None],
"tree__min_samples_leaf": [1, 5, 10],
"tree__criterion": ["gini", "entropy"]},
scoring="f1", cv=cv, n_jobs=2,
)
search.fit(X_train, y_train)
print(search.best_params_)
print(search.best_score_)
criterion="entropy" and "gini" are both supported tree split criteria; neither is universally better. The search evaluates 4 × 3 × 2 × 5 = 120 CV fits, then refits the selected pipeline once on all of X_train. Its winner and score depend on package version and downloaded data, so this article does not present fixed output as an expected result.
Evaluate the chosen pipeline once
from sklearn.metrics import classification_report, ConfusionMatrixDisplay
best_model = search.best_estimator_
predictions = best_model.predict(X_test)
print(classification_report(y_test, predictions, target_names=["did not survive", "survived"]))
ConfusionMatrixDisplay.from_predictions(y_test, predictions)
Read the confusion matrix with the decision in mind: recall answers how many actual survivors were found, while precision answers how often a predicted survivor was correct. Titanic is a historical teaching dataset, not evidence that a model is suitable for a high-stakes setting. For a real system, define error costs, keep an untouched final set (or use nested CV when data is scarce), and monitor data after release.
Sources
Are you building an AI or data platform?
If you need to adapt this approach to your system, tell me about your data and the technical decision you need to resolve.