Concepts / Splitting the Dataset into Training, Validation, and Test Sets

Splitting the Dataset into Training, Validation, and Test Sets

Splitting a dataset into training, validation, and testing subsets is a common practice in machine learning.

  • Programming

Why a Dataset Gets Divided

Before a model can be trained and judged, the data it works with has to be divided. In the workflow this article belongs to, the spam dataset has already been downloaded and balanced, and the next step is to separate it into parts that serve different purposes. Splitting a dataset into training, validation, and testing subsets is a common practice in machine learning. The goal of this article is to make that split concrete in code, and to store the result on disk so it can be reused.

The split is not arbitrary. Each part exists so that the model can be trained, adjusted, and then evaluated on data it was not trained on. Keeping those jobs separate is what makes the final evaluation meaningful. Before you read the code in this article, keep a question in mind: when the data is divided three ways and each part is saved to its own file, how can you be sure that each file holds the rows you intended?

The Three Jobs of the Subsets

Definition

A dataset is split into three parts: a training subset to learn from, a validation subset to adjust the model, and a testing subset to evaluate it. Common ratios for these parts are 70, 10, and 20 percent.

The training subset is the portion the model learns from. The validation subset is used to adjust the model while it is being developed. The testing subset is held back to evaluate the model at the end. The source describes these three uses as training, adjusting, and evaluating the model, and these are the roles the ratios are meant to support.

Training subsetmodel learns from itValidation subsetused to adjust the modelAdjusted modeltuned during developmentTesting subsetheld back for finalevaluationmodel trained, then checkedfeeds back into tuningfinal evaluation
In what order are the three subsets used, and which one feeds back into tuning while another is held back for the final evaluation?

The diagram shows the order of use. The validation subset is the one that feeds back into development, because it is used to adjust the model while it is being built. The testing subset does not feed back: it is kept out of the adjustment loop so that it can judge the finished model on data the model has not been shaped around.

Reading the random_split Function

The source introduces a function named random_split to perform the split. It takes the DataFrame and two fractions, one for training and one for validation. Notice what it does not take: a fraction for testing. The testing share is whatever remains after the training and validation shares are removed, which is why the test size is implied to be 0.2 when the other two fractions are 0.7 and 0.1.

Read the function as a sequence of steps. The first step is a shuffle. The call df.sample with frac=1 returns every row in a new order, and random_state=123 fixes that order so that running the code again produces the same split. The reset_index call with drop=True gives the shuffled rows fresh positional numbering. The second and third steps compute where the cuts go. The fourth step slices the DataFrame at those cuts. The function returns the three pieces as a tuple, and the final line unpacks that tuple into three named variables.

  • Step 1: shuffle the whole DataFrame with a fixed random_state
  • Step 2: compute the training boundary from the training fraction and the row count
  • Step 3: compute the validation boundary from the validation fraction and the row count
  • Step 4: slice the shuffled DataFrame at the two boundaries into three pieces
  • Step 5: return the three pieces as a tuple and unpack them into named variables

The three pieces never overlap. Each slice begins where the previous one ended, so every row lands in exactly one subset. The shuffle is what makes the assignment of rows to subsets unrelated to the original order of the file, and random_state is what makes that assignment repeatable.

Where the Boundaries Land in a Shuffled Table

The easiest way to see the mechanism is to picture the shuffled rows as a row of numbered positions. The boundaries are cut into that row. Positions before the training boundary belong to the training subset, positions between the two boundaries belong to the validation subset, and everything after belongs to the testing subset.

Position 0trainingPosition 1trainingPosition 2trainingPosition 3trainingPosition 4trainingPosition 5trainingPosition 6trainingPosition 7validationPosition 8testingPosition 9testing
After the rows are shuffled, which positions fall into the training, validation, and test ranges, and where does each boundary land?

In this picture, the training boundary falls after position 6 and the validation boundary falls after position 7. The row at position 7 is the only validation row, and the last two positions form the testing subset. The positions are the order after the shuffle, not the order in the original file, which is why the shuffle matters.

Saving Each Subset with to_csv

Once the split is made, the subsets can be saved as CSV files, where CSV stands for comma-separated value. A DataFrame is the object that holds the data while your program runs. A CSV file is a plain text file on disk. The to_csv method is the bridge between the two: the DataFrame stays in memory, and the method produces a file that holds the same data in a form that other programs and later sessions can read. Because the method is called on the DataFrame itself, the DataFrame variable name appears before the dot.

