🐍 3. Pythonでロジックツリーを実装
ここからは、Pythonを使ってロジックツリーとイシューツリーを実装し、データ分析に活用する方法を学びます。コードは段階的に解説しますので、1つずつ理解しながら進めましょう。
Step 1:ライブラリのインポートとデータ準備
まず、必要なライブラリをインポートし、分析用のサンプルデータを作成します。
※コードは横スクロールできます
# ============================================
# Step 1:ライブラリのインポートとデータ準備
# ============================================
# データ操作用ライブラリ
import numpy as np # 数値計算(配列操作、乱数生成など)
import pandas as pd # データフレーム操作(表形式データの処理)
# 可視化用ライブラリ
import matplotlib.pyplot as plt # グラフ描画の基本ライブラリ
# 日本語フォントの設定(文字化け防止)
# Mac/Windows/Linuxで使用可能なフォントを順に指定
plt.rcParams[‘font.sans-serif’] = [‘Arial Unicode MS’, ‘Yu Gothic’, ‘Hiragino Sans’]
plt.rcParams[‘axes.unicode_minus’] = False # マイナス記号の文字化け防止
# 乱数シードを固定(実行するたびに同じ結果を得るため)
np.random.seed(42)
print(“✓ ライブラリのインポート完了”)
print(“✓ 日本語フォント設定完了”)
print()
# ============================================
# サンプルデータ:ECサイトの売上データ
# ============================================
# 売上データを作成(商品カテゴリ × 販売チャネル × 顧客タイプ)
data = {
“カテゴリ”: [“家電”, “家電”, “家電”, “家電”,
“アパレル”, “アパレル”, “アパレル”, “アパレル”,
“食品”, “食品”, “食品”, “食品”],
“チャネル”: [“店舗”, “店舗”, “EC”, “EC”] * 3,
“顧客タイプ”: [“新規”, “既存”, “新規”, “既存”] * 3,
“売上_前年”: [1200, 1800, 800, 1000, # 家電
600, 900, 400, 500, # アパレル
300, 450, 200, 250], # 食品
“売上_今年”: [1000, 1700, 900, 1100, # 家電(店舗減、EC増)
500, 800, 500, 600, # アパレル(店舗減、EC増)
280, 420, 250, 300] # 食品(微減、EC増)
}
# DataFrameに変換
df = pd.DataFrame(data)
# 前年比を計算(今年の売上 ÷ 前年の売上 – 1)
df[“前年比”] = (df[“売上_今年”] / df[“売上_前年”] – 1) * 100
print(“【ECサイト売上データ(単位:万円)】”)
print(“=” * 70)
print(df.to_string(index=False))
print()
# 全体の売上を集計
total_last_year = df[“売上_前年”].sum()
total_this_year = df[“売上_今年”].sum()
total_growth = (total_this_year / total_last_year – 1) * 100
print(f”【全体サマリー】”)
print(f”前年売上合計: {total_last_year:,}万円”)
print(f”今年売上合計: {total_this_year:,}万円”)
print(f”前年比: {total_growth:+.1f}%”)
✓ ライブラリのインポート完了
✓ 日本語フォント設定完了
【ECサイト売上データ(単位:万円)】
======================================================================
カテゴリ チャネル 顧客タイプ 売上_前年 売上_今年 前年比
家電 店舗 新規 1200 1000 -16.67
家電 店舗 既存 1800 1700 -5.56
家電 EC 新規 800 900 12.50
家電 EC 既存 1000 1100 10.00
アパレル 店舗 新規 600 500 -16.67
アパレル 店舗 既存 900 800 -11.11
アパレル EC 新規 400 500 25.00
アパレル EC 既存 500 600 20.00
食品 店舗 新規 300 280 -6.67
食品 店舗 既存 450 420 -6.67
食品 EC 新規 200 250 25.00
食品 EC 既存 250 300 20.00
【全体サマリー】
前年売上合計: 8,400万円
今年売上合計: 8,350万円
前年比: -0.6%
📝 コード解説
・np.random.seed(42):乱数シードを固定することで、再実行しても同じ結果が得られます
・pd.DataFrame():辞書形式のデータをDataFrame(表形式)に変換
・df["前年比"]:新しい列を追加して計算結果を格納
・データは「カテゴリ × チャネル × 顧客タイプ」の3次元でMECEに分解されています
Step 2:What Tree(要素分解型)の実装
売上を階層的に分解して、全体の構造を可視化します。
# ============================================
# Step 2:What Tree(要素分解型)の実装
# ============================================
# 目的:売上を階層的に分解して全体像を把握する
print(“【What Tree:売上の要素分解】”)
print(“=” * 70)
print()
# —– レベル1:全体 —–
print(“■ レベル1:全体”)
print(f” 売上合計: {total_this_year:,}万円(前年比 {total_growth:+.1f}%)”)
print()
# —– レベル2:カテゴリ別に分解 —–
print(“■ レベル2:カテゴリ別分解”)
# groupby()でカテゴリごとに集計
# agg()で複数の集計を一度に実行
by_category = df.groupby(“カテゴリ”).agg({
“売上_前年”: “sum”, # 前年売上の合計
“売上_今年”: “sum” # 今年売上の合計
}).reset_index()
# 前年比と構成比を計算
by_category[“前年比”] = (by_category[“売上_今年”] / by_category[“売上_前年”] – 1) * 100
by_category[“構成比”] = by_category[“売上_今年”] / total_this_year * 100
# 売上が大きい順にソート
by_category = by_category.sort_values(“売上_今年”, ascending=False)
# 結果を表示(ツリー形式)
for _, row in by_category.iterrows():
print(f” ├── {row[‘カテゴリ’]}: {row[‘売上_今年’]:,.0f}万円”)
print(f” │ 構成比: {row[‘構成比’]:.1f}% 前年比: {row[‘前年比’]:+.1f}%”)
print()
# —– レベル3:カテゴリ×チャネルで分解 —–
print(“■ レベル3:カテゴリ × チャネル別分解”)
# 2つの軸でグループ化
by_cat_channel = df.groupby([“カテゴリ”, “チャネル”]).agg({
“売上_前年”: “sum”,
“売上_今年”: “sum”
}).reset_index()
by_cat_channel[“前年比”] = (by_cat_channel[“売上_今年”] / by_cat_channel[“売上_前年”] – 1) * 100
# カテゴリごとにツリー表示
for category in by_category[“カテゴリ”]:
cat_data = by_cat_channel[by_cat_channel[“カテゴリ”] == category]
cat_total = cat_data[“売上_今年”].sum()
print(f” {category} ({cat_total:,.0f}万円)”)
for _, row in cat_data.iterrows():
share = row[“売上_今年”] / cat_total * 100
print(f” ├── {row[‘チャネル’]}: {row[‘売上_今年’]:,.0f}万円”)
print(f” │ カテゴリ内構成比: {share:.1f}% 前年比: {row[‘前年比’]:+.1f}%”)
print()
# MECEチェック
level2_total = by_category[“売上_今年”].sum()
level3_total = by_cat_channel[“売上_今年”].sum()
print(“【MECEチェック】”)
print(f” 全体: {total_this_year:,}万円”)
print(f” レベル2合計: {level2_total:,.0f}万円”)
print(f” レベル3合計: {level3_total:,.0f}万円”)
if abs(total_this_year – level2_total) < 1 and abs(total_this_year - level3_total) < 1:
print(" ✓ MECE検証OK:すべてのレベルで合計が一致")
else:
print(" ✗ MECE違反:合計が一致しません")
【What Tree:売上の要素分解】
======================================================================
■ レベル1:全体
売上合計: 8,350万円(前年比 -0.6%)
■ レベル2:カテゴリ別分解
├── 家電: 4,700万円
│ 構成比: 56.3% 前年比: -2.1%
├── アパレル: 2,400万円
│ 構成比: 28.7% 前年比: +0.0%
├── 食品: 1,250万円
│ 構成比: 15.0% 前年比: +4.2%
■ レベル3:カテゴリ × チャネル別分解
家電 (4,700万円)
├── 店舗: 2,700万円
│ カテゴリ内構成比: 57.4% 前年比: -10.0%
├── EC: 2,000万円
│ カテゴリ内構成比: 42.6% 前年比: +11.1%
アパレル (2,400万円)
├── 店舗: 1,300万円
│ カテゴリ内構成比: 54.2% 前年比: -13.3%
├── EC: 1,100万円
│ カテゴリ内構成比: 45.8% 前年比: +22.2%
食品 (1,250万円)
├── 店舗: 700万円
│ カテゴリ内構成比: 56.0% 前年比: -6.7%
├── EC: 550万円
│ カテゴリ内構成比: 44.0% 前年比: +22.2%
【MECEチェック】
全体: 8,350万円
レベル2合計: 4,700万円 → 8,350万円
レベル3合計: 8,350万円
✓ MECE検証OK:すべてのレベルで合計が一致
💡 What Treeから得られる洞察
・店舗チャネルが全カテゴリで減少(家電▲10%、アパレル▲13.3%、食品▲6.7%)
・ECチャネルが全カテゴリで増加(家電+11.1%、アパレル+22.2%、食品+22.2%)
・店舗からECへのシフトが進行中
→ 次はWhy Treeで「なぜ店舗が減少しているか」を分析
Step 3:Why Tree(原因追求型)の実装
店舗売上減少の原因を、データに基づいて深掘りします。
# ============================================
# Step 3:Why Tree(原因追求型)の実装
# ============================================
# 目的:店舗売上減少の原因を特定する
print(“【Why Tree:店舗売上減少の原因分析】”)
print(“=” * 70)
print()
# —– 店舗データを抽出 —–
store_data = df[df[“チャネル”] == “店舗”].copy()
# 店舗全体の売上変化
store_last = store_data[“売上_前年”].sum()
store_this = store_data[“売上_今年”].sum()
store_change = store_this – store_last
store_growth = (store_this / store_last – 1) * 100
print(“■ メインイシュー:店舗売上が減少している”)
print(f” 店舗売上: {store_last:,}万円 → {store_this:,}万円”)
print(f” 変化額: {store_change:+,}万円(前年比 {store_growth:+.1f}%)”)
print()
# —– レベル1:顧客タイプ別の分解 —–
print(“■ 原因レベル1:どの顧客タイプが減少?”)
by_customer = store_data.groupby(“顧客タイプ”).agg({
“売上_前年”: “sum”,
“売上_今年”: “sum”
}).reset_index()
by_customer[“変化額”] = by_customer[“売上_今年”] – by_customer[“売上_前年”]
by_customer[“前年比”] = (by_customer[“売上_今年”] / by_customer[“売上_前年”] – 1) * 100
by_customer[“寄与度”] = by_customer[“変化額”] / store_last * 100
for _, row in by_customer.iterrows():
print(f” ├── {row[‘顧客タイプ’]}顧客: {row[‘変化額’]:+,.0f}万円”)
print(f” │ 前年比: {row[‘前年比’]:+.1f}% 寄与度: {row[‘寄与度’]:+.1f}%”)
print()
# —– レベル2:カテゴリ×顧客タイプの詳細分解 —–
print(“■ 原因レベル2:カテゴリ別の詳細”)
by_cat_cust = store_data.groupby([“カテゴリ”, “顧客タイプ”]).agg({
“売上_前年”: “sum”,
“売上_今年”: “sum”
}).reset_index()
by_cat_cust[“変化額”] = by_cat_cust[“売上_今年”] – by_cat_cust[“売上_前年”]
by_cat_cust[“前年比”] = (by_cat_cust[“売上_今年”] / by_cat_cust[“売上_前年”] – 1) * 100
# 変化額が大きい順にソート(減少が大きい順)
by_cat_cust_sorted = by_cat_cust.sort_values(“変化額”)
print(” 【減少が大きい順】”)
for _, row in by_cat_cust_sorted.iterrows():
impact = “★” if row[“変化額”] < -100 else ""
print(f" ├── {row['カテゴリ']}×{row['顧客タイプ']}: {row['変化額']:+,.0f}万円 ({row['前年比']:+.1f}%) {impact}")
print()
# ----- 原因の要約 -----
print("■ Why Treeの結論")
print("=" * 70)
# 最大の減少要因を特定
worst = by_cat_cust_sorted.iloc[0]
print(f" 【根本原因の特定】")
print(f" 最大の減少: {worst['カテゴリ']}×{worst['顧客タイプ']} ({worst['変化額']:+,.0f}万円)")
print()
print(" 【原因の構造】")
print(f" 店舗売上減少 {store_change:+,}万円")
print(f" └── 新規顧客の減少が主因(寄与度大)")
print(f" ├── 家電×新規: ▲200万円 ★最大")
print(f" ├── アパレル×新規: ▲100万円")
print(f" └── 食品×新規: ▲20万円")
print()
print(" → 「なぜ新規顧客が店舗に来なくなったか」をさらに深掘りする必要あり")
【Why Tree:店舗売上減少の原因分析】
======================================================================
■ メインイシュー:店舗売上が減少している
店舗売上: 5,250万円 → 4,700万円
変化額: -550万円(前年比 -10.5%)
■ 原因レベル1:どの顧客タイプが減少?
├── 既存顧客: -230万円
│ 前年比: -7.3% 寄与度: -4.4%
├── 新規顧客: -320万円
│ 前年比: -15.2% 寄与度: -6.1%
■ 原因レベル2:カテゴリ別の詳細
【減少が大きい順】
├── 家電×新規: -200万円 (-16.7%) ★
├── アパレル×新規: -100万円 (-16.7%)
├── 家電×既存: -100万円 (-5.6%)
├── アパレル×既存: -100万円 (-11.1%)
├── 食品×既存: -30万円 (-6.7%)
├── 食品×新規: -20万円 (-6.7%)
■ Why Treeの結論
======================================================================
【根本原因の特定】
最大の減少: 家電×新規 (-200万円)
【原因の構造】
店舗売上減少 -550万円
└── 新規顧客の減少が主因(寄与度大)
├── 家電×新規: ▲200万円 ★最大
├── アパレル×新規: ▲100万円
└── 食品×新規: ▲20万円
→ 「なぜ新規顧客が店舗に来なくなったか」をさらに深掘りする必要あり
📝 寄与度の計算方法
寄与度 = 各要素の変化額 ÷ 全体の前年売上 × 100
例:新規顧客の寄与度
= ▲320万円 ÷ 5,250万円 × 100 = ▲6.1%
寄与度を使うことで、「全体の減少のうち、どの要素がどれだけ影響しているか」が分かります。
Step 4:How Tree(問題解決型)の実装
Why Treeで特定した原因に対する解決策を、データに基づいて立案します。
# ============================================
# Step 4:How Tree(問題解決型)の実装
# ============================================
# 目的:店舗売上回復のための施策を立案する
print(“【How Tree:店舗売上回復の施策立案】”)
print(“=” * 70)
print()
# —– 目標設定 —–
# 前年水準への回復を目標
target_increase = abs(store_change) # 550万円の回復が必要
print(“■ 目標:店舗売上を前年水準に回復”)
print(f” 必要な売上増: +{target_increase:,}万円”)
print()
# —– 施策案の定義 —–
# 各施策の効果を試算(仮説ベース)
strategies = [
{
“施策”: “店舗限定セールの実施”,
“対象”: “全カテゴリ×新規”,
“投資”: 50, # 万円
“期待効果”: 150, # 万円
“実現性”: “高”,
“期間”: “1ヶ月”
},
{
“施策”: “店舗スタッフの接客強化”,
“対象”: “家電×新規”,
“投資”: 100,
“期待効果”: 200,
“実現性”: “中”,
“期間”: “3ヶ月”
},
{
“施策”: “店舗とECのポイント共通化”,
“対象”: “全カテゴリ×既存”,
“投資”: 80,
“期待効果”: 120,
“実現性”: “高”,
“期間”: “2ヶ月”
},
{
“施策”: “店舗限定商品の開発”,
“対象”: “アパレル×新規”,
“投資”: 200,
“期待効果”: 180,
“実現性”: “低”,
“期間”: “6ヶ月”
}
]
# DataFrameに変換して分析
df_strategies = pd.DataFrame(strategies)
# ROI(投資対効果)を計算
df_strategies[“ROI”] = (df_strategies[“期待効果”] – df_strategies[“投資”]) / df_strategies[“投資”] * 100
# ROIが高い順にソート
df_strategies = df_strategies.sort_values(“ROI”, ascending=False)
print(“■ 施策案(ROI順)”)
print(“-” * 70)
total_effect = 0
total_invest = 0
selected = []
for i, (_, row) in enumerate(df_strategies.iterrows(), 1):
roi_star = “★” if row[“ROI”] >= 100 else “”
print(f” {i}. {row[‘施策’]}”)
print(f” 対象: {row[‘対象’]}”)
print(f” 投資: {row[‘投資’]:,}万円 → 期待効果: {row[‘期待効果’]:,}万円”)
print(f” ROI: {row[‘ROI’]:+.0f}% 実現性: {row[‘実現性’]} 期間: {row[‘期間’]} {roi_star}”)
print()
# ROI 100%以上かつ実現性が中以上を選択
if row[“ROI”] >= 100 and row[“実現性”] in [“高”, “中”]:
total_effect += row[“期待効果”]
total_invest += row[“投資”]
selected.append(row[“施策”])
# —– 施策の組み合わせ評価 —–
print(“■ 推奨施策の組み合わせ”)
print(“=” * 70)
print(” 【選定基準】”)
print(” ・ROI 100%以上”)
print(” ・実現性が「高」または「中」”)
print()
print(” 【選定された施策】”)
for s in selected:
print(f” ✓ {s}”)
print()
print(” 【期待される効果】”)
print(f” 投資合計: {total_invest:,}万円”)
print(f” 期待効果合計: {total_effect:,}万円”)
print(f” 目標達成率: {total_effect / target_increase * 100:.1f}%”)
print()
# 目標達成判定
if total_effect >= target_increase:
print(f” ✓ 目標(+{target_increase:,}万円)を達成見込み!”)
else:
gap = target_increase – total_effect
print(f” △ 目標まであと {gap:,}万円の施策が必要”)
【How Tree:店舗売上回復の施策立案】
======================================================================
■ 目標:店舗売上を前年水準に回復
必要な売上増: +550万円
■ 施策案(ROI順)
———————————————————————-
1. 店舗限定セールの実施
対象: 全カテゴリ×新規
投資: 50万円 → 期待効果: 150万円
ROI: +200% 実現性: 高 期間: 1ヶ月 ★
2. 店舗スタッフの接客強化
対象: 家電×新規
投資: 100万円 → 期待効果: 200万円
ROI: +100% 実現性: 中 期間: 3ヶ月 ★
3. 店舗とECのポイント共通化
対象: 全カテゴリ×既存
投資: 80万円 → 期待効果: 120万円
ROI: +50% 実現性: 高 期間: 2ヶ月
4. 店舗限定商品の開発
対象: アパレル×新規
投資: 200万円 → 期待効果: 180万円
ROI: -10% 実現性: 低 期間: 6ヶ月
■ 推奨施策の組み合わせ
======================================================================
【選定基準】
・ROI 100%以上
・実現性が「高」または「中」
【選定された施策】
✓ 店舗限定セールの実施
✓ 店舗スタッフの接客強化
【期待される効果】
投資合計: 150万円
期待効果合計: 350万円
目標達成率: 63.6%
△ 目標まであと 200万円の施策が必要
Step 5:イシューツリーの実装
「問い」の形式で課題を分解し、仮説検証の計画を立てます。
# ============================================
# Step 5:イシューツリーの実装
# ============================================
# 目的:問いを分解して仮説検証計画を立てる
print(“【イシューツリー:新規顧客減少の仮説検証】”)
print(“=” * 70)
print()
# イシューツリーを辞書で定義
# 各イシューには「問い」「仮説」「検証方法」「判定基準」を設定
issue_tree = {
“メインイシュー”: “なぜ店舗の新規顧客が減少しているか?”,
“サブイシュー”: [
{
“問い”: “集客が減っているか?”,
“仮説”: “来店客数が前年比▲20%減少”,
“検証方法”: “店舗の入店カウンターデータ”,
“判定基準”: “前年比▲10%以上で問題あり”,
“検証結果”: “▲18%(仮説支持)”,
“詳細イシュー”: [
{
“問い”: “認知が低下しているか?”,
“仮説”: “広告露出が前年比▲30%”,
“検証方法”: “広告出稿データ”,
“判定基準”: “前年比▲15%以上で問題あり”,
“検証結果”: “▲25%(仮説支持)”
},
{
“問い”: “競合に流出しているか?”,
“仮説”: “近隣競合の売上が+15%”,
“検証方法”: “競合調査データ”,
“判定基準”: “競合+10%以上で影響大”,
“検証結果”: “+12%(仮説支持)”
}
]
},
{
“問い”: “購買率が下がっているか?”,
“仮説”: “来店からの購買率が前年比▲5%”,
“検証方法”: “POS×入店データ”,
“判定基準”: “前年比▲3%以上で問題あり”,
“検証結果”: “▲2%(仮説棄却)”,
“詳細イシュー”: []
},
{
“問い”: “客単価が下がっているか?”,
“仮説”: “新規顧客の客単価が▲10%”,
“検証方法”: “POSデータ(新規顧客)”,
“判定基準”: “前年比▲5%以上で問題あり”,
“検証結果”: “▲3%(仮説棄却)”,
“詳細イシュー”: []
}
]
}
# イシューツリーを表示
print(f”■ {issue_tree[‘メインイシュー’]}”)
print()
for i, sub in enumerate(issue_tree[“サブイシュー”], 1):
# 検証結果から支持/棄却を判定
is_supported = “支持” in sub[“検証結果”]
status = “★要対策” if is_supported else “→問題なし”
print(f” {i}. {sub[‘問い’]}”)
print(f” 仮説: {sub[‘仮説’]}”)
print(f” 検証: {sub[‘検証方法’]}”)
print(f” 結果: {sub[‘検証結果’]} {status}”)
# 詳細イシューがある場合
if sub[“詳細イシュー”]:
for j, detail in enumerate(sub[“詳細イシュー”], 1):
detail_supported = “支持” in detail[“検証結果”]
detail_status = “★★根本原因” if detail_supported else “”
print(f” {i}-{j}. {detail[‘問い’]}”)
print(f” 仮説: {detail[‘仮説’]}”)
print(f” 結果: {detail[‘検証結果’]} {detail_status}”)
print()
# —– 検証結果のサマリー —–
print(“■ イシューツリーの結論”)
print(“=” * 70)
print()
print(” 【根本原因として特定】”)
print(” 1. 広告露出の減少(▲25%)→ 認知低下”)
print(” 2. 競合への流出(競合+12%)→ 相対的魅力度低下”)
print()
print(” 【問題なしと判定】”)
print(” ・購買率: ▲2%(基準の▲3%以内)”)
print(” ・客単価: ▲3%(基準の▲5%以内)”)
print()
print(” 【次のアクション】”)
print(” → 広告投資の回復と、競合差別化施策の検討”)
【イシューツリー:新規顧客減少の仮説検証】
======================================================================
■ なぜ店舗の新規顧客が減少しているか?
1. 集客が減っているか?
仮説: 来店客数が前年比▲20%減少
検証: 店舗の入店カウンターデータ
結果: ▲18%(仮説支持) ★要対策
1-1. 認知が低下しているか?
仮説: 広告露出が前年比▲30%
結果: ▲25%(仮説支持) ★★根本原因
1-2. 競合に流出しているか?
仮説: 近隣競合の売上が+15%
結果: +12%(仮説支持) ★★根本原因
2. 購買率が下がっているか?
仮説: 来店からの購買率が前年比▲5%
検証: POS×入店データ
結果: ▲2%(仮説棄却) →問題なし
3. 客単価が下がっているか?
仮説: 新規顧客の客単価が▲10%
検証: POSデータ(新規顧客)
結果: ▲3%(仮説棄却) →問題なし
■ イシューツリーの結論
======================================================================
【根本原因として特定】
1. 広告露出の減少(▲25%)→ 認知低下
2. 競合への流出(競合+12%)→ 相対的魅力度低下
【問題なしと判定】
・購買率: ▲2%(基準の▲3%以内)
・客単価: ▲3%(基準の▲5%以内)
【次のアクション】
→ 広告投資の回復と、競合差別化施策の検討
📌 イシューツリーのポイント
・各「問い」に対して仮説と判定基準を事前に設定
・データで検証し、仮説が支持されるか棄却されるかを判定
・棄却された仮説は「問題なし」として除外し、支持された仮説を深掘り
・これにより、効率的に根本原因にたどり着ける
Step 6:分析結果の可視化
ここまでの分析結果を、グラフで分かりやすく可視化します。
# ============================================
# Step 6:分析結果の可視化
# ============================================
# 目的:分析結果を4つのグラフで可視化する
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# ============================================
# グラフ1:チャネル別売上の前年比較(What Tree)
# ============================================
ax1 = axes[0, 0]
# チャネル別にデータを集計
channel_data = df.groupby(“チャネル”).agg({
“売上_前年”: “sum”,
“売上_今年”: “sum”
}).reset_index()
x = np.arange(len(channel_data)) # X軸の位置
width = 0.35 # 棒の幅
# 前年と今年の棒グラフを並べて表示
bars1 = ax1.bar(x – width/2, channel_data[“売上_前年”], width,
label=”前年”, color=”#90caf9″, alpha=0.8)
bars2 = ax1.bar(x + width/2, channel_data[“売上_今年”], width,
label=”今年”, color=”#1976d2″, alpha=0.8)
ax1.set_xlabel(“チャネル”, fontsize=11)
ax1.set_ylabel(“売上(万円)”, fontsize=11)
ax1.set_title(“【What Tree】チャネル別売上の前年比較”, fontsize=13, fontweight=”bold”)
ax1.set_xticks(x)
ax1.set_xticklabels(channel_data[“チャネル”])
ax1.legend()
ax1.grid(axis=”y”, alpha=0.3)
# 前年比を棒の上に表示
for i, (last, this) in enumerate(zip(channel_data[“売上_前年”], channel_data[“売上_今年”])):
growth = (this / last – 1) * 100
color = “#d32f2f” if growth < 0 else "#388e3c"
ax1.annotate(f"{growth:+.1f}%", xy=(i + width/2, this),
ha="center", va="bottom", fontsize=10, color=color, fontweight="bold")
# ============================================
# グラフ2:顧客タイプ別の変化(Why Tree)
# ============================================
ax2 = axes[0, 1]
# 顧客タイプ別にデータを集計
customer_data = df.groupby("顧客タイプ").agg({
"売上_前年": "sum",
"売上_今年": "sum"
}).reset_index()
customer_data["変化額"] = customer_data["売上_今年"] - customer_data["売上_前年"]
# 変化額を水平棒グラフで表示
colors = ["#d32f2f" if x < 0 else "#388e3c" for x in customer_data["変化額"]]
bars = ax2.barh(customer_data["顧客タイプ"], customer_data["変化額"], color=colors, alpha=0.8)
ax2.set_xlabel("売上変化(万円)", fontsize=11)
ax2.set_title("【Why Tree】顧客タイプ別の売上変化", fontsize=13, fontweight="bold")
ax2.axvline(x=0, color="black", linewidth=0.5) # ゼロ線
ax2.grid(axis="x", alpha=0.3)
# 値を表示
for bar, val in zip(bars, customer_data["変化額"]):
x_pos = val + 10 if val >= 0 else val – 10
ha = “left” if val >= 0 else “right”
ax2.text(x_pos, bar.get_y() + bar.get_height()/2,
f”{val:+,.0f}万円”, ha=ha, va=”center”, fontsize=10, fontweight=”bold”)
# ============================================
# グラフ3:カテゴリ×チャネル別の前年比(ヒートマップ風)
# ============================================
ax3 = axes[1, 0]
# ピボットテーブルを作成
pivot = df.pivot_table(index=”カテゴリ”, columns=”チャネル”,
values=”前年比”, aggfunc=”mean”)
# カラーマップで可視化(赤:減少、緑:増加)
im = ax3.imshow(pivot.values, cmap=”RdYlGn”, aspect=”auto”, vmin=-20, vmax=25)
# ラベル設定
ax3.set_xticks(np.arange(len(pivot.columns)))
ax3.set_yticks(np.arange(len(pivot.index)))
ax3.set_xticklabels(pivot.columns)
ax3.set_yticklabels(pivot.index)
ax3.set_title(“【分析】カテゴリ×チャネル別 前年比(%)”, fontsize=13, fontweight=”bold”)
# 各セルに値を表示
for i in range(len(pivot.index)):
for j in range(len(pivot.columns)):
val = pivot.values[i, j]
color = “white” if abs(val) > 10 else “black”
ax3.text(j, i, f”{val:+.1f}%”, ha=”center”, va=”center”,
color=color, fontsize=12, fontweight=”bold”)
# カラーバー
plt.colorbar(im, ax=ax3, label=”前年比(%)”)
# ============================================
# グラフ4:施策のROI比較(How Tree)
# ============================================
ax4 = axes[1, 1]
# 施策データ
strategy_names = [“店舗限定セール”, “接客強化”, “ポイント共通化”, “限定商品開発”]
strategy_roi = [200, 100, 50, -10]
strategy_effect = [150, 200, 120, 180]
# ROIを棒グラフで表示
colors = [“#388e3c” if x >= 100 else “#ff9800” if x >= 0 else “#d32f2f” for x in strategy_roi]
bars = ax4.bar(strategy_names, strategy_roi, color=colors, alpha=0.8)
ax4.set_ylabel(“ROI(%)”, fontsize=11)
ax4.set_title(“【How Tree】施策別ROI比較”, fontsize=13, fontweight=”bold”)
ax4.axhline(y=100, color=”green”, linestyle=”–“, linewidth=2, label=”ROI 100%ライン”)
ax4.axhline(y=0, color=”black”, linewidth=0.5)
ax4.legend()
ax4.grid(axis=”y”, alpha=0.3)
# ROIの値を表示
for bar, roi, effect in zip(bars, strategy_roi, strategy_effect):
height = bar.get_height()
y_pos = height + 5 if height >= 0 else height – 15
ax4.text(bar.get_x() + bar.get_width()/2., y_pos,
f”{roi:+}%\n({effect}万円)”, ha=”center”, va=”bottom” if height >= 0 else “top”,
fontsize=9, fontweight=”bold”)
# X軸ラベルを斜めに
ax4.set_xticklabels(strategy_names, rotation=15, ha=”right”)
plt.tight_layout()
plt.savefig(“logic_tree_analysis.png”, dpi=150, bbox_inches=”tight”)
plt.show()
print(“✓ グラフを保存しました: logic_tree_analysis.png”)
💡 可視化のポイント
・グラフ1(What Tree):構造を把握(どこが減っているか)
・グラフ2(Why Tree):原因を特定(新規/既存どちらが問題か)
・グラフ3(詳細分析):カテゴリ×チャネルのクロス分析
・グラフ4(How Tree):施策の優先順位付け(ROI比較)