train_test_splitのstratifyエラーは、ラベルの形、欠損、クラスごとの件数、分割先の行数の順に確認します。 同じエラーに見えても、ラベルが一件しかない場合と、テスト側の枠がクラス数より少ない場合では、見直す場所が違います。

対象は、Pythonで分類モデル用の訓練・テストデータを作っている人です。本稿では架空の行IDとラベルで八つのケースを試します。エラーがなくなったことと、評価に必要なクラスが両側に残ることも別です。 少数クラスを消したり、テスト行を複製したりして形だけ整える方法は採りません。

公式資料確認日:2026年10月21日。train_test_split、StratifiedShuffleSplit、クラス比率・グループ・時系列の分割を確認しました。掲載コードの実行環境はPython 3.12.14、NumPy 2.5.4、scikit-learn 1.9.1です。数値は編集部の架空教材での実行結果であり、どのデータでも同じ比率になるという説明ではありません。

stratifyを使う前にラベル欠損とクラス別件数、分割先の行数を確認し、使った後にも両側のクラス別件数とID重複を調べる。全100行のうち少数クラス2行では、テスト20行にそのクラスが残らない教材例がある。
編集部作成。分割が実行できた後にも、目的に合う評価ができるかを確認します。

stratifyのエラーを、ラベル・クラス数・分割行数に分ける

stratifyへ渡したものが、分類したいラベルか確認する

stratifyは、指定したラベルのクラス比率をおおむね保つように分割するための引数です。分類モデルの目的変数がyなら、一般的な指定はstratify=yです。特徴量の表を、そのまま渡す引数ではありません。

例えば受付ごとの対応区分をゼロと一で分類するなら、ラベルはその区分です。行IDがA01、A02と一件ずつ違っていても、行IDをクラスとして揃えるわけではありません。行IDをstratifyへ指定すると、一件しかないクラスが大量にできることがあります。

まず、Xとyの行数、yの形、使っている列名を確認します。公式APIのstratifyの説明は、一行につき一つのラベルという形です。表全体や複数の目的変数を渡している場合は、どの分類を評価するのかへ戻ってください。

yが一列の表であることと、複数列のラベルであることも分けます。本来一列なのに形だけが二次元なら、目的の一列を選ぶ方法を検討できます。一方、複数列を無条件に平らにすると、行数もラベルの意味も変わります。エラーを消す目的で形だけ変えないようにします。

実装や入力によっては、二次元のラベルを渡しても処理が進む場合があります。しかし、そのことは複数のラベルそれぞれの比率を望みどおり保つという説明にはなりません。行の組み合わせがどう扱われているかを確認せずに、複数ラベルの評価へ流用するのは避けましょう。

最初の診断用教材は、数値ラベルに欠損が一つある六行です。次のコードは、欠損の個数と、欠損以外のクラス件数を表示するだけです。欠損行をモデル学習から自動で削除する処理ではありません。

import numpy as np

labels = np.array([0., 0., 1., 1., np.nan, 1.])
print(len(labels), labels.shape, int(np.isnan(labels).sum()))
values, counts = np.unique(
    labels[~np.isnan(labels)], return_counts=True
)
print(dict(zip(values.tolist(), counts.tolist())))
# 6 (6,) 1
# {0.0: 2, 1.0: 3}

この確認では、六行中一行のラベルが不明だと分かります。ゼロと一の件数だけを見て、五行のデータへいつの間にか変わったと考えないでください。欠損をどう扱うか決める前の、入力確認の段階です。

一件しかないクラスは、乱数を変えても二つに分かれない

エラーにleast populatedという語が出る場合は、最も件数が少ないクラスを調べます。本稿の実行環境では、一クラス一件の入力で、件数が足りないことを示すValueErrorが出ました。文言の細部はバージョンで変わる場合があります。

一件しかないラベルは、その一件を訓練とテストの両方へ独立して分けられません。乱数を変えることや、行の並びを変えることでは、元の件数は増えません。最初に、ラベルの入力間違いや、行IDを誤って指定していないかを調べます。

from sklearn.model_selection import train_test_split

