HuatuCodeHuatuCode
Volver a Aprende
Data Science
HUATUCODE LECCIÓN/Data Science/DIFICULTAD: PRINCIPIANTE

Train-Test Split: La Regla Inquebrantable de la Validación

¿Por qué evaluar tu modelo en los mismos datos de entrenamiento es trampa? Aprende a dividir tus datos correctamente, evitar el Data Leakage y usar muestreo estratificado.

#Train-Test#Validación#Data Leakage#Estratificación#Overfitting
Train-Test Split: La Regla Inquebrantable de la Validación

01La Regla Inquebrantable de la Validación Científica

Evaluar un algoritmo de Machine Learning sobre los mismos datos con los que fue entrenado equivale a entregarle a un estudiante el examen resuelto antes de la prueba: obtendrá un 100%, pero no habrá aprendido a resolver problemas nuevos.

El objetivo supremo de cualquier modelo estadístico no es memorizar los datos históricos, sino generalizar con precisión ante observaciones nunca antes vistas. La partición Train-Test Split es la primera línea de defensa contra el sesgo de optimismo y el peligroso Data Leakage (fuga de datos).

02Anatomía de la Partición: 80/20, 70/30 y el rol de random_state

Conjunto de Entrenamiento (80%)

Es el material de estudio exclusivo del algoritmo. Aquí se ajustan los pesos $w$, sesgos $b$ o las divisiones de los nodos.

Conjunto de Prueba / Test (20%)

Permanece sellado en una "bóveda". Solo se expone al modelo al final del ciclo de vida para auditar su verdadero rendimiento en producción.

El parámetro random_state=42 fija la semilla del generador pseudoaleatorio, permitiendo que cualquier colega o evaluador reproduzca la misma partición exacta en su computadora.

03Muestreo Estratificado (Stratified Split)

¿Qué ocurre si estás detectando fraudes con tarjetas donde solo el 1% de las transacciones son fraudulentas?

Una división aleatoria simple podría colocar todas las transacciones fraudulentas en el conjunto de entrenamiento y dejar al conjunto de prueba sin ninguna, destruyendo la validez de la evaluación. Usar stratify=y obliga a Scikit-Learn a conservar la misma proporción de clases (1% fraudes) tanto en Train como en Test.

04El Peligro del Data Leakage y Validación Cruzada (K-Fold)

El error más destructivo en Machine Learning es la Fuga de Datos (Data Leakage): contaminar el conjunto de entrenamiento con información que proviene del conjunto de evaluación.

⚠️ La Regla Sagrada del Escalado y Preprocesamiento:

Si normalizas o calculas la media con todo el dataset ANTES de hacer la partición, el set de entrenamiento ya "conoce" la media de las filas de prueba. El orden inquebrantable debe ser:

1. Dividir primero: X_train, X_test, y_train, y_test = train_test_split(...)
2. Aprender parámetros solo de Train: scaler.fit(X_train)
3. Transformar ambos por separado: X_train_s = scaler.transform(X_train); X_test_s = scaler.transform(X_test)

05¿En qué situaciones conviene hacer 80/20, 70/30, 75/25 o 90/10?

No existe un porcentaje mágico único. La proporción óptima entre entrenamiento y evaluación depende estrictamente del volumen total de datos, la varianza del problema y la necesidad de ajustar hiperparámetros:

80% Train / 20% TestEl Estándar de Oro de la Industria

Datasets Medianos (1,000 a 50,000 filas)

¿En qué situación conviene?: Es la partición por defecto de referencia para la inmensa mayoría de proyectos con volumen moderado.

Por qué funciona: Concede el 80% del volumen a los pesos del modelo para capturar la distribución estadística subyacente, al tiempo que reserva un 20% sellado (entre 200 y 10,000 instancias) que ofrece suficiente significancia estadística para calcular métricas (R², F1-Score, RMSE) con intervalos de confianza estrechos.

70% Train / 30% TestDatasets Pequeños o Alta Varianza

Datasets Escasos (< 1,000 filas)

