qcutの「Bin edges must be unique」は、分位点から作った境界値が重なったときに出るエラーです。 duplicates="drop"で動いても、指定した数だけグループができたとは限りません。実際の境界、カテゴリ数、各グループの件数をセットで確認します。

対象は、pandasで数値列を読み込めるようになり、値を四分位などへ分類したい人です。大量のコードを直す前に、九行の架空データで問題を再現します。元データの重複行を消す話とは異なります。同じ値が何度も出ること自体は、記録の誤りとは限りません。

公式資料の確認日:2026年10月24日。qcut、cut、Series.rankを参照しています。確認時の現行資料はpandas 3.1.0、以下のコードの実行確認環境はPython 3.12.14、pandas 2.2.3です。資料の版と実行環境の版を区別し、実務では利用中の版でも結果を確認してください。

9行中8行が有効な教材。四分位の境界0・0・0・12.5・30が重複し、drop後は2ビン、件数は6件と2件、欠損1件になる。
四分割を指定したことと、四つの区分が完成したことを分けて確認します。

qcutの重複エラーは、値ではなく境界から調べる

qcutとcutは、何を揃えたいかで使い分ける

qcutはデータの分位点を基準に区間を作ります。一方、cutでは等間隔や、あらかじめ決めた境界で値を分けられます。例えば「観測値の分布を四つに分けたい」と「10分以下、20分以下、それを超える時間に分けたい」では、必要な基準が違います。

qcutを選んでも、同じ値が多いと各区間の件数は揃わない場合があります。ゼロが五件、正の値が三件しかないとき、ゼロを同じ区間に残しながら二件ずつ四つに分けることはできません。エラーを消す設定と、分類ルールの実現可能性を切り離して考えましょう。

同じ値を同じ扱いにすることが目的なら、人数の不均等を許容するか、グループ数を見直します。逆に一定の人数へ割り当てる運用なら、同値の順番をどう決めるかが必要です。どちらも技術的には実装できますが、完成したグループの意味は同じではありません。

値を分ける前に決める目的
方法基準注意点
qcutそのデータの分位点同値が多いと境界が重なる。件数均等を前提にしない
cutに境界を指定決めた数値の区切り境界の左右、最小値、範囲外を確認する
順位を付けてqcut値と同値の順番同じ値が別の区分になり得る。順序の根拠が必要

この記事では、作業時間の教材を使います。ゼロは「記録済みで所要時間が0分」、欠損は「未記録」と定義します。ゼロを欠損へ変えたり、未記録をゼロで補ったりするルールは採用しません。自分の資料でゼロの意味が違う場合は、その定義から見直してください。

九行で分位点の重なりとエラーを再現する

入力は0、0、0、0、0、10、20、30、欠損の九行です。数値がある八行の四分位点は、最小値から順に0、0、0、12.5、30になります。重なっているのは元データの行番号ではなく、区間を切るための境界です。

最小値と25%点と50%点が同じ0です。0と0の間に幅のある区間を作れないため、そのまま四分割を要求するとエラーになります。12.5は入力に存在しませんが、分位点の計算から得た境界です。元の値の一覧だけでなく、分位点も表示すると原因が見えます。

import pandas as pd

s = pd.Series(
    [0, 0, 0, 0, 0, 10, 20, 30, None],
    index=[f"R{i}" for i in range(1, 10)],
    name="minutes",
)
print(s.quantile([0, .25, .5, .75, 1]).to_list())
# [0.0, 0.0, 0.0, 12.5, 30.0]

try:
    pd.qcut(s, q=4)
except ValueError as exc:
    assert "Bin edges must be unique" in str(exc)
    print("境界の重複を再現しました")
else:
    raise AssertionError("教材で想定したエラーが出ていません")

この例のエラーを、CSVの重複行削除で解決しようとしないでください。R1からR5が別々の作業なら、値が同じ0でも五件の記録です。一件だけ残すと、分類方法を直したのではなく、分析対象を減らしたことになります。前処理前の件数も失われます。