X = np.arange(6).reshape(-1, 1)
y = np.array([0, 0, 0, 0, 0, 1])
try:
    train_test_split(
        X, y, test_size=2, stratify=y, random_state=0
    )
except ValueError:
    print("hold: check the count of each class")

一というクラスを消すと、処理は進むかもしれません。しかし、少数クラスを識別するための分類だったなら、目的が変わっています。除外してよいラベルなのか、入力の誤りなのか、本当に少ない現象なのかを分けて考えます。

同じ行を複製して両側へ置く方法も、別の問題を生みます。コピーした行は、独立した新しい観測にはなりません。モデルが見た行に近いものをテストへ混ぜると、未知の入力での評価と条件が違います。件数の不足を、行数の見かけで埋めないようにします。

必要なデータを追加で集められるか、評価の分割方法を変更するか、対象とする分類の定義自体を見直すかを検討します。どれが妥当かは目的とデータに依存します。データが少ない段階で、特定の割合へ分割できることだけを成果にしないでください。

test_sizeとtrain_sizeは、割合ではなく実際の行数も見る

各クラスに二件あっても、分割先の枠が足りなければエラーになります。例えばゼロ、一、二が各二件の六行なら、クラスは三種類です。テストを二行にすると、三クラスを一件ずつ置く枠が足りません。

本稿では、test_size=2のケースと、train_size=2のケースを分けて実行しました。前者はテスト側、後者は訓練側の行数がクラス数より少ないため、いずれもValueErrorになりました。エラーの語がtestとtrainのどちらかを読んで、該当する設定を確認します。

整数の二と、小数の0.2は意味が違います。整数は二行、小数は全件に対する割合です。同じ数字のつもりで指定すると、想定と違う行数になります。割合から行数へ換算すると端数も生じるため、画面上の比率だけで条件を判定しないようにします。

例えば六行を0.2でテストへ分けると、単純な手計算は1.2行ですが、一部だけの行を置くことはできません。実装の丸めを含む実際の行数を確認します。本稿の数値例では、分割先の件数を明示し、割合と件数を混ぜないようにしています。

クラス数以上の枠を用意できれば、どのクラスも目的どおりに残る、という十分条件にはなりません。特に件数の偏りが大きい場合は、比率に近づける割り当てと端数の影響があります。後半で、エラーが出ないまま少数クラスがテストに残らない例を確認します。

また、test_sizeとtrain_sizeの両方を明示した設計では、全件がどちらかへ入るとは限りません。使わない行を意図的に残す場合は、その役割も記録します。「分割後の合計は元と同じ」という確認は、全件を二つへ分ける設計に対して行います。

欠損・連続値・shuffleのエラーを別々に再現する

ラベルにNaNがある場合は、クラス数の問題とは分けて扱います。本稿の六行の教材では、欠損ラベルを含むstratifyの指定でValueErrorが出ました。ラベル不明の行をどのクラスとして扱うか、モデル側へ任せて解消する問題ではありません。

欠損をゼロで埋めると、「分類ゼロだと分かっている行」と「ラベルが分からない行」が混ざります。ゼロが本当のクラス名なら、特に注意してください。ラベルが未確定なのか、読み込みで欠けたのかを確認し、扱いを決めてから分割します。

回帰のように連続した数値を予測する場面では、目的変数をそのままクラスとして指定する意図を見直します。すべて違う数値なら、一件のクラスが並ぶ形になり得ます。区間へ分ければよいと一律に決めず、その区間が評価目的に合うかを検討する必要があります。

さらに、shuffle=Falseとstratifyを同時に指定することは、train_test_splitではできません。時間順を守りたいからshuffleを止めたのであれば、無理にTrueへ戻して実行する前に、時間の制約に合う評価方法を選び直します。

