🈴

PythonでパパッとL9直交表作成してコンジョイント分析#1 – 準備編

に公開

本記事はコンジョイント分析連載の第1回です。

第1回 PythonでパパッとL9直交表作成してコンジョイント分析#1 – 準備編 ← 今回
第2回 PythonでL9直交表を使ったコンジョイント分析#2 – 効用値計算編 ← 次回予定
第3回 Excelでできる!コンジョイント分析#3 – 購買確率シミュレーション編 ← 予定
第4回 Python でコンジョイント分析#4 - 購買確率 Streamlit編 ← 予定


📌はじめに

Python を使って L9直交表 を簡単に作成し、コンジョイント分析や実験計画法にすぐ使える形に整える方法を紹介します。


📌直交表とは?

直交表は、複数の因子(要因)の水準の組み合わせを
バランスよく、かつ最小限の数でまとめた表 のことです。

すべての組み合わせ(=水準表)を試すのではなく、
直交表を使うと 効率よく実験やアンケートを設計 できます。

例えば、3つの因子がそれぞれ3つの水準を持つ場合、
全組み合わせは 3×3×3 = 27通り あります。
しかし直交表を使えば、たった 9通り の実験で済みます。


📌なぜ直交表が便利なのか?

コンジョイント分析では「商品のデザイン」「価格」「色」など、複数の条件を組み合わせたパターンを作りますが、すべての組み合わせを網羅するとパターン数が膨大になり、回答者の負担が大きくなります。

例えば、3つの因子がそれぞれ4段階ある場合、
全組み合わせは 4×4×4 = 64通り
これを全部答えてもらうのは現実的ではありません。

直交表を使えば、この64通りを 16パターン程度 に減らしつつ、
因子の効果を偏りなく調べることができます。
つまり、回答者の負担を減らしながら有効な分析が可能になる わけです。

❓交互作用とは❓

L9直交表でよく出てくる交互作用(インタラクション) とは、
ある因子の効果が他の因子の条件によって変化する現象です。

例えば「赤 × 丸」と「赤 × 三角」での効果が、
単純に「色の効果+形の効果」で説明できないときに交互作用があります。
簡単に言うと、因子同士が互いに影響し合うことです。


📌注意点と対応策

  • L9直交表は 主効果の効率的な推定 に向いています
  • 交互作用の評価には注意が必要で、単純な直交表だけでは正確に推定できません
🔍交互作用の影響を抑える方法
  • パターン数を増やして検証する
  • 同じ条件で繰り返し実験してデータを増やす

📌フォルダ構成

├─ 1_flow/
│   └─ orthogonal-table.py       # R 実行スクリプト
├─ 2_data/
│   └─ levels.csv                # 水準表 サンプル
├─ 3_output/                     # 決定木出力画像用(自動作成)

📌サンプルデータ(levels.csv:水準表の例)

image.png
levels.csv
👉 この水準表を使って Pythonで直交表を作成 していきます。

この記事のコードで対応可能な水準表

例:3因子水準表

OS(ブランド) バッテリー 画面サイズ
iPhone 3000mAh 5インチ
Android(一般) 4500mAh 6インチ
Galaxy M51(高バッテリー) 6500mAh 6.8インチ

例:4因子水準表

OS(ブランド) バッテリー 画面サイズ 価格
iPhone 3000mAh 5インチ 6万
Android(一般) 4500mAh 6インチ 13万
Galaxy M51(高バッテリー) 6500mAh 6.8インチ 24万

📌環境

python3.x


📌コード解説

import os
import sys
import pandas as pd
import numpy as np

# =========================
# ディレクトリ・ファイル設定
# =========================
INPUT_FOLDER = '2_data'
OUTPUT_FOLDER = '3_output'

parent_path = os.path.dirname(os.getcwd())
input_path = os.path.join(parent_path, INPUT_FOLDER, 'levels.csv')
output_path = os.path.join(parent_path, OUTPUT_FOLDER)
os.makedirs(output_path, exist_ok=True)

save_name = os.path.join(output_path, "直交表.csv")
# =========================
# Input(levels.csv)読み込み
# =========================
try:
    levels_df = pd.read_csv(input_path, encoding="utf-8")
except UnicodeDecodeError:
    levels_df = pd.read_csv(input_path, encoding="cp932")

1. Input(levels.csv)読み込み

  • 文字化け対策として、まず UTF-8 で読み込み、失敗した場合は Windows 環境向けに cp932 で再読み込みします。
  • これにより、OS や環境に依存せず安定してデータを取得できます。

#=============================================
# 各列ごとにユニーク値(非NA)を水準として抽出
#=============================================
factor_levels = {col: levels_df[col].dropna().unique().tolist() for col in levels_df.columns}
for col, lv in factor_levels.items():