python
Notes on the code
  • Each line saves one DataFrame to one file
  • The first argument is the filename as a string in double quotes
  • The index argument is passed by name and is the same in all three calls
train_dfthe DataFrame being saved.to_csvmethod called on theDataFrame"train.csv"output filename, a stringindex=Nonepassed by name; configuresthe save
In a call like train_df.to_csv('train.csv', index=None), which part is the DataFrame, which is the output filename, and what does the index argument control?

Read each line from left to right. The DataFrame name tells you what is being saved, the method name tells you what happens to it, the string tells you where it goes, and the index argument tells you how the save is configured. A wrong name in any one position produces a file that does not match what you intended, so the reading habit is the main skill here.

train_dftraining rows in memorytrain.csvfile on diskvalidation_dfvalidation rows in memoryvalidation.csvfile on disktest_dftesting rows in memorytest.csvfile on diskto_csvto_csvto_csv
When the train, validation, and test DataFrames are each saved with to_csv, which DataFrame goes to which filename?

The diagram shows the pattern for this section. Each DataFrame has its own call to to_csv, and each call writes to its own file. Nothing is combined: the training data goes to train.csv, the validation data goes to validation.csv, and the test data goes to test.csv. Together, the three files hold the complete dataset in the three parts produced by the split.

Mistakes When Saving Splits

  • Common mistake

    Writing the index column into the file

    What it looks like

    Saving with index=True, or with the index argument left at a setting that writes row labels, produces a file with an extra first column.

    Why it fails

    The row labels are not part of the data. When the file is reloaded, that column appears as an extra unnamed column, which can confuse later code that expects only the original features and labels.

    Do this instead

    Pass index=False when you do not want the row labels written, and check the header of the saved file after saving.

  • Common mistake

    Saving the wrong DataFrame to a filename

    What it looks like

    Calling validation_df.to_csv('train.csv') when you meant to write the validation rows.

    Why it fails

    The DataFrame name, the filename, and the data must match. A wrong name in one position produces a file that holds different rows from the ones you intended.

    Do this instead

    Read each save line left to right: DataFrame, method, filename, index argument, and confirm the name and data match.

  • Common mistake

    Assuming the file updates when the DataFrame changes

    Why it fails

    to_csv takes a snapshot at the moment it runs. Later changes to the DataFrame are not written to the file.

    Do this instead

    Call to_csv again after any change you want to keep.

  • Common mistake

    Assuming the test fraction is passed to random_split

    Why it fails

    random_split takes only two fractions. The testing share is the remainder, so passing a third fraction is not how the function is designed to work.

    Do this instead

    Pass the training and validation fractions only, and compute the testing size from what remains.

  • Common mistake

    Re-splitting without a fixed random_state

    Why it fails

    Without a fixed seed, each run shuffles differently, so the saved files and the later results may not match.

    Do this instead

    Keep random_state fixed, or save the subsets once and reuse the files, which is the purpose of saving them.

Practice: Trace a Split and Its Files

MEDIUM

A dataset has 20 rows. random_split is called with training fraction 0.7 and validation fraction 0.1. Work out how many rows each of the three subsets receives, then name the file that each of the three DataFrames should be saved to, using the source's filenames.

Hints
  • Multiply 0.7 by 20 to find the training boundary, then find the validation boundary from 0.8 times 20.
  • The testing subset is whatever is left after the validation boundary.
  • Each DataFrame goes to its own file: train_df to train.csv, validation_df to validation.csv, test_df to test.csv.
EASY

Save a split that you have made yourself, then reload the file and check its header. Does an unnamed column appear? If it does, which index setting produced it?

Hints
  • Compare the save call you used with the setting used in the source.
  • Reading the file back and printing its columns is enough to answer the question.

Key Takeaways

  • A dataset is divided into a training subset to learn from, a validation subset to adjust the model, and a testing subset to evaluate it, with 70, 10, and 20 percent as common ratios.
  • random_split shuffles the DataFrame with a fixed random_state, computes two integer boundaries from the training and validation fractions, and slices the frame into three non-overlapping pieces, with the testing subset as the remainder.
  • Integer truncation means subset sizes can differ slightly from nominal percentages, so check actual lengths when exact counts matter.
  • to_csv, called on a DataFrame, writes a snapshot of that DataFrame to the CSV file named by its first argument. Each split is saved with its own call to its own file.
  • Read every save call by checking the DataFrame, the filename, and the index argument, because a mismatch in any one of them produces a file that does not hold what you intended.

More concepts starting with S