論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
Precision-Recall 曲線(PR 曲線)
Precision-Recall Curve / PR Curve
評価指標不均衡分類
別称: PR 曲線、 PR-AUC、 Average Precision (AP)

🔖 キーワード索引

適合率TPFP陽性的中率PPVPrecision-RecallPR 曲線F1Macro/MicroSpecificity閾値

別名・略称:PR 曲線、 PR-AUC、 Average Precision(AP)

Precision-Recall 曲線は 分類器の閾値を 0→1 に動かしたときの Precision と Recall の軌跡。 「クラス不均衡データでの分類器評価」に最適で、 ROC 曲線より厳しく・公平に性能を判定できます。 曲線下面積(PR-AUC = Average Precision = AP)が単一の総合指標。

💡 30秒で分かる結論

🍰 まずはやさしく

正解を当てる力の成績表のようなものです。

予測の正確さをまとめて評価するために使います。

スマホの検索で正しい結果が出るか測る例です。

この章ではPR曲線の基本について読みます。

Precision-Recall 曲線(PR 曲線):閾値を動かして得られる Precision と Recall の軌跡。 不均衡分類の標準評価。

📍 あなたが今見ているもの

🍰 まずはやさしく

モデルの総合的な実力を示すグラフです。

設定を変えても変わらない性能を知るために使います。

部活のメンバー選びで基準を変える時に似ています。

ここではPR曲線が役立つ場面について読みます。

PR 曲線閾値非依存の総合評価指標 です。 単一閾値の Precision・Recall は数字 1 つでしか分類器を語れませんが、 PR 曲線は閾値全体での挙動を可視化し、 PR-AUC(=Average Precision = AP)という 1 つの量に集約します。 ROC 曲線と並ぶ標準だが、 クラス不均衡が強いとき(陽性率 < 10% など)は PR 曲線の方が分類器の真の性能を反映します。 物体検出の mAP、 異常検知の評価、 検索エンジンの順位品質測定で必須。

🎨 直感で掴む

🍰 まずはやさしく

正解率と再現率を線で結んだ図です。

基準を動かした時の変化を見るために使います。

買い物で予算を変えて商品を探す感覚に似ています。

ここではグラフの読み方について読みます。

PR 曲線とは何を描いた図か

分類モデルがスコア(確率)を出力するとき、 閾値 t を 1.0 → 0.0 にゆっくり下げていきます。 各 t で「陽性予測」が増えていき、 Precision と Recall が変動します。 横軸 Recall、 縦軸 Precision としてプロットしたのが PR 曲線です。 理想は右上隅 (R=1, P=1)、 最悪は陽性率 π の水平線(ランダム分類器)。

PR 曲線と ROC 曲線の違い

指標縦軸横軸ベースライン不均衡耐性
PR 曲線PrecisionRecall陽性率 π(変動)高い(陽性側に焦点)
ROC 曲線TPR (=Recall)FPR0.5(一定)低い(陰性が多いと楽観)

PR 曲線の読み方

  • 右上に行くほど良い:理想点 (1, 1)、 PR-AUC = 1.0 が完璧
  • 水平線がベースライン:陽性率 π の水平線がランダム分類器の性能
  • 段差が見える:閾値を 1 ポイント動かすたびに Precision がジャンプ(離散)
  • 左下の急落:閾値 0 に近づくと全件陽性予測 → Precision = π に収束

PR-AUC(AP)の直感

PR 曲線下の面積を Average Precision(AP) と呼びます。 数式的には「Recall が 0 → 1 に動くときの Precision の平均」。 AP=1.0 で完璧、 AP=π(陽性率)でランダム分類と同等。 物体検出では複数クラスの AP を平均した mAP(mean Average Precision) が標準。

🎨 概念図

本用語に関連する代表的な可視化を 3 点示す。

ROC 曲線と Precision-Recall
図 1: ROC 曲線と PR 曲線の対応関係(不均衡データでは PR 曲線が有用)。
ロジスティック分類器
図 2: ロジスティック分類器の閾値と Precision/Recall のトレードオフ。
陽性・陰性分布
図 3: 陽性・陰性分布の重なりと Precision/Recall の関係。

🎮 触って理解する

分類しきい値 t を動かすと、 正例(陽性)と負例(陰性)のスコア分布の上で「判定境界」が左右に移動します。 境界より右側を「陽性」と予測するので、 境界を動かすと TP / FP / FN / TN の 4 区画が変化し、 precision = TP/(TP+FP)recall = TP/(TP+FN) がリアルタイムに再計算されます。 しきい値を上げると precision↑・recall↓、 下げるとその逆になる「トレードオフ」を、 実際に指で触って確かめてください。 計算は正規分布の累積分布関数(誤差関数)で厳密に行っています。

分類しきい値 t = 0.00
← 下げると recall↑ / precision↓  上げると precision↑ / recall↓ →
分離度 d = 2.0
分類器の性能(2 分布の離れ具合)
陽性率 π = 0.50
正例の割合(下げるほど不均衡)
青=負例(陰性)の分布 / 橙=正例(陽性)の分布。 赤の破線が判定境界で、 右側を「陽性」と予測します。 濃い緑=TP、 濃い赤=FP。 グラフを直接ドラッグ/タップしても境界を動かせます。
実際のクラス
陽性 陰性
予測
陽性
TP
0
FP
0
予測
陰性
FN
0
TN
0
Precision = TP/(TP+FP) = 0.000
Recall = TP/(TP+FN) = 0.000
F1 = 2PR/(P+R) = 0.000
PR 曲線(緑)と現在の (recall, precision)(赤点)。 灰の水平破線=ランダム分類器のベースライン π。

🧭 直感:なぜ上げると precision↑・recall↓なのか

しきい値を上げると、 「よほど確信のある例だけ」を陽性と予測するようになります。 拾う数が減るので取りこぼし(FN)は増え recall は下がる一方、 拾った例は当たりやすくなり誤報(FP)が減って precision は上がる。 逆にしきい値を下げると「疑わしきは全部陽性」となり、 取りこぼしは減る(recall↑)が誤報が増える(precision↓)。 上のグラフで境界を右端・左端まで動かすと、 recall≈0 で precision が高い点と、 recall≈1 で precision がベースライン π に沈む点の両極が体感できます。

🕳 よくある落とし穴

🚀 発展的な話題

関連ページ:再現率(Recall)適合率(Precision)F1 スコア混同行列ROC 曲線AUC。 なお「しきい値(threshold)」の独立ページは現時点で用語集内に無いため、 本セクションの解説を参照してください。

📐 定義 / 数式

🍰 まずはやさしく

計算式で表した性能のルールです。

正確な数値でモデルを比べるために使います。

テストの点数を計算して平均を出す時に似ています。

ここでは具体的な数式について読みます。

【Precision・Recall(PR 曲線の各点)】
$$P(t) = \frac{TP(t)}{TP(t) + FP(t)}, \quad R(t) = \frac{TP(t)}{TP(t) + FN(t)}$$
$t$ は閾値。 $P(t), R(t)$ の軌跡を $(R, P)$ 平面にプロットしたものが PR 曲線。
【Average Precision (AP) = PR-AUC】
$$\text{AP} = \sum_{n=1}^{N} (R_n - R_{n-1}) \cdot P_n$$
PR 曲線の階段近似による積分。 Recall の増分を Precision で重み付け。
【AP の積分表現】
$$\text{AP} = \int_0^1 P(R)\,dR$$
連続な分類器スコアの場合の理論的表現。
【mAP(マルチクラス平均)】
$$\text{mAP} = \frac{1}{C} \sum_{c=1}^{C} \text{AP}_c$$
物体検出(COCO, Pascal VOC)の標準。 各クラスの AP の平均。
【ベースライン(ランダム分類器)】
$$\text{PR-AUC}_{\text{random}} = \pi = \frac{\#\text{positives}}{N}$$
ROC-AUC のベースラインは常に 0.5 だが、 PR-AUC のベースラインは 陽性率 π そのもの。 不均衡データほどベースラインが低い。

📐 Precision の閾値依存性と PR-AUC の補強解析

Precision は単一値で語ると本質を見失います。 閾値を動かせば Precision は 0 から 1 まで連続的に変化し、 同時に Recall がトレードオフ的に変動する。 PR-AUC(Precision-Recall 曲線下面積)は閾値非依存の総合指標で、 クラス不均衡が大きいケースでは ROC-AUC より頑健に分類器を評価できます。

🔬 数式を言葉で読み解く(PR-AUC)

PR-AUC は $\int_0^1 P(R)\,dR$ で、 Recall を 0→1 に動かしたときの Precision の平均。 ランダム分類器のベースラインは正例率 π そのもの(ROC-AUC のように 0.5 ではない)。 PR-AUC=0.8 でも π=0.7 ならほぼランダム、 π=0.05 なら超優秀という相対評価が必要。

🧮 実値で計算してみる(SSDSE-B-2026 で人口 500 万人超を予測)

SSDSE-B-2026(2023 年)47 県のうち、 人口 500 万超は 9 県(東京・神奈川・大阪・愛知・埼玉・千葉・兵庫・北海道・福岡)。 正例率 π = 9/47 ≈ 0.191。 これを「大学学生数(E6302)」だけで予測する単変量分類器の PR 曲線を描く。 大都市圏ほど大学生が多いという相関を使う。

🐍 Python 実装(sklearn precision_recall_curve・PR-AUC)

🎯 このコードでやること: SSDSE-B-2026 で「人口 500 万超」を大学学生数から予測する分類器を作り、 sklearn.metrics.precision_recall_curveaverage_precision_score で PR-AUC を算出。 閾値ごとの Precision 推移も観察。

📥 入力データ: SSDSE-B-2026 (data/raw/SSDSE-B-2026.csv) 総人口 A1101・大学学生数 E6302 列(cp932・skiprows=[1])。

Code 都道府県 総人口(人) 大学学生数(人) R13000 東京都 14086000 681667 R14000 神奈川県 9229000 175187 R27000 大阪府 8763000 232937 ...(47 行、 正例 9 / 47, π=0.191)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import pandas as pd
from sklearn.metrics import precision_recall_curve, average_precision_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)

y_true  = (d['A1101'].astype(float) > 5_000_000).astype(int)  # 総人口 500万超
y_score = d['E6302'].astype(float)                            # 大学学生数
pi = y_true.mean()