同じ値があるだけで、常にqcutが失敗するわけではありません。指定した分位数と分布によって、境界が重なるかが変わります。例外が出た列、指定したq、有効件数、分位点の一覧を残すと、自分のデータで何が起きたか説明しやすくなります。

duplicatesをdropにしたら、実際のビン数を数える

duplicates="drop"は重なった境界を取り除く指定です。今回の境界は0、12.5、30の三つになり、間の区間は二つになります。四つに分けたつもりで「第1四分位から第4四分位」と表示すると、コードの出力と説明が合いません。

実際のカテゴリ数はcat.categoriesで、数値の境界はretbins=Trueで確認できます。表示される最初の区間ラベルには、最小値を含めるために小さく調整した下端が出る場合があります。表示文字列を切り取って、元の境界として保存する方法は避けましょう。

group, edges = pd.qcut(s, q=4, duplicates="drop", retbins=True)
counts = group.value_counts(sort=False, dropna=False)
print(edges.tolist())  # [0.0, 12.5, 30.0]
print(counts)

assert len(edges) - 1 == 2
assert len(group.cat.categories) == 2
assert group.notna().sum() == 8
assert group.isna().sum() == 1
assert group.value_counts(sort=False).to_list() == [6, 2]
assert len(group) == len(s) == 9

最初の区間には0が五件と10が一件で計六件、二つ目には20と30で二件が入ります。欠損一件も含めて六足す二足す一が九になることを確かめます。処理が成功したかだけでなく、元の行が説明なく消えていないかも確認できる形です。

precisionは区間ラベルの精度に関する指定で、観測値の意味を変えずに重複を解消する万能の設定ではありません。ラベルの桁を増やす前に、数値として同じ境界なのか、表示だけが似ているのかをretbinsの値から調べます。

四つのラベルを先に渡すと、別のエラーが残る

四分割を想定してラベルを四つ渡し、同時に重複境界をdropすると、ラベル数と実際の区間数が合わなくなる場合があります。これは最初の境界エラーを直せていないというより、結果のビン数が変わったために次の条件を満たせなくなった状態です。

今回のように二つのビンしかできないなら、四つの文字ラベルをそのまま付けられません。ラベル数を一つ減らせばよい、と固定で直す方法も、別の分布で再び合わなくなります。まずラベルなしで計算し、何区分になったかを確かめます。

try:
    pd.qcut(s, 4, labels=["Q1", "Q2", "Q3", "Q4"], duplicates="drop")
except ValueError as exc:
    print(type(exc).__name__, "ラベル数と区間数を確認")
else:
    raise AssertionError("4ラベルがそのまま通る教材ではありません")

display_names = [f"区分{i + 1}" for i in range(len(group.cat.categories))]
named = group.cat.rename_categories(display_names)
print(named.value_counts(sort=False, dropna=False))
assert list(named.cat.categories) == ["区分1", "区分2"]

「低・中・高」のような意味のある名前を使う場合は、ビン数に合わせて機械的に削るだけでは不十分です。高という区分がなくなったのか、二つをまとめたのかを利用者が判断できません。教材では意味を付け過ぎない「区分1・区分2」とし、境界を隣に表示します。

ラベルの問題と、文字列の型が原因で分類できない問題も分けてください。数値に見える文字に円記号やカンマが混ざる場合は、先に数値化の前後を検算する手順で入力を確認します。分類だけ成功させるために元の列を消さないことも大切です。

rankのfirstは、同じ値を別の区分へ分ける

重複エラーへの対処例として、rank(method="first")の結果をqcutへ渡す方法があります。firstは同じ値へ出現順の順位を付けます。八行の有効データへ1から8の順位が付けば、順位を四つの区間へ分けやすくなります。

ただし、分類対象は元の作業時間ではなく順位に変わっています。今回の五つのゼロは、順位1から5になり、四区分のうち三つへまたがります。同じ所要時間を同じ扱いにしたい分析であれば、エラーが消えても目的に合っていません。

