🌐
商品と顧客のイメージの関係をコレスポンデンス分析してみる
📌はじめに
商品とイメージの関係を可視化する方法のひとつにコレスポンデンス分析(対応分析) があります。
利用シーンは限られますが、その応用例を紹介します。

📌調査内容と分析に使用するInputデータ
- モニターに実際の A車・B車・C車 の3台を提示。
- 各車について、あらかじめ用意した共通のイメージワード一覧 から、当てはまると思うものをすべて選択してもらいました。
- 選択は 複数回答可であり、1人が1台に対して複数のイメージワードを選択可能(MA)。
- 特に当てはまるものがなければ、1つも選ばないことも可能。
- その回答結果が、分析のInputに使用する
sample_car_data.csvです。

sample_car_data.csv
📌環境
python3.x
📌フォルダ構成
├─ 1_flow/
│ └─ corres_analysis.py # 実行スクリプト
├─ 2_data/
│ └─ sample_car_data.csv # Inputデータ
├─ 3_output/ # データ出力先(自動作成)
📌コード
import pandas as pd
import numpy as np
import os
import sys
import subprocess
import prince # Correspondence Analysis
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
import japanize_matplotlib # 日本語対応
#=============================================
# 調査の車両数
#=============================================
num_car = 3
#=============================================
# Inputファイル情報
#=============================================
INPUT_folder = "2_data"
INPUT_DNAME = "sample_car_data.csv"
#=============================================
# Outputファイル情報
#=============================================
OUTPUT_folder = "3_output"
OUTPUT_DNAME = "1_平均.csv"
OUTPUT_DIS = "2_行列入れ替え.csv"
OUTPUT_AVE = "3_コレポンデータ.xlsx"
#=============================================
# パス設定
#=============================================
parent_path = os.path.dirname(os.getcwd())
input_path = os.path.join(parent_path, INPUT_folder, INPUT_DNAME)
output_path = os.path.join(parent_path, OUTPUT_folder)
os.makedirs(output_path, exist_ok=True)
save_name_1 = os.path.join(output_path, OUTPUT_DNAME)
save_name_2 = os.path.join(output_path, OUTPUT_DIS)
save_name_3 = os.path.join(output_path, OUTPUT_AVE)
#=============================================
# 1.CSV読み込み
#=============================================
try:
df = pd.read_csv(input_path, encoding="utf-8")
except UnicodeDecodeError:
df = pd.read_csv(input_path, encoding="cp932")
1. CSV読み込み
文字化け対応として、UTF-8で読み込めない場合は
Windows環境向けに cp932 で再読み込みするようにしています。
# =============================================
# 2. Car列セットの開始インデックスを取得
# =============================================
car_indices = [i for i, col in enumerate(df.columns) if col.startswith("Car")]
car_indices.append(len(df.columns))
# =============================================
# 3. Car列セットごとにデータを抽出して縦連結
# =============================================
dfs = []
for idx in range(len(car_indices)-1):
start = car_indices[idx]
end = car_indices[idx+1]
cols = [df.columns[0]] + list(df.columns[start:end])
temp_df = df[cols].copy()
# 列名を統一
temp_df.columns = ['ID', 'Car', 'Elegant', 'Luxurious', 'Sporty', 'Technology',
'Futuristic', 'Traditional', 'Cute', 'Cool', 'Sleek', 'Trendy']
dfs.append(temp_df)
df_long = pd.concat(dfs, axis=0, ignore_index=True)
2. Car列の開始インデックス取得
- 「Car」で始まる列の位置を探す
- 最後に列数を追加して、ループで範囲指定しやすくする
3. Car列ごとのデータ抽出と縦連結
- 車ごとに ID列とイメージ列 を抽出
- 列名を統一して、後続の分析で扱いやすくする
- すべてのデータを縦に結合(縦連結)して1つのデータフレームにまとめる

#=============================================
# 4. イメージワードの開始列を指定(ID列とCar列以降の列)
#=============================================
image_cols = df_long.columns[2:]
# NaNを空文字に置換
df_long[image_cols] = df_long[image_cols].fillna('')
# "1"(文字)または1(数値)を1に、それ以外は0に変換
df_long[image_cols] = df_long[image_cols].applymap(lambda x: 1 if x == 1 or x == '1' else 0)
4. イメージワードの開始列を指定(ID列とCar列以降の列)
- ID列とCar列以降の列を対象に、イメージワードの列を指定
-
NaNを空文字に置換して欠損を整理 - "1"(文字)や 1(数値)を
1に、それ以外を0に変換