¿En qué situación conviene?: Cuando los datos son muy costosos de recolectar (ensayos clínicos médicos, muestras biológicas escasas, encuestas reducidas de 200 a 800 pacientes).

Por qué funciona: Si tienes solo 300 filas y dejas un 20% en test, tendrías apenas 60 observaciones de prueba. En 60 filas, fallar o acertar en 2 ó 3 casos por puro azar distorsiona la métrica final de forma errática. Subir el test al 30% (90 a 120 filas) otorga la masa crítica estadística mínima para que la evaluación sea rigurosa.

75% Train-Val / 25% TestPartición de Tres Vías para Tuning

Ajuste Intensivo de Hiperparámetros

¿En qué situación conviene?: Cuando vas a iterar decenas de modelos, optimizar regularizaciones o realizar búsquedas en malla (GridSearchCV / Optuna).

Por qué funciona: Permite subdividir el bloque del 75% en un 60% Train puro y un 15% Validation (usado para elegir el mejor modelo y regularización), dejando el 25% final totalmente congelado e intocado hasta el despliegue final como evaluación ciega.

90/10 ó 99/1La Era de Big Data y Deep Learning

Datasets Masivos (> 500,000 a Millones de Filas)

¿En qué situación conviene?: Modelos de Computer Vision (ImageNet), procesamiento de lenguaje natural (LLMs) o telemetría de millones de transacciones de usuarios.

Por qué funciona: En un dataset de 2,000,000 de muestras, guardar un 20% para test desperdiciaría 400,000 muestras valiosísimas que la red neuronal necesita para generalizar. Con un 1% o 5% de test (20,000 a 100,000 muestras), ya tienes una precisión estadística con error estándar prácticamente nulo, permitiendo que el 95%-99% de los datos alimente el aprendizaje.

Time-Series Split¡Prohibido mezclar datos al azar en Series Temporales!

Datos con Orden Cronológico

¿En qué situación aplica?: Precios de bolsa, demanda eléctrica hora a hora, ventas diarias de retail o métricas de servidores en tiempo real.

La regla inquebrantable: Nunca uses partición aleatoria convencional; introducirías el destructivo Look-Ahead Bias (el modelo entrenaría con datos del jueves para predecir el martes). La partición DEBE ser cronológica: entrenas con el pasado (ej. Enero a Octubre) y evalúas en el futuro (Noviembre a Diciembre).

06Código Reproducible en Python

train_test_split_scratch.py (NumPy)
# 1. Train-Test Split desde cero con permutación aleatoria
import numpy as np

def train_test_split_scratch(X, y, test_size=0.2, random_state=None):
    if random_state is not None:
        np.random.seed(random_state)
        
    n_samples = len(X)
    n_test = int(n_samples * test_size)
    
    # Permutación aleatoria de índices para evitar sesgos de orden secuencial
    shuffled_indices = np.random.permutation(n_samples)
    
    test_indices = shuffled_indices[:n_test]
    train_indices = shuffled_indices[n_test:]
    
    return X[train_indices], X[test_indices], y[train_indices], y[test_indices]

# Demostración
X = np.array([[i, i*2] for i in range(10)])
y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])

X_train, X_test, y_train, y_test = train_test_split_scratch(X, y, test_size=0.3, random_state=42)
print(f"Muestras de Entrenamiento (Train): {len(X_train)}")
print(f"Muestras de Evaluación (Test):     {len(X_test)}")
sklearn_stratified_split.py (Producción)
# 2. Partición Estratificada con Scikit-Learn (Sin Data Leakage)
from sklearn.model_selection import train_test_split
import numpy as np

# Dataset con clases desbalanceadas (Detección de fraude: 90% normales, 10% anomalías)
X = np.random.randn(100, 4)
y = np.array([0]*90 + [1]*10)

# stratify=y preserva la distribución de clases en ambos subconjuntos
X_train, X_test, y_train, y_test = train_test_split(
    X, y, 
    test_size=0.20, 
    stratify=y, 
    random_state=42
)

print(f"Proporción de clase 1 en Train: {np.mean(y_train)*100:.1f}%")
print(f"Proporción de clase 1 en Test:  {np.mean(y_test)*100:.1f}%")