rank_groups = pd.qcut(s.rank(method="first"), 4, labels=False)
print(pd.DataFrame({"minutes": s, "rank_group": rank_groups}))
zero_groups = rank_groups[s.eq(0)]
assert zero_groups.nunique() == 3
assert rank_groups.value_counts().sort_index().to_list() == [2, 2, 2, 2]

順位による割り当てを使うなら、同値の順番の根拠を明記します。入力ファイルの行順がたまたま変わっただけで割り当ても変わる運用では、後から同じ結果を再現しにくくなります。受付順などを基準にする場合も、その列が正しく保存されているかを確かめます。

ノイズを足して値を少しずらす方法も、元の測定値と分類ルールを変えます。本稿では採用しません。「エラーをなくす」「同値の公平な扱いを決める」「件数を揃える」は別の判断です。何を守るための変更なのかを説明できる方法を選びましょう。

順位で分ける方法を検討するときは、行順を変えても同じ対象に同じ区分が付くかを試すと、仕様の違いが見えます。例えばゼロの五件だけを逆順にしてからfirstで順位を付けると、先頭の順位を得るIDが変わります。人数は二件ずつのままでも、各IDの区分は元の結果と異なり得ます。件数の検算だけでは、この変化を検出できません。

同値を分けないqcutと、順位で分けるqcutを比較する表には、グループ数と件数に加えて「同じ値を別扱いにするか」を一列置いてください。利用者が時間の差を見たいなら前者、抽選や順番など別の割当基準が明示されているなら後者、というように判断材料を分けられます。ライブラリの短い対処法だけから方針を決めないための確認です。

分類した後は、件数・境界・新しいデータまで確認する

欠損・空データ・全件同じ値を入口で分ける

どんな列でも四分位を作れる前提にせず、入力の状態を先に確認します。有効な数値がない列と、すべて同じ値の列では、分析上の扱いを決める必要があります。例外をまとめて握りつぶし、全行を同じ名前へ入れると、未記録と定数列の違いが消えます。

全件5の列へdropを指定すると、確認環境では境界が5一つだけとなり、区間ができず全行が欠損になります。「dropを付ければ分類が完成する」と考えないための確認点です。区間が二つ以上必要という用途なら、最初に条件を満たすか検査します。

def make_quantile_groups(values, q=4):
    valid = values.dropna()
    if valid.empty:
        raise ValueError("有効な数値がありません")
    if valid.nunique() < 2:
        raise ValueError("全件同値です。分位区分を作る目的を再確認してください")
    result, boundaries = pd.qcut(values, q, duplicates="drop", retbins=True)
    if len(boundaries) < 2:
        raise ValueError("区間ができていません")
    assert result.index.equals(values.index)
    assert result.notna().sum() == valid.size
    return result, boundaries

checked, saved_edges = make_quantile_groups(s)
for bad in [pd.Series([], dtype=float), pd.Series([None, None], dtype=float),
            pd.Series([5, 5, 5], dtype=float)]:
    try:
        make_quantile_groups(bad)
    except ValueError:
        pass
    else:
        raise AssertionError("入口で保留にする教材です")

これは教材用の入口確認で、文字列や無限大を含むすべての入力に対応した汎用関数ではありません。数値として妥当か、欠損を除いてよいか、同値の列を一つの固定区分として扱うべきかは資料のルールで決めます。停止理由を記録できる形にしてから対象を広げましょう。

入力検査では行数だけでなく、実行前の列名、単位、有効な数値の最小・最大、欠損数も控えます。ゼロが多いのか、読み込み時に文字が欠損へ変わったのかで対応は違います。CSVの欠損・重複の確認も入口の点検に使えます。

全件同じ値を保留にした場合は、その理由をそのまま利用者へ渡します。「処理エラー」とだけ表示するより、「有効値は三件、異なる値は一種類なので、今回の分位区分の作成を止めた」と書く方が、入力不足か分類不要かを判断できます。全件同値なら全件を一つの固定区分にする、という運用を選ぶ場合も、欠損へ変換した結果を完成品にするのではなく、別の明示したルールとして実装します。

