メインコンテンツへスキップ
ブログ一覧に戻る
データ分析・KPI

コレスポンデンス分析|クロス表を二次元に可視化(行列の関係を読み解く)

2025年10月30日
最終更新: 2026年10月8日
10分で読めます

この記事でわかること

対応分析の図で近くにある行と列は、関連が強いと言えるでしょうか。旧3×2表を1軸として読み直し、架空3×3表で質量・プロファイル・主座標・慣性・寄与・cos²を計算します。同種類の点の距離と異種点の配置、度数の一律倍増と構成変更を区別し、NumPyの実行教材と練習問題で、図から施策を決める前の判断条件を整理します。

2026年10月8日に、Princeの現行リポジトリとCRAN caの実装、summary.caを再読しました。PrinceはCAを含む多変量探索分析をscikit-learn風APIで扱い、補助行・列や重みを案内しています。CRANの実装と要約メソッドは、分析次元、補助行列、主座標、寄与、cosineの計算・表示範囲を扱います。これらは座標と診断量の仕様であり、図の近さを因果、顧客セグメントの効果、売上へ変換する根拠ではありません。

色と評価のクロス表を図にしたら、「赤」と「好き」が近くに出ました。これだけで赤を推せば売れる、と判断してよいでしょうか。

対応分析(コレスポンデンス分析、CA)は、度数表の構成比の違いを少数の軸に整理する方法です。ただし、行の点と列の点の近さを、そのまま関連の強さとは読めません。 座標の尺度、表の内訳、どの距離を保存した図かを先に確かめます。

この記事では旧来の「赤/青×好き/普通/嫌い」の教材を1軸で読み直し、別の架空3×3表で2軸の意味を計算します。実在する購入者調査や施策の効果ではありません。

30秒で要点

  • 入力は同じ観測単位から作った非負の度数表。行・列の合計が0のカテゴリは、そのまま座標計算に入れません。
  • 3×2表の非自明な軸は最大1本です。「二次元の図」を作ることを目的にしないでください。
  • 行同士は行プロファイル、列同士は列プロファイルを比較します。主座標で全有効軸を使えば、それぞれのχ²距離を再現できます。
  • 軸の寄与率、点の寄与、点のcos²は別の指標です。遠い点や大きな点を見つけただけでは解釈は決まりません。
  • 見つけた違いは仮説の候補です。広告・訴求の効果は、別に検証します。

元の3×2表で、人数と割合を分ける

1人が1つの色と1つの評価に分類された、という架空の130人を考えます。行は評価、列は色です。単位を変えた売上金額や複数回答を、同じ人数表であるかのように混ぜないことが出発点です。

表が収まらない場合は、横にスクロールできます

評価赤青行合計赤の行内割合
好き30154566.67%
普通20305040.00%
嫌い10253528.57%
列合計607013046.15%

「好き」の赤の割合は30/45です。赤の人数30だけを「普通」の20と比べると、行の大きさと内訳が混ざります。行を合計1にしたベクトルを行プロファイルと呼びます。全体の赤/青の割合は60/130と70/130です。

独立な構成を基準にすると、「好き×赤」の期待度数は 45×60/130 ≈ 20.7692。全6セルについて χ² = Σ (観測度数 - 期待度数)² / 期待度数 を足すと、χ² = 624/49 ≈ 12.734694 です。CAの総慣性は χ²/N = 24/245 ≈ 0.097959 になります。

これは表の独立からのずれを記述する計算です。ここでは架空表からp値や母集団の結論を出しません。検定をするなら、標本抽出や観測の独立性を別に確認します。

3×2表を2軸にしても、情報は増えない

通常のCAの有効次元の上限は min(行数 - 1, 列数 - 1)。この表では min(2, 1) = 1 です。1軸が総慣性の100%を持っていても、「市場を完全に説明した」という意味ではありません。この表のプロファイル差が1軸に収まるという意味です。

計算では、度数を総数で割った相対度数行列Pと、行・列の質量r、cを使います。質量は各行・列の合計が全体に占める割合です。標準化残差 S(i,j) = (P(i,j) - r(i)c(j)) / √(r(i)c(j)) を特異値分解(SVD)し、特異値の2乗を各軸の慣性とします。