# =============================================
# 5. Carごとに平均値を算出
# =============================================
df_grouped = df_long.groupby('Car')[image_cols].mean().reset_index()
df_grouped.to_csv(save_name_1, index=False, encoding='utf-8-sig')
5. Carごとの平均値を算出
- 各車ごとに各イメージワードが選ばれた割合 を算出
(例:0.6 → モニターの60%がそのイメージを選択)

# =============================================
# 6. 行列入れ替え
# =============================================
data = df_grouped.set_index('Car').T
# =============================================
# 7.コレスポンデンス分析
# =============================================
data_c = data.copy()
ca = prince.CA(n_components=2, n_iter=10, copy=True, check_input=True, engine='sklearn')
ca = ca.fit(data_c)
row_coords = ca.row_coordinates(data_c)
col_coords = ca.column_coordinates(data_c)
6. 行列の入れ替え
「イメージワード × 車」の表形式に変換。
7. コレスポンデンス分析の実行
-
n_components=2で2次元空間にプロット可能 -
n_iter=10は計算の安定化用 - 各イメージワードと各車の位置を2次元空間上で確認できる

# =============================================
# 8. 散布図プロット
# =============================================
fig, ax = plt.subplots(figsize=(8,6))
ax.scatter(row_coords[0], row_coords[1], color='blue', label='Impression')
for i, txt in enumerate(row_coords.index):
ax.annotate(txt, (row_coords.iloc[i,0], row_coords.iloc[i,1]), color='blue')
ax.scatter(col_coords[0], col_coords[1], color='red', label='Car')
for i, txt in enumerate(col_coords.index):
ax.annotate(txt, (col_coords.iloc[i,0], col_coords.iloc[i,1]), color='red')
ax.axhline(0, color='grey', linewidth=0.5)
ax.axvline(0, color='grey', linewidth=0.5)
ax.set_title('Correspondence Analysis: Car Brands and Impressions')
ax.legend()
plt.show()
# =============================================
# 9.結果をエクセルに出力
# =============================================
col_coords['type'] = 'Car'
row_coords['type'] = 'Impression'
combined = pd.concat([col_coords, row_coords])
combined.to_excel(save_name_3, index=False, engine='openpyxl')
8. 散布図プロット
- 行(イメージワード)を青、列(車)を赤でプロット
- ax.annotate で各点にラベルを付けて、どのイメージや車か分かるようにする
- 原点に縦横の線を引いて、座標の位置関係を見やすくする

以前上司から「見た瞬間に理解できない報告書は報告書とは言えない」と言われのを思い出しました
グラフも同じで、見た瞬間に何がわかるかが大事
9.結果をエクセルに出力
- 行(イメージワード)と列(車)の座標にタイプ情報を追加
type = 'Car' / type = 'Impression'
- Excelに保存して、あとから分析や可視化に使いやすくする
📌参考:エクセルでグラフ作成
私の場合、報告書に使うグラフは加工しやすさを重視してエクセルで作成していました。
(日本語にラベルを直したり、細かい調整がしやすいんですよね)

サンプルデータのざっくり考察(参考用です)
-
A車:Cool をコンセプトにデザインしましたが、実際には「Traditional(古臭い)」の近くに位置しており、ユーザーには少し違う印象を持たれているようです。デザインの見直しが必要かもしれません。
-
B車:Luxurious や Futuristic と近くに位置しており、コンセプト通りのイメージがユーザーに伝わっています。
-
C車:Cute や Sporty と近くに位置しています。特に Sporty を重視していたので、この点をさらに強調していくのが良さそうです。
📌まとめ
- コレスポンデンス分析を行うと商品と印象語の関係性を2次元空間で視覚的に把握できます。
- 関係の強さや特徴を 一目で確認できる 点が大きなメリットです。
この記事が少しでも、マーケティングのお役に立てると嬉しいです。
参考リンクについて
GitHubリポジトリ
本記事で紹介したコードやサンプルデータはこちらで公開しています。
Discussion