TimeSeriesSplit(gap=2)は、テストの直前にある2行を、その回の訓練から外します。 適切な行数は、予測を始める時刻と、訓練に使う正解が分かる時刻の関係で決めます。2日後を予測する例でも、日付が飛ぶ表では2行が2日とは限りません。

Pythonで交差検証を始めた人向けです。架空の受付件数を2日先まで予測する設計を、モデルの精度を出す前に点検します。入力の1行は「その日に予測を発行した」ときの記録です。正解は2日後の営業終了時点で確定するものとします。

12行のどこが訓練・除外・テストに入るか

日付順は、分割器へ渡す前に保証する

TimeSeriesSplitが返すのは入力の行位置です。日付列を読み取って並べ替えたり、祝日や集計遅延を見たりはしません。モデルに使う行を確定してから、予測発行時刻で昇順に並べます。同じ時刻に複数店舗の行がある場合は、同時刻をまたいで訓練とテストに分けてよいかも決めます。

公式のTimeSeriesSplit(2026年11月9日確認)では、gapは各回のテスト直前で訓練から除外するサンプル数です。test_sizeは1回のテスト行数、max_train_sizeは訓練窓の最大行数です。役割を分けて指定します。

同じ最初のテストを、gap=0と2で比べる

11月1日から12日まで毎日1行のD01〜D12を用意します。例のn_splits=3, test_size=2では、各テストはD07〜D08、D09〜D10、D11〜D12です。正解が後から判明する設計を試すため、ここではダミーの入力で分割だけを実行します。

from sklearn.model_selection import TimeSeriesSplit

X = [[i] for i in range(12)]

def describe(gap):
    splitter = TimeSeriesSplit(n_splits=3, test_size=2, gap=gap)
    result = []
    for train, test in splitter.split(X):
        excluded = list(range(int(train[-1]) + 1, int(test[0])))
        result.append((train.tolist(), excluded, test.tolist()))
    return result

no_gap = describe(0)
with_gap = describe(2)
assert no_gap[0] == ([0, 1, 2, 3, 4, 5], [], [6, 7])
assert with_gap[0] == ([0, 1, 2, 3], [4, 5], [6, 7])
assert [x[2] for x in no_gap] == [x[2] for x in with_gap]
for fold, split in enumerate(with_gap, start=1):
    print(fold, split)
回gap=0の訓練gap=2の訓練gapで除外共通テスト
1D01–D06D01–D04D05・D06D07・D08
2D01–D08D01–D06D07・D08D09・D10
3D01–D10D01–D08D09・D10D11・D12

コードの配列は0始まりで、記事のD番号は1始まりです。位置4と5はD05とD06を指します。除外した2行は、その回のテストへ移りません。回が進んで正解が使える時刻になれば、前の回で除外した行を後の訓練に含めることはできます。

行数より先に、正解が使える時刻を置く

「D05はD07の件数を予測した行」と整理します。D07の朝にテストを始める時点では、その日の受付件数は確定していません。D05を学習に含めれば、後から分かったD07の正解を先に見せた状態になります。

gap=1ならD05がまだ訓練に残ります。gap=2なら最後はD04です。D04の正解はD06の夕方に確定するので、D07朝に使えます。翌日まで集計が遅れる運用なら、行数の選択をもう一度見直します。

from datetime import datetime, timedelta

prediction_at = [datetime(2026, 11, 1, 9) + timedelta(days=i)
                 for i in range(len(X))]
label_ready_at = [t + timedelta(days=2, hours=9) for t in prediction_at]

def availability_ok(split):
    train, _, test = split
    first_prediction = prediction_at[test[0]]
    return all(label_ready_at[i] < first_prediction for i in train)

assert not availability_ok(describe(1)[0])
assert all(availability_ok(split) for split in with_gap)

ここでは「確定が予測発行より前」を採用しました。同時刻に発生するデータを使えると仮定するなら、処理の順序や、データが実際に参照できるまでの時間を確認します。日付だけの列では朝と夕方が区別できないため、可能なら時刻まで記録します。

日付の抜けと、gapでは直らない漏洩を点検する

2行を空けても、必ずしも2営業日は空かない

月・火・木・金のように日付が抜けた表では、同じ2行でも経過日数が場所によって異なります。また、午前と午後が別行なら、2行を外しても1日分に届かないかもしれません。公式リファレンスでは、各回のテスト結果を同じ時間幅で比較するには、観測間隔が等しいことが前提です。

欠けた日付を0にして間隔をそろえる必要はありません。暦日ごとの予測なら、日付範囲でテストを区切った上で、その開始時刻より前に確定した訓練ラベルだけを選ぶ方法があります。営業日で評価するなら、営業日のカレンダーを先に固定します。いずれも「テスト行数」と「評価対象の期間」は別々に記録します。