八ケースの実行結果。すべて編集部の架空データ
入力・設定結果戻って確認する点
二クラス各6件、テスト4行実行。テストは各2件ID・件数も分割後に照合
一方のクラスが1件ValueErrorラベルの指定と元のクラス件数
三クラス各2件、テスト2行ValueErrorテスト側の枠がクラス数より少ない
三クラス各2件、訓練2行ValueError訓練側の枠がクラス数より少ない
二クラス98件と2件、テスト20行実行。ただしテストの少数クラス0件エラーなしでも必要な評価ができるか
数値ラベルにNaNを含むValueError不明ラベルの原因と扱い
0から7まで各1件の数値ValueError分類か回帰か、ラベルの意味
shuffle=Falseとstratifyを併用ValueError時間順など、分割に必要な制約

この表は、すべての入力検証を列挙したものではありません。Xとyの長さが違う場合などもエラーの原因になります。一つ直したら別の条件で止まることがあるため、入力と設定を一度に大量に変更せず、原因ごとに切り分けます。

CSVの型や抽出条件が原因で行が減っているなら、分割の前へ戻って調べます。pandasのisinで型と対象漏れを確認する方法のように、どの行を残したかを記録してから、分割する件数を数えましょう。

Xとyは、同じ行の組として絞り込み・分割する

分割前に欠損や対象外の行を除く場合は、特徴量とラベルの対応を守ります。Xだけを絞り、yを元のまま使うと、長さが違ったり、別の行のラベルを対応させたりするおそれがあります。行番号が並び直されて見えても、同じ対象とは限りません。

予習では、各行に説明用のIDを付けると照合しやすくなります。このIDはラベルではなく、同じ行かを追うためのものです。モデルの特徴量として利用できるという意味もありません。分割と対応の確認に使います。

例えば一つの表で欠損ラベルの扱いを決め、同じ条件で残した行からXとyを作る方法があります。その後は、Xとyを同じtrain_test_splitの呼び出しへ渡します。別々の呼び出しへ同じ乱数を指定する方法に頼るより、行を一組として扱う意図を示せます。

表の結合でラベルを付けた場合は、結合時の重複も見ます。一人の対象が二行になっていれば、クラス数だけ合っていても、独立した二件とは言えない場合があります。pandasのmergeで行数を検算する方法を使い、元の粒度を確認してください。

行番号ではなくIDでラベルを対応させる場合は、IDの型も揃えます。先頭ゼロのある文字列と数値が混ざると、別の処理で対象漏れが起こることがあります。分割に成功した後も、元の表から意図した対象を使えたかを確認しましょう。

ここで入力を直した場合は、クラス件数をもう一度数えます。欠損行を保留した結果、二件あったクラスが一件になることも考えられます。分割前の条件は、元のCSV全体ではなく、実際に分割する行に対して確かめます。

エラーのない分割も、クラス別件数と評価の目的を確かめる

十二行を分け、IDの重複とラベルの件数を照合する

まず、ゼロと一が六件ずつある十二行を分けます。Xの一列は行IDを表す教材用の整数で、分類の説明変数として適切かは評価していません。ここでは予測モデルを作らず、分割された行だけを確認します。

テストは四行、訓練は八行です。この教材では、両側に両クラスが残ること、IDが重複していないこと、合わせて元の十二行になることを確認条件にします。別の設計では、必要な条件を目的に合わせて変更してください。

from collections import Counter
import numpy as np
from sklearn.model_selection import train_test_split

ids = np.arange(12)
y = np.array([0] * 6 + [1] * 6)
train_ids, test_ids, y_train, y_test = train_test_split(
    ids, y, test_size=4, stratify=y, random_state=0
)

assert len(train_ids) == 8
assert len(test_ids) == 4
assert not (set(train_ids) & set(test_ids))
assert set(train_ids) | set(test_ids) == set(ids)
assert set(y_train) == set(y_test) == set(y)
assert Counter(y_train.tolist()) == {0: 4, 1: 4}
assert Counter(y_test.tolist()) == {0: 2, 1: 2}
print(dict(Counter(y_test.tolist())))
# {0: 2, 1: 2}

乱数を固定したのは、この教材の条件で結果を再確認するためです。random_stateが同じなら、どのライブラリ版でも永続的に同じID順になると約束するものではありません。版と入力を記録し、必要なら分割したID自体も残します。

