Scenario
We have policy-renewal history and want to create:
70% training
15% validation
15% test
Step 1: Install ML.NET
Create a console application:
dotnet new console -n DataSplitDemo
cd DataSplitDemo
dotnet add package Microsoft.ML
Step 2: Define the input class
Replace Program.cs with:
using Microsoft.ML;
public sealed class PolicyRenewalRow
{
public float CustomerAge { get; set; }
public float PremiumAmount { get; set; }
public float ClaimCount { get; set; }
public float ComplaintCount { get; set; }
public bool Renewed { get; set; }
}
var rows = Enumerable.Range(1, 100)
.Select(index => new PolicyRenewalRow
{
CustomerAge = 20 + index % 55,
PremiumAmount = 5000 + index * 250,
ClaimCount = index % 4,
ComplaintCount = index % 5,
Renewed = index % 3 != 0
})
.ToList();
var mlContext = new MLContext(seed: 42);
IDataView completeData = mlContext.Data.LoadFromEnumerable(rows);
The seed makes random operations reproducible.
This is important for:
- Debugging
- Comparing experiments
- Reproducing reported results
Step 3: Reserve 15% for final testing
var developmentAndTest = mlContext.Data.TrainTestSplit(
completeData,
testFraction: 0.15,
seed: 42);
IDataView developmentData = developmentAndTest.TrainSet;
IDataView testData = developmentAndTest.TestSet;
The test data should now be treated as sealed.
Step 4: Create validation data
The development set contains approximately 85% of the original records.
We need approximately 15% of the complete dataset for validation:
15 ÷ 85 ≈ 0.1765
Split about 17.65% from the development set:
var trainingAndValidation = mlContext.Data.TrainTestSplit(
developmentData,
testFraction: 0.1765,
seed: 42);
IDataView trainingData = trainingAndValidation.TrainSet;
IDataView validationData = trainingAndValidation.TestSet;
This produces approximately:
Training : 70%
Validation : 15%
Test : 15%
Step 5: Verify row counts
long trainingCount = trainingData.GetRowCount() ?? 0;
long validationCount = validationData.GetRowCount() ?? 0;
long testCount = testData.GetRowCount() ?? 0;
Console.WriteLine($"Training rows: {trainingCount}");
Console.WriteLine($"Validation rows: {validationCount}");
Console.WriteLine($"Test rows: {testCount}");
Console.WriteLine($"Total rows: " +
$"{trainingCount + validationCount + testCount}");
dotnet run
The exact counts may vary slightly because splitting is probabilistic.
Step 6: Understand how each subset will be used
Later, the flow will become:
// Conceptual preview—we have not created the pipeline yet.
var model = pipeline.Fit(trainingData);
var validationPredictions = model.Transform(validationData);
// Compare model choices and tune settings here.
var finalPredictions = model.Transform(testData);
// Use only after selecting the final model.
Do not repeatedly inspect test results and then modify the model. Doing that turns the test set into another validation set.
Step 7: Time-based splitting with SQL
For renewal prediction, chronological splitting may be more realistic:
SELECT *,
CASE
WHEN PredictionDate < '2025-01-01'
THEN 'Training'
WHEN PredictionDate >= '2025-01-01'
AND PredictionDate < '2025-04-01'
THEN 'Validation'
WHEN PredictionDate >= '2025-04-01'
AND PredictionDate < '2025-07-01'
THEN 'Test'
END AS DatasetPurpose
FROM PolicyRenewalHistory
WHERE PredictionDate < '2025-07-01';
Verify the distribution:
WITH SplitData AS
(
SELECT
Renewed,
CASE
WHEN PredictionDate < '2025-01-01'
THEN 'Training'
WHEN PredictionDate < '2025-04-01'
THEN 'Validation'
WHEN PredictionDate < '2025-07-01'
THEN 'Test'
END AS DatasetPurpose
FROM PolicyRenewalHistory
WHERE PredictionDate < '2025-07-01'
)
SELECT
DatasetPurpose,
COUNT(*) AS TotalRows,
SUM(CASE WHEN Renewed = 1 THEN 1 ELSE 0 END) AS RenewedRows,
SUM(CASE WHEN Renewed = 0 THEN 1 ELSE 0 END) AS NonRenewedRows
FROM SplitData
GROUP BY DatasetPurpose;
Each subset should be inspected for:
- Adequate row count
- Both label classes
- Unexpected period changes
- Missing values
- Duplicate entities
- Representative business categories
Step 8: Prevent customer overlap
Check whether a customer appears in more than one subset:
WITH SplitData AS
(
SELECT
CustomerId,
CASE
WHEN PredictionDate < '2025-01-01'
THEN 'Training'
WHEN PredictionDate < '2025-04-01'
THEN 'Validation'
ELSE 'Test'
END AS DatasetPurpose
FROM PolicyRenewalHistory
WHERE PredictionDate < '2025-07-01'
)
SELECT CustomerId
FROM SplitData
GROUP BY CustomerId
HAVING COUNT(DISTINCT DatasetPurpose) > 1;
A returned customer is not automatically an error. You must decide based on the intended production scenario.
For example:
- Predicting future policies for existing customers may permit overlap.
- Evaluating performance on entirely new customers requires customer-grouped splitting.
The evaluation design must match the production question.