この計算関係と座標の種類は、CAの理論付録とcaの実装で確認できます。特異ベクトルの符号は反転できるので、左右が入れ替わっただけで別の結論になったとは考えません。

2軸の教材では「何を説明できたか」を数で見る

次は2軸を区別できるように作った別の架空150人です。先ほどの130人を追加調査した結果ではありません。行A/B/Cを評価群、列X/Y/Zを選択肢とします。

表が収まらない場合は、横にスクロールできます

評価群XYZ合計
A3015550
B15201550
C5153050
合計505050150

各期待度数は50/3。この例の特異値は0.5と0.1、軸の慣性は0.25と0.01です。総慣性0.26、χ²は39。第1軸は 0.25/0.26 ≈ 96.15%、第2軸は約3.85%を占めます。

架空3×2表は1軸、別の3×3表は2軸。3×3の行主座標と列主座標を別パネルに描き、軸1の96.15%と軸2の3.85%を示す
自作の架空教材。左右のパネルは同じ尺度の行主座標と列主座標だが、異種点間の距離は結ばない。対称に作った表なので両パネルの座標が一致するだけで、一般の表での一致や因果を示すものではない。全座標と数値は本文にも掲載。

図では第1軸の符号をAが正になる向きにそろえました。点の主座標は次のとおりです。この対称な教材では行と列の座標が同じになります。

表が収まらない場合は、横にスクロールできます

行点 / 列点第1軸第2軸行の第1軸への寄与行の第1軸のcos²
A / X0.6123720.07071150.00%98.68%
B / Y0.000000-0.1414210.00%0.00%
C / Z-0.6123720.07071150.00%98.68%

第1軸が全体の96.15%を説明しても、Bの特徴は第1軸には表れません。 Bの原点からのずれはすべて第2軸にあり、その軸への寄与は66.67%です。全体として小さい軸を機械的に捨てると、対象とする点の違いを落とす場合があります。

距離・寄与・cos²を混同しない

表が収まらない場合は、横にスクロールできます

見るもの答える問い読むときの条件
行同士のχ²距離行の構成比はどれだけ違うか列質量で重み付け。行主座標の全有効軸なら距離を再現、低次元なら近似
列同士のχ²距離列の構成比はどれだけ違うか行質量で重み付け。列主座標の尺度を確認
軸の慣性 / 総慣性表全体のずれをその軸がどれだけ担うか総慣性0なら、この割合は定義できない
点の軸への寄与その軸を作るうえで点がどれだけ重いか行では r(i)×F(i,k)²/λ(k)。距離だけでなく質量が効く
点のcos²点の原点からのずれをその軸がどれだけ表すかF(i,k)²/Σ F(i,l)²。原点そのものの点では定義できない

例えば行Aと行Bのプロファイルは (0.6,0.3,0.1) と (0.3,0.4,0.3)。列質量が各1/3なので、χ²距離の2乗は (0.3²+(-0.1)²+(-0.2)²)/(1/3) = 0.42。主座標でも 0.612372² + (0.070711+0.141421)² ≈ 0.42 です。

この等式は行同士についてのものです。両方の主座標を重ねた対称マップで、行Aと列Xが近いことを同じ距離の意味で読まないでください。MASSの説明も、その等尺度プロットの行・列スコア間の解釈に注意を促しています。組合せを見るときは元のセル・行内割合・独立期待値に戻ります。

点の寄与とcos²の計算はcaのsummary実装でも分かれています。PrinceのCA資料も座標・寄与・cosine similaritiesを別に出力します。数字の名前だけでなく、分母が何かを確認してください。

Pythonで旧表と追加表を確かめる

以下はNumPyだけで、標準化残差のSVDから行・列主座標までを計算する独立した教材です。旧ミニコードの「クロス表から座標を得る」役割を残し、3×2表に2つの有効軸を要求しない形にしました。

import numpy as np