分類を中止した列があることを、正常な列の結果へ混ぜないようにしましょう。列ごとに入力件数、有効件数、異なる値の数、処理状態、使った境界を記録すれば、たくさんの列を一度に処理するときも確認できます。空データの失敗で全体を止めるか、その列だけ保留にするかは、使う場面に合わせて先に決めておくと迷いにくくなります。

新しいデータへ同じ基準を使うなら、境界を保存する

別の日のデータにも同じ区分を付けたい場合、毎回qcutを実行すると、その日の分布に合わせて境界が変わります。同じ10分でも、日によって別の区分になる可能性があります。「同じ名前の区分だから同じ範囲」とは限りません。

基準日などで決めた区間を使い続けるなら、qcutで得た数値の境界を保存し、後の分類ではcutへ明示します。何のデータから、いつ、どのqで作った境界かも一緒に残します。区間ラベルの見た目だけをコピーすると、端点の調整や丸めが混ざるおそれがあります。

機械学習の検証に使う場合は、訓練・評価の分割を先に行い、訓練データで境界を決めます。評価データを含めて分位点を決めると、評価側の分布を前処理へ取り込んでしまいます。標準化とは処理が違いますが、訓練データだけで前処理を決める考え方と同じ順序で整理できます。

今回の九行は仕組みを手計算するための教材で、学習モデルの性能を評価するデータではありません。境界を保存できたことから、その区分が業務に適している、予測精度が上がる、とまでは言えません。区分にすることで失う数値の差も考えて選びます。

境界を更新する運用なら、更新前と更新後の基準を区別します。旧区分と新区分を一つの列で上書きして比較すると、件数の変化が元の値によるのか、区間変更によるのか判断しにくくなります。生の数値を残し、基準の識別名を添えると見直しができます。

保存した境界の外側と、元の欠損を区別する

教材で保存した境界は0、12.5、30です。この外側にある値へcutを適用すると、その区分は欠損になります。元から空欄だった場合と、新しい数値が保存範囲の外に出た場合は、同じ欠損表示でも理由が違います。

次の七件では、マイナス1と31が範囲外、最後の一件が元から欠損です。0と12.5は最初の区間、15と30は二つ目へ入ります。最小値の0を含めるため、cutにはinclude_lowest=Trueを指定しています。

new_values = pd.Series([-1, 0, 12.5, 15, 30, 31, None], name="minutes")
new_groups = pd.cut(new_values, saved_edges, labels=False, include_lowest=True)
source_missing = new_values.isna()
out_of_range = new_values.notna() & (
    new_values.lt(saved_edges[0]) | new_values.gt(saved_edges[-1])
)
assert new_groups.notna().sum() == 4
assert source_missing.sum() == 1
assert out_of_range.sum() == 2
assert new_groups.isna().sum() == source_missing.sum() + out_of_range.sum()
assert new_groups.iloc[[1, 2, 3, 4]].to_list() == [0, 0, 1, 1]
print(pd.DataFrame({"minutes": new_values, "group": new_groups,
                    "source_missing": source_missing, "out_of_range": out_of_range}))

範囲外を最小・最大の区分へ押し込むか、確認用へ分けるかは用途の判断です。とりあえず0で埋めると、範囲外と最初の区分が混ざります。本稿では範囲外の二件を保留として残し、境界の設計か入力値を確認する対象にします。

端点の扱いは、右側を含むか左側を含むかでも変わります。今回の例はcutの既定で右端を含み、最初の区間だけ最小値も含めています。保存する資料には単に「0から12.5」と書くだけでなく、0と12.5がどちらへ入るかを具体的な値で示してください。

保存した境界の単位も確認します。基準の列が分、新しい列が秒なら、同じ数値をcutへ渡しても同じ長さを比較していません。境界を適当に広げる前に、元の値の単位と変換式を照合してください。区間外が急に増えた場合も、分布が変わったという結論の前に、列の取り違えや単位の変更を調べる価値があります。

数値の丸めも同様です。12.5を資料上12へ丸め、それを新しい境界として使うと、12より大きく12.5以下の値の区分が変わります。読みやすい表示と分類用の数値を別に保存すれば、説明の都合で判定条件まで変えることを防げます。元の境界を保存する理由は、再計算を省くためだけでなく、同じ基準を保つためでもあります。

