2026年10月8日にpandasのqcutとDataFrame.rankの現行本文を再確認した。デシルは値や順位を分位へ区切る整理で、qcutの境界、同値、欠損、重複、サンプル数、rankの方式で群の人数と境界が変わる。分位群を顧客の品質、原因、将来利益、施策効果と読み替えず、元の値・時点・分母・欠損処理・境界・再計算条件を保存し、群間差を別の検証へつなげる。
顧客ごとの売上を大きい順に並べ、人数ができるだけ均等な10群へ分けると、売上がどの層に集中しているかを確認できます。これがデシル分析です。上位群が大きいという観察と、上位群へ特典を出すと利益が増えるという因果の主張は分けて考えます。
30秒で要点
- 10等分するのは顧客の人数です。各群の売上が10%ずつになるわけではありません。
- 同じ期間と金額の定義で、人数・合計・平均・構成比を並べます。同額の扱いも決めます。
- 新規客の観測期間や販促の影響を確認し、集計結果から施策の効果を断定しません。
集計対象と期間が変わると、順位の意味も変わる
「過去6か月の購入客」だけを見るのか、「登録済みの全顧客」を見るのかで、ゼロ購入の人を含むかが変わります。購入履歴から取ったデータに、購入しなかった顧客の行が存在するとは限りません。
表が収まらない場合は、横にスクロールできます
| 設計すること | 例 | 読み方の違い |
|---|---|---|
| 母集団 | 同じ店舗で期間内に購入した顧客 | 未購入者への施策の評価は含まれない |
| 金額 | 税・返品の扱いをそろえた売上 | 値引きや返品の処理が違うと順位も変わる |
| 観測期間 | 全員の暦上の同じ6か月 | 期間途中の新規客は購入機会が短い |
| コホート比較 | 同じ獲得月の顧客を獲得後90日で見る | 全員の90日が経過したかを確認する |
| 同額の規則 | 売上降順、同額なら顧客ID昇順 | 同額でも別の群に入ることがある |
6か月や90日は説明用の例で、どの商材にも適した固定期間ではありません。購入間隔、契約更新、季節性に合わせて決めます。期間を長くすると短期の変動は平均化されやすくなる一方、最近の変化を見落とす場合もあります。
10人の例で「上位20%の寄与」を計算する
以下は同一期間の税抜売上を集計した仮想データです。顧客はAからJの10人で、実在する企業や顧客の実績ではありません。この小さな例では各群が1人になり、デシル1が最上位、10が最下位です。
表が収まらない場合は、横にスクロールできます
| デシル | 顧客 | 人数 | 売上合計・平均(円) | 売上構成比 | 累積構成比 |
|---|---|---|---|---|---|
| 1 | A | 1 | 120,000 | 26.61% | 26.61% |
| 2 | B | 1 | 80,000 | 17.74% | 44.35% |
| 3 | J | 1 | 70,000 | 15.52% | 59.87% |
| 4 | C | 1 | 60,000 | 13.30% | 73.17% |
| 5 | F | 1 | 40,000 | 8.87% | 82.04% |
| 6 | I | 1 | 30,000 | 6.65% | 88.69% |
| 7 | G | 1 | 22,000 | 4.88% | 93.57% |
| 8 | D | 1 | 15,000 | 3.33% | 96.90% |
| 9 | H | 1 | 9,000 | 2.00% | 98.89% |
| 10 | E | 1 | 5,000 | 1.11% | 100.00% |
全体売上は451,000円です。上位2人は人数では2/10=20%、売上では200,000/451,000=約44.35%を占めます。「上位20%なら売上80%になる」と決めているわけではありません。表示値は小数第2位へ丸め、計算は丸める前の金額で行います。
10人の例は手計算しやすい反面、1人の購入だけで分布が大きく動きます。実データでも各群の人数を必ず出し、金額の大きさだけから再現性のある顧客層が見つかったと判断しないでください。
Pythonで順位・人数・売上を集計する
pandasのqcutは、順位や標本の分位点に基づいて群を作る関数です。ここでは売上を順位に変換してから10群へ分けます。同額の順位を登場順にするrank(method="first")を使うため、先に顧客IDの昇順を含む並べ替えを行います。pandas「qcut」、pandas「DataFrame.rank」
必要なライブラリはpandasと、次の図用のMatplotlibです。売上は非負の整数円、顧客は10人以上、全体売上は正という教材の入力条件を明示しています。返品で負になる顧客や顧客IDの重複を黙って落とさず、返品の定義や顧客単位の集約を先に確認します。
import pandas as pd
df = pd.DataFrame({
"customer": list("ABCDEFGHIJ"),
"sales": [120000, 80000, 60000, 15000, 5000, 40000, 22000, 9000, 30000, 70000],
})
if len(df) < 10 or df[["customer", "sales"]].isna().any().any():
raise ValueError("need at least ten customers and no missing input")
if not df["customer"].is_unique:
raise ValueError("aggregate to one row per customer first")
if not pd.api.types.is_integer_dtype(df["sales"]) or (df["sales"] < 0).any():
raise ValueError("this example requires nonnegative integer yen")
if sum(int(x) for x in df["sales"]) <= 0:
raise ValueError("total sales must be positive")
df = df.sort_values(["sales", "customer"], ascending=[False, True]).reset_index(drop=True)
rank = df["sales"].rank(method="first", ascending=False)
df["decile"] = pd.qcut(rank, 10, labels=False) + 1
summary = df.groupby("decile").agg(
sales_sum=("sales", "sum"),
sales_mean=("sales", "mean"),
n=("customer", "size"),
).sort_index()
summary["share"] = summary["sales_sum"] / summary["sales_sum"].sum()
summary["cum_share"] = summary["share"].cumsum()
print(summary.to_string())
print("top two share:", summary.loc[[1, 2], "share"].sum())
期待する順序はA、B、J、C、F、I、G、D、H、Eで、最後の表示値は約0.443459です。顧客数が10で割り切れない場合、各群の人数は完全にはそろいません。結果のnを報告してください。
売上額へ直接qcutを使うと、同額が多いときに分位点の境界が重複してエラーになる場合があります。duplicates="drop"で境界を減らす方法はありますが、10群が維持されるとは限りません。本例は一意の順位を作る方式なので、同額でも別の群へ分ける可能性を受け入れていることが重要です。同額を同じ群に保つ方式とは区別します。
売上の棒と累積の線を描く
最初のコードに続けて、同じPythonセッションで実行します。
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 1, figsize=(7.2, 6), sharex=True)
axes[0].bar(summary.index, summary["sales_sum"], color="#246879")
axes[0].set_ylabel("Sales (yen)")
axes[0].set_ylim(bottom=0)
axes[1].plot(summary.index, summary["cum_share"] * 100, marker="o", color="#b66b25")
axes[1].set_ylabel("Cumulative share (%)")
axes[1].set_ylim(0, 105)
axes[1].set_xticks(range(1, 11))
axes[1].set_xlabel("Decile (1 = highest sales)")
fig.tight_layout()
plt.show()
棒が人数を表しているわけではありません。累積線が終点で100%になることも、良い業績の証拠ではなく集計の性質です。
分布から施策の仮説を作り、効果は別に確かめる
上位群に売上が集中していれば、購入理由や欠品・対応の問題を優先して調べる価値があります。ただし、値引きが大きい顧客、対応工数が多い顧客では、売上と利益への寄与が一致しません。
架空の店舗で会員特典を検討するなら、対象期間と対象顧客を先に決め、特典がなくても購入した分を区別できる比較方法を用意します。見るのは追加売上だけでなく、商品の原価、特典費用、連絡や対応の費用まで含めた差額です。連絡の対象や利用目的も社内ルールと顧客への説明に沿って確認します。分析結果があること自体を、特典の配信許可や顧客ごとの扱いを決める根拠にはしません。
表が収まらない場合は、横にスクロールできます
| 観察 | まず調べること | 集計だけでは言えないこと |
|---|---|---|
| 上位群の売上比率が大きい | 粗利、対応費用、継続購入の理由 | 特典を増やせば利益が増える |
| 下位群が多い | 各群人数、獲得時期、購入機会、離脱要因 | 全員へ同じ施策が最適である |
| 上位群が月ごとに変わる | 同じ人の変化か、母集団の入替えか | 顧客の価値が恒久的に変化した |
毎月並べ直した上位群同士を比べると、対象者が入れ替わります。「今月の分布を知る」集計と、「先月の上位顧客が今月どうなったか」を追う集計は別に作ってください。
三つの場面で判断を練習する
- 新規客が増えた月に上位群の寄与が下がった。既存客の売上低下とは限りません。人数と売上の内訳を確認し、新規・既存を分け、同じ観測期間のコホートでも比較します。
- セール期間だけ順位が変動した。通常価格の時期と分け、返品の反映時期や購入の前倒しを確認します。翌月の低下を施策の失敗と即断しません。
- 購入回数で並べると売上順位と逆転した。少額を頻繁に買う顧客と、高額を少数回買う顧客では必要な対応が違う可能性があります。何を改善したいかを決めて指標を選びます。
CVRを併記する場合にも分母が重要です。架空の2人が1回中1回、9回中1回の行動に至ったとき、個人率の単純平均は約55.56%ですが、全10回の行動機会に対する率は2/10=20%です。どちらを答えたいのかを決め、群の「機会に対する率」なら分子・分母をそれぞれ合計します。
商品の重要度や購入の時期も確認する
商品ごとの管理優先度ならABC分析、購入の時期・頻度・金額を組み合わせるならRFM分析、併買を調べるならアソシエーション分析へ進めます。デシルは分布を見る一つの入口として、判断したい問いに合わせて使います。