Model evaluation is meaningful only when test rows stay outside model fitting and learned preprocessing. A held-out partition creates that boundary, while scikit-learn keeps each feature row aligned with its label when the dataset is divided.
The train_test_split() helper accepts multiple arrays, sparse matrices, or dataframes with the same row count and returns a training and test portion for each input. An integer random_state makes the selected rows repeatable for unchanged inputs, while stratify=y keeps the class proportions close across a classification split.
A random row split assumes samples can be shuffled independently. Grouped subjects, repeated measurements, or time-ordered observations need a group-aware or time-series splitter because related or future information could otherwise cross the evaluation boundary.
Steps to split a scikit-learn dataset into train and test sets:
- Create split_train_test.py with the imports and aligned input arrays.
- split_train_test.py
from collections import Counter import numpy as np import sklearn from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split dataset = load_breast_cancer() X = dataset.data y = dataset.target row_ids = np.arange(X.shape[0])
The row_ids array travels through the same split as X and y, which makes row overlap directly measurable without changing the feature data.
- Append the stratified train/test split with a 25 percent holdout.
X_train, X_test, y_train, y_test, train_ids, test_ids = train_test_split( X, y, row_ids, test_size=0.25, stratify=y, random_state=42, )
test_size=0.25 reserves one quarter of the rows for testing. stratify=y keeps both target classes represented, and the integer random_state fixes the shuffled row selection for unchanged inputs.
- Append a repeated split that selects the test row IDs again.
_, _, _, _, _, repeated_test_ids = train_test_split( X, y, row_ids, test_size=0.25, stratify=y, random_state=42, )
The underscores discard the repeated feature, label, and training arrays because only the second set of test row IDs is needed for the reproducibility check.
- Append a helper that converts numeric label counts to dataset class names.
def class_counts(labels): counts = Counter(labels) return { str(dataset.target_names[int(label)]): int(count) for label, count in sorted(counts.items()) }
- Append the shape, class-balance, overlap, and reproducibility checks.
print(f"scikit-learn {sklearn.__version__}") print(f"total rows: {X.shape[0]}") print(f"train shape: {X_train.shape}") print(f"test shape: {X_test.shape}") print(f"train class counts: {class_counts(y_train)}") print(f"test class counts: {class_counts(y_test)}") print(f"test share: {len(test_ids) / len(row_ids):.3f}") print(f"overlap rows: {len(np.intersect1d(train_ids, test_ids))}") print(f"repeat split matches: {np.array_equal(test_ids, repeated_test_ids)}")
- Run the completed split script.
$ python3 split_train_test.py scikit-learn 1.9.0 total rows: 569 train shape: (426, 30) test shape: (143, 30) train class counts: {'malignant': 159, 'benign': 267} test class counts: {'malignant': 53, 'benign': 90} test share: 0.251 overlap rows: 0 repeat split matches: True - Verify that the output proves a separate, stratified, repeatable holdout.
The 569 rows become 426 training rows and 143 test rows, and both subsets contain malignant and benign labels. overlap rows: 0 proves no row ID belongs to both subsets, while repeat split matches: True proves the same integer random state selects the same held-out rows.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.