ここで確認したのは、行IDの重複がないことです。もし同じ人の別の日の記録が異なるIDを持つなら、この確認だけで同一人物が両側へ混ざっていないとは言えません。後で述べるグループの制約を、別に確認する必要があります。

また、二クラスが揃ったことだけで、評価に十分な件数とは判断できません。テスト四行は仕組みを見るための小さな教材です。実務での評価には、知りたい指標や失敗を見たい対象に対して、どの程度の情報があるかを検討します。

九十八件と二件では、実行できてもテストに残らない

次に、ゼロが九十八件、一が二件の百行を使います。テストを二十行にしてstratifyを指定します。各クラス二件以上、訓練もテストも二クラス以上の行数という、先ほどの確認を満たしています。

それでも、本稿の実行環境と設定では、テスト側の一はゼロ件でした。比率に近づける割り当てには端数が生じるため、少数クラスのすべてを両側へ一件以上残すという保証ではありません。分割後の実際の件数が必要です。

ids = np.arange(100)
y = np.array([0] * 98 + [1] * 2)
train_ids, test_ids, y_train, y_test = train_test_split(
    ids, y, test_size=20, stratify=y, random_state=0
)
counts = Counter(y_test.tolist())
assert counts[0] == 20
assert counts[1] == 0
print(dict(counts))
# {0: 20}

この例では、少数クラスについてテスト側での予測を確認できません。例外が発生していないから、そのまま目的に合う評価ができるとは言えないのです。エラーが出た場合の修正と、実行後の採否判定を分けましょう。

少数クラスが入る乱数を探せばよい、と単純化もしません。評価する行の選び方を、都合のよい結果に合わせて変えると、後から結果を説明しにくくなります。データ数の不足と、分割の設計を明示して相談するほうが先です。

公式の交差検証の説明も、層化が統計上の不確実性を解消する仕組みではないと注意しています。比率を揃えた見た目だけで安心せず、少ない観測に依存していることを記録してください。この教材では、少数クラスを含む評価としての採用を保留します。

分割を直す前に、時系列と同一グループの制約を読む

クラス比率を揃えることは、分割に必要な条件の一つにすぎません。例えば同じ顧客の複数の記録、同じ機械の連続した測定、時間に沿って発生する注文では、単純な行単位のランダム分割が目的に合わない場合があります。

新しい顧客への予測を評価したいなら、同じ顧客が訓練とテストの両方へ入らないようにする設計が候補です。すでに知っている顧客の記録を使った成績と、未知の顧客へ適用する成績では、知りたいことが違うためです。

将来の売上を予測したいなら、未来の行を訓練へ混ぜないことを考えます。比率を揃えるために日付をばらばらにすることで、実際の予測時点では手に入らない情報を使う状態になるかもしれません。クラスの件数を優先するだけでは足りません。

scikit-learnには、グループや時系列に対応する分割方法の説明があります。本稿では、それらをすべて同じコードへ置き換える手順は示しません。何を未知とする評価かを定めてから、必要な制約を満たす方法を選んでください。

たとえ分割方法を変えても、少数クラスの情報が増えるわけではありません。グループを分けたら一方のクラスがなくなる、といった場合は、その事実を残します。比率、グループ、時間のすべてを満たせると思い込まないことが大切です。

ここでは、「一行が何を表すか」「どの時点で分かる情報か」「新しい何へ予測したいか」を三行で書いてみましょう。この三行を説明できない場合は、先に評価の目的を整理し、splitの引数だけを調整する作業から離れます。

クラスの表記を見直すときも、文字列が違う理由を調べてから修正します。例えば「完了」と「完了 」のように末尾の空白が違うだけなら、同じ意味のラベルが分かれている可能性があります。ただし、似た名前をすべて結合してよいという意味ではありません。

反対に、「確認済」と「承認済」は、業務上別の段階かもしれません。件数が少ないからという理由で同じクラスへまとめると、分類したい状態が変わります。表記ゆれの修正と、分類定義の変更を、同じデータ清掃として処理しないようにします。

確認用には、元のラベルと候補のラベルを二列で並べ、どの入力を何へ変えるかを目で読めるようにします。変更した行数とIDも残します。読み込み時に消えた空白や、文字列の「nan」と実際の欠損値も区別し、元の表と比較できる状態を保ちます。