#=============================================
# 因子数に応じて直交表インデックスを選択
#=============================================
num_factors = len(factor_levels)

if num_factors == 3:
    orthogonal_index = [
        [0, 0, 0],
        [0, 1, 1],
        [0, 2, 2],
        [1, 0, 1],
        [1, 1, 2],
        [1, 2, 0],
        [2, 0, 2],
        [2, 1, 0],
        [2, 2, 1],
    ]
elif num_factors == 4:
    orthogonal_index = [
        [0, 0, 0, 0], 
        [0, 1, 1, 1], 
        [0, 2, 2, 2], 
        [1, 0, 1, 2], 
        [1, 1, 2, 0], 
        [1, 2, 0, 1], 
        [2, 0, 2, 1], 
        [2, 1, 0, 2], 
        [2, 2, 1, 0],
    ]
else:
    raise ValueError(f"L9直交表は3因子または4因子のみ対応しています。入力因子数: {num_factors}")

2. 各列ごとにユニーク値(非NA)を水準として抽出

因子ごとの水準を抽出する

  • levels_df[col].dropna(): 欠損値(NA)を除外
  • .unique(): その列に含まれるユニークな値だけを取得
  • .tolist(): で Python のリストに変換
    これを辞書にまとめて、各列(因子)の水準リストを作っています。
  • 例:
factor_levels = {
   'A': [10, 20, 30],
   'B': ['赤', '青', '緑'],
   'C': [0, 1, 2]
}

こんな感じで、列ごとに「どの水準があるか」を簡単に確認できる形に変換しています。

出力例


3. L9 直交表のインデックス選択

  • num_factors = len(factor_levels) で因子の数を確認します。
  • 直交表(L9)は 3因子または4因子 に対応しているので、それぞれの組み合わせを手動でリスト化。
  • リストの数字は「水準番号」の組み合わせで、あとで実際の値に置き換える準備です。
  • 対応していない因子数が来た場合は ValueError を出して処理を止めます。
❓自動で作成できないの?
  • 完全に自動化すると、unique() の順序や辞書の順序によって
    毎回組み合わせが変わる可能性 があります
  • 直交表は 実験計画法で事前に定義された標準表 を使う方が安全
  • そのため、ここでは 固定の直交表 を使用しています

#=============================================
# L9のインデックスを水準値に変換して直交表作成
#=============================================
selected_factors = list(factor_levels.keys())
selected_levels = [factor_levels[f] for f in selected_factors]

data_OA = []
for row in orthogonal_index:
    entry = [selected_levels[i][row[i]] for i in range(num_factors)]
    data_OA.append(entry)

df_OA = pd.DataFrame(data_OA, columns=selected_factors)

#=============================================
# コンジョイント分析用に行列入れ替え
#=============================================
df_OA = df_OA.T

# =========================
# 保存
# =========================
df_OA.to_csv(save_name, index=True, encoding="utf-8-sig")

# =========================
# 出力フォルダを開く
# =========================
if sys.platform.startswith('win'):
    os.startfile(output_path)
elif sys.platform.startswith('darwin'):
    subprocess.run(['open', output_path])
else:
    subprocess.run(['xdg-open', output_path])

print("完了")

4. L9のインデックスを水準値に変換しながら直交表を作成

  • 事前に用意した L9 直交表のインデックス(0始まり)を使い、実際の水準名に置き換えます
  • 各行は 1回の実験条件の組み合わせ を表します
  • 列名は元の因子名を使うことで、どの列がどの因子か分かりやすくしています
  • 最終的にリストにまとめて DataFrame に変換します

💡 ポイント
L9表の数値インデックスを実際の水準名に置き換えて、直交表として使える形にします。

出力例


5. コンジョイント分析用に行列入れ替え

  • 作成した直交表を 転置 し、行と列を入れ替えます
  • この形式にすることで、コンジョイント分析で扱いやすくなります
  • 必要に応じて CSV に保存して再利用可能です

💡 ポイント
分析で使いやすい形式に整え、転置後も因子と水準の対応が保持されます。

出力例


6. 保存と出力フォルダを開く

  • OS に応じて出力フォルダを自動で開きます

📌まとめ

「どんな調査をしたいのか」「何を目的に分析するのか」をしっかり決めることが
とても大切です。

目的や調査内容がはっきりしていないと、
水準表の内容がブレてしまい、調査や分析の意味が薄れてしまいます。

だから、直交表を作る前に目的 をクリアにしましょう!

次の投稿では、コンジョイント分析を解説します。
https://zenn.dev/swatchp/articles/ed86b13dd4081e


参考リンクについて

GitHubリポジトリ
本記事で紹介したコードやサンプルデータはこちらで公開しています。

Discussion