dataset-splitting

Par mkurman · zorai

Créez des découpages train/validation/test reproductibles avec stratification, prévention des fuites de données et validation de la distribution. Couvre les stratégies de découpage aléatoire, stratifié, groupé et par série temporelle.

npx skills add https://github.com/mkurman/zorai --skill dataset-splitting

Partitionnement de Dataset

Vue d'ensemble

Les partitions train/validation/test sont la garde-fou la plus importante contre le surapprentissage et la fuite de données. Une mauvaise partition invalide tout ce qui en découle. Partitionner une fois, verrouiller la partition, et ne jamais laisser les données de test influencer aucune décision.

Quand utiliser

Utilisez cette skill quand :

  • Préparer les données pour l'apprentissage supervisé.
  • Concevoir des protocoles d'évaluation pour la comparaison de modèles.
  • Configurer des folds de validation croisée.
  • Assurer l'absence de fuite de données entre les partitions.

Ne pas utiliser pour :

  • Évaluation en apprentissage non supervisé — des règles différentes s'appliquent.
  • Prévision de séries temporelles avec backtesting — utilisez les skills darts ou prophet.
  • Nettoyage de données — utilisez dataset-cleaning avant le partitionnement.

Stratégies de partitionnement

1. Partitionnement Aléatoire Standard (IID)

from sklearn.model_selection import train_test_split

# Partition unique (graine fixe, stratifiée)
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,
    stratify=y,
    random_state=42  # NE JAMAIS changer cela à la légère
)

# Train / validation / test (deux étapes)
X_temp, X_test, y_temp, y_test = train_test_split(
    X, y, test_size=0.15, stratify=y, random_state=42
)
X_train, X_val, y_train, y_val = train_test_split(
    X_temp, y_temp, test_size=0.1765, stratify=y_temp, random_state=42
)
# Résultat : 70% train, 15% val, 15% test

2. Partitionnement Stratifié (Déséquilibre de classe)

# Stratifier sur la cible ET les attributs protégés
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,
    stratify=df[['target', 'gender', 'region']].apply(tuple, axis=1),
    random_state=42
)

3. Partitionnement au Niveau du Groupe (Pas de Contamination Croisée)

from sklearn.model_selection import GroupShuffleSplit

# Quand les lignes du même groupe DOIVENT rester ensemble
# Exemple : plusieurs échantillons par patient
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
train_idx, test_idx = next(gss.split(X, y, groups=df['patient_id']))
X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]

4. Partitionnement de Série Temporelle (Pas de Fuite Future)

# Partitionnement chronologique — NE JAMAIS mélanger les données temporelles
df = df.sort_values('timestamp')
split_idx = int(len(df) * 0.8)
train = df.iloc[:split_idx]
test = df.iloc[split_idx:]

# Pour plusieurs fenêtres de backtesting :
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
    X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
    # Chaque fold utilise des données anciennes pour l'entraînement, plus récentes pour le test

Configuration de Validation Croisée

from sklearn.model_selection import StratifiedKFold, RepeatedStratifiedKFold

# 5-fold standard
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Répétée pour les petits datasets
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=42)

Liste de vérification Prévention de Fuite

Avant de verrouiller la partition, vérifiez :

  1. Pas de fuite d'ID : la même entité n'apparaît pas en train et test.
  2. Pas de fuite temporelle : tous les timestamps d'entraînement précèdent tous les timestamps de test.
  3. Pas de fuite de cible : aucune feature dérivée des données de test (y compris imputation, mise à l'échelle, encodage).
  4. Pas de fuite de groupe : les groupes (patients, utilisateurs, expériences) sont complètement dans une seule partition.
  5. Stratification préservée : distribution de la cible similaire entre les partitions.
# Validation de la distribution de partition
for split_name, split_df in [('train', train_df), ('val', val_df), ('test', test_df)]:
    print(f"{split_name}: {split_df['target'].value_counts(normalize=True).to_dict()}")

# Vérification d'intégrité du groupe
train_groups = set(train_df['group_id'])
test_groups = set(test_df['group_id'])
assert len(train_groups & test_groups) == 0, "Fuite de groupe détectée !"

Verrouillage de la Partition

Une fois créée, sauvegardez les affectations de partition de manière immuable :

# Ajouter colonne de partition et sauvegarder
df['split'] = 'train'
df.loc[val_idx, 'split'] = 'val'
df.loc[test_idx, 'split'] = 'test'

# Sauvegarder avec version
df.to_parquet('dataset_v1.0.0_with_splits.parquet', index=False)

# Sauvegarder les indices de partition pour la reproductibilité
np.savez('split_indices_v1.0.0.npz',
         train=train_idx, val=val_idx, test=test_idx)

Porte de Qualité

Une partition est valide quand :

  • Chaque ligne appartient exactement à une partition.
  • Aucune fuite de groupe, entité ou timestamp n'existe.
  • La distribution de la cible est cohérente entre les partitions (dans la tolérance).
  • Les artefacts de partition (indices, affectations) sont sauvegardés et versionnés.
  • Toutes les décisions de prétraitement sont prises en utilisant uniquement l'ensemble d'entraînement.

Skills similaires