2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SPSS Modelerの判別モデルを外部プログラムで実装する

2
Last updated at Posted at 2026-10-27

1. はじめに

みなさん、こんにちは。久しぶりにSPSS関連の記事となります。あるユーザー様でSPSS Modelerで作った判別分析モデルを、自分たちのシステムに組み込みたいというお話がありました。

もちろんSPSSで構築した判別分析モデルは、SPSS環境がなければスコアリングができません。
本記事では、SPSSの判別分析出力結果から必要な係数を読み取り、外部プログラムで同等の分類を再現する手順を書いてみます。例として Python で実装しています。

使用ストリームと使用データ: SPSS Modelerにデモ用サンプルとして同梱されている"telco_custcat_discriminant.str"と"japanese_ja/telco.sav"を使います。顧客1,000件を4グループ(ベーシック・E・プラス・トータル サービス)に分類。説明変数は収入・雇用年数・世帯人数の3変数(ステップワイズで選択済み)。

⚠️ 判別分析ノード はSPSS Modeler v18.5から Professionalエディションでは利用ができなくなり、Premiumエディションが必要となりました。

本記事で使用しているストリームは、GitHubにアップロードしていますので、ご自由ダウンロードしてください。

目次

  1. 判別分析の理論
  2. SPSS出力の読み方
  3. 分類方式の選択肢と比較
  4. Python実装(Fisherの線形判別関数)
  5. 精度検証
  6. まとめ
  7. 最後に

2. 判別分析の理論

SPSSの判別分析には2種類の関数が登場します。用途が異なるため、まず違いを整理します。

正準判別関数(2-1, 2-2) Fisherの線形判別関数(2-3)
主な目的 可視化・構造の解釈 所属先の自動判定(分類)
式の数 最大 k−1 本(軸の数) k 本(グループの数だけ)
得られる値 散布図に打つための「座標」 各グループへの「適合度スコア」
判定ルール 各グループ重心との距離を測る スコアが最大のグループを選ぶ

外部プログラムでSPSSの判別モデルと近い結果を得たい場合は、右列のFisherの線形判別関数を使います。
ユーザー様は、正準判別関数の方を採用しました。

2-1. 正準判別分析とは

複数グループを最もよく分離する方向(正準判別関数)を求める手法です。グループ数が k のとき、最大 k−1 本の軸が得られます(本例:4グループ → 最大3軸)。

  • 関数の数が最大 $k - 1$ 個になる理由
    各グループの「中心点(平均値)」の位置関係で決まります。
    • 2グループの中心点:2点を結ぶと「1本の直線(1次元)」ができる $\rightarrow$ 軸は最大1本
    • 3グループの中心点:3点を結ぶと「1つの平面(2次元)」ができる $\rightarrow$ 軸は最大2本
    • 4グループの中心点:4点を結ぶと「立体の空間(3次元)」ができる $\rightarrow$ 軸は最大3本
      このように、$k$ 個のグループの重心は最大 $(k - 1)$ 次元の空間に収まるため、作れる判別関数の本数も最大 $k - 1$ 本(※変数の数がそれより少ない場合は変数個数まで)になります。第1関数(D1)が最もグループを分ける力が強く、第2関数(D2)、第3関数(D3)と順に判別力が弱くなります。

各レコードをこの軸に射影した値が判別スコア(D1, D2, ...) で、グループの散らばりを可視化するために使います。

2-2. 判別関数スコア(非標準化)

SPSSが出力する正準判別関数係数を使うと、変数値をそのまま代入して判別スコアが計算できます。標準化(Zスコア変換)は不要です。

D1 = a11×X1 + a12×X2 + ... + 定数1
D2 = a21×X1 + a22×X2 + ... + 定数2

判別スコアを求めたあと、各グループの重心(SPSSの「グループ重心の関数」テーブル)までの距離を測ることで分類できますが、距離計算の方式(ユークリッド・マハラノビス)によって精度が変わります。

2-3. Fisherの線形判別関数(分類関数)

SPSSの分類の内部処理は以下の流れです。

  1. 正準判別スコア f = X×B + a を計算
  2. 各グループ重心からのカイ2乗距離 χ²_j を計算
  3. 事後確率 P(G_j|X) ∝ exp(log(p_j) - (log|D_j| + χ²_j)/2) を計算
  4. 事後確率が最大のグループに分類

SPSSが「分類関数係数」として出力するFisherの線形判別関数は、グループごとに k 本の一次式を持ち、スコアが最大のグループに分類します。

  • 判定の仕組み
    このスコアは、統計的には「そのグループに所属する確率(もっともらしさ)」の高さを反映しています。すべてのグループの式に同じデータを代入し、最も高い数値を出したグループを「所属グループ」として判定します。SPSSの出力テーブル「分類関数係数(Classification Function Coefficients)」に並んでいる数値が、この各式の係数($b$)と定数です。