例えばクラス一件の原因が入力誤りだと確認できたなら、正しい元情報へ戻してから件数を数え直します。元情報を確認できない場合は、都合のよい多数クラスへ移さず、未確認として扱います。ラベルを確定できたかどうかは、splitが実行できるかとは別の判断です。

もう一つ、同じ評価を後から追えるようにするには、乱数だけでなく、元のデータの範囲も記録します。同じrandom_stateでも、行を追加したり、順序やクラスを変えたりすれば、元と同じ分割を確認したことにはなりません。対象IDの一覧が変わった場合は、新しい実行として残します。

集計では、ゼロ件のクラスも表示する工夫が役立ちます。Counterや件数表にキーが出ないと、もともとクラスがなかったのか、分割でなくなったのかを見落としがちです。元のクラス一覧を基準に、訓練とテストの件数を同じ順に並べ、存在しないクラスはゼロと記録します。

修正記録には、元の件数と採用・保留の理由を残す

調査中は、入力と設定を変えるたびに短い記録を作ります。元の全件数、クラス別件数、欠損の個数、訓練・テストの行数、分割後のクラス別件数、採用するか保留するかを同じ行に置くと、どの修正が何を変えたか追えます。

例えば十二行の教材は、両側に両クラスが残り、IDの重複がないという教材用の条件を満たしたので、仕組みの確認として採用できます。一方、百行の教材は実行できても、少数クラスの評価という目的に対しては保留です。

この採用は、実務モデルの承認や、予測精度の合格を意味しません。何の確認条件に対して採用したかを書きます。「エラーなし」とだけ残すと、後から少数クラスを見たか、行の重複を見たかが分からなくなります。

分割した後の前処理も別に点検します。StandardScalerを訓練データだけで学習する手順のように、平均などの学習基準へテストが混ざっていないかを確認します。stratifyの条件を満たしても、前処理のデータリークを防いだことにはなりません。

学習用の表を集め直す段階なら、PythonでCSV分析を始める学習手順へ戻り、行の粒度や列の型を整理できます。データを直す作業と、評価の分割を選ぶ作業を分けると、相談内容が具体的になります。

講座で支援を受けたい場合は、AIスクールの無料相談で聞くことも参考に、質問できる課題の範囲を確認します。エラーの全文を貼るだけでなく、小さく再現できる架空データ、期待した分割、実際の件数を添えてください。

stratifyのエラーと分割後確認のFAQ

よくある質問

Q. 各クラスが二件以上なら、エラーはなくなりますか?
A.

それだけでは足りません。訓練・テスト側の行数がクラス数に足りるか、欠損や形の問題がないかなども確認します。さらに、実行できても各クラスが両側へ残るとは限らないため、分割後の件数を調べます。

Q. stratifyを外せば解決ですか?
A.

実行できる場合はありますが、クラス比率を保つ意図や評価の条件が変わります。何を評価したいかを定め、分割後に必要なラベルと件数が残るかを確認して判断してください。

Q. 少数クラスをコピーして二件にしてよいですか?
A.

コピーは独立した新しい観測ではありません。訓練とテストへ近い記録が混ざる問題にもつながります。元の件数不足と評価設計を見直し、形だけ件数を増やさないようにします。

Q. NaNラベルはゼロで埋めればよいですか?
A.

ラベル不明とクラスゼロは別です。欠損の原因と扱いを決め、特徴量と同じ行の対応を保って整理します。整理後にはクラス件数を再確認してください。

Q. 時間順を守りつつstratifyを使えますか?
A.

train_test_splitではshuffle=Falseとstratifyを併用できません。時間の制約を守る評価方法を先に選び、クラス件数の不足も別に確認します。単にshuffle=Trueへ変える判断は避けましょう。

Q. random_stateを変えて少数クラスが入るまで試しますか?
A.

結果に都合のよい分割を探す前に、必要な評価条件とデータ数を確認します。少数クラスが入った場合も情報量が増えたわけではありません。分割方法・ID・件数と採否理由を記録してください。