3.8 Cross-Validation vs. a Train-Validation Split

Key Takeaways

  • K-fold cross-validation trains K models per parameter combination and averages the K holdout scores, giving a lower-variance estimate at K times the cost.
  • `TrainValidationSplit` evaluates each parameter combination once against a single holdout governed by `trainRatio`, which is far cheaper but noisier.
  • Cross-validation is preferred on small or medium datasets, where a single split's composition can swing the metric materially.
  • A single split is preferred on very large datasets, where the holdout is already big enough to be stable and K-fold cost is prohibitive.
  • Both are executed by passing an Estimator, a `ParamGrid`, and an `Evaluator`; putting preprocessing inside the pipeline is what keeps each fold leak-free.
Last updated: August 2026

3.8 Cross-Validation vs. a Train-Validation Split

Cross-Validation Paradigms on Databricks

Cross-validation estimates how well a predictive model generalizes to unseen independent data, mitigating evaluation variance.

+-----------------------------------------------------------------------------+
|                        CROSS-VALIDATION METHODOLOGIES                       |
|                                                                             |
|   [1] K-FOLD CROSS-VALIDATION (numFolds = 5)                                |
|   Fold 1: [ VAL ] [ TRAIN ] [ TRAIN ] [ TRAIN ] [ TRAIN ]  --> Score 1      |
|   Fold 2: [ TRAIN ] [ VAL ] [ TRAIN ] [ TRAIN ] [ TRAIN ]  --> Score 2      |
|   Fold 3: [ TRAIN ] [ TRAIN ] [ VAL ] [ TRAIN ] [ TRAIN ]  --> Score 3      |
|   Fold 4: [ TRAIN ] [ TRAIN ] [ TRAIN ] [ VAL ] [ TRAIN ]  --> Score 4      |
|   Fold 5: [ TRAIN ] [ TRAIN ] [ TRAIN ] [ TRAIN ] [ VAL ]  --> Score 5      |
|   Final Metric = Mean(Score 1..5) +/- StdDev                                |
|                                                                             |
|   [2] STRATIFIED K-FOLD                                                     |
|   Guarantees identical class proportions (e.g. 2% positive) in every fold.  |
|                                                                             |
|   [3] TIME-SERIES WALK-FORWARD VALIDATION (Expanding Window)                |
|   Split 1: [ Train: Month 1-3 ] -> [ Val: Month 4 ]                         |
|   Split 2: [ Train: Month 1-4 ] -> [ Val: Month 5 ]                         |
|   Split 3: [ Train: Month 1-5 ] -> [ Val: Month 6 ]                         |
|   * Avoids temporal lookahead leakage: Never train on future to test past!  |
+-----------------------------------------------------------------------------+

PySpark CrossValidator vs. TrainValidationSplit

In pyspark.ml.tuning, Databricks provides two primary tuning orchestrators:

FeatureCrossValidatorTrainValidationSplit
Data PartitioningSplits training set into $K$ disjoint folds (e.g., $K=5$).Splits training set once into train/validation sets (e.g., 80/20 ratio via trainRatio=0.8).
Number of Models Fitted$\text{numFolds} \times \text{len}(\text{ParamGrid}) = K \times M$ models.$1 \times \text{len}(\text{ParamGrid}) = M$ models.
Evaluation VarianceLow (averages metrics across all $K$ holdout folds).Higher (single validation split is sensitive to partitioning noise).
Computational CostHigh ($K \times$ more expensive in CPU hours and cluster time).Low (evaluates parameter grid in a single pass).
Best ForSmall to medium datasets where robust validation is critical.Massive terabyte-scale datasets where K-fold CV is computationally prohibitive.

Benefits and Downsides, Stated Plainly

K-fold CrossValidatorTrainValidationSplit
Models fittednumFolds × len(paramGrid)1 × len(paramGrid)
Estimate varianceLow — averages K holdout scoresHigher — one split's composition drives the result
Uses all data for validationYes, every row is validated exactly onceNo, only the holdout fraction
Compute cost
Reports a spreadYes — the standard deviation across folds is itself diagnosticNo
Best forSmall to medium data; when the decision between candidates is closeVery large data; expensive models; a first coarse sweep

The underrated benefit of K-fold is the standard deviation across folds. Two candidate models with the same mean score but very different fold spreads are not equally good; the high-variance one is unstable and will behave unpredictably in production. A single split cannot reveal that at all.

The underrated cost is that K-fold multiplies every expense: cluster hours, wall-clock time, and — with a large parameter grid — the risk of overfitting the validation procedure itself by selecting from too many candidates.

