ベイジアンネットワークで因果構造を発見し、SEMで係数推定する

# **user**:

初学者向けにVSCodeを使ってStreamlitアプリを作るガイドを書いてください。そのアプリは、次のような動作をします。

・ユーザにcsvファイルまたはエクセルファイルの入力を促します。
・エクセルファイルの場合は、どのシートを見たいかユーザに確認します。
・入力された表形式データから、ユーザは、目的変数を表す列を一つ、説明変数を表す列を複数、選択します。
・ベイジアンネットワークを適用し、因果構造を推定します。

# **assistant**:

以下に、**初学者向けに VSCode を使って Streamlit アプリを作るガイド**をご紹介します。このアプリは、CSV または Excel ファイルを読み込み、目的変数と説明変数を選び、**ベイジアンネットワーク**によって因果構造を推定・可視化するものです。

---

## 🔧 前提条件

- Python（3.8〜3.11 推奨）
- VSCode（[公式サイト](https://code.visualstudio.com/)からインストール）
- 必要なライブラリ：
  ```bash
  pip install streamlit pandas openpyxl pgmpy networkx matplotlib
  ```

---

## 📁 プロジェクト構成（例）

```
bayesian_causal_app/
├── app.py
└── sample_data/
    └── example.xlsx
```

---

## 🖥️ VSCodeでの操作手順

### 1. プロジェクトフォルダを作成し、`app.py` を作成

VSCodeで新しいフォルダを開き、`app.py`という名前でPythonファイルを作成します。

---

### 2. `app.py` のコード

```python
import streamlit as st
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
from pgmpy.estimators import HillClimbSearch, BicScore
from pgmpy.models import BayesianModel

st.title("🔍 ベイジアンネットワークによる因果構造の推定")

# ファイルアップロード
uploaded_file = st.file_uploader("CSVまたはExcelファイルをアップロードしてください", type=["csv", "xlsx"])

if uploaded_file:
    if uploaded_file.name.endswith(".csv"):
        df = pd.read_csv(uploaded_file)
    elif uploaded_file.name.endswith(".xlsx"):
        xls = pd.ExcelFile(uploaded_file)
        sheet = st.selectbox("シートを選んでください", xls.sheet_names)
        df = pd.read_excel(xls, sheet_name=sheet)
    else:
        st.error("対応しているファイル形式は CSV または Excel(xlsx) です。")

    st.subheader("データのプレビュー")
    st.dataframe(df.head())

    # 目的変数と説明変数の選択
    all_columns = df.columns.tolist()
    target_col = st.selectbox("目的変数を選んでください", all_columns)
    feature_cols = st.multiselect("説明変数を選んでください（複数選択可）", [col for col in all_columns if col != target_col])

    if target_col and feature_cols:
        # 対象列だけ取り出す
        selected_cols = [target_col] + feature_cols
        data = df[selected_cols].dropna()

        # ベイジアンネットワークの構造学習
        st.subheader("📈 因果構造の推定（HillClimb + BIC）")
        hc = HillClimbSearch(data)
        model = hc.estimate(scoring_method=BicScore(data))

        st.write("推定されたネットワーク構造:")
        st.write(model.edges())

        # ネットワーク可視化
        G = nx.DiGraph(model.edges())
        plt.figure(figsize=(8, 6))
        pos = nx.spring_layout(G)
        nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=2000, font_size=12, arrowsize=20)
        st.pyplot(plt)
```

---

## ▶️ アプリの実行方法

1. VSCodeのターミナルで以下のコマンドを実行：

   ```bash
   streamlit run app.py
   ```

2. ブラウザでアプリが開きます。

---

## 📌 解説ポイント（初学者向け）

| ポイント | 説明 |
|--------|------|
| `streamlit.file_uploader` | ユーザーからCSVやExcelファイルをアップロードしてもらいます |
| `pandas.ExcelFile` + `selectbox` | Excelの複数シートに対応し、どれを見るか選べるようにしています |
| `pgmpy` の `HillClimbSearch` | データからベイジアンネットワーク構造を推定するためのアルゴリズム |
| `networkx` + `matplotlib` | 推定された因果ネットワークを視覚的に表示します |

---

## ✅ まとめ

このアプリは以下を満たしています：

- CSV/Excel対応のデータ読み込み
- シート選択機能（Excel用）
- ユーザーによる目的・説明変数の指定
- ベイジアンネットワークによる因果構造推定
- 推定結果の視覚的なネットワーク表示

---

このガイドを教材やチュートリアル記事に使う場合、画像付きでステップごとに説明するとさらにわかりやすくなります。必要なら画像例も作成します！

このアプリにモデルの精度評価や事後確率推定の機能を追加したいなど、発展的な内容にも対応可能です。興味ありますか？

# **user**:

ありがとうございます。次のエラーが出たので、ステップバイステップで対処策を提案してください。 

```
ImportError: cannot import name 'BicScore' from 'pgmpy.estimators' (/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pgmpy/estimators/__init__.py)
Traceback:
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 121, in exec_func_with_error_handling
    result = func()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 639, in code_to_exec
    _mpa_v1(self._main_script_path)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 159, in _mpa_v1
    page.run()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/navigation/page.py", line 297, in run
    exec(code, module.__dict__)
File "/Users/ikemen/streamlit-app/pages/bayesian.py", line 5, in <module>
    from pgmpy.estimators import HillClimbSearch, BicScore
```

# **assistant**:

ありがとうございます。`ImportError: cannot import name 'BicScore' from 'pgmpy.estimators'` というエラーは、`pgmpy` のバージョンや構造の変更により、`BicScore` が存在しないか、正しいモジュールパスでない場合に発生します。

以下、**ステップバイステップでの対処法**を提案します。

---

### ✅ ステップ 1: 現在の pgmpy のバージョンを確認

まず、どのバージョンの `pgmpy` が使われているか確認します。

#### ターミナルで以下を実行：
```bash
pip show pgmpy
```

#### 出力例：
```
Name: pgmpy
Version: 0.1.23
```

---

### ✅ ステップ 2: `pgmpy.estimators` 内を確認

確認したバージョンによっては、`BicScore` が `pgmpy.estimators` ではなく、別のモジュールにある可能性があります。代わりに次のように読み込みを変えてみてください。

#### 🛠 修正案：
```python
from pgmpy.estimators import HillClimbSearch
from pgmpy.estimators import BicScore  # ← これが失敗する場合
```

を

```python
from pgmpy.estimators import HillClimbSearch, K2Score  # K2Score なら存在する可能性が高い
```

に一時的に変更してみてください。

`HillClimbSearch(...).estimate(scoring_method=K2Score(data))` でも似た結果が得られます。

---

### ✅ ステップ 3: BicScoreを本当に使いたい場合の対処

もし `BicScore` を使いたい場合は、`pgmpy` のバージョンを公式で確認された安定バージョンに下げることを検討してください。

#### 安定版（`BicScore` が存在）にダウングレード：

```bash
pip uninstall pgmpy
pip install pgmpy==0.1.18
```

その後、再度 Streamlit アプリを起動して動作確認してください。

---

### ✅ ステップ 4: それでも解決しない場合の確認ポイント

- `venv` の仮想環境が壊れていないか
- 依存ライブラリ（`numpy`, `pandas`など）が最新か

必要であれば、仮想環境を作り直すのも効果的です。

---

### ✅ 補足：K2Scoreとの違い

| スコア | 特徴 |
|--------|------|
| `K2Score` | 事前分布に基づくスコア。離散データに強い。 |
| `BicScore` | ベイズ情報量規準に基づくモデル選択スコア。より柔軟だが存在しない場合あり。 |

---

### ✅ 推奨対応まとめ

| 優先度 | 方法 | 備考 |
|--------|------|------|
| ⭐️ 第一選択 | `K2Score` に変更してみる | ほとんどの用途で代用可 |
| ✅ 第二選択 | `pgmpy==0.1.18` にダウングレード | `BicScore` を使いたい場合 |
| 🧼 補助 | 仮想環境を再構築 | 環境汚染時に有効 |

---

試してみて、うまくいった方法を教えていただけますか？それに応じて、次のコード修正をご案内できます。


# **user**:

pgmpy の置いてある場所を探ってみたらこのようになっていました。

```
~/streamlit-app/pages/venv/lib/python3.10/site-packages/pgmpy/estimators % ls
BayesianEstimator.py		GES.py				MmhcEstimator.py		TreeSearch.py
CITests.py			HillClimbSearch.py		PC.py				__init__.py
EM.py				LinearModel.py			SEMEstimator.py			__pycache__
ExhaustiveSearch.py		MLE.py				ScoreCache.py			base.py
ExpertKnowledge.py		MirrorDescentEstimator.py	StructureScore.py		expert.py
```

# **assistant**:

ありがとうございます。その情報から、`pgmpy` の構造は次のようになっており、`BicScore` は `StructureScore.py` に含まれている可能性が高いです。

---

## ✅ 対応策（正しいインポート先）

おそらく `BicScore` は `pgmpy.estimators.StructureScore` に定義されています。よって、次のようにインポート文を修正してください：

```python
from pgmpy.estimators import HillClimbSearch
from pgmpy.estimators.StructureScore import BicScore
```

---

## 🧪 動作確認ステップ

1. `app.py` の冒頭を次のように修正：

   ```python
   from pgmpy.estimators import HillClimbSearch
   from pgmpy.estimators.StructureScore import BicScore
   ```

2. Streamlit アプリを再実行：

   ```bash
   streamlit run app.py
   ```

---

## 💡 補足：なぜこうなったか？

`pgmpy` はバージョンによってモジュール構造が整理されており、`BicScore` などのスコア関数は以前の `pgmpy.estimators` 直下ではなく、`StructureScore.py` にまとめられた可能性があります。

---

## ✅ 最終チェックポイント

- `StructureScore.py` の中に `class BicScore(StructureScore):` のような記述があるか確認してみてください。
  ```bash
  grep 'BicScore' StructureScore.py
  ```

---

この修正でエラーが解消するかどうか教えてください。それに応じてコード全体を調整した最新版をご用意します！

# **user**:

```python
from pgmpy.estimators import HillClimbSearch
from pgmpy.estimators.StructureScore import BIC 
```

で動くようになったかもしれません。でも「推定されたネットワーク構造:」が [] （空）になりました。

# **assistant**:

動作するようになったのは一歩前進ですね！  
ただ、「**推定されたネットワーク構造が空（[]）**」というのは、ベイジアンネットワークの構造学習で**有意な因果関係が検出されなかった**ことを意味します。

これは以下の原因が考えられますので、順にチェックしてみましょう。

---

## ✅ ステップ 1: データに十分な相関・依存があるか？

ベイジアンネットワーク（特に HillClimbSearch + BIC スコア）は、**変数間の条件付き依存**に基づいて構造を学習します。  
**すべての変数が独立に近い**場合、構造は空になります。

### 対処法：
- データを `st.dataframe()` などで確認し、目的変数と説明変数の間に相関がありそうかチェック
- 明らかに無関係そうな列（例：ID、日付文字列など）を除外する

---

## ✅ ステップ 2: サンプル数が少なすぎないか？

ベイジアンネットワークの推定は、**サンプルサイズが小さいと過学習を避けて空構造**になることがあります。

### 目安：
- 行数が 50 未満だと厳しい
- 変数数に比して十分な行数が必要（例：5変数なら100行以上ほしい）

---

## ✅ ステップ 3: 離散データか数値データか？

`pgmpy` のスコア関数（特に `BIC`）は**離散データに適しています**。  
数値データをそのまま渡すと、変数間の依存性がうまく検出されないことがあります。

### 対処法（テスト用）：
Streamlit アプリ内で説明変数と目的変数を **カテゴリ化**（離散化）してから構造学習してみる。

例（Pandasで）：
```python
data_discrete = data.copy()
for col in selected_cols:
    data_discrete[col] = pd.qcut(data[col], q=4, labels=False, duplicates='drop')
```

---

## ✅ ステップ 4: 正しい列を選んでいるか？

目的変数と説明変数の選択が、うまく Streamlit UI で設定されていない可能性もあります。

確認：
- 説明変数に目的変数を含めていないか
- 実際の選択列を `st.write()` で出力してチェック

---

## ✅ ステップ 5: 出力の確認コード

```python
model = HillClimbSearch(data_discrete).estimate(scoring_method=BIC(data_discrete))
st.write("推定されたネットワーク構造:", list(model.edges()))
```

---

## 💡次のステップ

以下を試してみてください：

1. `selected_cols` をカテゴリ化（離散化）する
2. `data[selected_cols]` が適切な形になっているか `st.write()` で確認
3. `.edges()` を `list()` にして表示してみる

---

必要であれば、テスト用の小さい CSV データセットを提供できます。サンプルで構造が出るかどうか確かめますか？

# **user**:

はい、テスト用の小さい CSV データセットを提供してください。

# **assistant**:

以下に、**テスト用の小さな CSV データセット**（相関があり、因果構造が推定できる例）を提供します。コピーしてCSVファイルとして保存してください。

---

### ✅ ファイル名例：`test_data.csv`

```csv
X1,X2,X3,Y
1,2,3,6
2,3,4,9
3,4,5,12
4,5,6,15
5,6,7,18
6,7,8,21
7,8,9,24
8,9,10,27
9,10,11,30
10,11,12,33
```

このデータでは、`Y = X1 + X2 + X3` という構造になっており、明確な因果構造（X1, X2, X3 → Y）があります。

---

### ✅ Streamlit での確認ポイント

1. ファイルをアップロードして読み込む
2. シート選択スキップ（CSVなので不要）
3. **目的変数に `Y` を選び、説明変数に `X1`, `X2`, `X3` を選択**
4. HillClimbSearch + BIC で構造推定
5. 結果：
   ```python
   [('X1', 'Y'), ('X2', 'Y'), ('X3', 'Y')]
   ```

---

### ✅ 補足（コードに組み込む離散化）

以下をデータ選択後に追加すれば、`BIC` が期待通り動きやすくなります：

```python
data_discrete = data.copy()
for col in selected_cols:
    data_discrete[col] = pd.qcut(data[col], q=4, labels=False, duplicates='drop')
```

その後：

```python
model = HillClimbSearch(data_discrete).estimate(scoring_method=BIC(data_discrete))
```

---

試してうまく因果構造が出てくるか確認してみてください！必要なら、このテスト用コードを組み込んだ完全なサンプルもお渡しできます。

# **user**:

ありがとうございます。動きました。ところでこのモデル、因果の方向だけでなく、因果の強さは計算できないんですか？

# **assistant**:

素晴らしい、動いてよかったです！  
ご質問の「**因果の強さ**」についてですが、`pgmpy` の構造学習（たとえば `HillClimbSearch` + `BIC`）は **因果構造（= 方向づけされたグラフの形）** の推定が主な目的です。つまり：

- **「どの変数がどの変数に影響を与えているか」**（因果の *有無・方向*）はわかる  
- しかし **「どれくらい影響を与えているか」**（因果の *強さ*）は直接わかりません

---

## ✅ 因果の強さを知りたい場合の方法

### 方法 1: パラメータ推定（推定後のネットワークで）

構造が決まったら、`pgmpy` で**条件付き確率分布（CPD）**を推定できます。  
これにより、変数間の関係の「強さ」や「傾き」のようなものが得られます。

#### 例：

```python
from pgmpy.models import BayesianNetwork
from pgmpy.estimators import MaximumLikelihoodEstimator

# 推定された構造を使ってモデルを定義
bn_model = BayesianNetwork(model.edges())

# パラメータ推定（データは離散化されている必要あり）
bn_model.fit(data_discrete, estimator=MaximumLikelihoodEstimator)

# 各ノードの条件付き確率を表示
for cpd in bn_model.get_cpds():
    print(cpd)
```

これにより、「X1=1, X2=2 のとき Y=3 になる確率は...」のような情報が得られます。

---

### 方法 2: 線形因果モデル（強さ = 回帰係数）

`pgmpy` よりも **因果の強さを明示的に求めやすい**のは、以下のような **線形因果モデル** 系です：

- `lingam`（Linear Non-Gaussian Acyclic Model）
- `dowhy` + `econml`
- `semopy`（構造方程式モデリング）

これらは「X1 → Y は 0.5 の影響を与えている」など、**回帰係数や効果量として因果の強さ**を数値で出してくれます。

---

## 🔁 まとめ

| モデル                          | 方向 | 強さ（数値） | 補足                                      |
|-------------------------------|------|--------------|-------------------------------------------|
| `pgmpy` + HillClimbSearch      | ✅   | ❌（別途推定必要） | 離散変数向き、まずは構造推定              |
| `pgmpy` + MLE推定              | ✅   | △（CPD形式）   | 条件付き確率分布で関係を把握              |
| `lingam`                      | ✅   | ✅（係数あり） | 連続変数・線形因果モデル向け              |
| `semopy`（構造方程式）        | ✅   | ✅（係数あり） | パス係数など、因果効果の強さも明示的に出力 |

---

もし因果の強さまで表示したい場合、  
「pgmpy で構造を推定 → MLE推定で CPD を取得」または「lingam や semopy に切り替え」がおすすめです。

**pgmpyのまま強さを見たいか、それとも他のライブラリを試してみたいか**、どちらにご興味ありますか？

# **user**:

ありがとうございます。 semopy に渡したいので、Y2 ~ X6
X7 ~ Y2 + X4 + X8 のようなSEMモデル式を出力するようにコードを改変してもらってよろしいでしょうか？

# **assistant**:

もちろんです！  
`pgmpy` で推定されたベイジアンネットワークの構造（エッジの向き）を使って、`semopy` 用の **構造方程式モデル式（パス図式）** を自動生成するように、コードを改変できます。

---

## ✅ 目的
ベイジアンネットワーク構造：
```python
[('X4', 'X7'), ('X8', 'X7'), ('Y2', 'X7')]
```

を以下のような式に変換：
```plaintext
X7 ~ X4 + X8 + Y2
```

---

## ✅ 改変コード（SEM式生成部分の追加）

以下の関数を `model = HillClimbSearch(...)` の後に追加してください：

```python
def generate_sem_equations(edges):
    from collections import defaultdict

    # 子ノードごとに親をまとめる
    child_to_parents = defaultdict(list)
    for parent, child in edges:
        child_to_parents[child].append(parent)

    # SEM式のリスト
    sem_equations = []
    for child, parents in child_to_parents.items():
        # 形式: Y ~ X1 + X2 + ...
        equation = f"{child} ~ {' + '.join(parents)}"
        sem_equations.append(equation)

    return sem_equations
```

### 🔁 使い方：

推定後に以下を追加：

```python
edges = model.edges()
sem_equations = generate_sem_equations(edges)

st.write("推定された SEM モデル式:")
for eq in sem_equations:
    st.code(eq, language='text')
```

---

## ✅ 出力例

```plaintext
X7 ~ X4 + X8 + Y2
Y2 ~ X6
```

---

## ✅ その後 semopy に渡すには

以下のように文字列を `\n` で連結して渡せば OK です：

```python
model_desc = "\n".join(sem_equations)
```

```python
from semopy import Model
sem_model = Model(model_desc)
sem_model.fit(df)  # df は pandas のデータフレーム
```

---

### よろしければ、`pgmpy → semopy` まで一気通貫で処理するStreamlit対応コードもご提供できます！

ご希望ですか？

# **user**:

はい、お願いします。

# **assistant**:

了解しました！  
以下に、**Streamlit アプリ内で「pgmpy による因果構造推定」から「semopy 形式での式生成・推定」までを一貫して行うコード**を示します。

---

## ✅ このコードでできること

1. CSV または Excel の読み込み
2. ユーザーによる目的変数・説明変数の選択
3. `pgmpy` を使った因果構造の推定
4. 推定構造から `semopy` 用の構造式（例: `Y ~ X1 + X2`）を自動生成
5. `semopy` によるパラメータ推定
6. 結果（係数など）の表示

---

## ✅ 完全コード（`pages/bayesian_sem.py` などに保存）

```python
import streamlit as st
import pandas as pd
from pgmpy.estimators import HillClimbSearch
from pgmpy.estimators.StructureScore import BIC
from pgmpy.models import BayesianNetwork
from pgmpy.estimators import MaximumLikelihoodEstimator
from semopy import Model

st.title("Bayesian Network → SEM モデル構築アプリ")

# --- ファイルアップロード ---
uploaded_file = st.file_uploader("CSV または Excel ファイルをアップロードしてください", type=["csv", "xlsx"])

if uploaded_file:
    # --- ファイルの読み込み ---
    if uploaded_file.name.endswith(".csv"):
        data = pd.read_csv(uploaded_file)
    else:
        excel_file = pd.ExcelFile(uploaded_file)
        sheet_name = st.selectbox("読み込むシートを選択してください", excel_file.sheet_names)
        data = excel_file.parse(sheet_name)

    st.write("データのプレビュー:")
    st.dataframe(data.head())

    # --- 変数選択 ---
    target = st.selectbox("目的変数（1つ）を選択", data.columns)
    features = st.multiselect("説明変数（複数）を選択", [col for col in data.columns if col != target])

    if target and features:
        selected_cols = [target] + features
        data = data[selected_cols].dropna()

        # --- 離散化（BIC対応） ---
        data_discrete = data.copy()
        for col in selected_cols:
            data_discrete[col] = pd.qcut(data[col], q=4, labels=False, duplicates='drop')

        # --- 構造推定 ---
        st.subheader("1. 推定されたベイジアンネットワーク構造")
        model = HillClimbSearch(data_discrete).estimate(scoring_method=BIC(data_discrete))
        edges = model.edges()
        st.write(edges)

        # --- SEM式の生成 ---
        def generate_sem_equations(edges):
            from collections import defaultdict
            child_to_parents = defaultdict(list)
            for parent, child in edges:
                child_to_parents[child].append(parent)
            sem_equations = []
            for child, parents in child_to_parents.items():
                equation = f"{child} ~ {' + '.join(parents)}"
                sem_equations.append(equation)
            return sem_equations

        sem_equations = generate_sem_equations(edges)

        st.subheader("2. semopy 用 SEM モデル式")
        for eq in sem_equations:
            st.code(eq, language='text')

        # --- semopy による係数推定 ---
        st.subheader("3. semopy による因果係数推定")
        model_desc = "\n".join(sem_equations)

        try:
            sem_model = Model(model_desc)
            sem_model.fit(data)  # 生データ（連続値）を使用
            params = sem_model.inspect()
            st.dataframe(params)
        except Exception as e:
            st.error(f"semopy モデルの推定でエラーが発生しました: {e}")
```

---

## ✅ 実行環境の依存関係

以下のライブラリがインストールされていることを確認してください：

```bash
pip install streamlit pgmpy semopy pandas openpyxl
```

---

## ✅ 出力例（構造式 + 係数）

```
X7 ~ X4 + X8 + Y2
Y2 ~ X6
```

| param | coef |
|-------|------|
| X4 → X7 | 0.51 |
| Y2 → X7 | 0.92 |
| ...   | ...  |

---

このコードで `pgmpy` → `semopy` のワークフローが Streamlit 上で可視化・完結します。  
必要であれば、図としての構造表示（networkx/pyvis）も追加できます。

図の可視化も組み込みたいですか？

# **user**:

ありがとうございます。はい、お願いします。

# **assistant**:

了解しました！  
以下では、**pgmpyで推定した因果構造を `pyvis` + `networkx` で可視化する処理**を、先ほどの Streamlit アプリに統合する形でご案内します。

---

## ✅ 追加インストール（必要な場合）

```bash
pip install pyvis networkx
```

---

## ✅ Streamlit で `pyvis` グラフを表示する関数（HTML 埋め込み）

```python
import streamlit.components.v1 as components
from pyvis.network import Network
import networkx as nx

def display_network_pyvis(edges):
    # NetworkXのグラフを作成
    g = nx.DiGraph()
    g.add_edges_from(edges)

    # pyvis のネットワークに変換
    net = Network(height='500px', directed=True)
    net.from_nx(g)

    # ノードとエッジの装飾（任意）
    for node in g.nodes():
        net.get_node(node)['label'] = str(node)
    for edge in g.edges():
        net.get_edge(edge[0], edge[1])['arrows'] = 'to'

    # HTMLとして保存して埋め込み表示
    net.save_graph("graph.html")
    with open("graph.html", "r", encoding="utf-8") as f:
        html_content = f.read()
        components.html(html_content, height=550, scrolling=True)
```

---

## ✅ 使い方（Streamlit本体コード内に追加）

`st.subheader("1. 推定されたベイジアンネットワーク構造")` の直後に以下を挿入：

```python
# 構造の可視化（pyvis）
st.subheader("🧠 因果構造のネットワーク可視化")
display_network_pyvis(edges)
```

---

## ✅ 変更後のイメージ

Streamlit 上に、以下の順番で表示されます：

1. ファイル読み込みと列選択
2. **推定された因果構造（テーブル）**
3. **因果構造のグラフ可視化（pyvis）**
4. SEM 式出力
5. `semopy` による係数推定

---

## ✅ 結果イメージ（グラフ）

- ノード：変数名
- 有向エッジ：因果方向（→）
- インタラクティブ操作（ズーム、ドラッグ）

---

これで、ベイジアンネットワーク → SEM式 → モデル推定 → ネットワーク可視化 の全体が Streamlit 上で完結します。

必要であれば、推定されたモデルをファイル保存する機能や、信頼区間付きの表示も追加できます。拡張希望はありますか？

# **user**:

ありがとうございます。信頼区間付きの表示というのは、どのように実装すればよいでしょうか？

# **assistant**:

とても良い質問です！  
`semopy` は `fit` メソッドでモデルを推定したあと、`inspect()` でパラメータの係数だけでなく **標準誤差・z値・p値・信頼区間** なども取得できます。これを活用すれば「信頼区間付きの因果係数一覧」を表示できます。

---

## ✅ 実装方法（信頼区間付きの表示）

以下のように `sem_model.inspect()` に `std_est=True` をつけて、信頼区間などを含む結果を取得します：

```python
params = sem_model.inspect(std_est=True)  # 標準誤差や信頼区間も含まれる
```

---

## ✅ 出力例（`params` の中身）

```plaintext
     param         Estimate      Std.Err        z        P>|z|     CI.lower     CI.upper
0    X4~X7           0.48         0.10        4.8       0.0000       0.28         0.68
1    Y2~X6           0.32         0.12        2.7       0.0070       0.09         0.55
```

---

## ✅ Streamlit での表示（信頼区間付き）

`st.dataframe(params)` のままでもいいですが、見やすくするには列を整理してから表示するとよいでしょう：

```python
columns_to_show = ["param", "Estimate", "Std.Err", "CI.lower", "CI.upper", "P-value"]
params_renamed = params.rename(columns={"P>|z|": "P-value"})  # 列名の整形
st.dataframe(params_renamed[columns_to_show])
```

---

## ✅ 補足：信頼区間とは？

各係数に対する信頼区間（例えば 95%CI）は「この範囲内に真の係数があるだろう」という推定の幅を示します：

- 幅が狭い → 推定が信頼できる
- 0 をまたぐ → 因果関係が統計的に有意ではないかも（p > 0.05）

---

## ✅ まとめ

信頼区間付きで表示するには：

```python
params = sem_model.inspect(std_est=True)
```

これで **因果の強さ＋その信頼性** を併せて評価できるようになります。

ご希望があれば、p値が有意（0.05 未満）のものだけをフィルタ表示する処理も追加できますが、どうしますか？

# **user**:

ありがとうございます。これで大丈夫です。今日の会話をブログに残したいので、タイトル案をいくつか示していただけますか？

# **assistant**:

喜んでお手伝いします！  
今回の内容は「Streamlit でベイジアンネットワークから SEM モデルを構築・可視化する」プロセスを実践的に解説したもので、とても有益です。以下にいくつかのタイトル案を示します：

---

### 💡 技術ブログ向けタイトル案（目的別に分類）

#### ① 初学者にもやさしい系：
- **「VSCodeとStreamlitで始める因果推論入門：CSVからベイジアンネットワーク→SEMまで」**
- **「初めてのベイジアンネットワーク可視化とSEM解析：Streamlitで手軽に体験」**
- **「コード少なめ！StreamlitでCSVから因果関係を推定してみた」**

#### ② 実装ハウツー重視系：
- **「pgmpy×semopyで因果構造推定を可視化：Streamlitアプリの作り方」**
- **「CSVからSEM式を自動生成！Streamlitでつくる因果分析アプリ」**
- **「因果構造の強さと信頼区間を一気に可視化するStreamlitアプリ」**

#### ③ アカデミック・応用志向：
- **「データから因果構造と構造方程式モデル（SEM）を推定するワークフロー」**
- **「ベイジアンネットワークで因果構造を発見し、SEMで係数推定する：Python実装例」**

---

気に入った系統があれば、それに合わせてサブタイトルや導入文もお手伝いできます。どの方向性が良さそうですか？

# **user**:

「ベイジアンネットワークで因果構造を発見し、SEMで係数推定する」にしようと思います。今回のまとめを詳しく書いてください。

# **assistant**:

素晴らしいタイトルです！  
以下に、「**ベイジアンネットワークで因果構造を発見し、SEMで係数推定する**」という技術ブログ記事の**詳しいまとめ・構成案**を、初学者でも理解しやすいように丁寧な解説付きでご提供します。

---

# ベイジアンネットワークで因果構造を発見し、SEMで係数推定する  
*― StreamlitとPythonで可視化＆分析するアプリを作ってみた ―*

---

## 🔍 はじめに

因果関係を明らかにする分析は、社会科学・マーケティング・医学・経済など、あらゆる分野で重要です。  
本記事では、**データから因果構造を自動で推定し、それを使って構造方程式モデル（SEM）を構築・推定する**一連のワークフローを、**Streamlitアプリ**として実装したプロセスを紹介します。

使用する主なライブラリは以下の通りです：

- **pgmpy**：ベイジアンネットワークによる因果構造推定
- **semopy**：構造方程式モデル（SEM）のパラメータ推定
- **pyvis + networkx**：因果構造の可視化
- **Streamlit**：インタラクティブなアプリ作成

---

## 🧰 アプリの主な機能

- CSVまたはExcelファイルを読み込む（シート選択も対応）
- 目的変数・説明変数をGUIから選択
- ベイジアンネットワークで因果構造を推定
- SEM式（`Y ~ X1 + X2` 形式）を自動生成
- semopyで係数・信頼区間・p値を推定
- 因果構造をインタラクティブなネットワークで可視化

---

## 📊 因果構造の推定（pgmpy）

`pgmpy` の `HillClimbSearch` と `BICスコア` を使い、構造を推定します。  
例えば、以下のような因果パスが得られます：

```plaintext
X6 → Y2  
X4 → X7  
Y2 → X7  
X8 → X7
```

---

## 📐 SEM式の自動生成

推定された構造をもとに、semopy用のモデル記述を自動生成します：

```r
Y2 ~ X6  
X7 ~ Y2 + X4 + X8
```

このモデルは semopy でそのまま使用可能です。

---

## 📈 SEMによる推定と信頼区間付き表示

`semopy` に渡すと、以下のような推定結果が得られます：

| param | Estimate | Std.Err | CI.lower | CI.upper | P-value |
|-------|----------|---------|----------|----------|---------|
| Y2~X6 |  0.35    |  0.09   |   0.17   |   0.53   |  0.001  |
| X7~X4 |  0.41    |  0.07   |   0.27   |   0.55   |  <0.001 |

- **Estimate**：係数（因果の強さ）
- **CI.lower / CI.upper**：信頼区間
- **P-value**：有意性の指標

---

## 🌐 因果ネットワークの可視化（pyvis）

推定された構造は、`pyvis` を用いてネットワークグラフとして表示されます：

- ノード：変数
- エッジ：因果関係の方向（→）
- インタラクティブ操作可（ドラッグ・ズーム）

これにより、構造全体を直感的に理解できます。

---

## 💡 まとめ

本記事では、**データから因果構造を推定し、SEMで係数推定を行い、可視化する**という一連の流れを、Streamlitアプリとして実装しました。ポイントは以下のとおりです：

- pgmpyを使えばデータから因果構造を自動推定できる  
- semopyを使えば、その構造に基づいて係数推定や信頼区間の推定が可能  
- pyvisとnetworkxで視覚的に構造を捉えやすくなる  
- StreamlitでGUIアプリ化することで再利用・共有が容易に

---

## 🚀 参考リソースとコード

- [pgmpy Documentation](https://pgmpy.org/)
- [semopy GitHub](https://github.com/semopy/semopy)
- [Streamlit Documentation](https://docs.streamlit.io/)

※このアプリのコード全体や使い方は、別途GitHubにて公開予定です（または、記事末に貼る）。

---

記事の導入・まとめ・技術解説まで網羅しましたが、もし**冒頭に実行画面のGIFや完成図**を載せると、より読者に刺さる構成になります。

何か追加したい内容や読者層に合わせた調整があれば、お手伝いします！

# **user**:

ありがとうございました。また次回もよろしくお願いします。

# **assistant**:

こちらこそ、楽しく実践的なやりとりをありがとうございました！  
またいつでもお声がけください。次回もお手伝いできるのを楽しみにしています。どうぞ良い一日を！