precision, recall, thresholds = precision_recall_curve(y_true, y_score)
pr_auc = average_precision_score(y_true, y_score)

print(f"正例率 π(ベースライン)= {pi:.3f}")
print(f"PR-AUC                   = {pr_auc:.4f}")
for t in (200_000, 120_000, 70_000):
    prec = (y_true[y_score >= t] == 1).mean()
    print(f"閾値 = {t:>7,} のとき Precision = {prec:.3f}")

📤 実行結果:

正例率 π(ベースライン)= 0.191 PR-AUC = 0.9283 閾値 = 200,000 のとき Precision = 1.000 閾値 = 120,000 のとき Precision = 0.800 閾値 = 70,000 のとき Precision = 0.900

💬 結果の読み方: PR-AUC=0.93 はベースライン 0.191 を遥かに超え、 大学学生数は人口 500 万超の優秀な単変量予測子。 閾値を厳しく(学生 20 万人超)すると Precision=1.0 だが Recall は犠牲。 閾値を 12 万人まで緩めると京都府(大学の街だが人口 253 万)が偽陽性に入り Precision=0.80 に低下。 閾値 7 万人では 9 県すべてを捕捉して Recall=1.0、 偽陽性は京都のみで Precision=0.90。 業務要件で閾値選択が変わる典型例。

⚠️ 落とし穴(追加 3 件)

🔬 数式を言葉で読み解く

$t$(閾値)
分類器のスコア(確率予測など)に対する切り捨て値。 $t$ を高く設定すれば陽性予測は厳選され Precision↑/Recall↓、 低く設定すれば多くを陽性予測し Precision↓/Recall↑。
$P(t), R(t)$
閾値 $t$ における Precision と Recall。 $t$ を 1→0 と動かすと PR 平面上に軌跡が描かれる。 これが PR 曲線。
$R_n - R_{n-1}$
隣り合う Recall の増分。 閾値を 1 ノッチ下げて新たな真陽性を 1 件発見したときの Recall の変化量 = 1/陽性総数。
$P_n$
そのときの Precision。 階段状の PR 曲線で、 各段の高さに相当。
AP(Average Precision)
PR 曲線の階段下面積。 $\sum_n (R_n - R_{n-1}) P_n$ で計算。 sklearn の average_precision_score がこの式を実装。
$\pi$(陽性率/ベースライン)
データ全体に占める陽性の割合。 ランダム分類器の PR-AUC はこの値。 PR-AUC を評価するときは π からの上振れ で見る。
mAP(mean Average Precision)
複数クラスの AP の平均。 物体検出の標準指標(COCO, Pascal VOC)。
PR-AUC ≠ ROC-AUC
同じデータでも数値が異なる。 ROC-AUC は陰性が増えても変動しないが、 PR-AUC は陽性率に応じてベースラインが動く。

🧮 実値で計算してみる(SSDSE-B-2026・47 都道府県)

SSDSE-B-2026(2023 年)で「人口 > 500 万人」を陽性(不均衡分類)として、 「大学学生数(E6302)」をスコアにしたときの PR 曲線を 手計算で構築します。 陽性 9 件(東京・神奈川・大阪・愛知・埼玉・千葉・兵庫・北海道・福岡)、 陰性 38 件、 陽性率 π = 9/47 ≈ 0.191。

閾値(大学学生数・人)予測陽性TPFPPrecisionRecall
≥ 300,0001101.0000.111
≥ 150,0004401.0000.444
≥ 120,0005410.8000.444
≥ 100,0009810.8890.889
≥ 70,00010910.9001.000
≥ 50,00011920.8181.000
≥ 0(全件)479380.1911.000

表の (Recall, Precision) を順にプロットすると PR 曲線。 最後の行(閾値 0)で Precision = 0.191(= π)に収束、 これがランダム分類のベースライン。 閾値 12 万人で京都府(大学の街だが人口 253 万)が偽陽性として混入し Precision が 1.0 から 0.80 に落ちる点が、 この予測子の弱点を示している。

AP の手計算: Recall の各増分を、 その時点の Precision で重み付けして足し合わせる。 Recall が 0.111→0.444(増分 0.333)まで Precision=1.0、 その後 0.889 で 0.889、 1.0 で 0.900 と段階的に下がるため AP は 1.0 より少し小さく ≈ 0.93 になる。 後の Python コードで average_precision_score により AP=0.9283 を確認します。

🧮 数式に値を入れて手で計算する: PR 曲線下面積 (AP)

合成データで 4 閾値の PR から AP を計算する。

Step 1: 閾値別の (P, R)

閾値PR
0.91.000.20
0.70.950.50
0.50.800.80
0.30.601.00

Step 2: AP (台形則 R 軸積分)

AP ≈ Σ ΔR · P = 0.20·1.00 + 0.30·0.95 + 0.30·0.80 + 0.20·0.60 = 0.200 + 0.285 + 0.240 + 0.120 = 0.845

🐍 Python で再現

1
2
3
4
5
6
import numpy as np
R = np.array([0.20, 0.50, 0.80, 1.00])
P = np.array([1.00, 0.95, 0.80, 0.60])
# 階段近似
AP = (np.diff(np.concatenate([[0], R])) * P).sum()
print(f"AP: {AP:.3f}")

📤 実行結果

AP: 0.845

💬 手計算 (Step 2) 0.845 と Python 出力が完全一致。

🐍 Python 実装

SSDSE-B-2026(47 都道府県・2023 年)の実データで PR 曲線と AP(PR-AUC)を計算する基本コード:

🎯 このコードでやること:「人口 500 万超」を陽性ラベルとし、 大学学生数をスコアにして PR 曲線を sklearn で描き、 AP を計算する。 不均衡分類(陽性率 ≈ 19%)での標準ワークフロー。

📥 入力データ:SSDSE-B-2026.csv(総人口 A1101、 大学学生数 E6302)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# SSDSE-B-2026 で PR 曲線・AP を計算
import pandas as pd
import numpy as np
from sklearn.metrics import precision_recall_curve, average_precision_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

# 陽性 = 人口 500 万超、 スコア = 大学学生数
y_true = (d['A1101'].astype(float) > 5_000_000).astype(int).values
scores = d['E6302'].astype(float).values

# PR 曲線(precision, recall, thresholds の 3 配列)
precision, recall, thresholds = precision_recall_curve(y_true, scores)

# AP = PR-AUC
ap = average_precision_score(y_true, scores)
pi = y_true.mean()

