Concepts / Train and Test Splits

Train and Test Splits

ShuffleSplit repeats a random split: each iteration draws train_size points for training and test_size disjoint points for testing.

  • Programming

Starting From a Fitted Model

Linear regression is a good first model to study because almost everything it does is visible in a few lines of code. Instead of starting with a definition, this article starts with what happens when you fit the model. You will follow the execution line by line and keep track of what exists in Python after each step. The goal is to understand where the numbers that a model uses come from, and then to see how the train and test splits that feed the model are produced.

What Fitting Writes Into the Model

Linear regression, also called ordinary least squares or OLS, is the simplest and most classic linear method for regression. Its goal is to choose two numbers, a slope $w$ and an offset $b$, so that the line they define makes predictions that sit as close as possible to the true target values $y$ on the training set. When the data has a single input feature, the prediction is the slope multiplied by the input, plus the offset. Once the two numbers are chosen, the model needs no other information about the training data to make a prediction.

Formula

Fitting means choosing $w$ and $b$ so that the error is as small as possible. The error used here is the mean squared error. For each sample, take the difference between the prediction and the true value, square that difference, and add up the squares. Squaring removes the sign of each miss, so a prediction that is too high and one that is too low both count as errors, and large misses count for much more than small ones. The word mean refers to dividing that sum by the number of samples $n$, so the error does not grow just because the dataset grows.

Rule

The small program below computes this quantity by hand for two samples. Read it as a template for the calculation that fitting tries to drive down, not as the code used to train a model.

python
Output (expected)
0.625
Notes on the code
  • The first miss is 2.5 minus 3.0, which is -0.5; squared it is 0.25
  • The second miss is 6.0 minus 5.0, which is 1.0; squared it is 1.0
  • Dividing the sum 1.25 by the two samples gives 0.625

In scikit-learn, the model is created from the LinearRegression class and fitted with the training features and targets. The fit method returns the fitted model, which is why the two steps can be written on one line. After fitting, the slope is in the coef_ attribute and the offset is in the intercept_ attribute.

python
Notes on the code
  • LinearRegression() creates the model object, which only describes what it will do
  • .fit(X_train, y_train) reads the training data and writes the learned values into the model
  • coef_ holds one slope per input feature, so it is an array
  • intercept_ holds the offset as a single float
Before
model = LinearRegression()

The model object exists and describes the linear regression procedure, but it holds no values learned from training data

After
model = LinearRegression().fit(X_train, y_train)

coef_ and intercept_ hold the slope and offset derived from the training data, and the model can make predictions

Reading Training and Test Scores Together

A fitted model's score method reports $R^{2}$ on whatever data you pass it. $R^{2}$ is a measure of how well the model explains the data. Calling score on the training data tells you how well the model fits the examples it learned from. Calling score on the test data tells you how well the model generalizes to examples it has not seen. Because the score describes whichever set you pass in, the two results must be compared together rather than read alone.

The two numbers are read together. When both scores are low and close to each other, the model is probably underfitting, meaning it is too simple to capture the pattern in the data. When the training score is high and the test score is much lower, that gap is a clear sign of overfitting.

Model complexity is part of the picture. A model with a single feature is simple, so there is little danger of overfitting it. A dataset with many features gives a linear model much more room to bend toward the particular training examples, and the chance of overfitting rises. The source illustrates this with the extended Boston Housing dataset, which has 506 samples and 105 derived features.

Training scorelowTraining score0.95Test scoreclose to the training scoreTest score0.61
Read the training and test scores together: a similar low pair suggests underfitting, while a high training score with a much lower test score suggests overfitting.
Worked example

Diagnosing a gap between training and test scores

After splitting the data and fitting the model in the usual way, the training score is 0.95 and the test score is 0.61. What does this result indicate?

  1. 1
    Compare the two scores

    The training score is 0.95 and the test score is 0.61. The gap between them is 0.34.

  2. 2
    Ask which pattern this matches

    The scores are not both low and close together, so this is not the underfitting pattern. The training score is high and the test score is much lower.

  3. 3
    Interpret the gap

    The model predicts the training examples very accurately but generalizes much worse to the held-out examples. A discrepancy like this is a clear sign of overfitting.