出力の表と検算を、次の作業へ渡せる形にする

分類後の納品物を作る練習では、区分列だけを残すのではなく、元の数値、分類結果、確認状態を並べます。集計表には実際のビン数、境界、各区間の件数、欠損数を付けます。これなら別の人がコードを実行しなくても、四分割が二区分へ変わった事実を確認できます。

例えば今回の教材の報告は、「九行中八行が有効。四分位の重複境界を除いたため二区分。件数は六件・二件、未記録一件。元の行は削除していない」です。件数が不均等であることを隠さず、同じ値を分けなかった結果として説明します。

チェック用には、元の九行からR1、R6、R8、R9を選びます。最小値0、最初の区間に入る10、最大値30、欠損の四種類です。追加データでは端点12.5と範囲外31も確認します。全件を目で追えなくても、仕様の違いが出る値から確認する練習になります。

カテゴリの数値コードも、そのまま大小の意味を持つ測定値として扱わないようにします。コード0と1は、この境界に対する区分名です。平均時間の代わりに区分コードの平均を計算しても、同じ量にはなりません。後続の集計が何を表すかまで説明できる状態を目指しましょう。

CSVへ保存して再利用するときは、元のID、数値、区分の対応が変わっていないかも確認します。保存したCSVを読み戻す確認を使い、不要な行番号列が増えていないか、欠損や列名の扱いが変わっていないかを点検してください。

学習で止まった箇所を相談する場合は、元の機密データの代わりに、このような架空の短い再現例を用意します。q、分位点、実際のビン数、期待した同値の扱いを添えると、文法の質問と分類方針の相談を分けられます。機械学習スクールの予習チェックにもつながる練習です。

よくある質問と、分類を終える前の確認

質問と回答

Q. duplicates='drop'なら指定した四つのグループができますか?
A.

重複した境界を除くため、実際のビン数が減る場合があります。教材では四分割を指定しても二つの区間になり、件数は六件と二件です。retbinsとcat.categoriesを確認してください。

Q. 重複エラーは元データの重複行を削除すれば直りますか?
A.

同じ数値が別々の正しい記録である場合、行を削除すると分析対象が変わります。まず重複している境界と、同値をどう扱いたいかを確認します。

Q. dropを付けたらラベル数のエラーになりました。
A.

残った区間数に対して、先に用意したラベルが多過ぎる可能性があります。ラベルなしで計算し、実際のカテゴリ数を確認してから命名します。

Q. rank(method='first')で直すのは問題ありませんか?
A.

順位を使うと同じ数値が別の区分になり得ます。出現順で割り当てる仕様が目的に合うときに限り、順序の根拠と再現方法を決めて使います。

Q. 毎月のデータへ毎回qcutして比較してもよいですか?
A.

毎回の分布から境界を決めると、同じ区分名でも数値の範囲が変わる場合があります。同じ基準で比較するなら保存した境界をcutへ渡し、範囲外を別に確認します。

Q. cutの結果が欠損なら元の数値も欠損ですか?
A.

元の欠損だけでなく、指定境界の外にある数値も欠損になります。入力のisnaと範囲外の判定を別々に持つと原因を区別できます。

完成の目安は、エラーが消えたことに加え、実際のビン数とラベルが合い、同値の扱いを説明でき、元件数と分類後の件数が一致していることです。新しい値にも適用するなら、保存境界、端点、範囲外の確認まで含めましょう。分類の意味を残せる小さなコードから始めると、別の資料へ応用する判断もしやすくなります。

分類の基準や前処理を、Pythonの基礎から講師と整理したい人は、Python Winnerも比較先になります。公式ではPythonデータ分析を含む講座を案内しています。今回のqcut課題を個別に添削してもらえるか、使うpandasの版、追加費用を含む総額は、申込前に受講相談で確かめてください。まずは本稿の九行の検算まで独学で試してから、必要な支援を決めましょう。(公式情報確認日:2026年10月24日、Python Winner公式)。

広告: 【PythonWinner】