Score_g = b1g×X1 + b2g×X2 + b3g×X3 + 定数g
→ Score が最大のグループ g に分類

グループ間で共分散行列が等しい(等共分散)と仮定した場合、FisherスコアのidxmaxはSPSSの事後確率最大化とほぼ等価になります。本記事ではこの方式を採用します(SPSSとの実測一致率:96.3%)。


3. SPSS出力の読み方

3-0. サンプルストリーム

** SPSS Modelerにデモ用サンプルとして同梱されている"telco_custcat_discriminant.str"をベースに、各計算例をストリーム内に例として作成しています。グループを特定するために拡張の変換ノードを利用しています。

{AE21D661-EF13-406C-A248-20A25943C1EE}.png

GitHubにアップロードしていますので、ご自由ダウンロードしてください。

3-1. 使う指標の優先順位

指標名 用途 優先度
分類関数係数(Fisherの線形判別関数) SPSSと同じ分類の再現 ★★★ 最優先
正準判別関数係数(非標準化) 判別スコア D1, D2 の計算 ★★☆
グループ重心の関数 距離ベースの分類で使用 ★☆☆
標準化された正準判別関数係数 変数の寄与解釈(分類には不向き) 解釈用
固有値 マハラノビス距離の重みに使用 ★☆☆

{A8D3D08E-F22A-45DE-BE0E-B8F0A12F521F}.png

{1482967E-7B28-4318-A629-3DBD5AC36A0F}.png

{0673433E-DFC7-4607-8A48-75458961A102}.png

{64352853-C47E-41EE-8009-4D73BA0AC7D7}.png

{B14A3BA3-AE47-4722-A178-03D94F46E4C8}.png

3-2. 分類関数係数の読み取り(本例)

変数 ベーシック E-サービス プラス トータル
収入 0.002 0.003 0.002 0.005
雇用 0.101 0.118 0.160 0.104
世帯人数 1.184 1.276 1.256 1.410
定数 −3.165 −3.610 −4.028 −3.992

💡 このテーブルはSPSSでデフォルト出力されない場合があります。「判別分析」→「エキスパート」→「出力」→「Fisher」にチェックを入れると出力されます。

{93CF6DEF-7F0E-45C5-9329-5F75E9417551}.png

⚠️ 分類関数の定数項には事前確率 p_j の対数(log p_j)が含まれています。本例は全グループ均等(各0.25)のため係数に吸収されていますが、事前確率を変更した場合は係数も変わるため再取得が必要です。係数を再利用する場合は、SPSSの「エキスパート」タブで「事前確率:すべてのグループが同じ」「共分散行列の使用:グループ内」になっていることを確認してください。

{D2CE5100-37D6-435B-9EA0-C4ADF4CCDB03}.png

3-3. 非標準化正準判別関数係数

変数 関数1 関数2 関数3
収入 −0.001 0.008 −0.007
雇用 0.107 −0.031 0.040
世帯人数 0.025 0.495 0.504
定数 −1.156 −1.449 −1.055

関数3は Wilks λ=1.000、p=.924 で有意でないため、実用上は関数1・2のみ使用します。

{3B726B69-CF0B-4681-A490-2C125ED7AE36}.png

{1482967E-7B28-4318-A629-3DBD5AC36A0F}.png

💡 このテーブルはSPSSでデフォルト出力されない場合があります。「判別分析」→「エキスパート」→「出力」→「非標準化」にチェックを入れると出力されます。

{93CF6DEF-7F0E-45C5-9329-5F75E9417551}.png


4. 分類方式の選択肢と比較

SPSSの判別分析モデルを外部で再現するには、大きく3つの方式が考えられます。

方式 方法 SPSSとの一致率 実際の正解率
Fisherスコア(推奨) 各グループのFisherスコアを計算 → 最大値のグループ(idxmax) 96.3% 34.5%
マハラノビス距離 判別スコア D1, D2 を固有値で重み付けした重心までの距離 → 最小値のグループ(idxmin) 92.5% 34.7%
ユークリッド距離 判別スコア D1, D2 の単純な重心までの距離 → 最小値のグループ(idxmin) — 34.8%

※ 本例の実測値。SPSSが出力する係数の小数点3桁への丸め等の影響と思われますが、僅差ケースで判定が逆転するため完全一致していません。

結論: 等共分散・等事前確率の条件下では、FisherスコアのidxmaxはSPSSの分類と数学的に100%一致します。実測で差が生じる場合は条件の不一致が原因の可能性が考えられます。(→ 2-3節参照)。