データの状態 点検する項目 選ぶ方法の目安
毎日同時刻・1行、確定が一定 テスト初日の時刻と直前の確定時刻 行数で分け、各回を時刻で検算
休日や欠損日で間隔が変わる テストに含める日付の範囲 時刻で分割し、確定時刻で訓練行を選ぶ
同時刻に複数拠点がある 同時刻の行が左右に割れていないか 時刻をまとまりとして扱う
正解の確定日が遅れることがある 実際に参照できる時刻 予定日と確定日を別々に記録

確定が遅れた行は、位置が古くても確認する

例えばD03の確定値だけ、記録の照合でD07の正午まで遅れたとします。D03は最初の分割で除外されるD05・D06より古く、gap=2だけでは訓練に残ります。予測時点で本当に利用できる値を再現するなら、D03をその回の訓練候補から除く必要があります。

ready_with_delay = label_ready_at.copy()
ready_with_delay[2] = datetime(2026, 11, 7, 12)
train, _, test = with_gap[0]
cutoff = prediction_at[test[0]]
usable_train = [i for i in train if ready_with_delay[i] < cutoff]
assert train == [0, 1, 2, 3]
assert usable_train == [0, 1, 3]

これは1回分を目で確認するコードです。実際に使う場合は、各回で確定時刻を照合した分割を明示し、残った件数で学習できるかを確認します。もし過去の確定時刻が保存されていなければ、評価で置いた仮定を記録し、確定の遅れをどの程度含む評価なのか説明できるようにします。

gapでは特徴量の未来参照を防げない

2日後の件数そのものを特徴量へ含めていれば、いくら訓練の末尾を削ってもテストの入力が未来を見ています。中央寄せの移動平均、全期間で計算した平均、後日修正された数値にも注意します。日々のログの初回値と、確定後の値は、同じ日付でも得られる時刻が違います。

scikit-learnの前処理とデータ漏洩のガイドでは、変換に必要な値を訓練データで学習し、テストへ適用する方法が説明されています。標準化などのfitを各回の訓練データに限定するには、モデルと一緒にPipelineに入れます。特徴量の出所が未来であれば、Pipelineを使っても修正されないため、特徴量を作る段階で参照時刻を確認します。

試行のたびに、条件と除外した行を残す

最初に表へ載せるのは精度ではなく、訓練の最終発行時刻、訓練ラベルの最終確定時刻、テストの最初と最後の発行時刻、使った行数です。n_splits、test_size、gapを大きくした結果、先頭の回に訓練行が残らない場合もあります。今回の12行で最初のテストが位置6なら、gap=6で最初の訓練が空になります。エラーを消すために条件だけ緩めるのではなく、目的に合うだけの観測があるかを先に確認します。

独学なら「D07朝では正解の何日分まで使えるか」を自分で説明し、次に正解の確定を1日遅らせて同じ表を作ってみてください。評価のやり方を学ぶ段階であれば、機械学習スクールに向けたPython・数学の準備で、実装前に必要な基礎を整理できます。

機械学習の評価まで講座で学ぶとき

Pythonで表を扱えるようになり、次は分割や評価方法も説明したい人は、Python WinnerのAI・機械学習マスターを比較候補にできます。公式案内ではPythonの基礎から機械学習の理論と実装までをマンツーマンで扱います(2026年11月9日確認)。時系列の分割が標準課題に含まれるかは、本稿のような例を持参して相談時に確認してください。

広告:【PythonWinner】

よくある質問

Q. gap=2はテストを2行後ろにずらしますか?
A.

本稿のように分割回数とtest_sizeを固定した例では、テストの位置は同じです。テスト直前の2行がその回の訓練から外れます。

Q. 2日先予測ならいつもgap=2ですか?
A.

行間隔と正解が使える時刻で決まります。本稿は毎日1行、朝に予測して2日後の夕方に正解が分かる条件です。

Q. 正解がすぐ確定するならgap=0でもよいですか?
A.

訓練に使う正解が初回テストより前に確定し、特徴量の参照条件も満たすかを確認してください。

Q. 除外行を後の分割で使ってもよいですか?
A.

その回の予測時刻までに正解が確定していれば、後の訓練に入る場合があります。全期間から削除する操作とは異なります。

Q. max_train_sizeでgapの代わりになりますか?
A.

max_train_sizeは訓練窓を過去側から制限します。テスト直前の除外行を設けるのはgapです。

Q. TimeSeriesSplitで並べ替えもできますか?
A.

入力順を使って位置を返すので、日時での並べ替えや同時刻の扱いは分割器を呼ぶ前に決めてください。