A held-out test set keeps model evaluation separate from fitting data. In scikit-learn, train_test_split() splits aligned feature, label, and row-id arrays in one call, so the inputs stay matched while a fixed share is reserved for final checks.
train_test_split() accepts arrays, sparse matrices, and dataframes with the same row count. Set test_size for the holdout share, pass a fixed random_state when an example or experiment needs repeatable rows, and use stratify=y for classification labels whose class mix should stay close between train and test sets.
A simple random split fits rows that can be shuffled independently. Grouped subjects, repeated measurements, or time-ordered data need a group-aware or time-series splitter because a random row split can leak information from training into evaluation.
Steps to split a scikit-learn dataset into train and test sets:
- Create split_train_test.py with the dataset, row IDs, and split call.
- 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]) 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, ) _, _, _, _, _, repeat_test_ids = train_test_split( X, y, row_ids, test_size=0.25, stratify=y, random_state=42, ) def class_counts(labels): counts = Counter(labels) return { str(dataset.target_names[int(label)]): int(count) for label, count in sorted(counts.items()) } 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, repeat_test_ids)}") print(f"first five test row ids: {test_ids[:5].tolist()}")
The optional row_ids array is included to prove that the training and test sets do not share rows.
- Run the script.
$ python 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 first five test row ids: [519, 408, 291, 518, 385] - Confirm that test_size=0.25 produces the expected holdout size.
The breast cancer dataset has 569 rows. A 25 percent split gives 426 training rows and 143 test rows because the requested share is rounded to whole samples.
- Confirm that stratify=y keeps both labels represented in each split.
The train and test class counts both include malignant and benign labels, with similar label proportions in each split.
- Confirm that random_state=42 makes the split repeatable and that no row ID appears in both sets.
overlap rows: 0 proves the held-out rows are separate from the training rows. repeat split matches: True shows the same test rows are selected when the same inputs and random state are used again.
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.