⚠️ **距離系(マハラノビス・ユークリッド)は 最小値、Fisherスコアは 最大値**のグループに属する。混同しやすいので注意。


5. Python実装(Fisherの線形判別関数)

5-1. 分類関数係数の定義

まず、SPSSの「分類関数係数」テーブルから読み取った係数を定義します。

# Fisherの線形判別関数係数(SPSSの「分類関数係数」テーブルより)
FISHER_COEF = {
    "ベーシック サービス": {"収入": 0.002, "雇用": 0.101, "世帯人数": 1.184, "定数": -3.165},
    "E-サービス"        : {"収入": 0.003, "雇用": 0.118, "世帯人数": 1.276, "定数": -3.610},
    "プラス サービス"   : {"収入": 0.002, "雇用": 0.160, "世帯人数": 1.256, "定数": -4.028},
    "トータル サービス" : {"収入": 0.005, "雇用": 0.104, "世帯人数": 1.410, "定数": -3.992},
}

5-2. 単一レコードの分類

1件ずつ分類したい場合(API・バッチ処理など)の実装です。

def 判別分類(収入, 雇用, 世帯人数):
    """
    Fisherの線形判別関数による顧客グループ分類
    生の値をそのまま渡す(標準化不要)

    Parameters
    ----------
    収入     : float
    雇用     : float  (雇用年数)
    世帯人数 : float

    Returns
    -------
    str : 予測グループ名
    """
    scores = {
        g: coef["収入"] * 収入 + coef["雇用"] * 雇用
           + coef["世帯人数"] * 世帯人数 + coef["定数"]
        for g, coef in FISHER_COEF.items()
    }
    return max(scores, key=scores.get)  # スコア最大のグループ

# --- 使用例 ---
print(判別分類(収入=80, 雇用=12, 世帯人数=3))
# → 'プラス サービス'

5-3. CSVファイルを読み込んで一括分類

💡 欠損値の扱い: SPSSのアルゴリズム仕様では、入力変数のいずれかに欠損値があるレコードはスコアリング不可で $null$ が割り当てられます。以下のコードでも同様に処理しています。

import pandas as pd

# --- 1. データ読み込み ---
# 日本語環境のSPSS出力CSVはShift-JIS(cp932)が多い
df = pd.read_csv('input.csv', encoding='cp932')

# 使用する列の確認
print(df[['収入', '雇用', '世帯人数']].head())
   収入  雇用  世帯人数
0  64.0   5    2
1 136.0   5    6
2 116.0  29    2
# --- 2. 欠損値チェック(SPSSと同様、欠損レコードはNullとして扱う)---
required_cols = ['収入', '雇用', '世帯人数']
valid_mask = df[required_cols].notna().all(axis=1)
df["予測グループ"] = None  # デフォルトはNull

# --- 3. 各グループのFisherスコアを計算(有効レコードのみ)---
for group, coef in FISHER_COEF.items():
    df.loc[valid_mask, f"score_{group}"] = (
          coef["収入"]     * df.loc[valid_mask, "収入"]
        + coef["雇用"]     * df.loc[valid_mask, "雇用"]
        + coef["世帯人数"] * df.loc[valid_mask, "世帯人数"]
        + coef["定数"]
    )

# --- 4. スコア最大のグループに分類(idxmax)---
score_cols = {g: f"score_{g}" for g in FISHER_COEF}
col_to_group = {v: k for k, v in score_cols.items()}

df.loc[valid_mask, "予測グループ"] = (
    df.loc[valid_mask, list(score_cols.values())]
    .idxmax(axis=1)
    .map(col_to_group)
)

# --- 5. 作業列を削除して結果を出力 ---
df.drop(columns=list(score_cols.values()), inplace=True)
df.to_csv('output.csv', encoding='cp932', index=False)

print(df[['収入', '雇用', '世帯人数', '予測グループ']].head())
   収入  雇用  世帯人数        予測グループ
0  64.0   5    2  ベーシック サービス
1 136.0   5    6   トータル サービス
2 116.0  29    2     プラス サービス

コードでも書いていますが、以下の計算式に当てはめて、一番スコアが高いサービスがそれぞれ選ばれています。
統計手法のため、計算式にすることができ外部プログラムでも実装が容易に行えますね。

ベーシックサービスのスコア = 0.002 * 収入 + 0.101 * 雇用 + 1.184 * 世帯人数 - 3.165
E-サービスのスコア = 0.003 * 収入 + 0.118 * 雇用 + 1.276 * 世帯人数 - 3.610
プラスサービスのスコア = 0.002 * 収入 + 0.160 * 雇用 + 1.256 * 世帯人数 - 4.028
トータルサービスのスコア = 0.005 * 収入 + 0.104 * 雇用 + 1.410 * 世帯人数 - 3.992