Result

The result diverges from expectation: the model does well on data it has seen and poorly on data it has not, which points to overfitting.

How One Shuffle-Split Iteration Picks Points

ShuffleSplit repeats a random split. A single iteration works in two draws from the same dataset. First, it selects train_size points to form the training set. Then it selects test_size points to form the test set, and those test points are chosen so that none of them is also in the training set for that iteration. The model is trained on the first group and scored on the second. Because the two groups are disjoint within an iteration, no point can be used both to learn and to be judged in the same iteration.

The sizes can be given as whole numbers or as fractions. A whole number is an absolute count of points, while a floating-point value is a fraction of the whole dataset. With the values 0.5 and 0.5, each training set holds half of the rows and each test set holds the other half of the same size.

Point 0test setPoint 1training setPoint 2test setPoint 3test setPoint 4training setPoint 5training setPoint 6test setPoint 7test setPoint 8training setPoint 9training set
Which of the ten original points go into the training set and which go into the test set in one iteration when train_size and test_size are both 0.5? This layout is an illustrative example; the actual points chosen depend on the random draw.

In the illustration, the five training points and the five test points together account for every point in the dataset, and no point appears in both groups. The next iteration draws again, so a point that was test data in this iteration can appear in a later training set.

python
Notes on the code
  • n_splits=10 asks for ten iterations
  • train_size=0.5 and test_size=0.5 give each iteration half the rows for training and the other half for testing
  • Each pass through the loop produces one training group and one test group

How n_splits Multiplies the Draws

Two settings in ShuffleSplit do different jobs. The n_splits argument (called n_iter in the textual description) decides how many times the splitting happens. The train_size and test_size arguments decide how big each split is. Because these are separate, you can ask for many iterations while keeping each split small, or for few iterations with large splits.

Iteration 1its own training and testpointsIteration 2a new random drawIteration 3a new random drawIteration 4a new random drawshuffle againshuffle againshuffle again
What happens across successive iterations? Each iteration draws a new training set and test set from the same ten-point dataset, so the scores can differ from run to run.

The source describes a run of four such iterations on the same ten-point dataset, each drawing its own training and test points. Because each iteration samples again, a point that serves as test data in one iteration can appear in the training set of a later one. This is what gives the method its variety, and it is also why the ten scores in the first example differ from each other. Each iteration produces one score, so the total count of scores equals n_splits.

python
Notes on the code
  • The iris dataset is scored with a logistic regression model
  • Instead of one fixed split, ShuffleSplit supplies ten different splits
  • One score is returned per split, so the length of scores equals n_splits

Where Shuffle-Split Surprises Happen

Most surprises with shuffle-split come from a few predictable places. When a result differs from what you expected, look at the setup lines first, then at what each line returned.

  • Common mistake

    Expecting one identical score on every run

    What it looks like

    Ten scores come back and they are not all the same value.

    Why it fails

    Each iteration resamples, so the individual scores differ from run to run. The variety is intended.

    Do this instead

    Read the spread of the scores as information about how sensitive the result is to which points were drawn.

  • Common mistake

    Assuming the whole dataset is always used

    What it looks like

    Setting train_size to 0.2 and test_size to 0.2 and assuming every row is involved.

    Why it fails

    train_size and test_size can sum to less than one, so each iteration uses only part of the data.

    Do this instead

    Add the two sizes. If the sum is below one, the leftover rows are left out of every split. This is useful when experimenting with large datasets, because a smaller subsample can be enough to explore an idea quickly.

  • Common mistake

    Treating n_splits and split size as the same setting

    What it looks like

    Raising n_splits and expecting each split to grow as well.

    Why it fails

    n_splits controls how many iterations run, and it is set separately from the size of each split.

    Do this instead

    Change n_splits for the number of iterations and train_size or test_size for the size of each split.

  • Common mistake

    Reading only the training score

    What it looks like

    A training score of 0.95 is reported as proof that the model works.

    Why it fails

    The score describes whichever set it was computed on. A high training score says little about examples the model has not seen.

    Do this instead

    Compare the training score with the test score together before deciding whether the model is underfitting or overfitting.