A practical progression

  1. Sweep coarsely with TrainValidationSplit to eliminate obviously poor regions.
  2. Refine the survivors with CrossValidator (numFolds=3 or 5).
  3. Retrain the winning configuration on the full training pool.
  4. Evaluate once on the untouched holdout test set.

Worked Implementation: CrossValidator over a Spark ML Pipeline

from pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler, StandardScaler
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.evaluation import BinaryClassificationEvaluator

df = spark.table("lakehouse_gold.customer_features")
train_df, test_df = df.randomSplit([0.8, 0.2], seed=42)

# Pipeline definition
assembler = VectorAssembler(inputCols=["f1", "f2", "f3"], outputCol="raw_features")
scaler = StandardScaler(inputCol="raw_features", outputCol="features")
lr = LogisticRegression(featuresCol="features", labelCol="label")
pipeline = Pipeline(stages=[assembler, scaler, lr])

# Build hyperparameter grid
param_grid = (
    ParamGridBuilder()
    .addGrid(lr.regParam, [0.01, 0.1, 1.0])
    .addGrid(lr.elasticNetParam, [0.0, 0.5, 1.0])
    .build()
)

evaluator = BinaryClassificationEvaluator(
    labelCol="label",
    rawPredictionCol="rawPrediction",
    metricName="areaUnderROC"
)

# Instantiate CrossValidator (5 folds * 9 param combinations = 45 models)
crossval = CrossValidator(
    estimator=pipeline,
    estimatorParamMaps=param_grid,
    evaluator=evaluator,
    numFolds=5,
    parallelism=4,
    seed=42
)

cv_model = crossval.fit(train_df)

# Evaluate best model on isolated holdout test set
test_predictions = cv_model.transform(test_df)
test_auc = evaluator.evaluate(test_predictions)
print(f"Isolated Holdout Test ROC-AUC: {test_auc:.4f}")

Stratified and Time-Ordered Variants

  • Stratified K-fold preserves the class proportions in every fold. With a 2% positive rate, plain random folds can produce a fold with almost no positives, making its score meaningless. scikit-learn's StratifiedKFold does this directly; in Spark ML the usual approach is to stratify the split with sampleBy on the label before handing folds to the tuner.
  • Walk-forward validation is mandatory for temporally ordered data. Random folds place future rows in the training set and past rows in validation, which leaks the future into the past and produces validation scores that never reproduce in production.

Strict Holdout Test Set Isolation

A golden rule in production machine learning is the strict isolation of the holdout test set:

+-----------------------------------------------------------------------------+
|                        STRICT DATA SPLITTING PROTOCOL                       |
|                                                                             |
|   FULL DATASET (Delta Table)                                                |
|        |                                                                    |
|        +----------------------------+-----------------------------+         |
|        | (80% Training Pool)                                      | (20%)   |
|        v                                                          v         |
|   TRAIN / VALIDATION POOL                                   HOLDOUT TEST SET|
|   +---------------------------------------------------+     +-------------+ |
|   | CrossValidator / Hyperopt Tuning Executes HERE:   |     | STRICTLY    | |
|   | - Fold 1..K Train / Validation                    |     | ISOLATED    | |
|   | - Feature Scaler & Imputer Fit                    |     | DO NOT TOUCH| |
|   | - Optimal Hyperparameters Selected                |     | DURING      | |
|   +---------------------------------------------------+     | TUNING!     | |
|        |                                                          |         |
|        v                                                          |         |
|   FINAL RETRAINED BEST MODEL                                      |         |
|        |                                                          |         |
|        +-------------------> [ EVALUATE ONCE ] <------------------+         |
|                                     |                                       |
|                                     v                                       |
|                        UNBIASED GENERALIZATION SCORE                        |
+-----------------------------------------------------------------------------+
Test Your Knowledge

An ML engineer is tuning a PySpark ML pipeline on a massive 2 TB Delta table. Running a 5-fold CrossValidator across a 20-parameter grid is estimated to take 14 hours. Which alternative tuning class in pyspark.ml.tuning provides faster hyperparameter evaluation by fitting each parameter candidate on a single train/validation split?

A
B
C
D
Test Your Knowledge

Why is standard randomized K-fold cross-validation inappropriate for evaluating models trained on time-series or sequential financial transaction data?

A
B
C
D
Test Your Knowledge

Two candidate models are compared with 5-fold cross-validation. Model A averages 0.81 AUC with a fold standard deviation of 0.005; Model B averages 0.81 AUC with a fold standard deviation of 0.070. What does this tell you, and why would a single train-validation split have missed it?

A
B
C
D