層化交差検証と時系列交差検証
StratifiedKFoldでクラス比率を維持し、TimeSeriesSplitで時間順データの過去のフォールドに未来のデータが漏洩するのを防ぎます。
「層化交差検証と時系列交差検証」はCoddyKit上の無料Machine Learning Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはMachine Learning Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Machine Learning Academyコースには全4レッスンが含まれています。
このレッスンの一部はまだ翻訳されておらず、英語で表示されています。
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.
よくある質問
「層化交差検証と時系列交差検証」レッスンは無料ですか?
はい。「層化交差検証と時系列交差検証」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Machine Learning Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Machine Learning Academyコースには全4レッスンが含まれています。
「層化交差検証と時系列交差検証」で何を学びますか?
StratifiedKFoldでクラス比率を維持し、TimeSeriesSplitで時間順データの過去のフォールドに未来のデータが漏洩するのを防ぎます。 ブラウザで直接実行するハンズオンコードでMachine Learning Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Machine Learning Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのMachine Learning Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「層化交差検証と時系列交差検証」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このMachine Learning Academyレッスンでコードを書いて実行できますか?
はい。すべてのMachine Learning Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。