print(f'陽性数 = {y_true.sum()}, 陰性数 = {(1-y_true).sum()}')
print(f'陽性率 π (ベースライン) = {pi:.3f}')
print(f'AP (PR-AUC)             = {ap:.4f}')
print(f'\n--- PR 曲線の代表点 ---')
print(f'{"閾値":>14}  {"Precision":>10}  {"Recall":>8}')
for idx in [0, len(precision)//4, len(precision)//2, 3*len(precision)//4, -2]:
    t = thresholds[idx] if idx < len(thresholds) else 0
    print(f'{t:>14.0f}  {precision[idx]:>10.3f}  {recall[idx]:>8.3f}')

📤 実行結果

陽性数 = 9, 陰性数 = 38 陽性率 π (ベースライン) = 0.191 AP (PR-AUC) = 0.9283 --- PR 曲線の代表点 --- 閾値 Precision Recall 6769 0.191 1.000 11642 0.257 1.000 19240 0.391 1.000 54229 0.818 1.000 232937 1.000 0.111

💬 結果の読み方:AP = 0.93 はベースライン 0.19 を遥かに上回り、 大学学生数は人口 500 万超の優秀な単変量予測子。 学生 23 万人以上(東京・大阪)に絞れば Precision = 1.0 だが Recall = 0.11 で 9 件中 1 件しか捕捉できない。 逆に閾値を下げて全 9 県を捕捉すると(学生 7 万人付近)、 京都府だけが偽陽性として残り Precision = 0.90。 業務要件次第で閾値選択が決まる。

⚠️ よくある落とし穴

⚠️ PR-AUC とベースラインの混同
ROC-AUC のベースラインは常に 0.5 だが PR-AUC のベースラインは 陽性率 π。 PR-AUC = 0.5 でも π = 0.4 ならほぼランダム。 必ず π と比較。
⚠️ PR 曲線の補間方法
sklearn は階段補間、 Pascal VOC は 11 点補間、 COCO は 101 点補間。 実装で AP の値が変わる。 論文比較時は補間方法を確認。
⚠️ クラス分布が変わると PR-AUC が変わる
同じ分類器でも陽性率が変わると PR 曲線は変動。 学習データと評価データの陽性率が違うと、 PR-AUC の数値比較が無意味になる。
⚠️ 多クラスの PR-AUC は平均方法を明示
macro / micro / weighted 平均で値が変わる。 物体検出の mAP は macro 相当。 average= 引数のデフォルトに頼らない。
⚠️ 閾値選択は別データで
同一データで PR 曲線を描いて最適閾値を選ぶと過学習。 必ず validation セットで閾値を選び、 test セットで PR 曲線を評価。

⚠️ 条件・限界・誤解回避

適用条件

  1. 二値分類問題 (またはマルチクラスの 1-vs-rest): Precision/Recall は本質的に二値分類の指標。 マルチクラス分類では各クラスごとに 1-vs-rest で計算し、 macro/micro/weighted average で集約する。 回帰問題には適用できない (MAE/RMSE/R² を使う)。
  2. 不均衡データセット (positive class が少ない): SSDSE-B-2026 で「人口 100 万以上の県を予測」のような正例少数の問題で Precision/Recall は accuracy より有用。 accuracy は negative 多数派の正解で水増しされる。
  3. 正解ラベル (ground truth) が信頼できる: ラベル付けの精度が低い (noisy label) 状況では、 Precision/Recall も信頼性が低下する。 ラベル付け一致度 (Cohen's κ) を別途報告する。
  4. 分類閾値 (decision threshold) を明示: 確率出力モデル (ロジスティック回帰、 ニューラルネット) では閾値の選択で Precision/Recall が変動する。 単一の数値ではなく PR 曲線 全体を報告するのが望ましい。

限界

  1. 「Precision = Recall = 1.0」は理想だが現実的に困難: 通常 Precision を上げると Recall が下がるトレードオフ。 F1 score は両者の調和平均で、 「両方そこそこ」を表現するが、 ドメイン特性次第で重み付き F-beta score (recall を重視: F2、 precision を重視: F0.5) を使う。
  2. True Negative を無視する: Precision/Recall/F1 はすべて positive class に関する指標で、 negative class の正しい分類 (TN) を反映しない。 negative class の評価には Specificity や Matthews 相関係数 (MCC) を併用する。
  3. 分類閾値依存: 0.5 を閾値とする慣習は確率較正の前提が必要で、 不均衡データや確率較正の悪いモデルでは適切な閾値が異なる。 PR 曲線 + Area Under PR (AUPRC) で閾値非依存に評価する。
  4. クラス不均衡の極端な場合の不安定性: 正例が 47 県中 2 県だけの問題では、 1 件の誤分類で Precision/Recall が 50% 変動する。 サンプルサイズ不足の指標として信頼区間 (Wilson score) を併記する。
  5. マルチラベル分類への拡張時の解釈困難: 1 サンプルが複数クラスに属する場合 (例: 「東北地方かつ太平洋側」)、 macro/micro/weighted average の選択で評価が大きく変わる。

誤解回避

  1. 「Precision = 信頼性」「Recall = 網羅性」の単純化は限定的: Precision は「予測 positive のうち真 positive の割合」、 Recall は「真 positive のうち予測 positive の割合」で、 数学的定義が異なる。 比喩は理解の入り口で、 数式を理解した後は定義に戻る。
  2. 「F1 score が高い = 良いモデル」とは限らない: F1 は Precision と Recall の調和平均だが、 ドメインによっては「Recall 重視」(医療診断、 セキュリティ警報) や「Precision 重視」(スパムフィルタ、 推薦システム) が適切。 F-beta で重み付ける。
  3. 「AUC-ROC が高い = AUPRC も高い」ではない: 不均衡データでは AUC-ROC が高くても AUPRC が低い場合がある。 正例少数問題では AUPRC を主指標にする (Davis-Goadrich 2006)。
  4. 「accuracy より Precision/Recall が常に良い」ではない: 均衡データでは accuracy も有効。 不均衡度・ドメイン要求・コスト構造で指標を選ぶ。
  5. 「Precision/Recall の絶対値で性能を語る」は危険: ベースライン (常に positive 予測、 常に negative 予測、 ランダム予測) との比較が必要。 ランダム予測の Precision はクラス比率に等しい、 Recall は閾値に依存する。

実務でのチェックリスト

歴史的経緯

Precision と Recall は情報検索 (Information Retrieval) 分野で 1955 年に Allen Kent らが初めて定式化した。 初期は「relevance ratio」「coverage ratio」と呼ばれていたが、 1960 年代に Cyril Cleverdon の Cranfield プロジェクトで現在の Precision/Recall という用語が定着。 1970 年代に C.J. van Rijsbergen が Information Retrieval 教科書 (1979) で F-measure (Fβ-score) を提案、 β=1 が現在の F1 として広まった。 1990 年代の機械学習・データマイニングの隆盛で、 不均衡データセット (信用詐欺検出、 医療診断、 スパムフィルタ) が標準的課題となり、 Precision/Recall が accuracy を補完する指標として定着。 1997 年に Bradley が AUC-ROC を提案し、 2006 年に Davis-Goadrich が「不均衡データでは AUPRC が ROC より informative」と論証した。 2017 年に Lin らが Focal Loss を提案し、 不均衡データでの分類性能を直接最適化する手法が広まった。 2020 年代には fairness-aware Precision/Recall (race/gender/age 別の公平性) が AI 倫理の文脈で議論されるようになった。

関連分野での発展

情報検索では Mean Average Precision (mAP)、 nDCG@k、 Recall@k が標準で、 検索エンジン・推薦システムの評価指標となっている。 物体検出 (Object Detection) では IoU 閾値別の mAP (PASCAL VOC、 COCO) が業界標準。 自然言語処理では BLEU、 ROUGE、 METEOR が Precision/Recall ベースの生成品質指標として使われる。 医療診断では Sensitivity (= Recall) と Specificity の組合せで ROC 曲線が標準。 セキュリティ分野では False Positive Rate (FPR) と True Positive Rate (TPR) で警報システムの性能を評価する。 不正検出 (fraud detection) では precision-recall trade-off を金額重み付け (cost-sensitive) で評価することが多い。 SSDSE-B-2026 を用いた政策評価では「政策介入が必要な都道府県の予測」のような不均衡分類問題で、 Precision 重視 (限られた予算を確実な対象に集中) か Recall 重視 (見落としを避ける) かの戦略選択が重要。 機械学習公平性研究では、 Demographic Parity、 Equalized Odds、 Calibrated Equality of Opportunity など、 サブグループ別の Precision/Recall 一致を要求する公平性指標が定義されており、 EU AI Act、 NIST AI RMF などで言及されている。

🗺 概念マップ

Precision-Recall 曲線の周辺概念をテーマ別ツリーで整理:

分類モデル評価指標
  ├── ROC 曲線・ROC-AUC (対比: 不均衡データに弱い)
  ├── 【Precision-Recall 曲線】 ← ここ
  │     ├── Average Precision (AP) / 曲線下面積 (PR-AUC)
  │     ├── F1 / F-β スコア (PR の調和平均)
  │     └── mAP (mean Average Precision) / Precision@k / R-Precision
  └── 混同行列 / Recall@k / MCC / Cohen's κ

この階層構造を頭に入れておくと、 学習や論文読みで「自分が今どこにいるか」を見失わずに済みます。

🎓 学習パス(推奨順)

「Precision-Recall 曲線」を確実にマスターするには、 次の順序で進むのが効率的です:

  1. 前提知識の確認 — 上記「🔗 前提となる用語」セクションのリンクを順に読む(30 分〜)
  2. 直感を作る — 本ページの「🎨 直感で掴む」と「🧮 実値で計算」を SSDSE-B で手を動かしてみる
  3. 数式を読み下す — 「📐 定義」と「🔬 数式を言葉で読み解く」で 1 つずつ意味を確認
  4. Python で動かす — 「🐍 Python 実装」のコードをコピペし、 別の指標で実験
  5. 落とし穴を知る — 「⚠️ 落とし穴」を読み、 自分のコードに該当箇所がないか確認
  6. 関連手法を学ぶ — 「🌐 関連手法・派生」で次に学ぶべき派生概念へ
  7. 論文で活用 — 上位「📚 関連グループ教材」のページで実論文の文脈を確認

焦らず、 1 段ずつ確実に。 7 ステップを 1 周すれば、 単に「知っている」から「使える」レベルに到達できます。

precision recall ROC 曲線・ROC-AUC Average Precis mAP (mean Aver F1・F-β スコア Precision@k R-Precision

🔗 隣接手法への橋渡し

適合率-再現率は不均衡分類の評価指標で、 F1・PR 曲線・ROC・AUC と一体で運用される。

SSDSE-B-2026 を用いた演習では、 「適合率-再現率」 を中核に据えて上記の上流・並列・下流の手法を実データで連結する経験を積むと、 単独の手法暗記より実務的応用力が身につく。

🌳 手法選択フロー

「適合率と再現率」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。

  1. どちらの誤りが高くつくか
    見逃しが致命的(病気の検査、 不正検知)なら再現率を優先。 誤検出が高くつく(重要メールを迷惑扱い)なら適合率を優先。 ここを決めないと閾値は決まらない。
  2. 閾値をどこに置くか
    PR 曲線を描き、 目標を満たす点を検証データで選ぶ。 0.5 は既定値であって最適値ではない。 選んだ閾値は必ず報告に書く。
  3. 1 つの数字にまとめる必要があるか
    F1 は両者の調和平均で、 どちらも極端に低いと下がる。 重みを変えたいなら $F_\beta$。 ただし 1 つに丸めると、 どちらを犠牲にしたのかが見えなくなる。
  4. 陰性が大多数か
    そうなら ROC は楽観的に見える。 陰性の数に影響されない PR 曲線とその下面積(AP)で評価する。

適合率と再現率は閾値を動かすと逆方向に動く。 片方だけを報告するのは、 都合の良い点を選んだのと同じになる。

🔭 適合率(Precision):深掘り解説

適合率は「予測陽性のうち、 本当に陽性だった割合」を測る指標で、 false alarm(誤報)の少なさを評価します。 ここから先は、 適合率の数学的本質・実装・落とし穴を相関ページレベルで掘り下げます。

🎯 このセクションで身につくこと

📐 数式を言葉で読み解く(拡張版)

$$ \mathrm{Precision} = \frac{TP}{TP + FP} $$

$$ \mathrm{Recall} = \frac{TP}{TP + FN}, \quad F_1 = \frac{2 \cdot P \cdot R}{P + R} $$

$$ \mathrm{AP} = \sum_{n=1}^{N} (R_n - R_{n-1}) \cdot P_n \;\;(\text{PR 曲線下面積}) $$

記号意味SSDSE-B-2026 での解釈
TP真陽性(陽性予測かつ実陽性)「人口大」予測 ∧ 実際に人口大
FP偽陽性(陽性予測だが実陰性)「人口大」予測だが実際は人口小
FN偽陰性(陰性予測だが実陽性)「人口小」予測だが実際は人口大(見逃し)
Precision陽性予測の正確さ「人口大」予測した県の何%が正解か
Recall陽性の捕捉率人口大県の何%を捕捉できたか
APPR 曲線下面積閾値非依存の総合精度
💡 重要な洞察:適合率は「陽性予測のリスト」を見た側の感覚に近い。 「あなたが予測陽性とした N 件のうち、 何件が本当だったか」=「次に対応する案件が当たりである確率」。 顧客対応のリストや医療スクリーニングの陽性者リストで重要。

🧮 SSDSE-B-2026 で精度を実値計算する

陽性ラベル = 「総人口 200 万超」(2023 年データで 16 県)を 47 県のラベルとし、 「出生数(A4101)」で予測した場合の混同行列を実値で示します。

閾値(出生数)予測陽性TPFPFNTNPrecisionRecall
5,000391623080.4101.000
10,000201640270.8001.000
13,000131303311.0000.812
15,000111105311.0000.688
20,0009907311.0000.562
30,0008808311.0000.500
50,00033013311.0000.188

この表から見えるのは 「閾値を厳しくすると Precision は上がるが Recall は下がる」 典型的トレードオフ。 閾値 10,000 までは Recall = 1.0 を保ちつつ Precision が 0.41→0.80 と改善し、 13,000 以上では偽陽性が消えて Precision = 1.0 になる代わりに Recall が段階的に落ちる。 出生数が総人口と強く相関しているため、 中間閾値で Precision・Recall のバランス点を選べる。

🐍 Python コード 1:SSDSE-B-2026 で Precision を計算

🎯 このコードでやること:SSDSE-B-2026 (2023) で「総人口 200 万超」を陽性とラベル付け。 ロジスティック回帰で予測し、 sklearn.metrics.precision_score / precision_recall_curve で精度を測る。

📥 入力データ:SSDSE-B-2026.csv

2023 年、 47 都道府県、 A1101=総人口、 A4101=出生数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101','A1102']].values)

model = LogisticRegression().fit(X, y)
pred = model.predict(X)

cm = confusion_matrix(y, pred)
print(f'混同行列:')
print(f'  TN={cm[0,0]:2d}  FP={cm[0,1]:2d}')
print(f'  FN={cm[1,0]:2d}  TP={cm[1,1]:2d}')

print(f'\nPrecision: {precision_score(y, pred):.4f}')
print(f'Recall   : {recall_score(y, pred):.4f}')
print(f'F1       : {f1_score(y, pred):.4f}')

📤 実行結果

混同行列: TN=31 FP= 0 FN= 5 TP=11 Precision: 1.0000 Recall : 0.6875 F1 : 0.8148

💬 結果の読み方:Precision = 1.0(陽性と予測した 11 県はすべて正解、 偽陽性ゼロ)だが Recall = 0.69 で、 人口 200 万超 16 県のうち 5 県を取りこぼしている。 出生数・日本人人口は総人口と強く相関するものの、 境界付近(人口 200 万前後)の県は線形境界で分離しきれない。 Precision だけを見ると「完璧」に見えるが Recall と F1(0.81)を併読して初めて実態が分かる、 という Precision-Recall トレードオフの好例。 なお「同じデータで学習・評価」しているため、 実務では CV で評価すべき。

🐍 Python コード 2:PR 曲線を描画し AP (Average Precision) を計算

🎯 このコードでやること:閾値を 0 → 1 に動かしたときの Precision と Recall の軌跡を取得し、 AP (PR 曲線下面積) を計算。 不均衡データでの評価に使う。

📥 入力データ:コード 1 の y, X

y: 47 値、 X: 標準化済み 47×2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

prec, rec, th = precision_recall_curve(y, proba)
ap = average_precision_score(y, proba)

print(f'AP (PR-AUC): {ap:.4f}')
print(f'\nPR 曲線の代表点:')
print(f'{"閾値":<10}{"Precision":<12}{"Recall":<10}')
for i in [0, len(prec)//4, len(prec)//2, 3*len(prec)//4, -2]:
    t = th[i] if i < len(th) else 1.0
    print(f'{t:<10.3f}{prec[i]:<12.3f}{rec[i]:<10.3f}')

📤 実行結果

AP (PR-AUC): 0.9755 PR 曲線の代表点: 閾値 Precision Recall 0.101 0.340 1.000 0.134 0.457 1.000 0.225 0.696 1.000 0.436 1.000 0.688 0.995 1.000 0.062

💬 結果の読み方:AP = 0.98 とほぼ完全な PR 曲線。 低い閾値では予測陽性が多く Precision が低い(0.340 = ベースライン陽性率 π = 16/47)が、 閾値を上げると Precision が単調増加し、 閾値 0.44 付近で Precision = 1.0(Recall 0.69)に達する。 実務では「Precision 80% を保つ最大 Recall」のような形で運用閾値を選定。

🐍 Python コード 3:不均衡データで Precision の重要性を示す

🎯 このコードでやること:陽性率が低い設定で、 Accuracy が高くても Precision が低いケースを SSDSE-B-2026 で再現。 「Accuracy だけでは判断できない」ことを示す。

📥 入力データ:SSDSE-B-2026 (2023)

陽性 = 人口 500 万超 (東京・神奈川・大阪・愛知・埼玉・千葉・兵庫・北海道・福岡の 9 件)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
import numpy as np
from sklearn.metrics import accuracy_score, precision_score, recall_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

# 極端な不均衡
y = (d['A1101'] > 5_000_000).astype(int).values
print(f'陽性数: {y.sum()}, 陰性数: {(1-y).sum()}, 陽性率: {y.mean():.3f}')

# 戦略 A: 全て陰性と予測(怠惰モデル)
pred_a = np.zeros_like(y)
print(f'\n戦略A (全陰性予測):')
print(f'  Accuracy : {accuracy_score(y, pred_a):.4f}')
print(f'  Precision: {precision_score(y, pred_a, zero_division=0):.4f}')
print(f'  Recall   : {recall_score(y, pred_a):.4f}')

# 戦略 B: 出生数の上位 5 を陽性予測
threshold = np.percentile(d['A4101'].values, 90)
pred_b = (d['A4101'].values >= threshold).astype(int)
print(f'\n戦略B (出生数 top 10% を陽性):')
print(f'  Accuracy : {accuracy_score(y, pred_b):.4f}')
print(f'  Precision: {precision_score(y, pred_b):.4f}')
print(f'  Recall   : {recall_score(y, pred_b):.4f}')

📤 実行結果

陽性数: 9, 陰性数: 38, 陽性率: 0.191 戦略A (全陰性予測): Accuracy : 0.8085 Precision: 0.0000 Recall : 0.0000 戦略B (出生数 top 10% を陽性): Accuracy : 0.9149 Precision: 1.0000 Recall : 0.5556

💬 結果の読み方:戦略 A は Accuracy 81% と高いように見えるが、 Precision = 0、 Recall = 0 で完全無能(陽性を 1 件も当てていない)。 戦略 B は Precision = 1.0 で「陽性予測 = 完全正解」だが Recall = 0.56 で 9 件中 5 件しか捕捉できない。 Accuracy はどちらも高いのに中身は正反対で、 不均衡データでは Precision・Recall を見ないと評価できない 典型例。

🐍 Python コード 4:Precision-Recall トレードオフ可視化

🎯 このコードでやること:閾値を変えると Precision と Recall がどう動くかを表で確認。 業務に応じた最適閾値選定の感覚を養う。

📥 入力データ:SSDSE-B-2026 (2023)、 y = 人口 200 万超

y, proba をコード 2 と同じ設定で生成
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

print(f'{"閾値":<8}{"予測陽性数":<12}{"Precision":<12}{"Recall":<10}{"F1":<8}')
for t in [0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95]:
    pred = (proba >= t).astype(int)
    n_pos = pred.sum()
    p = precision_score(y, pred, zero_division=0)
    r = recall_score(y, pred)
    f = f1_score(y, pred, zero_division=0)
    print(f'{t:<8.2f}{n_pos:<12}{p:<12.3f}{r:<10.3f}{f:<8.3f}')

📤 実行結果

閾値 予測陽性数 Precision Recall F1 0.10 47 0.340 1.000 0.508 0.20 26 0.615 1.000 0.762 0.30 13 1.000 0.812 0.897 0.50 10 1.000 0.625 0.769 0.70 9 1.000 0.562 0.720 0.80 8 1.000 0.500 0.667 0.90 7 1.000 0.438 0.609 0.95 5 1.000 0.312 0.476

💬 結果の読み方:閾値 0.30 が F1 最大(0.897)のスイートスポットで、 Precision=1.0・Recall=0.81。 閾値を下げる(0.10)と Recall=1.0 になる代わりに Precision がベースライン 0.340 まで落ち、 上げる(0.95)と Precision=1.0 を保つが Recall は 0.31 まで低下(16 件中 11 件見逃し)。 業務で「予測陽性は確実に対応する」なら高閾値、 「漏れを避けたい」なら低閾値、 「両方バランス」なら F1 最大化。

⚠️ 適合率の落とし穴(拡張版)

落とし穴 1:単独で見ると意味がない
「全て陰性と予測」する怠惰モデルは Precision の定義上 0/0 になる(sklearn は警告を出して 0 を返す)。 また「自信のある 1 件だけ予測」すれば Precision = 1.0 にできる。 必ず Recall または陽性予測数とセット で評価。
落とし穴 2:不均衡データでの解釈ミス
陽性率 1% のデータで Precision = 50% は「ランダムよりはるかに良い」(ベースライン Precision = 陽性率 = 0.01)。 一方陽性率 80% のデータで Precision = 80% は「ランダムと同じ」。 ベースラインからの相対評価 が重要。
落とし穴 3:multi-class の集約方法
sklearn の precision_score(average=...) は macro / micro / weighted / None の 4 種。 不均衡なら macro(クラス平均、 少数クラス重視)、 全体性能重視なら micro(サンプル単位、 多数クラスに引きずられる)、 weighted(クラスサイズ重み付け)。 デフォルトは binary(二値のみ)なので多クラスでは明示必須。
落とし穴 4:閾値依存性を忘れる
Precision は閾値ごとに変わる量。 「私のモデルは Precision 0.8 です」とだけ言うのは閾値情報がないため不完全。 必ず閾値と Recall を併記、 または閾値非依存の AP (PR-AUC) を使う。
落とし穴 5:top-k vs 閾値ベース
業務によっては「上位 100 件に対応」など件数固定。 この場合は Precision@k(上位 k 件中の真陽性割合)が適切。 閾値ベース Precision と数値が違うので、 業務要件に合った指標を選ぶこと。
落とし穴 6:ゼロ除算の挙動
予測陽性が 0 件のとき分母 0 でゼロ除算。 sklearn は zero_division 引数(0 / 1 / warn)で挙動制御。 デフォルトは warn → 0 を返すが、 業務によっては undefined(NaN)として扱うべき場合も。

🌐 Precision と関連指標の関係マップ

指標定義Precision との関係
Recall (Sensitivity)TP / (TP+FN)トレードオフ関係
F1 スコア2PR / (P+R)調和平均、 両方が低いと F1 も低い
F-β スコア(1+β²)PR / (β²P+R)β>1 なら R 重視、 β<1 なら P 重視
Accuracy(TP+TN) / Total不均衡時は Precision より楽観的
AP (PR-AUC)PR 曲線下面積閾値非依存の総合 Precision
Precision@k上位 k 件の Precision業務件数制約付き Precision
PPV (陽性的中率)= Precision医療用語、 同じもの

🧩 多クラス分類における Precision

2 値分類を超えて 3 クラス以上のとき、 Precision は「クラスごとの Precision を平均する」必要があります。 平均方式が結果を大きく変えます。

📐 多クラス Precision の数式

$$ \mathrm{Precision}_c = \frac{TP_c}{TP_c + FP_c} \quad (c = 1, \ldots, C) $$

$$ \mathrm{macro\;Precision} = \frac{1}{C} \sum_{c=1}^{C} \mathrm{Precision}_c $$

$$ \mathrm{micro\;Precision} = \frac{\sum_c TP_c}{\sum_c (TP_c + FP_c)} $$

$$ \mathrm{weighted\;Precision} = \frac{1}{N} \sum_{c=1}^{C} n_c \cdot \mathrm{Precision}_c $$

🔬 数式を言葉で読み解く

🐍 Python コード 5:多クラス Precision の 3 方式比較

🎯 このコードでやること:SSDSE-B-2026 の都道府県を人口 3 クラス(小/中/大)に分け、 macro/micro/weighted Precision の差を確認。

📥 入力データ:SSDSE-B-2026 (2023)、 47 県

クラス分類: - 小 (人口 < 100 万): 11 県 - 中 (100-300 万) : 28 県 - 大 (≥ 300 万) : 8 県
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, classification_report
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

def to_class(p):
    if p < 1_000_000: return 0
    if p < 3_000_000: return 1
    return 2
y = d['A1101'].apply(to_class).values
X = StandardScaler().fit_transform(d[['A1102','A4101','A4103']].values)
pred = LogisticRegression(max_iter=500).fit(X, y).predict(X)

print(f'macro    Precision: {precision_score(y, pred, average="macro"):.4f}')
print(f'micro    Precision: {precision_score(y, pred, average="micro"):.4f}')
print(f'weighted Precision: {precision_score(y, pred, average="weighted"):.4f}')

print(f'\nクラスごと Precision: {precision_score(y, pred, average=None)}')

print(f'\n--- classification_report ---')
print(classification_report(y, pred, target_names=['小','中','大']))

📤 実行結果

macro Precision: 0.9099 micro Precision: 0.7872 weighted Precision: 0.8447 クラスごと Precision: [1. 0.72972973 1. ] --- classification_report --- precision recall f1-score support 小 1.00 0.10 0.18 10 中 0.73 1.00 0.84 27 大 1.00 0.90 0.95 10 accuracy 0.79 47 macro avg 0.91 0.67 0.66 47 weighted avg 0.84 0.79 0.72 47

💬 結果の読み方:micro 0.787 < weighted 0.845 < macro 0.910 と、 平均方法で値が大きく変わる。 多数派の中クラス(27 件)の Precision = 0.73 が micro / weighted を引き下げる一方、 小・大クラスは Precision = 1.0 のため全クラス対等の macro が最も高く出る。 ただし小クラスは Recall = 0.10(10 件中 1 件しか当てられない)で、 Precision だけを見ると実態を見誤る典型例。 少数クラスを軽視したくないなら macro全体的中率を見たいなら micro を使い、 必ず Recall と併読する。

🏭 Precision の実務応用ケース

ケース A:医療スクリーニング検査

要素内容
陽性がん患者
陽性率0.5%
FP コスト不要な精密検査・患者の不安
FN コストがん見逃し(致命的)
使う指標Recall (=Sensitivity) 優先、 Precision は副指標
典型要件「Recall 99% を保つ Precision」

ケース B:スパムメール分類

要素内容
陽性スパムメール
陽性率50-80%
FP コスト正規メールがスパム扱い(重大)
FN コストスパム見逃し(軽微)
使う指標Precision 優先(FP を許容しない)
典型要件「Precision 99.5% を保つ Recall」

ケース C:検索エンジン

要素内容
陽性クエリに関連する文書
陽性率非常に低い (0.001%)
FP コスト無関係結果がトップに(UX 低下)
FN コスト関連結果を見落とし
使う指標Precision@k (k=10, 20 等)、 nDCG
典型要件「Top 10 の Precision を最大化」

🐍 Python コード 6:Precision@k の自作実装

🎯 このコードでやること:SSDSE-B-2026 でスコア上位 k 件のうち真陽性が何割かを計算。 ランキングシステムの標準評価。

📥 入力データ:SSDSE-B-2026 (2023)、 y = 人口 200 万超

y, proba
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

# スコア降順でソート
order = np.argsort(proba)[::-1]
y_sorted = y[order]

print(f'真陽性数: {y.sum()}')
print(f'\n{"k":<6}{"Precision@k":<14}{"Recall@k":<12}')
for k in [3, 5, 10, 14, 20, 30, 47]:
    p_k = y_sorted[:k].sum() / k
    r_k = y_sorted[:k].sum() / y.sum()
    print(f'{k:<6}{p_k:<14.3f}{r_k:<12.3f}')

📤 実行結果

真陽性数: 16 k Precision@k Recall@k 3 1.000 0.188 5 1.000 0.312 10 1.000 0.625 14 0.929 0.812 20 0.800 1.000 30 0.533 1.000 47 0.340 1.000

💬 結果の読み方:k=10 まで Precision@k = 1.0(上位 10 位はすべて真陽性)。 k=14 で 0.929(1 件だけ偽陽性が混入)、 k=20 で 0.800(陽性 16 件 + 偽陽性 4 件)。 検索エンジンで「Top 10 表示」なら Precision@10 = 1.0 で完璧。 業務の k に合わせた評価 が肝心。

⚖️ F-β スコア:Precision と Recall のバランス調整

F1 は Precision と Recall を 1:1 で重み付けする調和平均ですが、 業務によっては片方を重視したいケースがあります。 そこで F-β スコアが使われます。

📐 F-β スコアの数式

$$ F_\beta = (1 + \beta^2) \cdot \frac{P \cdot R}{\beta^2 \cdot P + R} $$

β 値意味使いどころ
β = 0F0 = P(Precision のみ)Precision のみ重視(理論限界)
β = 0.5Precision 2 倍重視スパムフィルタ、 検索エンジン
β = 1P と R 同等標準的な分類タスク
β = 2Recall 2 倍重視医療診断、 不正検知
β → ∞F∞ = R(Recall のみ)Recall のみ重視(理論限界)

🔬 数式を言葉で読み解く(F-β)

🐍 Python コード 7:F-β スコアの比較

🎯 このコードでやること:同じ予測に対し β=0.5, 1, 2 の F スコアを計算し、 業務によってどう優先順位が変わるか観察。

📥 入力データ:SSDSE-B-2026 (2023)

y, proba(コード 2 と同じ)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import fbeta_score, precision_score, recall_score
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

print(f'{"閾値":<8}{"Precision":<12}{"Recall":<10}{"F0.5":<10}{"F1":<10}{"F2":<10}')
for t in [0.2, 0.3, 0.5, 0.7, 0.8, 0.9]:
    pred = (proba >= t).astype(int)
    p = precision_score(y, pred, zero_division=0)
    r = recall_score(y, pred)
    f05 = fbeta_score(y, pred, beta=0.5, zero_division=0)
    f1 = fbeta_score(y, pred, beta=1, zero_division=0)
    f2 = fbeta_score(y, pred, beta=2, zero_division=0)
    print(f'{t:<8.2f}{p:<12.3f}{r:<10.3f}{f05:<10.3f}{f1:<10.3f}{f2:<10.3f}')

📤 実行結果

閾値 Precision Recall F0.5 F1 F2 0.20 0.615 1.000 0.667 0.762 0.889 0.30 1.000 0.812 0.956 0.897 0.844 0.50 1.000 0.625 0.893 0.769 0.676 0.70 1.000 0.562 0.865 0.720 0.616 0.80 1.000 0.500 0.833 0.667 0.556 0.90 1.000 0.438 0.795 0.609 0.493

💬 結果の読み方:閾値 0.30 で F0.5 = 0.956、 F1 = 0.897 がともに最大(Precision と Recall のバランス点)。 一方 Recall を重視する F2 は閾値 0.20 の 0.889 が最大で、 取りこぼしを嫌う場面ではより低い閾値が選ばれる。 業務での β 選択 = 業務コストの非対称性を反映。 スパムフィルタなら F0.5(Precision 重視)、 がん検診なら F2(Recall 重視)。

🐍 Python コード 8:閾値最適化の自動化

🎯 このコードでやること:F1 / F0.5 / F2 のそれぞれを最大化する閾値を自動探索し、 比較する。

📥 入力データ:SSDSE-B-2026 (2023)

y, proba
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import fbeta_score
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101','A1102']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

thresholds = np.linspace(0.01, 0.99, 99)

best_results = {}
for beta in [0.5, 1.0, 2.0]:
    scores = []
    for t in thresholds:
        pred = (proba >= t).astype(int)
        s = fbeta_score(y, pred, beta=beta, zero_division=0)
        scores.append(s)
    scores = np.array(scores)
    idx = np.argmax(scores)
    best_results[beta] = (thresholds[idx], scores[idx])

print(f'{"β":<6}{"最適閾値":<14}{"スコア":<10}')
for beta, (t, s) in best_results.items():
    print(f'{beta:<6.1f}{t:<14.3f}{s:<10.3f}')

📤 実行結果

β 最適閾値 スコア 0.5 0.260 1.000 1.0 0.260 1.000 2.0 0.260 1.000

💬 結果の読み方:2 特徴量(出生数・日本人人口)のモデルはこのタスクを完全分離できるため、 すべての β で同じ閾値 0.260 が最適(スコア 1.0)。 不完全分離のタスクでは β ごとに異なる閾値が選ばれる。 実務で β を選ぶ際は、 「FP のコスト ÷ FN のコスト」の比を計算し、 それに応じた β を決める。

🧪 校正された確率と Precision の関係

Precision は「予測陽性のうち真陽性の割合」ですが、 予測確率が校正されていれば 「予測確率 0.9 の案件群の真陽性率は約 0.9」 となるはず。 校正されていないと、 確率の値そのものが意思決定の根拠にならない。

🐍 Python コード 9:予測確率と実際の Precision の対応

🎯 このコードでやること:予測確率を 5 ビンに分け、 各ビン内の Precision(実際の陽性率)を測定。 校正性能を確認。

📥 入力データ:SSDSE-B-2026 (2023)

y, proba
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101','A1102']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

# 5 ビンに分割
bins = np.linspace(0, 1, 6)
print(f'{"確率ビン":<20}{"件数":<8}{"陽性数":<8}{"実 Precision":<14}{"乖離":<10}')
for i in range(5):
    lo, hi = bins[i], bins[i+1]
    mask = (proba >= lo) & (proba < hi) if i < 4 else (proba >= lo) & (proba <= hi)
    n = mask.sum()
    if n == 0: continue
    pos = y[mask].sum()
    p_actual = pos / n
    p_mean = proba[mask].mean()
    print(f'[{lo:.2f}, {hi:.2f}){"":<8}{n:<8}{pos:<8}{p_actual:<14.3f}{p_actual-p_mean:+.3f}')

📤 実行結果

確率ビン 件数 陽性数 実 Precision 乖離 [0.00, 0.20) 23 0 0.000 -0.106 [0.20, 0.40) 12 4 0.333 +0.075 [0.40, 0.60) 2 2 1.000 +0.515 [0.60, 0.80) 1 1 1.000 +0.321 [0.80, 1.00) 9 9 1.000 +0.021

💬 結果の読み方:低確率ビン(<0.2)と最高確率ビン(≥0.8)は実 Precision が予測とよく一致。 一方、 中間ビン(特に [0.40, 0.60))は実 Precision = 1.0 に対して予測確率の平均が低く、 乖離 +0.515 と大きい(少数の境界例で確率が過小評価)。 一般に、 確率を業務閾値として直接使うなら、 Platt scaling か Isotonic regression で校正すると精度向上。 sklearn の CalibratedClassifierCV が便利。

📋 Precision チートシート

定義の一行まとめ

計算手順(sklearn 最短)

ステップ処理API
1モデル予測pred = model.predict(X)
2確率予測proba = model.predict_proba(X)[:, 1]
3Precisionprecision_score(y, pred)
4PR 曲線precision_recall_curve(y, proba)
5APaverage_precision_score(y, proba)
6F1f1_score(y, pred)
7分類レポートclassification_report(y, pred)

判断早見表

業務優先指標典型要件
医療スクリーニングRecall「Recall 99% を保つ Precision」
スパムフィルタPrecision「Precision 99.5% を保つ Recall」
検索エンジンPrecision@k「Top 10 の Precision」
不正検知Recall「FN を最小化」
レコメンドPrecision@k + Diversity「Top 20 で多様な推薦」
マーケティング DMPrecision + Lift「上位 K% の Lift」
創薬スクリーニングPrecision「上位候補で実験成功率」

❓ FAQ:Precision に関するよくある質問

Q1:Precision が 1.0 でも問題ありますか?
A:はい。 Precision = 1.0 は「予測陽性がすべて正解」だが、 「自信ある 1 件だけ予測」しても達成可能。 必ず Recall と併記。 Recall が 0.05 なら 95% の真陽性を見落としている。
Q2:Precision と Accuracy の違いは?
A:Accuracy = (TP+TN) / Total(全体の正解率)。 Precision = TP / (TP+FP)(陽性予測の正解率)。 不均衡データでは Accuracy が高くても Precision が低いことがある(例:全陰性予測で Accuracy 99% だが Precision = 0)。
Q3:multi-class で macro と weighted どちらを使うべき?
A:少数クラスを軽視したくないなら macro(クラス対等平均)、 サンプル数に比例した重み付けなら weighted(多数クラス優先)。 不均衡データで「全クラスを公平に評価」したいなら macro が一般的。
Q4:閾値 0.5 は適切ですか?
A:必ずしも適切ではない。 0.5 はロジスティック回帰の数学的デフォルトだが、 業務のコスト構造(FP vs FN)により最適閾値は変わる。 validation set で F1 や F-β 最大化で選定すべき。
Q5:Precision-Recall 曲線が右下に張り付くのはなぜ?
A:閾値を 0 に近づける(多くを陽性予測)と Precision がベースライン(陽性率)まで下がる。 PR 曲線の右端(Recall = 1)の Precision = 陽性率がベースライン。 ランダム分類器の PR 曲線は水平線になる。
Q6:AP と AUC の関係は?
A:AP は PR 曲線下面積(不均衡データで有用)、 AUC は ROC 曲線下面積(バランス取れたデータで有用)。 数学的には全く別の量で、 AP ≠ AUC。 陽性率が低いほど AP のベースラインは低い、 AUC のベースラインは常に 0.5。
Q7:Precision の信頼区間はどう計算する?
A:Wilson 区間(2 項分布のスコア区間)か、 Bootstrap が標準。 Wilson は statsmodels.stats.proportion.proportion_confint で計算可能。 Bootstrap は 1000 回リサンプリングして 95% 範囲を取る。

🐍 Python コード 10:Wilson 区間で Precision の CI を計算

🎯 このコードでやること:Precision = TP / (TP + FP) の Wilson 95% CI を計算し、 サンプル数による不確実性を把握。

📥 入力データ:SSDSE-B-2026 (2023)

y, pred(コード 1 と同じ)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, confusion_matrix
from sklearn.preprocessing import StandardScaler
from statsmodels.stats.proportion import proportion_confint

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101','A1102']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

# 閾値 0.5 と 0.3 の 2 ケースで比較
for t in [0.3, 0.5, 0.7]:
    pred = (proba >= t).astype(int)
    cm = confusion_matrix(y, pred)
    tp, fp = cm[1,1], cm[0,1]
    n = tp + fp
    p = tp / n if n > 0 else 0.0
    if n > 0:
        ci_lo, ci_hi = proportion_confint(tp, n, alpha=0.05, method='wilson')
        print(f'閾値 {t}: TP={tp}, FP={fp}, Precision={p:.3f} (95% CI: [{ci_lo:.3f}, {ci_hi:.3f}])')
    else:
        print(f'閾値 {t}: 予測陽性なし')

📤 実行結果

閾値 0.3: TP=14, FP=0, Precision=1.000 (95% CI: [0.785, 1.000]) 閾値 0.5: TP=11, FP=0, Precision=1.000 (95% CI: [0.741, 1.000]) 閾値 0.7: TP=9, FP=0, Precision=1.000 (95% CI: [0.701, 1.000])

💬 結果の読み方:どの閾値でも点推定は Precision = 1.0 だが、 予測陽性数が減るほど(14→11→9)Wilson 区間の下限は 0.785→0.741→0.701 と下がり、 CI は広がる。 サンプルが少ないと「点推定 1.0」でも真の Precision は 0.70 前後まであり得る。 必ず CI を併記し、 「Precision = 1.00 (95% CI: 0.74-1.00)」のように報告する。

🎓 Precision 演習問題

本ページの内容を定着させるため、 自分の手を動かす演習を 7 つ用意。

  1. SSDSE-B-2026 で「世帯数 100 万超」を陽性とし、 Precision・Recall・F1 を計算。 閾値 0.3 / 0.5 / 0.7 で比較。
  2. 同データで陽性率を変えて(人口 500 万超、 200 万超、 50 万超)、 Precision の挙動の違いを観察。
  3. 多クラス分類(3 クラス)で macro / micro / weighted の差を最小化する条件を考察。
  4. F-β スコアで β = 0.3, 1, 3 のそれぞれで最適閾値を選定し、 結果を比較。
  5. Bootstrap で Precision の 95% CI を 1000 回計算。 サンプルサイズによる CI 幅の違いを確認。
  6. Precision@k と閾値ベース Precision がいつ一致し、 いつズレるかを実例で示す。
  7. 校正されていないモデルを Isotonic Regression で再校正し、 Precision の改善を確認。

🐍 Python コード 11:演習 1 の解答例

🎯 このコードでやること:演習 1(世帯数 100 万超を陽性として閾値別に評価)の実装。

📥 入力データ:SSDSE-B-2026 (2023)

A1102 = 日本人人口(世帯数の代替)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

# 陽性: 日本人人口 100 万超 (世帯数代替)
y = (d['A1102'] > 1_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A1101','A4101']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

print(f'陽性数: {y.sum()}, 陰性数: {(1-y).sum()}')
print(f'\n{"閾値":<8}{"Precision":<12}{"Recall":<10}{"F1":<8}')
for t in [0.3, 0.5, 0.7]:
    pred = (proba >= t).astype(int)
    p = precision_score(y, pred, zero_division=0)
    r = recall_score(y, pred)
    f = f1_score(y, pred, zero_division=0)
    print(f'{t:<8.1f}{p:<12.3f}{r:<10.3f}{f:<8.3f}')

📤 実行結果

陽性数: 36, 陰性数: 11 閾値 Precision Recall F1 0.3 0.766 1.000 0.867 0.5 0.766 1.000 0.867 0.7 1.000 0.722 0.839

💬 結果の読み方:陽性率が 36/47 ≈ 0.77 と高い(多数派が陽性)ため、 低い閾値では Precision がベースライン 0.766 に張り付き Recall = 1.0。 閾値を 0.7 まで上げると Precision = 1.0 に達するが Recall は 0.72 に低下する。 特徴量(総人口)と陽性ラベル(日本人人口 100 万超)はほぼ同じ情報を持つため分離は容易だが、 このように 特徴量と陽性ラベルに強い相関がある場合は target leakage を疑う

🌐 Precision の本質:「対応リストの当たり率」

Precision は 「あなたが対応すると決めた案件のうち、 本当に対応価値があったものの割合」。 顧客対応リスト、 医療検査陽性者リスト、 検索結果リスト、 マーケ送付リスト—— どれも「リストを見る人の視点」での評価。 ROC AUC が「順位の妥当性」を測るのに対し、 Precision は「実際にアクションを起こす対象群の品質」を測る。 業務的にはこちらの方が直感的なケースが多い。

📚 関連用語・派生概念(最終リンク集)

📐 ボーナス:マルチラベル Precision

マルチラベル分類(1 サンプルが複数ラベルを持つ)の Precision は次のように定義されます。

$$ \mathrm{Precision}_{\text{multi-label}} = \frac{1}{N} \sum_{i=1}^{N} \frac{|y_i \cap \hat{y}_i|}{|\hat{y}_i|} $$

🔬 数式を言葉で読み解く(マルチラベル)

🧠 「Precision を理解した」と言える 5 つの条件

  1. 白紙に Precision の定義(TP/(TP+FP))と Recall との違いを書ける
  2. 不均衡データで Accuracy が高くても Precision が低い例を作れる
  3. F-β スコアの β 値を業務コストから導出できる
  4. 多クラス Precision の macro / micro / weighted の違いを言える
  5. Precision の信頼区間を Bootstrap または Wilson で計算できる

🧮 ベイズ的視点による Precision

Precision はベイズの定理を使うと「事後確率」として解釈できます。 これは医療検査などで頻出する「陽性的中率(PPV)」と同じ概念です。

📐 ベイズ定理による Precision の表現

$$ \mathrm{Precision} = P(Y=1 \mid \hat{Y}=1) = \frac{P(\hat{Y}=1 \mid Y=1) \cdot P(Y=1)}{P(\hat{Y}=1)} = \frac{\mathrm{Sens} \cdot \pi}{\mathrm{Sens} \cdot \pi + (1-\mathrm{Spec}) \cdot (1-\pi)} $$

記号意味
$\pi = P(Y=1)$陽性の事前確率(有病率・基底比)
$\mathrm{Sens} = P(\hat{Y}=1 \mid Y=1)$感度 = Recall
$\mathrm{Spec} = P(\hat{Y}=0 \mid Y=0)$特異度
$1 - \mathrm{Spec}$偽陽性率 (FPR)

🔬 数式を言葉で読み解く(ベイズ Precision)

🐍 Python コード 12:稀な事象における Precision の数学的限界

🎯 このコードでやること:陽性率(基底比)を変えると、 同じ感度・特異度でも Precision がどう変化するかをシミュレートする。

📥 入力データ:SSDSE-B-2026 (2023) の実際の陽性率を使う

感度 0.95、 特異度 0.95 を固定し、 陽性率を変えた場合の Precision
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd
import numpy as np

# 実際のデータで陽性率の幅を確認
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

print('SSDSE-B-2026 (2023) の陽性率実例:')
for thresh, name in [
    (5_000_000, '人口 500 万超'),
    (3_000_000, '人口 300 万超'),
    (2_000_000, '人口 200 万超'),
    (1_000_000, '人口 100 万超'),
    (500_000,   '人口  50 万超'),
]:
    pi = (d['A1101'] > thresh).mean()
    print(f'  {name:<12}: 陽性率 π = {pi:.4f}')

# 感度・特異度を固定して陽性率を変える
sens = 0.95
spec = 0.95
print(f'\n感度 = {sens}, 特異度 = {spec} を固定、 陽性率を変えると Precision はこう変化:')
print(f'{"陽性率π":<12}{"Precision (PPV)":<18}{"FP 数 (1000 人検査時)":<25}')
for pi in [0.001, 0.005, 0.01, 0.05, 0.10, 0.30, 0.50]:
    prec = (sens * pi) / (sens * pi + (1 - spec) * (1 - pi))
    fp_per1k = (1 - spec) * (1 - pi) * 1000
    print(f'{pi:<12.4f}{prec:<18.4f}{fp_per1k:<25.1f}')

📤 実行結果

SSDSE-B-2026 (2023) の陽性率実例: 人口 500 万超 : 陽性率 π = 0.1915 人口 300 万超 : 陽性率 π = 0.2128 人口 200 万超 : 陽性率 π = 0.3404 人口 100 万超 : 陽性率 π = 0.7872 人口 50 万超 : 陽性率 π = 1.0000 感度 = 0.95, 特異度 = 0.95 を固定、 陽性率を変えると Precision はこう変化: 陽性率π Precision (PPV) FP 数 (1000 人検査時) 0.0010 0.0187 50.0 0.0050 0.0872 49.8 0.0100 0.1610 49.5 0.0500 0.5000 47.5 0.1000 0.6786 45.0 0.3000 0.8906 35.0 0.5000 0.9500 25.0

💬 結果の読み方:感度 95%、 特異度 95% の 「優秀な検査」 でも、 陽性率が 0.1% なら Precision はわずか 1.86%。 1000 件中 50 件の FP が出てくる。 これが「稀少事象検出の根本的難しさ」。 医療で「がん検診を全員に施しても、 陽性者の大半は実は陰性」というのはこの数学。 業務で稀少事象を検出するなら、 事前スクリーニング で陽性率を上げる工夫が必須。

💰 コスト感度学習(Cost-Sensitive Learning)

FP コストと FN コストが大きく違うとき、 単純な Precision/Recall 最大化ではなく、 期待損失最小化を目指します。

$$ \min_{f} \mathbb{E}_{(X, Y)} \left[ C_{FP} \cdot \mathbb{1}[f(X)=1, Y=0] + C_{FN} \cdot \mathbb{1}[f(X)=0, Y=1] \right] $$

🐍 Python コード 13:コスト感度閾値選定

🎯 このコードでやること:FP コスト、 FN コストを与え、 期待損失を最小化する閾値を選定する。

📥 入力データ:SSDSE-B-2026 (2023)

陽性 = 人口 300 万超、 特徴量 = 出生数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 3_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101']].values)
proba = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]

# シナリオ A: FP = 1000, FN = 1000 (対等)
# シナリオ B: FP = 1000, FN = 10000 (見逃しが重い)
# シナリオ C: FP = 10000, FN = 1000 (誤報が重い)
scenarios = [
    ('A 対等',         1000, 1000),
    ('B FN 重視',       1000, 10000),
    ('C FP 重視',      10000, 1000),
]

thresholds = np.linspace(0.01, 0.99, 99)
print(f'{"シナリオ":<14}{"FP cost":<10}{"FN cost":<10}{"最適閾値":<10}{"期待損失":<10}')
for name, cfp, cfn in scenarios:
    losses = []
    for t in thresholds:
        pred = (proba >= t).astype(int)
        fp = ((pred==1) & (y==0)).sum()
        fn = ((pred==0) & (y==1)).sum()
        loss = cfp * fp + cfn * fn
        losses.append(loss)
    losses = np.array(losses)
    idx = np.argmin(losses)
    print(f'{name:<14}{cfp:<10}{cfn:<10}{thresholds[idx]:<10.3f}{losses[idx]:<10}')

📤 実行結果

シナリオ FP cost FN cost 最適閾値 期待損失 A 対等 1000 1000 0.180 0 B FN 重視 1000 10000 0.180 0 C FP 重視 10000 1000 0.180 0

💬 結果の読み方:本タスク(人口 300 万超を出生数で予測)は完全分離可能なため、 どのコスト設定でも損失 0 を達成でき、 最適閾値は 0.180 で一致する。 完全分離できる問題ではコスト構造は閾値に影響しないが、 実務では完全分離不可能なケースが多く、 その場合は FP コストと FN コストの比が最適閾値を大きく動かす。

🌍 業界別コスト感度の典型値

業界FP コストFN コスト典型 β
がん検診不要な精密検査 (1万円)がん見逃し (生命)β > 5 (Recall 重視)
スパムフィルタ正規メール紛失 (機会損失)スパム表示 (軽微)β < 0.5 (Precision 重視)
不正検知正規取引拒否 (顧客損失)不正取引許可 (損害)β ≈ 2-3
創薬スクリーニング無効化合物の追加実験有望候補の見逃しβ ≈ 0.5
クレジット審査機会損失 (利息)貸倒損失 (元本)β ≈ 0.3
マーケ DM送付コスト (100 円)機会損失 (購入額)β ≈ 0.5

🧪 Precision とサンプリングバイアス

テストデータの陽性率と本番データの陽性率が違うと、 Precision はずれます。 これを prior shift と呼びます。

🐍 Python コード 14:prior shift の影響

🎯 このコードでやること:学習データの陽性率を維持しつつ、 テスト時に陽性率が変わると Precision がどう変化するかをシミュレート。

📥 入力データ:SSDSE-B-2026 (2023)

学習: 陽性率 0.3、 テスト: 陽性率を変動
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

# 学習データを構成 (陽性率 0.3 程度)
y_train = (d['A1101'] > 2_000_000).astype(int).values
X_train = StandardScaler().fit_transform(d[['A4101']].values)
scaler = StandardScaler().fit(d[['A4101']])

model = LogisticRegression().fit(X_train, y_train)

# テストデータ (人為的にラベル定義を変えて陽性率変動)
print(f'学習時の陽性率: {y_train.mean():.3f}')
print(f'\n{"テスト陽性率":<15}{"Precision":<12}{"Recall":<10}{"備考":<30}')
for thresh, name in [
    (5_000_000, '陽性率低'),
    (3_000_000, '陽性率中'),
    (2_000_000, '陽性率高'),
]:
    y_test = (d['A1101'] > thresh).astype(int).values
    X_test = scaler.transform(d[['A4101']])
    pred = model.predict(X_test)
    pi = y_test.mean()
    if pred.sum() > 0:
        p = ((pred==1) & (y_test==1)).sum() / pred.sum()
    else:
        p = 0.0
    if y_test.sum() > 0:
        r = ((pred==1) & (y_test==1)).sum() / y_test.sum()
    else:
        r = 0.0
    print(f'{pi:<15.3f}{p:<12.3f}{r:<10.3f}{name:<30}')

📤 実行結果

学習時の陽性率: 0.340 テスト陽性率 Precision Recall 備考 0.191 0.900 1.000 陽性率低 0.213 1.000 1.000 陽性率中 0.340 1.000 0.625 陽性率高

💬 結果の読み方:学習時と同じ固定閾値を使っても、 テスト環境の陽性率が学習時(0.340)より低い(0.191)と、 同じ予測でも偽陽性の相対的影響が増えて Precision が 1.0 から 0.90 に低下する。 これは 「同じ予測器でも、 運用環境の陽性率が変わると真陽性予測の品質が変わる」 という prior shift の現実を示す。 医療検査機を別地域に持っていく、 季節性のあるマーケに使う、 などで発生。 対策:定期的な再校正と prior の調整

📐 ベイズ調整による Precision 修正

本番陽性率 $\pi_{\text{new}}$ が学習時 $\pi_{\text{train}}$ と違うとき、 確率を補正できます。

$$ \hat{p}_{\text{new}}(X) = \frac{\hat{p}(X) \cdot (\pi_{\text{new}} / \pi_{\text{train}})}{\hat{p}(X) \cdot (\pi_{\text{new}} / \pi_{\text{train}}) + (1 - \hat{p}(X)) \cdot ((1-\pi_{\text{new}}) / (1-\pi_{\text{train}}))} $$

🔬 数式を言葉で読み解く(prior shift 補正)

🏁 最終まとめ:Precision を使いこなす 10 の心得

  1. 必ず Recall と一緒に見る:Precision = 1.0 でも Recall = 0.01 なら無能
  2. 不均衡データでは Accuracy より信頼:Accuracy は楽観的になる
  3. 陽性率(基底比)を必ず確認:Precision のベースラインは陽性率
  4. 業務コストから β を選ぶ:FP/FN コスト比に応じた F-β
  5. 閾値選定は validation set で:同じデータで学習・閾値選定は過学習
  6. CI を必ず併記:小サンプルでは点推定だけは不誠実
  7. multi-class は macro/micro/weighted を意識:自動デフォルトを盲信しない
  8. Precision@k も評価する:業務件数制約があるなら必須
  9. prior shift に備える:定期的な再評価・再校正
  10. ベイズ定理を覚えておく:稀少事象の数学的限界を理解する

📜 Precision・Recall の歴史と用語の混乱

Precision と Recall は分野ごとに異なる名前で呼ばれることがあります。 用語の混乱を整理します。

📚 分野別の用語対応表

分野Precision の別名Recall の別名備考
情報検索 (IR)PrecisionRecall1960 年代の文献検索評価に由来
医療診断陽性的中率 (PPV)感度 (Sensitivity)米国 CDC ガイドラインで標準
機械学習 (ML)PrecisionRecallIR 由来、 最も一般的
統計学陽性的中率真陽性率 (TPR)仮説検定の文脈
信号検出理論(あまり使わない)Hit Rate心理物理学で発祥
レーダー工学Detection ProbabilityDetection Rate第二次大戦の用語

🕰️ 概念の歴史的展開

年代事項
1940sレーダー信号検出で「真陽性 / 偽陽性」概念が成立
1950s心理物理学で「信号検出理論」が体系化
1960s情報検索 (Cleverdon 1966) で Precision/Recall を正式定義
1970s医療診断分野で「感度・特異度」が広まる
1990s機械学習で標準指標として定着
2000sF1 スコア (van Rijsbergen 1979) が爆発的に普及
2010s不均衡データ問題で PR-AUC が注目
2020s公平性・校正性能の評価指標として再評価

📐 Precision の統計的検定

「2 つのモデルの Precision に有意差があるか」を判定するには、 McNemar 検定(同一テストセット上)または Fisher の正確検定(小サンプル)が使われます。

🐍 Python コード 15:McNemar 検定で 2 モデル比較

🎯 このコードでやること:ロジスティック回帰とランダムフォレストの予測結果を比較し、 McNemar 検定で「予測の差が有意か」を判定。

📥 入力データ:SSDSE-B-2026 (2023)

y, X、 2 モデルで予測比較
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from statsmodels.stats.contingency_tables import mcnemar

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023].reset_index(drop=True)

y = (d['A1101'] > 2_000_000).astype(int).values
X = StandardScaler().fit_transform(d[['A4101','A1102']].values)

pred_lr = LogisticRegression().fit(X, y).predict(X)
pred_rf = RandomForestClassifier(n_estimators=50, random_state=0).fit(X, y).predict(X)

# McNemar 検定: 一方だけ正解の数を比較
b = ((pred_lr == y) & (pred_rf != y)).sum()  # LR 正解、 RF 誤り
c = ((pred_lr != y) & (pred_rf == y)).sum()  # LR 誤り、 RF 正解
print(f'LR 正解 & RF 誤り: {b}')
print(f'LR 誤り & RF 正解: {c}')

table = [[0, b], [c, 0]]
result = mcnemar(table, exact=True)
print(f'\nMcNemar 検定 p 値: {result.pvalue:.4f}')
print(f'  → p < 0.05 なら有意差ありと判断')
print(f'  本ケースは {"有意差あり" if result.pvalue < 0.05 else "有意差なし"}')

📤 実行結果

LR 正解 & RF 誤り: 0 LR 誤り & RF 正解: 5 McNemar 検定 p 値: 0.0625 → p < 0.05 なら有意差ありと判断 本ケースは 有意差なし

💬 結果の読み方:ランダムフォレストが 5 件、 ロジスティック回帰の誤りを正しく分類しており(逆は 0 件)、 RF がやや優勢に見える。 しかし不一致は片側 5 件のみで McNemar 検定の p 値は 0.0625 > 0.05、 統計的には有意差なし。 実務で「モデル A と B どちらが優秀か」と問われたら、 McNemar 検定で「予測の差が偶然の範囲内か」を判定するのが正攻法。 Accuracy や Precision の数値差を単純比較するだけでは不十分。

🔗 完結リンク集(最終版)

🌟 Precision に関するベストプラクティス集

実務で Precision を扱う際の「これだけは守る」べき原則を、 失敗事例と共に提示します。

原則 1:陽性率(基底比)を最初に確認する

データを受け取ったら、 まず陽性率を計算してください。 陽性率 50% と 1% では、 同じ Precision = 0.6 でも意味が全く違います。

失敗事例:「Precision 60% のモデルができました」と報告したが、 陽性率が 80% で、 実は ランダム予測(陽性率と同じ)より悪い モデルだった。 陽性率を先に確認していれば気づけた。

原則 2:単一指標で判断しない

Precision、 Recall、 F1、 AUC、 PR-AUC、 Accuracy をすべて並べて確認。 1 つの指標が好結果でも、 他が悪化していたら要注意。

失敗事例:閾値を高く設定して Precision を 0.95 に上げたが、 Recall が 0.05 に低下。 「クライアントは大満足」だったが、 実際は 95% の機会損失。 F1 を見ていれば検知できた。

原則 3:CI(信頼区間)を必ず報告する

Precision の点推定だけ報告するのは統計的に不誠実。 Bootstrap か Wilson 区間で 95% CI を併記しましょう。

失敗事例:「Precision 90%」だけ報告したが、 サンプル 20 件のため CI は [0.68, 0.98]。 実際には 不確実性が大きい。 CI 込みなら誤ったビジネス判断を防げた。

原則 4:閾値選定は別データで

同じデータで学習と閾値選定すると過学習。 必ず train / validation / test に分割し、 validation で閾値を決定。

失敗事例:全データで Precision 最大化する閾値を選んだら、 本番投入で Precision が 0.4 まで低下。 過学習の典型。 validation set で選定していれば 0.7 程度の本番性能が出せた。

原則 5:定期的な再評価とドリフト監視

本番環境では時間と共にデータ分布が変わる(concept drift)。 月 1 回は Precision を測定し、 閾値の見直しを検討。

🏁 深掘り解説の総まとめ

本ページでは、 Precision-Recall 曲線を次の観点から掘り下げました:
  • 📐 数式と数学的本質(ベイズ定理による表現)
  • 🧮 SSDSE-B-2026 での実値計算(複数閾値での比較)
  • 🐍 Python での実装(10+ コードブロック)
  • ⚠️ 落とし穴と典型的失敗パターン
  • 🌐 関連指標との関係マップ
  • 🏭 業界別ベストプラクティス
  • 🎓 演習問題と自己チェック
これらをすべて手を動かして実装した時、 Precision を 「知っている」から「使える」 へと変えることができます。

🧩 さらに深掘り:同じモデルでも「PR で見るか ROC で見るか」で評価が激変する

姉妹ページ(precision / recall / roc-curve / f1-score)とは切り口を変え、 ここでは PR-AUC(AP)と ROC-AUC を同一タスク・同一ランキングで並べて比較し、 不均衡データで両者がどれだけ食い違うかを実データで検証します。 すべて SSDSE-B-2026(2023 年・47 都道府県)の実測値です。

🎨 直感:PR 曲線の「合格ライン」はタスクごとに動く

ROC 曲線のベースライン(当てずっぽう)は 常に AUC=0.5 で固定です。 一方 PR 曲線のベースラインは 陽性率 π そのもので、 タスクによって上下します。 つまり「PR-AUC=0.5」という同じ数字でも、 π=0.5 のタスクなら「当てずっぽうと同じ」、 π=0.1 のタスクなら「ベースラインの 5 倍」で意味が真逆になります。 PR-AUC は絶対値だけ見ても評価できず、 必ず π と並べて読む——これが ROC との最大の違いです。

🕳 落とし穴(重要):ROC-AUC=0.95 で安心 → PR-AUC=0.67 という現実

「総人口 > 700 万人」を陽性(陽性 5 件=埼玉・東京・神奈川・愛知・大阪、 陰性 42 件、 π=0.106)とし、 「一般病院数(I510120)」をスコアにして順位付けした場合の実測 AUC:

ROC-AUC = 0.9524 (一見「優秀」) PR-AUC (AP) = 0.6726 (同じモデル・同じ順位なのに大きく低い) 差 = 0.28 / PR ベースライン(π) = 0.106、 ROC ベースライン = 0.500

なぜ同じランキングでこれほど差が出るのか。 病院数で上位から並べると:

順位県(病院数)正解PrecisionRecall
1東京都(588)陽性1.0000.20
2北海道(464)陰性(FP)0.5000.20
3大阪府(463)陽性0.6670.40
4福岡県(390)陰性(FP)0.5000.40
5兵庫県(312)陰性(FP)0.4000.40
6埼玉県(296)陽性0.5000.60
7神奈川県(289)陽性0.5710.80
8愛知県(278)陽性0.6251.00

北海道(病院 464=全国 2 位だが人口 509 万で陰性)、 福岡・兵庫(人口 500 万台で 700 万に届かず陰性)が上位に食い込み、 再現率がまだ低い序盤で Precision を 0.5 前後まで削り取ります。 病院数は「面積・医療体制」を色濃く反映し「人口規模」とはズレるためです。 ROC-AUC はこの誤りを 42 件の真陰性(TN)の多さで薄めて 0.95 と楽観的に見せますが、 PR は TN を一切見ないため、 少数の陽性を取り違えるコストがそのまま数字に出て 0.67 に落ちます。 AP を階段近似で手計算しても
0.2×1.000 + 0.2×0.667 + 0.2×0.500 + 0.2×0.571 + 0.2×0.625 = 0.6726
と sklearn の average_precision_score に一致します。 教訓:不均衡タスクで「ROC-AUC が高い=良いモデル」と早合点すると危険。 少数派の検出品質は PR-AUC でしか正しく見えない。

📝 補足:同じ陽性ラベルでも「大学学生数(E6302)」をスコアにすると ROC-AUC=0.9810 / PR-AUC=0.9111(差 0.07)、 「出生数(A4101)」なら両方 1.0000。 スコアの良し悪しによって PR-ROC の乖離幅そのものが変わる点も、 実データで確認できます(いずれも 2023 年実測)。

🚀 発展:π が動くと PR 曲線ごと動く/閾値はコスト比で選ぶ

🔗 関連ページ