Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych
Uczą się Państwo stosować StratifiedKFold do zachowania proporcji klas oraz TimeSeriesSplit, aby zapobiec przedostawaniu się przyszłych danych do przeszłych foldów w zbiorach uporządkowanych chronologicznie.
Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych to bezpłatna lekcja Machine Learning Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Machine Learning Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Machine Learning Academy zawiera 4 lekcji w sumie.
Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.
When Standard K-Fold Fails
Standard KFold splits data randomly without regard for class distribution or temporal order. This creates two serious problems: (1) for imbalanced datasets, some folds may have very few or no minority class examples, making fold-to-fold variation extreme; (2) for time-ordered data, training on future examples to predict the past creates data leakage that inflates CV scores far beyond real-world performance. Specialised CV strategies solve these problems without sacrificing honest evaluation.
Stratified K-Fold: Preserving Class Ratios
StratifiedKFold ensures that each fold contains approximately the same proportion of each class as the full dataset. For example, if 10% of your data is fraudulent, each fold will have approximately 10% fraud cases. This prevents the scenario where one fold has no fraud cases, making it impossible for the classifier to learn to detect fraud in that round. Use StratifiedKFold whenever you have more than 2 classes or significant class imbalance.
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import numpy as np
X, y = load_breast_cancer(return_X_y=True)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(RandomForestClassifier(random_state=42), X, y, cv=skf)
print('Stratified 5-fold:', np.round(scores, 4))
print('Mean:', round(scores.mean(), 4), 'Std:', round(scores.std(), 4))Verifying Stratification
You can verify that StratifiedKFold maintains class ratios by inspecting the target distribution in each fold. For each test fold, the proportion of positive examples should match the overall positive rate. Comparing the class distributions from standard KFold and StratifiedKFold on an imbalanced dataset clearly shows why stratification matters — fold class ratios can differ by 20%+ without stratification.
from sklearn.model_selection import StratifiedKFold, KFold
import numpy as np
# Imbalanced data: 90% negative, 10% positive
np.random.seed(42)
y = np.array([0]*90 + [1]*10)
X = np.random.randn(100, 5)
for name, cv in [('KFold', KFold(n_splits=5, shuffle=True, random_state=42)),
('StratifiedKFold', StratifiedKFold(n_splits=5, shuffle=True, random_state=42))]:
ratios = [y[test].mean() for _, test in cv.split(X, y)]
print(f'{name} positive rates per fold:', np.round(ratios, 2))cross_val_score Uses Stratification Automatically
When you pass cv=5 (an integer) to cross_val_score for classification tasks, scikit-learn automatically uses StratifiedKFold. For regression, it uses plain KFold. This means most users get stratification for free without explicitly constructing a StratifiedKFold object. Only override this default when you need custom split behaviour (e.g., group splits, time-series splits, or a different number of folds with shuffling).
Time Series Data: The Unique Challenge
Financial, sensor, weather, and many other real-world datasets are ordered in time. A key property of time-series models: predictions must be made using only past data. If you train a model on data from April and use it to predict January values, you are training on the future — this is called temporal data leakage. Even standard K-Fold CV violates this: when fold 3 is the test set and fold 4 is in the training set, future data predicts the past. TimeSeriesSplit prevents this.
TimeSeriesSplit: Always Train on the Past
TimeSeriesSplit creates folds where training data always precedes test data chronologically. In each split, the training set expands by including the previous test fold. For example, with 5 splits: split 1 trains on the first 20% and tests on the next 20%, split 2 trains on the first 40% and tests on the next 20%, and so on. This ensures no future information enters the training set and the CV score reflects realistic temporal generalisation.
from sklearn.model_selection import TimeSeriesSplit
import numpy as np
n = 100
X = np.random.randn(n, 5)
y = np.random.randn(n)
tscv = TimeSeriesSplit(n_splits=5)
for fold, (train_idx, test_idx) in enumerate(tscv.split(X, y), 1):
print(f'Fold {fold}: train [{train_idx[0]}-{train_idx[-1]}], '
f'test [{test_idx[0]}-{test_idx[-1]}]')Using TimeSeriesSplit with cross_val_score
Pass a TimeSeriesSplit object directly to the cv parameter of cross_val_score. The resulting scores measure how well the model generalises when trained on earlier data and evaluated on later data — which is exactly the real-world use case. Note that the training sets grow in size across folds, so earlier folds have fewer training examples and may show lower scores — this is expected, not a bug.
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.ensemble import GradientBoostingRegressor
import numpy as np
# Simulated time-series: price = trend + noise
np.random.seed(42)
n = 200
X = np.arange(n).reshape(-1, 1) + np.random.randn(n, 1) * 5
y = 0.5 * np.arange(n) + np.random.randn(n) * 10
tscv = TimeSeriesSplit(n_splits=5)
scores = cross_val_score(GradientBoostingRegressor(n_estimators=50, random_state=42),
X, y, cv=tscv, scoring='r2')
print('TimeSeriesSplit R² scores:', np.round(scores, 4))
print('Mean R²:', round(scores.mean(), 4))GroupKFold: Preventing Sample Leakage
Some datasets have multiple samples per entity — multiple ECGs per patient, multiple transactions per user, multiple images per scene. If the same patient appears in both training and test folds, the model learns patient-specific patterns rather than general patterns. GroupKFold ensures all samples from the same group (patient, user, scene) are in the same fold, preventing this within-group leakage. Pass group identifiers to the groups parameter of cross_val_score.
from sklearn.model_selection import GroupKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
import numpy as np
np.random.seed(42)
n = 100
X = np.random.randn(n, 5)
y = np.random.randint(0, 2, n)
groups = np.repeat(np.arange(10), 10) # 10 patients, 10 samples each
gkf = GroupKFold(n_splits=5)
scores = cross_val_score(LogisticRegression(max_iter=1000), X, y, cv=gkf, groups=groups)
print('GroupKFold scores:', np.round(scores, 4))
print('Mean:', round(scores.mean(), 4))Expanding Window vs Sliding Window for Time Series
TimeSeriesSplit uses an expanding window: the training set grows with each split. An alternative is a sliding window: use only the most recent N days/months for training, discarding older data. Sliding windows are better when the relationship between features and target changes over time (concept drift) and older data is no longer representative. scikit-learn does not provide a sliding window CV out of the box, but you can implement it using a custom CV generator or TimeSeriesSplit with max_train_size.
from sklearn.model_selection import TimeSeriesSplit
import numpy as np
# Sliding window: fix the training size
tscv = TimeSeriesSplit(n_splits=4, max_train_size=50) # max 50 training samples
X = np.random.randn(200, 3)
for fold, (train_idx, test_idx) in enumerate(tscv.split(X), 1):
print(f'Fold {fold}: train size={len(train_idx)}, '
f'test [{test_idx[0]}-{test_idx[-1]}]')Choosing the Right CV Strategy
A simple decision guide: (1) Classification with balanced classes → standard KFold or cross_val_score integer (auto-stratified); (2) Classification with imbalance → StratifiedKFold explicitly; (3) Time-ordered data → TimeSeriesSplit; (4) Multiple samples per entity → GroupKFold; (5) Imbalanced + grouped → StratifiedGroupKFold (scikit-learn 0.24+). Getting the CV strategy wrong can make a poorly generalising model look great or a good model look mediocre — always match the CV method to your real-world deployment scenario.
Walk-Forward Validation for Production
In real production time-series systems, models are retrained periodically and evaluated on the subsequent period. Walk-forward validation simulates this: train on months 1-6, evaluate on month 7; train on months 1-7, evaluate on month 8; and so on. This is equivalent to TimeSeriesSplit with gap parameter to add a buffer between train end and test start (to avoid leakage from closely adjacent time points). The resulting score sequence also reveals whether model performance is stable over time or degrading.
from sklearn.model_selection import TimeSeriesSplit
import numpy as np
# Simulate 24 months of data
n_months = 24
np.random.seed(42)
X = np.random.randn(n_months, 5)
y = np.random.randn(n_months)
# Walk-forward: 12 train periods, 1 test each
tscv = TimeSeriesSplit(n_splits=12, test_size=1)
for fold, (tr, te) in enumerate(tscv.split(X), 1):
if fold <= 3 or fold == 12:
print(f'Fold {fold:2d}: train months 1-{len(tr)}, test month {te[0]+1}')Quick Check
Test your understanding of Stratified and Time-Series cross-validation from this lesson.
Lesson Recap
In this lesson you learned: StratifiedKFold preserves class proportions across folds for imbalanced classification, TimeSeriesSplit prevents temporal leakage by always training on past data, and GroupKFold prevents within-group leakage when multiple samples belong to the same entity. Next up we compare Grid Search and Random Search for hyperparameter optimisation.
Często zadawane pytania
Czy lekcja „Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych” jest bezpłatna?
Tak — pełny tekst „Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Machine Learning Academy, przejdź na CoddyKit PRO. Kurs Machine Learning Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych”?
Uczą się Państwo stosować StratifiedKFold do zachowania proporcji klas oraz TimeSeriesSplit, aby zapobiec przedostawaniu się przyszłych danych do przeszłych foldów w zbiorach uporządkowanych chronolo… Ćwiczysz Machine Learning Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Machine Learning Academy?
Nie wymagamy żadnego doświadczenia. Machine Learning Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Machine Learning Academy?
Tak. Każda lekcja Machine Learning Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Walidacja krzyżowa K-krotna: podział bez wycieku danych
- Stratyfikowana walidacja krzyżowa i walidacja szeregów czasowych
- Grid Search a Random Search
- Zagnieżdżona walidacja krzyżowa: jednoczesny wybór i ocena