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.
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
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.
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
DataFramewith a fixedrandom_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
DataFrameat 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.
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.
- Each line saves one
DataFrameto 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
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.
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 likeSaving 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 failsThe 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 insteadPass 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 likeCalling validation_df.to_csv('train.csv') when you meant to write the validation rows.
Why it failsThe
DataFramename, 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 insteadRead 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 failsto_csvtakes a snapshot at the moment it runs. Later changes to theDataFrameare not written to the file.Do this insteadCall to_csv again after any change you want to keep.
Common mistake Assuming the test fraction is passed to random_split
Why it failsrandom_splittakes 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 insteadPass 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 failsWithout a fixed seed, each run shuffles differently, so the saved files and the later results may not match.
Do this insteadKeep 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
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
DataFramegoes to its own file:train_dftotrain.csv,validation_dftovalidation.csv,test_dftotest.csv.
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_splitshuffles theDataFramewith a fixedrandom_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 aDataFrame, writes a snapshot of thatDataFrameto 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.