def ca(counts):
    n = np.asarray(counts, dtype=float)
    assert np.isfinite(n).all() and (n >= 0).all()
    assert (n.sum(axis=0) > 0).all() and (n.sum(axis=1) > 0).all()
    p = n / n.sum()
    r, c = p.sum(axis=1), p.sum(axis=0)
    s = (p - np.outer(r, c)) / np.sqrt(np.outer(r, c))
    u, values, vt = np.linalg.svd(s, full_matrices=False)
    keep = values > 1e-12  # この教材の数値誤差用。一般の有意性判定ではない
    u, values, v = u[:, keep], values[keep], vt.T[:, keep]
    for k in range(len(values)):
        if u[np.argmax(np.abs(u[:, k])), k] < 0:
            u[:, k] *= -1
            v[:, k] *= -1
    f = u * values / np.sqrt(r[:, None])
    g = v * values / np.sqrt(c[:, None])
    inertia = values ** 2
    chi2 = n.sum() * inertia.sum()
    return inertia, f, g, chi2

old = [[30, 15], [20, 30], [10, 25]]
new = [[30, 15, 5], [15, 20, 15], [5, 15, 30]]
for label, table in [("old", old), ("new", new)]:
    inertia, rows, cols, chi2 = ca(table)
    print(label, "axes", len(inertia), "inertia", inertia, "chi2", chi2)
    print("row principal coordinates", rows)
    print("column principal coordinates", cols)
assert len(ca(old)[0]) == 1
np.testing.assert_allclose(ca(old)[0], [24 / 245])
np.testing.assert_allclose(ca(new)[0], [0.25, 0.01])
np.testing.assert_allclose(ca(new)[3], 39)
np.testing.assert_allclose(ca(2 * np.array(new))[0], ca(new)[0])

出力は旧表が1軸・慣性約0.097959・χ²約12.734694、新表が2軸・慣性0.25/0.01・χ²39です。軸の符号は環境や同率の選び方で変わり得ます。座標を比較するときは符号を対応させ、同じ尺度かも確認します。

Princeを使う場合のパッケージ名は公式READMEでは princeです。旧例のprince-caという導入名は引き継ぎません。本記事の実行教材は上のNumPy版であり、Prince APIの実行結果として表示していません。

ケースを施策へつなぐ前の3問

色×評価のケースなら、図を見て訴求を決める前に「どの対象者の、どの割合を比較したいか」を決めます。赤が好きな層に広告を出す判断と、広告によって赤の評価を上げられるかという問いは異なります。

  1. 1つの行が極端に大きいときは? 行の人数と行内割合を並べ、母集団構成・重複集計・欠測の扱いを確認します。質量は解析の一部です。見やすくするためだけに人数をそろえると問いが変わります。カテゴリ統合にも内容上の理由が必要です。
  2. 行と列を混同しそうなときは? まず同種類の点だけを表示し、行なら行プロファイルの表に戻ります。異種点の組合せは元セルと期待値で確かめます。
  3. 第1・第2軸の割合が低いときは? 残りの軸と各点のcos²を確認します。今回の3×3教材には第3の非自明軸はありません。図に残らなかった構造を、配置だけで断定しないでください。

5分で確認する、分析の前提と次の検証

目的は「図を作る」ではなく、比較したい組合せを明確にすることです。次の3点を短く書けなければ、その点を補ってから進みます。

  • 目的・判断軸:誰に対する何の判断か。割合差、費用、対象人数のどれが意思決定に必要か。
  • データ:1件の単位、期間、カテゴリ、欠測・重複・複数回答を説明できるか。ゼロ行列和を除く場合も理由を残す。
  • 検証の循環:図で作った仮説を、新しい対象や適切に設計した比較でどう確かめるか。探索図を施策効果の証拠にしない。

全セルの度数を一律2倍にしてもP、質量、プロファイル、慣性は変わりません。一方、χ²は2倍になります。ある行だけを増やす場合は構成が変わるので別問題です。「規模が違えば標準化すればよい」と一括りにしないことが、旧ケースを判断に使う要点です。

距離からの配置を比較したいならMDS、検定の条件はカイ二乗検定、まとめ方の目的はクラスター分析へ進めます。連続変数の軸を考えるPCAとの違いも確認し、全体の選択は統計の判断ハブで整理してください。

よくある質問(FAQ)