group split
train test split
data partitioning
machine learning
model validation

Split on train and test separating by group

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

If rows belong to larger groups such as patients, users, stores, or devices, a plain random train-test split can leak information across the boundary. The model then appears to generalize even though it has already seen related examples from the same group during training.

The fix is to split by group instead of by row. In scikit-learn, the usual tool for a single train-test split is GroupShuffleSplit, while GroupKFold is used for grouped cross-validation.

Why Group Separation Matters

Imagine a dataset where each patient contributes ten records. If you randomly split rows, the same patient may appear in both training and testing. A model can then exploit patient-specific patterns rather than learning a rule that generalizes to new patients.

This is a textbook form of leakage. The test score looks better than reality because the split is not independent at the group level.

Typical cases where grouped splitting is required:

  • medical records grouped by patient
  • event logs grouped by user or session
  • manufacturing measurements grouped by machine or lot
  • repeated observations grouped by subject or household

Using GroupShuffleSplit

The code below creates a single train-test split while guaranteeing that each group appears in only one side.

python
1import numpy as np
2from sklearn.model_selection import GroupShuffleSplit
3
4X = np.array([
5    [1.0], [1.1], [2.0], [2.1], [3.0], [3.1], [4.0], [4.1]
6])
7y = np.array([0, 0, 1, 1, 0, 0, 1, 1])
8groups = np.array(["A", "A", "B", "B", "C", "C", "D", "D"])
9
10splitter = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=42)
11train_idx, test_idx = next(splitter.split(X, y, groups))
12
13print("Train groups:", set(groups[train_idx]))
14print("Test groups:", set(groups[test_idx]))
15print("Overlap:", set(groups[train_idx]) & set(groups[test_idx]))

The overlap set should be empty. That is the key invariant to verify whenever you use grouped splitting.

When to Use GroupKFold

If you want cross-validation instead of one held-out test set, use GroupKFold. It rotates whole groups across folds, again preventing leakage between training and validation partitions.

python
1from sklearn.model_selection import GroupKFold
2
3gkf = GroupKFold(n_splits=4)
4
5for fold, (train_idx, test_idx) in enumerate(gkf.split(X, y, groups), start=1):
6    print(f"Fold {fold}")
7    print("  train groups:", sorted(set(groups[train_idx])))
8    print("  test groups:", sorted(set(groups[test_idx])))

This is the safer choice when hyperparameter tuning depends on grouped data. Otherwise, a model selection loop can accidentally optimize toward leakage.

Practical Tradeoffs

Grouped splits reduce leakage, but they can make balancing harder. If some groups are very large or carry only one class label, the train-test proportions may be less tidy than a random row-level split.

That tradeoff is normal. The goal is not perfect row balance. The goal is an evaluation protocol that matches production reality.

If your future predictions will be made on entirely new users or devices, then your validation should also hold out entirely new users or devices.

Verifying the Split

Do not assume the library call is enough. Inspect the result.

Useful checks include:

  • no group appears in both partitions
  • class distribution is still acceptable
  • test size is reasonable in terms of groups, not just rows

Simple validation code:

python
1train_groups = set(groups[train_idx])
2test_groups = set(groups[test_idx])
3
4assert train_groups.isdisjoint(test_groups)

That assertion catches the exact mistake grouped splitting is meant to prevent.

Common Pitfalls

  • Using train_test_split on grouped data and assuming row-level randomness is good enough.
  • Passing the wrong array as groups, such as row IDs instead of real group IDs.
  • Optimizing for class balance while ignoring leakage across groups.
  • Using grouped cross-validation for training but a random split for the final test set.
  • Forgetting to inspect whether the held-out groups reflect the real deployment scenario.

Summary

  • Grouped data should be split by group, not by row.
  • 'GroupShuffleSplit is the standard choice for a single grouped train-test split.'
  • 'GroupKFold is the right tool for grouped cross-validation.'
  • Always verify that train and test groups are disjoint.
  • Slightly messier balance is acceptable if it removes leakage and matches production reality.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.