Choosing StratifiedShuffleSplit for Classification

StratifiedShuffleSplit is a stratified variant of ShuffleSplit. Its main use is classification problems, where the source says it can give more reliable results than plain shuffle-split. The name hints at the difference: the word stratified refers to keeping each split organized by the class labels, so that the training and test sets reflect the classes in the data rather than depending only on chance.

FeatureShuffleSplitStratifiedShuffleSplit
How points are chosenRandom draw of train_size and test_size pointsRandom draw organized by the class labels
Class balance in each splitDepends on chanceReflects the classes in the full dataset
Main useGeneral random repeated splittingClassification problems
Controlsn_splits, train_size, test_sizen_splits, train_size, test_size, organized by class labels

Random splitting versus class-aware splitting

When you are splitting data for a classification task, prefer StratifiedShuffleSplit when you want each split to keep the class structure of the full dataset. Plain ShuffleSplit can produce splits whose class mix differs from the whole, because the draw depends only on chance.

Practice: Trace a Split

EASY

A dataset has 10 rows. You create ShuffleSplit with n_splits=4, train_size=0.5, and test_size=0.5, then loop over the splits. How many iterations run, how many rows are in each training set, and how many rows are in each test set? Then say whether a row can be in both the training set and the test set of the same iteration.

Hints
  • n_splits sets the number of iterations, separately from the size of each split
  • A fraction of 0.5 of 10 rows is 5 rows
  • Within one iteration, the test points are chosen so that none of them is also in the training set

What do you think happens?

With train_size=0.5 and test_size=0.5 on a 10-row dataset, how many training rows does each iteration use?

Reveal answer
Answer:

5 training rows

A fraction of 0.5 means half the dataset, and half of 10 rows is 5. The test set is also 5 rows, and the two groups do not overlap within the iteration.

What do you think happens?

If train_size=0.2 and test_size=0.2 on a 10-row dataset, what fraction of the data is used in each iteration?

Reveal answer
Answer:

40 percent, which is 4 training rows and 2 test rows out of 10

A fraction of 0.2 of 10 rows is 2 rows, so the training set has 2 rows and the test set has 2 rows, using 4 rows in total. The sizes sum to 0.4, which is less than one, so 60 percent of the rows are left out of every split.

Summary of the Splitting Mechanics

  1. 1
    Linear regression chooses a slope w and an offset b to minimize the mean squared error on the training set; after fitting, coef_ holds the slope and intercept_ holds the offset.
  2. 2
    A score describes the data it is computed on, so training and test scores must be read together to diagnose underfitting or overfitting.
  3. 3
    One ShuffleSplit iteration draws train_size points for training and disjoint test_size points for testing, and n_splits sets how many iterations run.
  4. 4
    Sizes can sum to less than one, in which case some data is left out of every split; StratifiedShuffleSplit is preferred for classification when the class balance should be reflected in each split.

At a glance

  • Linear regression chooses a slope w and an offset b to minimize the mean squared error on the training set; coef_ holds the slope and intercept_ holds the offset after fitting.
  • Training and test scores must be compared together: similar low scores suggest underfitting, while a high training score with a much lower test score suggests overfitting.
  • Each ShuffleSplit iteration draws train_size points for training and disjoint test_size points for testing, and n_splits controls how many iterations run.
  • If train_size and test_size sum to less than one, only part of the data is used; check this before interpreting results.
  • StratifiedShuffleSplit is preferred for classification tasks when the splits should reflect the class structure of the data.

Related concepts

More concepts starting with T