Please consider the following code: import pandas as pd from sklearn.model_selection import train_test_split # step 1 ids = list(range(1000)) label = 500 * [1.0] + 500 * [0.0] df = pd.DataFrame({"id": ids, "label": label}) # step 2 train_p = 0.8 val_p = 0.1 test_p = 0.1 # step 3 n_train = int(len(df) * train_p) n_val = int(len(df) * val_p) n_test = len(df) - n_train - n_val print("* Step 3") print("train:", n_train) print("val:", n_val) print("test:", n_test) print() # step 4 train_ids, test_ids = train_test_split(df["id"], stratify=df.label, test_size=n_test, random_state=42) # step 5 print("* Step 5. First split") print( df.loc[df.id.isin(train_ids), "label"].value_counts() ) print( df.loc[df.id.isin(test_ids), "label"].value_counts() ) print() # step 6 train_ids, val_ids = train_test_split(train_ids, stratify=df.loc[df.id.isin(train_ids), "label"], test_size=n_val, random_state=42) # step 7 train_df = df[df["id"].isin(train_ids)] val_df = df[df["id"].isin(val_ids)] test_df = df[df["id"].isin(test_ids)] # step 8 print("* Step 8. Final split") print("train:", train_df["label"].value_counts()) print("val:", val_df["label"].value_counts()) print("test:", test_df["label"].value_counts()) with output: * Step 3 train: 800 val: 100 test: 100 * Step 5. First split label 1.0 450 0.0 450 Name: count, dtype: int64 label 1.0 50 0.0 50 Name: count, dtype: int64 * Step 8. Final split train: label 0.0 404 1.0 396 Name: count, dtype: int64 val: label 1.0 54 0.0 46 Name: count, dtype: int64 test: label 1.0 50 0.0 50 Name: count, dtype: int64 Create a Dataframe with 1000 elements perfectly balanced between class 1 and 0 (positive and negative); Define the ratio of examples that should go into the training, validation and test partitions. I would like 800 examples in the training split, 100 examples in each one of the other two. Compute the sizes of the three partitions and print their values. Perform the first split to get the test set, stratified on label. Print label stats of the first split. The two partitions are still balanced. Perform the second splitting into training and validation, stratified on label. Select examples Print label stats. As you can see the second split at step 6 does not produce a balanced split (stats printed at step 8). After the first split, the examples (output at step 5) are still balanced and it would be possible to perform a second split keeping a perfect class balance. What am I doing wrong? Continue reading...