5-4. 判別関数スコア(非標準化)では?

使う係数が変わるだけです。

{1482967E-7B28-4318-A629-3DBD5AC36A0F}.png

{0673433E-DFC7-4607-8A48-75458961A102}.png

ユークリッド距離を使う場合

正準判別関数係数を使って判別スコアを計算します。
関数3つ目は、有意ではないので使いません。

判別スコア1 = -0.001 * 収入 + 0.107 * 雇用 + 0.025 * 世帯人数 - 1.156
判別スコア2 = 0.008 * 収入 - 0.031 * 雇用 + 0.495 * 世帯人数 - 1.449

そこから重心との距離を計算して一番重心に近い(値が小さい)サービスを選択するようにします。

ベーシックサービス = sqrt((判別スコア1 - (-0.248))**2 + (判別スコア2 - (-0.174))**2 )
E-サービス = sqrt((判別スコア1 - (-0.041))**2 + (判別スコア2 - (-0.007))**2 )
プラスサービス = sqrt((判別スコア1 - 0.342)**2 + (判別スコア2 - (-0.060))**2 )
トータルサービス = sqrt((判別スコア1 - (-0.090))**2 + (判別スコア2 - 0.273)**2 )

マハラノビス距離を使う場合

ユークリッド距離の式において関数毎の固有値で除算した値を使います。
もちろん、距離が近いものを選択するようにします。

{B14A3BA3-AE47-4722-A178-03D94F46E4C8}.png

ベーシックサービス = sqrt((判別スコア1 - (-0.248))**2 / 0.052 + (判別スコア2 - (-0.174))**2 / 0.027)
E-サービス = sqrt((判別スコア1 - (-0.041))**2 / 0.052 + (判別スコア2 - (-0.007))**2 / 0.027)
プラスサービス = sqrt((判別スコア1 - 0.342)**2 / 0.052 + (判別スコア2 - (-0.060))**2 / 0.027)
トータルサービス = sqrt((判別スコア1 - (-0.090))**2 / 0.052 + (判別スコア2 - 0.273)**2 / 0.027)


6. 精度検証

6-1. SPSSモデルとの一致率

FisherスコアによるPython実装とSPSSモデルの予測結果の一致率:963 / 1000(96.3%)

理論上は等共分散・等事前確率の条件下で100%一致しますが、本例ではSPSSが出力する係数の小数点3桁への丸め等の影響? により僅差ケースで判定が逆転し、3.7%の差が生じています(→ 2-3節参照)。

6-2. 実際のグループとの正解率(混同行列)

実際 \ 予測 ベーシック E-サービス プラス トータル 正解率
ベーシック 145 5 59 57 54.5%
E-サービス 85 3 70 59 1.4%
プラス 101 6 123 51 43.8%
トータル 98 5 55 78 33.1%
全体 34.9%

⚠️ E-サービスの正解率が極めて低い(1.4%) のはモデル自体の限界です。E-サービス顧客の特性が他グループと重複しており、判別が困難な状況です。全体正解率34.9%は偶然水準(25%)を上回っているものの、実用面では改善が必要です。


7. まとめ

判別分析モデルを外部プログラムで利用する際のポイントは以下の通りです。

ポイント 内容
SPSSと同じ分類を再現する方法 分類関数係数(Fisherの線形判別関数)+idxmax
標準化の必要性 非標準化係数を使えば不要(生の値をそのまま代入)
距離 vs Fisherスコア idxmin(距離)vs idxmax(Fisher) を混同しない
Fisherスコアの同値性 等共分散・等事前確率の条件下でSPSSと数学的に100%一致
実測で差が出る場合 係数の丸め・事前確率設定・共分散指定・交差検証との比較を確認

8. 最後に

さて、いかがでしたでしょうか?
SPSS Modelerで作成したモデルをそのまま使用してスコアリングするためには、SPSSソフトウェアの環境が必要ですが、統計手法のモデルであればモデル詳細出力の値と計算式を用いてスコアリングが可能です。

システムへのモデルの組み込みなどを考えている場合は、参考になれば幸いです。

今回は以上となります。

参考リンク集

SPSS Modeler ノードリファレンス目次

SPSS Modeler 逆引きストリーム集

SPSS funさん記事集

IBM 斎藤さんの記事集

SPSS連載ブログバックナンバー

SPSSヒモトクブログなどは以下のTechXchangeのコミュニティに統合されました。
ご興味がある方は、ぜひiBM IDを登録して参加してみてください!!!お待ちしています。

IBM TechXchange Data Science Japan

IBM Bob紹介ページ

IBM Quantum Platform

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?