Wen1201 commited on
Commit
0ba59a3
·
verified ·
1 Parent(s): a7037b9

Upload 9 files

Browse files
Files changed (8) hide show
  1. README.md +19 -316
  2. app_bayesian.py +709 -0
  3. bayesian_core.py +193 -192
  4. bayesian_llm_assistant.py +397 -215
  5. bayesian_utils.py +425 -0
  6. fire_water_converted.csv +47 -0
  7. requirements.txt +10 -9
  8. runtime.txt +1 -1
README.md CHANGED
@@ -1,331 +1,34 @@
1
  ---
2
- title: BayePyMC
3
- emoji: 🔬
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: streamlit
7
  sdk_version: 1.31.0
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- # 貝氏階層模型分析系統 - 寶可夢速度對勝率影響
13
 
14
- ## 📋 系統簡介
15
 
16
- 這是一個基於 Streamlit 和 PyMC 的貝氏階層模型分析系統,專為分析寶可夢速度對不同屬性勝率的影響而設計,結合 AI 助手提供深入的統計解釋和對戰策略建議。
17
 
18
- ## 🎯 主要功能
 
 
 
19
 
20
- ### 1. 貝氏階層模型分析
21
- - ✅ MCMC 抽樣(Markov Chain Monte Carlo)
22
- - ✅ 階層結構(跨屬性資訊借用)
23
- - ✅ 完整的不確定性量化
24
- - ✅ 後驗分佈估計
25
- - ✅ 收斂診斷
26
 
27
- ### 2. 完整視覺化(4 個圖表 + 1 個文字摘要)
28
- - 🔀 **DAG 圖**:模型結構視覺化
29
- - 📉 **Trace Plot**:MCMC 收斂診斷圖
30
- - 🎯 **Posterior Plot**:後驗佈圖
31
- - 🌲 **Forest Plot**:各屬性效應圖
32
- - 📋 **文字摘要**:統計結果表格
33
 
34
- ### 3. AI 智能助手
35
- - 💬 自然語言對話(雙語支援)
36
- - 📖 統計概念解釋(貝氏、階層模型)
37
- - 🎮 對戰策略建議
38
- - 🔍 結果深度分析
39
- - 📚 參數詳細說明
40
 
41
- ## 📦 安裝步驟
42
-
43
- ### 1. 安裝 Python 依賴套件
44
- ```bash
45
- pip install -r bayesian_requirements.txt
46
- ```
47
-
48
- ### 2. 安裝 Graphviz(系統級套件,用於生成 DAG 圖)
49
-
50
- **Windows (使用 Chocolatey):**
51
- ```bash
52
- choco install graphviz
53
- ```
54
-
55
- **Mac:**
56
- ```bash
57
- brew install graphviz
58
- ```
59
-
60
- **Ubuntu/Debian:**
61
- ```bash
62
- sudo apt-get install graphviz
63
- ```
64
-
65
- ### 3. 準備資料
66
- 將寶可夢速度分析資料 CSV 檔放在同一目錄下,檔名為 `pokemon_speed_meta_results.csv`
67
-
68
- **資料格式要求:**
69
-
70
- | 欄位 | 說明 | 範例 |
71
- |------|------|------|
72
- | `Trial_Type` | 寶可夢屬性 | Water, Fire, Grass |
73
- | `rc` | 控制組(速度慢)勝場數 | 45 |
74
- | `nc` | 控制組總場數 | 100 |
75
- | `rt` | 實驗組(速度快)勝場數 | 60 |
76
- | `nt` | 實驗組總場數 | 100 |
77
-
78
- **範例資料:**
79
- ```csv
80
- Trial_Type,rc,nc,rt,nt
81
- Water,45,100,60,100
82
- Fire,38,100,55,100
83
- Grass,42,100,58,100
84
- Electric,50,100,65,100
85
- ```
86
-
87
- ### 4. 設定 Google Gemini API Key
88
- - 在系統左側邊欄輸入您的 Google Gemini API Key
89
- - API Key 用於 AI 助手功能
90
- - 取得 API Key:https://ai.google.dev/
91
-
92
- ### 5. 執行程式
93
- ```bash
94
- streamlit run bayesian_app.py
95
- ```
96
-
97
- ## 🔧 檔案結構
98
-
99
- ```
100
- bayesian_hierarchical_model/
101
- ├── bayesian_app.py # Streamlit 主程式
102
- ├── bayesian_core.py # 貝氏階層模型核心邏輯
103
- ├── bayesian_llm_assistant.py # AI 對話助手
104
- ├── bayesian_requirements.txt # 依賴套件
105
- ├── README.md # 說明文件
106
- └── pokemon_speed_meta_results.csv # 資料檔(需自行準備)
107
- ```
108
-
109
- ## 📊 使用方式
110
-
111
- ### Step 1: 載入資料
112
- 1. 選擇「使用預設資料集」或「上傳您的資料」
113
- 2. 如果上傳,請確保 CSV 格式正確(需包含必要欄位)
114
-
115
- ### Step 2: 設定抽樣參數(可選)
116
- 1. 展開「進階設定」調整 MCMC 參數
117
- 2. **建議設定**:
118
- - Samples: 2000(更多 = 更準確但更慢)
119
- - Tuning: 1000
120
- - Chains: 1(多條鏈可檢測收斂問題)
121
- - Target Accept: 0.95
122
-
123
- ### Step 3: 執行分析
124
- 1. 點擊「開始貝氏分析」按鈕
125
- 2. 等待分析完成(通常需要 2-5 分鐘)
126
- 3. 查看結果的四個子頁面:
127
- - **📊 概覽**:關鍵指標、摘要、各屬性詳細結果
128
- - **📉 Trace Plot**:收斂診斷
129
- - **🎯 Posterior**:後驗分佈
130
- - **🌲 Forest Plot**:各屬性效應比較
131
-
132
- ### Step 4: 使用 AI 助手
133
- 1. 切換到「AI 助手」頁面
134
- 2. 在聊天框輸入問題,或點擊快速問題按鈕
135
- 3. AI 會根據分析結果提供解釋和建議
136
-
137
- ## 💡 統計指標說明
138
-
139
- ### 關鍵參數
140
-
141
- | 參數 | 說明 | 解讀 |
142
- |------|------|------|
143
- | **d** | 整體平均效應(log OR) | 所有屬性的平均速度效應 |
144
- | **sigma** | 屬性間變異 | 不同屬性對速度反應的差異程度 |
145
- | **or_speed** | 速度勝算比(exp(d)) | 速度快的寶可夢獲勝機率倍數 |
146
- | **delta[i]** | 第 i 個屬性的效應 | 該屬性的速度效應(相對於整體) |
147
-
148
- ### 判斷準則
149
-
150
- **顯著性:**
151
- - 95% HDI 不包含 0 → 效應顯著
152
- - 95% HDI 包含 0 → 效應不顯著
153
-
154
- **勝算比解讀:**
155
- - OR > 1:速度快有利
156
- - OR = 1:無差異
157
- - OR < 1:速度慢有利(罕見)
158
-
159
- **收斂診斷:**
160
- - Trace plot 應該像「毛毛蟲」(平穩、混合良好)
161
- - 不應有明顯趨勢或週期性
162
-
163
- ## 🎮 應用場景
164
-
165
- ### 1. 屬性特定分析
166
- 判斷哪些屬性的寶可夢特別受益於速度(如電系、飛行系)
167
-
168
- ### 2. 組隊策略制定
169
- 根據統計結果選擇是否優先速度特訓
170
-
171
- ### 3. 對戰機制理解
172
- 理解速度在不同對戰情境中的重要性
173
-
174
- ### 4. 教學用途
175
- 學習貝氏階層模型的原理和應用
176
-
177
- ## 📈 視覺化圖表說明
178
-
179
- ### 1️⃣ DAG 圖(模型結構)
180
- - **作用**:展示變數之間的依賴關係
181
- - **元素**:
182
- - 圓形/橢圓:隨機變數
183
- - 矩形:觀測資料
184
- - 菱形:推導變數
185
- - 箭頭:依賴關係
186
-
187
- ### 2️⃣ Trace Plot(收斂診斷)
188
- - **左欄**:MCMC 抽樣軌跡
189
- - **右欄**:後驗分佈密度
190
- - **良好收斂**:軌跡像「毛毛蟲」,平穩無趨勢
191
- - **問題跡象**:有趨勢、卡住、未混合
192
-
193
- ### 3️⃣ Posterior Plot(後驗分佈)
194
- - 顯示 d、sigma、or_speed 的後驗分佈
195
- - 自動標註 95% HDI
196
- - 顯示平均值
197
-
198
- ### 4️⃣ Forest Plot(各屬性效應)
199
- - **最重要的圖!**
200
- - Y 軸:各屬性
201
- - X 軸:delta(log OR)
202
- - 點:平均效應
203
- - 線:95% 信賴區間
204
- - 星號:顯著效應
205
- - 紅虛線:無效應參考線
206
-
207
- ## ⚙️ 技術架構
208
-
209
- ### 核心技術
210
- - **Streamlit**: Web 應用框架
211
- - **PyMC**: 貝氏推論引擎
212
- - **ArviZ**: 貝氏分析視覺化
213
- - **NumPy/Pandas**: 數值運算與資料處理
214
- - **Matplotlib**: 圖表繪製
215
- - **Google Gemini**: AI 助手
216
-
217
- ### 統計方法
218
- - **Hierarchical Bayesian Model**: 階層貝氏模型
219
- - **MCMC Sampling**: 馬可夫鏈蒙地卡羅抽樣
220
- - **Logit Link Function**: Logit 連結函數
221
- - **Partial Pooling**: 部分池化(資訊借用)
222
-
223
- ### 特色設計
224
- - ✅ Session 隔離(多用戶支援)
225
- - ✅ 執行緒安全
226
- - ✅ 自動清理過期資料
227
- - ✅ 響應式 UI 設計
228
- - ✅ 進度條回饋
229
- - ✅ 完整錯誤處理
230
-
231
- ## 🔒 隱私與安全
232
-
233
- - 所有分析在本地執行
234
- - Session 資料獨立儲存
235
- - 超過 1 小時自動清理
236
- - API Key 不會被儲存
237
-
238
- ## 📝 範例問題(給 AI 助手)
239
-
240
- ### 基本概念
241
- - "什麼是貝氏統計?"
242
- - "什麼是階層模型?"
243
- - "什麼是先驗、後驗、似然?"
244
- - "HDI 和信賴區間有什麼不同?"
245
-
246
- ### 結果解讀
247
- - "d 參數是什麼意思?"
248
- - "sigma 大表示什麼?"
249
- - "如何判斷速度效應是否顯著?"
250
- - "為什麼有些屬性顯著,有些不顯著?"
251
-
252
- ### 收斂診斷
253
- - "如何看 Trace Plot?"
254
- - "什麼是毛毛蟲圖?"
255
- - "我的模型收斂了嗎?"
256
-
257
- ### 實戰應用
258
- - "給我分析總結"
259
- - "哪些屬性最受益於速度?"
260
- - "我該如何組建隊伍?"
261
- - "這對對戰策略有什麼啟示?"
262
-
263
- ## 🆚 與 McNemar 系統的比較
264
-
265
- | 特性 | McNemar 系統 | 貝氏階層模型 |
266
- |------|--------------|--------------|
267
- | 方法 | 頻率論統計 | 貝氏推論 |
268
- | 資料 | 配對資料(勝vs敗) | 獨立兩組(快vs慢) |
269
- | 分析單位 | 單一特徵 | 多屬性同時分析 |
270
- | 輸出 | p 值、OR | 後驗分佈、HDI |
271
- | 階層性 | 無 | 有(跨屬性借用資訊) |
272
- | 不確定性 | 點估計 + CI | 完整後驗分佈 |
273
- | 小樣本 | 可能不穩定 | 穩健(借用資訊) |
274
-
275
- ## 🚀 未來功能規劃
276
-
277
- - [ ] 多特徵聯合分析(速度 + 攻擊 + HP)
278
- - [ ] 模型比較(DIC, WAIC)
279
- - [ ] 預測新屬性的效應
280
- - [ ] 互動式後驗預測檢查
281
- - [ ] 匯出完整 PDF 報告
282
- - [ ] 批次分析多個資料集
283
-
284
- ## 🐛 常見問題排解
285
-
286
- ### Q1: DAG 圖無法生成
287
- **A**: 請確保已安裝系統級的 Graphviz
288
- ```bash
289
- # 檢查是否安裝
290
- dot -V
291
-
292
- # 如果未安裝,請依照上述安裝步驟安裝
293
- ```
294
-
295
- ### Q2: MCMC 抽樣太慢
296
- **A**: 可以降低抽樣數或調整參數
297
- - 減少 Samples(但會降低精確度)
298
- - 增加 Chains(利用多核心)
299
- - 降低 Target Accept(但可能影響收斂)
300
-
301
- ### Q3: Trace Plot 顯示未收斂
302
- **A**: 嘗試以下方法
303
- - 增加 Tuning samples
304
- - 增加 Samples
305
- - 提高 Target Accept
306
- - 檢查資料是否有問題
307
-
308
- ### Q4: AI 助手無法使用
309
- **A**: 請檢查
310
- - API Key 是否正確
311
- - 是否已執行分析
312
- - 網路連線是否正常
313
-
314
- ## 📧 聯絡資訊
315
-
316
- 如有問題或建議,歡迎聯繫開發團隊。
317
-
318
- ## 📄 授權
319
-
320
- 本專案僅供學術研究和教學使用。
321
-
322
- ---
323
-
324
- **Powered by PyMC, ArviZ & Google Gemini** 🚀
325
-
326
- ## 🎓 延伸閱讀
327
-
328
- - [PyMC 官方文件](https://www.pymc.io/)
329
- - [ArviZ 官方文件](https://arviz-devs.github.io/arviz/)
330
- - [Bayesian Data Analysis (Gelman et al.)](http://www.stat.columbia.edu/~gelman/book/)
331
- - [Hierarchical Models 教學](https://www.pymc.io/projects/examples/en/latest/case_studies/hierarchical_partial_pooling.html)
 
1
  ---
2
+ title: Bayesian Hierarchical Model Analysis
3
+ emoji: 🎲
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: streamlit
7
  sdk_version: 1.31.0
8
+ app_file: app_bayesian.py
9
  pinned: false
10
+ python_version: "3.11"
11
  ---
12
 
13
+ # 🎲 貝氏階層模型分析系統
14
 
15
+ 寶可夢速度對勝率影響的貝氏階層分析
16
 
17
+ ## 功能特色
18
 
19
+ - 🎲 貝氏階層模型分析
20
+ - 📊 4 種視覺化圖表
21
+ - 💬 AI 助手(Google Gemini)
22
+ - 📈 完整統計報告
23
 
24
+ ## 使用方式
 
 
 
 
 
25
 
26
+ 1. 上傳資料或使用範例資料
27
+ 2. 設定 MCMC 參數
28
+ 3. 輸入 Google Gemini API Key
29
+ 4. 開始
 
 
30
 
31
+ ## 系統需求
 
 
 
 
 
32
 
33
+ - Python 3.11
34
+ - Google Gemini API Key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_bayesian.py ADDED
@@ -0,0 +1,709 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import uuid
4
+ from datetime import datetime, timedelta
5
+ import atexit
6
+ import os
7
+ import sys
8
+
9
+ # 頁面配置
10
+ st.set_page_config(
11
+ page_title="Bayesian Hierarchical Model - Pokémon Speed Analysis",
12
+ page_icon="🎲",
13
+ layout="wide",
14
+ initial_sidebar_state="expanded"
15
+ )
16
+
17
+ # 自定義 CSS
18
+ st.markdown("""
19
+ <style>
20
+ .streamlit-expanderHeader {
21
+ background-color: #e8f1f8;
22
+ border: 1px solid #b0cfe8;
23
+ border-radius: 5px;
24
+ font-weight: 600;
25
+ color: #1b4f72;
26
+ }
27
+ .streamlit-expanderHeader:hover {
28
+ background-color: #d0e7f8;
29
+ }
30
+ .stMetric {
31
+ background-color: #f8fbff;
32
+ padding: 10px;
33
+ border-radius: 5px;
34
+ border: 1px solid #d0e4f5;
35
+ }
36
+ .stButton > button {
37
+ width: 100%;
38
+ border-radius: 20px;
39
+ font-weight: 600;
40
+ transition: all 0.3s ease;
41
+ }
42
+ .stButton > button:hover {
43
+ transform: translateY(-2px);
44
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
45
+ }
46
+ .success-box {
47
+ background-color: #d4edda;
48
+ border: 1px solid #c3e6cb;
49
+ border-radius: 5px;
50
+ padding: 10px;
51
+ margin: 10px 0;
52
+ }
53
+ .warning-box {
54
+ background-color: #fff3cd;
55
+ border: 1px solid #ffeaa7;
56
+ border-radius: 5px;
57
+ padding: 10px;
58
+ margin: 10px 0;
59
+ }
60
+ </style>
61
+ """, unsafe_allow_html=True)
62
+
63
+ # 導入自定義模組
64
+ from bayesian_core import BayesianHierarchicalAnalyzer
65
+ # 注意:如果要啟用 DAG 動態生成功能,請將下行改為:
66
+ # from bayesian_llm_assistant_enhanced import BayesianLLMAssistant
67
+ from bayesian_llm_assistant import BayesianLLMAssistant
68
+ from bayesian_utils import (
69
+ plot_trace,
70
+ plot_posterior,
71
+ plot_forest,
72
+ plot_model_dag,
73
+ create_summary_table,
74
+ create_trial_results_table,
75
+ export_results_to_text,
76
+ plot_odds_ratio_comparison
77
+ )
78
+
79
+ # 清理函數
80
+ def cleanup_old_sessions():
81
+ """清理超過 1 小時的 session"""
82
+ current_time = datetime.now()
83
+ for session_id in list(BayesianHierarchicalAnalyzer._session_results.keys()):
84
+ result = BayesianHierarchicalAnalyzer._session_results.get(session_id)
85
+ if result:
86
+ result_time = datetime.fromisoformat(result['timestamp'])
87
+ if current_time - result_time > timedelta(hours=1):
88
+ BayesianHierarchicalAnalyzer.clear_session_results(session_id)
89
+
90
+ # 註冊清理函數
91
+ atexit.register(cleanup_old_sessions)
92
+
93
+ # 初始化 session state
94
+ if 'session_id' not in st.session_state:
95
+ st.session_state.session_id = str(uuid.uuid4())
96
+ if 'analysis_results' not in st.session_state:
97
+ st.session_state.analysis_results = None
98
+ if 'chat_history' not in st.session_state:
99
+ st.session_state.chat_history = []
100
+ if 'analyzer' not in st.session_state:
101
+ st.session_state.analyzer = None
102
+ if 'trace_img' not in st.session_state:
103
+ st.session_state.trace_img = None
104
+ if 'posterior_img' not in st.session_state:
105
+ st.session_state.posterior_img = None
106
+ if 'forest_img' not in st.session_state:
107
+ st.session_state.forest_img = None
108
+ if 'dag_img' not in st.session_state:
109
+ st.session_state.dag_img = None
110
+
111
+ # 標題
112
+ st.title("🎲 Bayesian Hierarchical Model Analysis")
113
+ st.markdown("### 火系 vs 水系寶可夢配對勝率的貝氏階層分析")
114
+ st.markdown("---")
115
+
116
+ # Sidebar
117
+ with st.sidebar:
118
+ st.header("⚙️ 配置設定")
119
+
120
+ # API 選擇
121
+ api_choice = st.radio(
122
+ "選擇 LLM API",
123
+ options=["Google Gemini", "Anthropic Claude"],
124
+ index=0,
125
+ help="選擇要使用的 AI 助手"
126
+ )
127
+
128
+ # API Key 輸入
129
+ if api_choice == "Google Gemini":
130
+ api_key = st.text_input(
131
+ "Google Gemini API Key",
132
+ type="password",
133
+ help="輸入您的 Google Gemini API Key"
134
+ )
135
+ else: # Claude
136
+ api_key = st.text_input(
137
+ "Anthropic Claude API Key",
138
+ type="password",
139
+ help="輸入您的 Anthropic API Key (https://console.anthropic.com)"
140
+ )
141
+
142
+ if api_key:
143
+ st.session_state.api_key = api_key
144
+ st.session_state.api_choice = api_choice # 新增:儲存 API 選擇
145
+ st.success(f"✅ {api_choice} API Key 已載入")
146
+
147
+ st.markdown("---")
148
+
149
+ # MCMC 參數設定
150
+ st.subheader("🔬 MCMC 參數")
151
+
152
+ n_samples = st.number_input(
153
+ "抽樣數 (Samples)",
154
+ min_value=500,
155
+ max_value=10000,
156
+ value=2000,
157
+ step=500,
158
+ help="每條鏈的抽樣數量"
159
+ )
160
+
161
+ n_tune = st.number_input(
162
+ "調整期 (Tune)",
163
+ min_value=200,
164
+ max_value=5000,
165
+ value=1000,
166
+ step=200,
167
+ help="調整期的樣本數"
168
+ )
169
+
170
+ n_chains = st.selectbox(
171
+ "鏈數 (Chains)",
172
+ options=[1, 2, 4],
173
+ index=1,
174
+ help="平行運行的鏈數"
175
+ )
176
+
177
+ target_accept = st.slider(
178
+ "目標接��率",
179
+ min_value=0.80,
180
+ max_value=0.99,
181
+ value=0.95,
182
+ step=0.01,
183
+ help="NUTS 採樣器的目標接受率"
184
+ )
185
+
186
+ st.markdown("---")
187
+
188
+ # 清理按鈕
189
+ if st.button("🧹 清理過期資料"):
190
+ cleanup_old_sessions()
191
+ st.success("✅ 清理完成")
192
+ st.rerun()
193
+
194
+ st.markdown("---")
195
+
196
+ # 資料來源選擇
197
+ st.subheader("📊 資料來源")
198
+ data_source = st.radio(
199
+ "選擇資料來源:",
200
+ ["使用預設資料集", "上傳您的資料"]
201
+ )
202
+
203
+ uploaded_file = None
204
+ if data_source == "上傳您的資料":
205
+ uploaded_file = st.file_uploader(
206
+ "上傳 CSV 檔案",
207
+ type=['csv'],
208
+ help="上傳寶可夢速度對戰資料"
209
+ )
210
+
211
+ with st.expander("📖 資料格式說明"):
212
+ st.markdown("""
213
+ **必要欄位格式:**
214
+ - `Trial_Type`: 配對名稱(例如:Pair_1, Pair_2)
215
+ - `rt`: 火系(治療組)的勝場數
216
+ - `nt`: 火系的總場數
217
+ - `rc`: 水系(對照組)的勝場數
218
+ - `nc`: 水系的總場數
219
+
220
+ **範例:**
221
+ ```
222
+ Trial_Type,rt,nt,rc,nc
223
+ Pair_1,122,133,22,145
224
+ Pair_2,85,132,17,135
225
+ Pair_3,52,129,41,134
226
+ ```
227
+ """)
228
+
229
+ st.markdown("---")
230
+
231
+
232
+ # 關於系統
233
+ with st.expander("ℹ️ 關於此系統"):
234
+ st.markdown("""
235
+ **貝氏階層模型分析系統**
236
+
237
+ 本系統使用貝氏階層模型來分析速度對寶可夢勝率的影響,
238
+ 並考慮不同屬性之間的異質性。
239
+
240
+ **主要功能:**
241
+ - 🎲 貝氏推論與後驗分佈
242
+ - 📊 階層模型(借用資訊)
243
+ - 📈 4 種視覺化圖表
244
+ - 💬 AI 助手解釋
245
+ - 🎮 屬性對抗策略建議
246
+
247
+ **適用場景:**
248
+ - 分析火系對水系的配對勝率
249
+ - 理解不同配對間的異質性
250
+ - 評估屬性優劣勢
251
+ """)
252
+
253
+ # 主要內容區 - 雙 Tab
254
+ tab1, tab2 = st.tabs(["📊 貝氏分析", "💬 AI 助手"])
255
+
256
+ # Tab 1: 貝氏分析
257
+ with tab1:
258
+ st.header("📊 貝氏階層模型分析")
259
+
260
+ # 載入資料
261
+ if data_source == "使用預設資料集":
262
+ # 檢查預設資料是否存在
263
+ default_data_path = "fire_water_converted.csv"
264
+ if os.path.exists(default_data_path):
265
+ df = pd.read_csv(default_data_path)
266
+ st.success(f"✅ 已載入預設資料集({len(df)} 組配對)")
267
+ else:
268
+ st.warning("⚠️ 找不到預設資料集,請上傳您的資料")
269
+ df = None
270
+ else:
271
+ if uploaded_file is not None:
272
+ df = pd.read_csv(uploaded_file)
273
+ st.success(f"✅ 已載入資料({len(df)} 組配對)")
274
+ else:
275
+ df = None
276
+ st.info("📁 請在左側上傳 CSV 檔案")
277
+
278
+ if df is not None:
279
+ # 顯示資料預覽
280
+ with st.expander("👀 資料預覽"):
281
+ st.dataframe(df, use_container_width=True)
282
+
283
+ st.markdown("---")
284
+
285
+ # 分析按鈕
286
+ col1, col2, col3 = st.columns([1, 2, 1])
287
+
288
+ with col2:
289
+ analyze_button = st.button(
290
+ "🔬 開始貝氏分析",
291
+ type="primary",
292
+ use_container_width=True
293
+ )
294
+
295
+ # 執行分析
296
+ if analyze_button:
297
+ with st.spinner(f"正在執行貝氏分析... (抽樣 {n_samples} × {n_chains} 條鏈)"):
298
+ try:
299
+ # 初始化分析器
300
+ if st.session_state.analyzer is None:
301
+ st.session_state.analyzer = BayesianHierarchicalAnalyzer(st.session_state.session_id)
302
+
303
+ # 載入資料
304
+ st.session_state.analyzer.load_data(df)
305
+
306
+ # 執行分析
307
+ results = st.session_state.analyzer.run_analysis(
308
+ n_samples=n_samples,
309
+ n_tune=n_tune,
310
+ n_chains=n_chains,
311
+ target_accept=target_accept
312
+ )
313
+
314
+ st.session_state.analysis_results = results
315
+
316
+ # 生成圖表
317
+ with st.spinner("生成視覺化圖表..."):
318
+ st.session_state.trace_img = plot_trace(st.session_state.analyzer.trace)
319
+ st.session_state.posterior_img = plot_posterior(st.session_state.analyzer.trace)
320
+ st.session_state.forest_img = plot_forest(
321
+ st.session_state.analyzer.trace,
322
+ results['trial_labels']
323
+ )
324
+ st.session_state.dag_img = plot_model_dag(st.session_state.analyzer)
325
+
326
+ st.success("✅ 分析完成!")
327
+ st.balloons()
328
+
329
+ except Exception as e:
330
+ st.error(f"❌ 分析失敗: {str(e)}")
331
+
332
+ # 顯示結果
333
+ if st.session_state.analysis_results is not None:
334
+ results = st.session_state.analysis_results
335
+
336
+ st.markdown("---")
337
+ st.subheader("📊 分析結果")
338
+
339
+ # 創建 4 個子頁面
340
+ result_tabs = st.tabs([
341
+ "📊 概覽",
342
+ "📈 Trace & Posterior",
343
+ "🌲 Forest Plot",
344
+ "🔍 DAG 模型圖",
345
+ "📋 詳細報告"
346
+ ])
347
+
348
+ # Tab: 概覽
349
+ with result_tabs[0]:
350
+ st.markdown("### 🎯 整體效應摘要")
351
+
352
+ overall = results['overall']
353
+ interp = results['interpretation']
354
+
355
+ # 關鍵指標
356
+ col1, col2, col3 = st.columns(3)
357
+
358
+ with col1:
359
+ st.metric(
360
+ "d (整體效應)",
361
+ f"{overall['d_mean']:.4f}",
362
+ delta=f"HDI: [{overall['d_hdi_low']:.3f}, {overall['d_hdi_high']:.3f}]"
363
+ )
364
+
365
+ with col2:
366
+ st.metric(
367
+ "勝算比 (OR)",
368
+ f"{overall['or_mean']:.3f}",
369
+ delta=f"HDI: [{overall['or_hdi_low']:.3f}, {overall['or_hdi_high']:.3f}]"
370
+ )
371
+
372
+ with col3:
373
+ st.metric(
374
+ "sigma (異質性)",
375
+ f"{overall['sigma_mean']:.4f}",
376
+ delta=f"HDI: [{overall['sigma_hdi_low']:.3f}, {overall['sigma_hdi_high']:.3f}]"
377
+ )
378
+
379
+ st.markdown("---")
380
+
381
+ # 結果解釋
382
+ st.markdown("### 📖 結果解釋")
383
+
384
+ st.info(f"""
385
+ **整體效應**: {interp['overall_effect']}
386
+
387
+ **顯著性**: {interp['overall_significance']}
388
+
389
+ **效果大小**: {interp['effect_size']}
390
+
391
+ **異質性**: {interp['heterogeneity']}
392
+ """)
393
+
394
+ st.markdown("---")
395
+
396
+ # 收斂診斷
397
+ st.markdown("### 🔍 模型收斂診斷")
398
+
399
+ diag = results['diagnostics']
400
+
401
+ col1, col2 = st.columns(2)
402
+
403
+ with col1:
404
+ st.markdown("**R-hat 診斷** (應 < 1.1):")
405
+ if diag['rhat_d']:
406
+ st.metric("R-hat (d)", f"{diag['rhat_d']:.4f}",
407
+ delta="✓ 良好" if diag['rhat_d'] < 1.1 else "✗ 需改善")
408
+ if diag['rhat_sigma']:
409
+ st.metric("R-hat (sigma)", f"{diag['rhat_sigma']:.4f}",
410
+ delta="✓ 良好" if diag['rhat_sigma'] < 1.1 else "✗ 需改善")
411
+
412
+ with col2:
413
+ st.markdown("**有效樣本數 (ESS)**:")
414
+ if diag['ess_d']:
415
+ st.metric("ESS (d)", f"{int(diag['ess_d'])}")
416
+ if diag['ess_sigma']:
417
+ st.metric("ESS (sigma)", f"{int(diag['ess_sigma'])}")
418
+
419
+ if diag['converged']:
420
+ st.success("✅ 模型已收斂,結果可信")
421
+ else:
422
+ st.warning("⚠️ 模型可能未完全收斂,建議增加抽樣數或鏈數")
423
+
424
+ st.markdown("---")
425
+
426
+ # 摘要表格
427
+ st.markdown("### 📊 統計摘要表")
428
+ summary_df = create_summary_table(results)
429
+ st.dataframe(summary_df, use_container_width=True)
430
+
431
+ st.markdown("---")
432
+
433
+ # 各屬性結果
434
+ st.markdown("### 🎮 各屬性詳細結果")
435
+ trial_df = create_trial_results_table(results)
436
+ st.dataframe(trial_df, use_container_width=True)
437
+
438
+ st.markdown("---")
439
+
440
+ # 勝算比比較圖
441
+ st.markdown("### 📊 各屬性速度效應比較")
442
+ or_fig = plot_odds_ratio_comparison(results)
443
+ st.plotly_chart(or_fig, use_container_width=True)
444
+
445
+ # Tab: Trace & Posterior
446
+ with result_tabs[1]:
447
+ st.markdown("### 📈 Trace Plot(收斂診斷)")
448
+ st.markdown("""
449
+ **Trace Plot 用途**:
450
+ - 檢查 MCMC 抽樣是否收斂
451
+ - 左圖:抽樣軌跡(應該像「毛毛蟲」)
452
+ - 右圖:後驗分佈密度
453
+ """)
454
+
455
+ if st.session_state.trace_img:
456
+ st.image(st.session_state.trace_img, use_column_width=True)
457
+ else:
458
+ st.info("請先執行分析以生成 Trace Plot")
459
+
460
+ st.markdown("---")
461
+
462
+ st.markdown("### 📊 Posterior Plot(後驗分佈)")
463
+ st.markdown("""
464
+ **Posterior Plot 用途**:
465
+ - 顯示參數的後驗分佈
466
+ - 包含 95% HDI(最高密度區間)
467
+ - 顯示平均值
468
+ """)
469
+
470
+ if st.session_state.posterior_img:
471
+ st.image(st.session_state.posterior_img, use_column_width=True)
472
+ else:
473
+ st.info("請先執行分析以生成 Posterior Plot")
474
+
475
+ # Tab: Forest Plot
476
+ with result_tabs[2]:
477
+ st.markdown("### 🌲 Forest Plot(各屬性效應)")
478
+ st.markdown("""
479
+ **Forest Plot 用途**:
480
+ - 顯示每個屬性的速度效應(delta)
481
+ - 點:平均效應
482
+ - 線:95% HDI
483
+ - ★ 標記:顯著正效應(HDI 不包含 0)
484
+ - ☆ 標記:顯著負效應
485
+ """)
486
+
487
+ if st.session_state.forest_img:
488
+ st.image(st.session_state.forest_img, use_column_width=True)
489
+ else:
490
+ st.info("請先執行分析以生成 Forest Plot")
491
+
492
+ # Tab: DAG 模型圖
493
+ with result_tabs[3]:
494
+ st.markdown("### 🔍 模型結構圖 (DAG)")
495
+ st.markdown("""
496
+ **DAG(有向無環圖)用途**:
497
+ - 視覺化模型的階層結構
498
+ - 顯示變數之間的依賴關係
499
+ - 圓形/橢圓:隨機變數
500
+ - 矩形:觀測資料
501
+ - 菱形:推導變數
502
+ """)
503
+
504
+ if st.session_state.dag_img:
505
+ st.image(st.session_state.dag_img, use_column_width=True)
506
+ else:
507
+ st.warning("⚠️ 無法生成 DAG 圖(可能需要安裝 Graphviz)")
508
+ st.markdown("""
509
+ **安裝 Graphviz:**
510
+ - Windows: `choco install graphviz`
511
+ - Mac: `brew install graphviz`
512
+ - Ubuntu: `sudo apt-get install graphviz`
513
+ """)
514
+
515
+ # Tab: 詳細報告
516
+ with result_tabs[4]:
517
+ st.markdown("### 📋 完整分析報告")
518
+
519
+ # 生成文字報告
520
+ text_report = export_results_to_text(results)
521
+
522
+ st.text_area(
523
+ "報告內容",
524
+ text_report,
525
+ height=500
526
+ )
527
+
528
+ # 下載按鈕
529
+ st.download_button(
530
+ label="📥 下載完整報告 (.txt)",
531
+ data=text_report,
532
+ file_name=f"bayesian_report_{results['timestamp'][:10]}.txt",
533
+ mime="text/plain"
534
+ )
535
+
536
+ # Tab 2: AI 助手
537
+ with tab2:
538
+ st.header("💬 AI 分析助手")
539
+
540
+ if not st.session_state.get('api_key'):
541
+ st.warning("⚠️ 請在左側輸入您的 Google Gemini API Key 以使用 AI 助手")
542
+ elif st.session_state.analysis_results is None:
543
+ st.info("ℹ️ 請先在「貝氏分析」頁面執行分析")
544
+ else:
545
+ # 初始化 LLM 助手
546
+ if 'llm_assistant' not in st.session_state:
547
+ api_choice = st.session_state.get('api_choice', 'Google Gemini')
548
+ st.session_state.llm_assistant = BayesianLLMAssistant(
549
+ api_key=st.session_state.api_key,
550
+ session_id=st.session_state.session_id,
551
+ api_provider=api_choice # 新增:傳遞 API 選擇
552
+ )
553
+
554
+ # 聊天容器
555
+ chat_container = st.container()
556
+
557
+ with chat_container:
558
+ for message in st.session_state.chat_history:
559
+ with st.chat_message(message["role"]):
560
+ st.markdown(message["content"])
561
+ # 如果訊息包含 DAG 圖,顯示圖片
562
+ if message.get("has_dag", False) and message.get("dag_image") is not None:
563
+ st.image(message["dag_image"], caption="🎨 生成的 DAG 圖", use_column_width=True)
564
+
565
+
566
+ # 使用者輸入
567
+ if prompt := st.chat_input("詢問關於分析結果的任何問題..."):
568
+ # 添加使用者訊息
569
+ st.session_state.chat_history.append({
570
+ "role": "user",
571
+ "content": prompt
572
+ })
573
+
574
+ with st.chat_message("user"):
575
+ st.markdown(prompt)
576
+
577
+ # AI 回應
578
+ with st.chat_message("assistant"):
579
+ with st.spinner("思考中..."):
580
+ try:
581
+ # 修改:接收回應和可能的 DAG 圖片
582
+ response, dag_image = st.session_state.llm_assistant.get_response(
583
+ user_message=prompt,
584
+ analysis_results=st.session_state.analysis_results
585
+ )
586
+ st.markdown(response)
587
+
588
+ # 如果有生成 DAG 圖,顯示它
589
+ if dag_image is not None:
590
+ st.image(dag_image, caption="🎨 AI 生成的 DAG 圖", use_column_width=True)
591
+ st.success("✨ DAG 圖已生成!你可以繼續詢問圖表相關問題。")
592
+
593
+ except Exception as e:
594
+ error_msg = f"❌ 錯誤: {str(e)}\n\n請檢查 API key 或重新表達問題。"
595
+ st.error(error_msg)
596
+ response = error_msg
597
+ dag_image = None
598
+
599
+ # 添加助手回應(包含 DAG 標記)
600
+ st.session_state.chat_history.append({
601
+ "role": "assistant",
602
+ "content": response,
603
+ "has_dag": dag_image is not None,
604
+ "dag_image": dag_image # 新增:保存圖片
605
+ })
606
+
607
+ st.markdown("---")
608
+
609
+ # 快速問題按鈕
610
+ st.subheader("💡 快速問題")
611
+
612
+ # 添加使用提示
613
+ st.info("💡 提示:你可以要求助手「畫一個 DAG 圖」來視覺化模型結構!")
614
+
615
+ quick_questions = [
616
+ "📊 給我這次分析的總結",
617
+ "🎯 解釋 d 和勝算比",
618
+ "🔍 解釋 sigma(異質性)",
619
+ "❓ 什麼是階層模型?",
620
+ "🎨 畫一個模型結構圖", # 新增 DAG 生成按鈕
621
+ "🆚 貝氏 vs 頻率論",
622
+ "⚔️ 對戰策略建議",
623
+ "🎮 比較不同屬性"
624
+ ]
625
+
626
+ cols = st.columns(4)
627
+ for idx, question in enumerate(quick_questions):
628
+ col_idx = idx % 4
629
+ if cols[col_idx].button(question, key=f"quick_{idx}"):
630
+ # 根據問題選擇對應的方法
631
+ if "總結" in question:
632
+ response = st.session_state.llm_assistant.generate_summary(
633
+ st.session_state.analysis_results
634
+ )
635
+ dag_image = None # 這些方法不返回圖片
636
+ elif "d 和勝算比" in question:
637
+ response = st.session_state.llm_assistant.explain_metric(
638
+ 'd',
639
+ st.session_state.analysis_results
640
+ )
641
+ dag_image = None
642
+ elif "sigma" in question or "異質性" in question:
643
+ response = st.session_state.llm_assistant.explain_metric(
644
+ 'sigma',
645
+ st.session_state.analysis_results
646
+ )
647
+ dag_image = None
648
+ elif "階層模型" in question:
649
+ response = st.session_state.llm_assistant.explain_hierarchical_model()
650
+ dag_image = None
651
+ elif "畫一個" in question or "結構圖" in question:
652
+ # DAG 生成請求
653
+ response, dag_image = st.session_state.llm_assistant.get_response(
654
+ "請畫一個貝氏階層模型的 DAG 圖,並用繁體中文解釋每個節點的意義",
655
+ st.session_state.analysis_results
656
+ )
657
+ elif "貝氏" in question and "頻率論" in question:
658
+ response = st.session_state.llm_assistant.explain_bayesian_vs_frequentist()
659
+ dag_image = None
660
+ elif "策略" in question:
661
+ response = st.session_state.llm_assistant.battle_strategy_advice(
662
+ st.session_state.analysis_results
663
+ )
664
+ dag_image = None
665
+ elif "比較" in question:
666
+ response = st.session_state.llm_assistant.compare_types(
667
+ st.session_state.analysis_results
668
+ )
669
+ dag_image = None
670
+ else:
671
+ response, dag_image = st.session_state.llm_assistant.get_response(
672
+ question,
673
+ st.session_state.analysis_results
674
+ )
675
+
676
+ # 添加到聊天歷史
677
+ st.session_state.chat_history.append({
678
+ "role": "user",
679
+ "content": question
680
+ })
681
+
682
+ st.session_state.chat_history.append({
683
+ "role": "assistant",
684
+ "content": response,
685
+ "has_dag": dag_image is not None if 'dag_image' in locals() else False,
686
+ "dag_image": dag_image if 'dag_image' in locals() else None
687
+ })
688
+
689
+ st.rerun()
690
+
691
+ # 重置對話按鈕
692
+ st.markdown("---")
693
+ if st.button("🔄 重置對話"):
694
+ st.session_state.llm_assistant.reset_conversation()
695
+ st.session_state.chat_history = []
696
+ st.success("✅ 對話已重置")
697
+ st.rerun()
698
+
699
+ # Footer
700
+ st.markdown("---")
701
+ st.markdown(
702
+ f"""
703
+ <div style='text-align: center'>
704
+ <p>🎲 Bayesian Hierarchical Model Analysis for Pokémon Speed | Built with Streamlit & PyMC</p>
705
+ <p>Session ID: {st.session_state.session_id[:8]} | Powered by Google Gemini 2.0 Flash</p>
706
+ </div>
707
+ """,
708
+ unsafe_allow_html=True
709
+ )
bayesian_core.py CHANGED
@@ -1,18 +1,16 @@
1
- import os
2
- import pymc as pm
3
- import numpy as np
4
  import pandas as pd
 
 
5
  import arviz as az
6
- import matplotlib.pyplot as plt
7
- import io
8
- import base64
9
- from datetime import datetime
10
  import threading
 
 
 
11
 
12
  class BayesianHierarchicalAnalyzer:
13
  """
14
  貝氏階層模型分析器
15
- 用於分析寶可夢速度對勝率的影響(屬性分層
16
  """
17
 
18
  # 類別級的鎖,用於執行緒安全
@@ -39,6 +37,13 @@ class BayesianHierarchicalAnalyzer:
39
 
40
  Args:
41
  csv_path_or_df: CSV 檔案路徑或 DataFrame
 
 
 
 
 
 
 
42
  """
43
  if isinstance(csv_path_or_df, str):
44
  self.df = pd.read_csv(csv_path_or_df)
@@ -51,8 +56,29 @@ class BayesianHierarchicalAnalyzer:
51
 
52
  if missing_cols:
53
  raise ValueError(f"資料缺少必要欄位: {missing_cols}")
 
 
54
 
55
- def run_analysis(self, n_samples=2000, n_tune=1000, n_chains=1, target_accept=0.95, progress_callback=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  """
57
  執行貝氏階層模型分析
58
 
@@ -61,243 +87,218 @@ class BayesianHierarchicalAnalyzer:
61
  n_tune: 調整期樣本數
62
  n_chains: 鏈數
63
  target_accept: 目標接受率
64
- progress_callback: 進度回調函數
65
 
66
  Returns:
67
  dict: 包含所有分析結果的字典
68
  """
69
  with self._lock:
70
  try:
71
- if self.df is None:
72
- raise ValueError("請先載入資料")
73
-
74
- if progress_callback:
75
- progress_callback("建立貝氏模型...", 10)
76
 
77
  # 準備資料
78
  trial_labels = self.df['Trial_Type'].values
79
- Num = len(self.df)
80
 
81
- # 建立貝氏模型
82
- with pm.Model() as model:
83
- # 先驗分佈
84
  d = pm.Normal('d', mu=0, sigma=10)
85
  tau = pm.Gamma('tau', alpha=0.001, beta=0.001)
86
  sigma = pm.Deterministic('sigma', 1 / pm.math.sqrt(tau))
87
 
88
- # 各屬性特定效應
89
- mu = pm.Normal('mu', mu=0, sigma=10, shape=Num)
90
- delta = pm.Normal('delta', mu=d, sigma=1 / pm.math.sqrt(tau), shape=Num)
91
 
92
- # 轉換與似然函數
93
  pc = pm.Deterministic('pc', pm.math.invlogit(mu))
94
  pt = pm.Deterministic('pt', pm.math.invlogit(mu + delta))
 
95
  rc_obs = pm.Binomial('rc_obs', n=self.df['nc'].values, p=pc, observed=self.df['rc'].values)
96
  rt_obs = pm.Binomial('rt_obs', n=self.df['nt'].values, p=pt, observed=self.df['rt'].values)
97
 
98
- # 其他統計量
99
  delta_new = pm.Normal('delta_new', mu=d, sigma=1 / pm.math.sqrt(tau))
100
  or_speed = pm.Deterministic('or_speed', pm.math.exp(d))
101
 
102
- # 生成 DAG 圖
103
- if progress_callback:
104
- progress_callback("生成 DAG 模型圖...", 20)
105
-
106
- try:
107
- dag_img = self._generate_dag(model)
108
- except Exception as e:
109
- print(f"DAG 生成失敗: {e}")
110
- dag_img = None
111
-
112
  # 執行 MCMC 抽樣
113
- if progress_callback:
114
- progress_callback("執行貝氏抽樣(這可能需要幾分鐘)...", 30)
115
-
116
- trace = pm.sample(
117
- n_samples,
118
- tune=n_tune,
119
- chains=n_chains,
120
- target_accept=target_accept,
121
  return_inferencedata=True,
122
- progressbar=False
 
123
  )
124
 
125
- self.model = model
126
- self.trace = trace
127
 
128
- if progress_callback:
129
- progress_callback("生成統計摘要...", 60)
 
 
 
130
 
131
- # 生成文字摘要
132
- summary = az.summary(trace, var_names=['d', 'sigma', 'or_speed'], hdi_prob=0.95)
133
- summary_text = self._format_summary(summary)
134
 
135
- if progress_callback:
136
- progress_callback("生成視覺化圖表...", 70)
 
137
 
138
- # 生成圖表
139
- trace_plot = self._generate_trace_plot(trace)
140
- posterior_plot = self._generate_posterior_plot(trace)
141
- forest_plot = self._generate_forest_plot(trace, trial_labels, Num)
142
-
143
- if progress_callback:
144
- progress_callback("整理結果...", 90)
145
 
146
  # 整理結果
147
  results = {
148
- 'trial_labels': trial_labels.tolist(),
149
- 'n_trials': Num,
150
- 'summary_table': summary.to_dict(),
151
- 'summary_text': summary_text,
152
- 'd_mean': float(summary.loc['d', 'mean']),
153
- 'd_sd': float(summary.loc['d', 'sd']),
154
- 'd_hdi_lower': float(summary.loc['d', 'hdi_2.5%']),
155
- 'd_hdi_upper': float(summary.loc['d', 'hdi_97.5%']),
156
- 'sigma_mean': float(summary.loc['sigma', 'mean']),
157
- 'sigma_sd': float(summary.loc['sigma', 'sd']),
158
- 'or_speed_mean': float(summary.loc['or_speed', 'mean']),
159
- 'or_speed_sd': float(summary.loc['or_speed', 'sd']),
160
- 'or_speed_hdi_lower': float(summary.loc['or_speed', 'hdi_2.5%']),
161
- 'or_speed_hdi_upper': float(summary.loc['or_speed', 'hdi_97.5%']),
162
- 'is_significant': summary.loc['d', 'hdi_2.5%'] > 0 or summary.loc['d', 'hdi_97.5%'] < 0,
163
- 'dag_plot': dag_img,
164
- 'trace_plot': trace_plot,
165
- 'posterior_plot': posterior_plot,
166
- 'forest_plot': forest_plot,
167
  'timestamp': datetime.now().isoformat(),
168
- 'sampling_params': {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  'n_samples': n_samples,
170
  'n_tune': n_tune,
171
  'n_chains': n_chains,
172
  'target_accept': target_accept
173
- }
 
 
 
 
 
 
 
 
 
 
 
174
  }
175
 
176
- # 添加各屬性的詳細結果
177
- delta_summary = az.summary(trace, var_names=['delta'], hdi_prob=0.95)
178
- results['delta_results'] = []
179
- for i, trial_type in enumerate(trial_labels):
180
- results['delta_results'].append({
181
- 'trial_type': trial_type,
182
- 'delta_mean': float(delta_summary.iloc[i]['mean']),
183
- 'delta_sd': float(delta_summary.iloc[i]['sd']),
184
- 'delta_hdi_lower': float(delta_summary.iloc[i]['hdi_2.5%']),
185
- 'delta_hdi_upper': float(delta_summary.iloc[i]['hdi_97.5%']),
186
- 'is_significant': delta_summary.iloc[i]['hdi_2.5%'] > 0 or delta_summary.iloc[i]['hdi_97.5%'] < 0
187
- })
188
-
189
  # 儲存到 session results
190
  self._session_results[self.session_id] = results
191
 
192
- if progress_callback:
193
- progress_callback("分析完成!", 100)
194
-
195
  return results
196
 
197
  except Exception as e:
198
  raise Exception(f"分析失敗: {str(e)}")
199
 
200
- def _generate_dag(self, model):
201
- """生成 DAG 圖"""
202
  try:
203
- gv = pm.model_to_graphviz(model)
204
- # 轉換為 PNG 圖片的 base64
205
- png_data = gv.pipe(format='png')
206
- return base64.b64encode(png_data).decode()
207
- except Exception as e:
208
- print(f"DAG 生成失敗: {e}")
209
- return None
210
-
211
- def _generate_trace_plot(self, trace):
212
- """生成 Trace Plot"""
213
- fig, axes = plt.subplots(2, 2, figsize=(14, 8))
214
- az.plot_trace(trace, var_names=['d', 'sigma'], axes=axes)
215
- plt.tight_layout()
216
-
217
- # 轉換為 base64
218
- buf = io.BytesIO()
219
- plt.savefig(buf, format='png', dpi=150, bbox_inches='tight')
220
- buf.seek(0)
221
- img_base64 = base64.b64encode(buf.read()).decode()
222
- plt.close()
223
-
224
- return img_base64
225
-
226
- def _generate_posterior_plot(self, trace):
227
- """生成 Posterior Plot"""
228
- az.plot_posterior(trace, var_names=['d', 'sigma', 'or_speed'], hdi_prob=0.95)
229
-
230
- # 轉換為 base64
231
- buf = io.BytesIO()
232
- plt.savefig(buf, format='png', dpi=150, bbox_inches='tight')
233
- buf.seek(0)
234
- img_base64 = base64.b64encode(buf.read()).decode()
235
- plt.close()
236
-
237
- return img_base64
238
 
239
- def _generate_forest_plot(self, trace, trial_labels, Num):
240
- """生成 Forest Plot"""
241
- delta_posterior = trace.posterior['delta'].values.reshape(-1, Num)
242
- delta_mean = delta_posterior.mean(axis=0)
243
- delta_hdi = az.hdi(trace, var_names=['delta'], hdi_prob=0.95)['delta'].values
244
-
245
- fig, ax = plt.subplots(figsize=(12, max(10, Num * 0.4)))
246
- y_pos = np.arange(Num)
247
-
248
- # 繪製信賴區間
249
- ax.hlines(y_pos, delta_hdi[:, 0], delta_hdi[:, 1], color='steelblue', linewidth=3)
250
- # 繪製平均值
251
- ax.scatter(delta_mean, y_pos, color='darkblue', s=120, zorder=3, edgecolors='white', linewidth=1.5)
252
-
253
- # 標註顯著的屬性
254
- for i, (mean, hdi) in enumerate(zip(delta_mean, delta_hdi)):
255
- if hdi[0] > 0: # 顯著正效應
256
- ax.text(mean + 0.05, i, '★', fontsize=15, ha='left', color='gold', va='center')
257
-
258
- # 設定軸
259
- ax.set_yticks(y_pos)
260
- ax.set_yticklabels(trial_labels, fontsize=11)
261
- ax.invert_yaxis()
262
- ax.axvline(0, color='red', linestyle='--', linewidth=2, label='No Effect (δ=0)')
263
- ax.set_xlabel('Delta (Log Odds Ratio)', fontsize=13)
264
- ax.set_title('Effect of Speed on Win Rate by Type', fontsize=15, fontweight='bold', pad=20)
265
- ax.legend(loc='lower right')
266
- ax.grid(axis='x', alpha=0.3)
267
-
268
- plt.tight_layout()
269
-
270
- # 轉換為 base64
271
- buf = io.BytesIO()
272
- plt.savefig(buf, format='png', dpi=150, bbox_inches='tight')
273
- buf.seek(0)
274
- img_base64 = base64.b64encode(buf.read()).decode()
275
- plt.close()
276
 
277
- return img_base64
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
- def _format_summary(self, summary):
280
- """格式化摘要表格為文字"""
281
- text = "="*70 + "\n"
282
- text += "貝氏階層模型分析結果摘要\n"
283
- text += "Bayesian Hierarchical Model Analysis Summary\n"
284
- text += "="*70 + "\n\n"
285
-
286
- for var in ['d', 'sigma', 'or_speed']:
287
- row = summary.loc[var]
288
- text += f"{var:12} | "
289
- text += f"Mean: {row['mean']:7.4f} | "
290
- text += f"SD: {row['sd']:7.4f} | "
291
- text += f"95% HDI: [{row['hdi_2.5%']:7.4f}, {row['hdi_97.5%']:7.4f}]\n"
292
 
293
- text += "\n" + "="*70 + "\n"
294
- text += "參數說明 (Parameter Descriptions):\n"
295
- text += " d : 整體平均效應 (Overall mean effect)\n"
296
- text += " sigma : 屬性間變異 (Between-type variability)\n"
297
- text += " or_speed : 速度勝算比 (Speed odds ratio = exp(d))\n"
298
- text += "="*70 + "\n"
299
-
300
- return text
301
 
302
  @classmethod
303
  def get_session_results(cls, session_id):
 
 
 
 
1
  import pandas as pd
2
+ import numpy as np
3
+ import pymc as pm
4
  import arviz as az
 
 
 
 
5
  import threading
6
+ from datetime import datetime
7
+ import warnings
8
+ warnings.filterwarnings('ignore')
9
 
10
  class BayesianHierarchicalAnalyzer:
11
  """
12
  貝氏階層模型分析器
13
+ 用於分析寶可夢速度對勝率的影響(屬性)
14
  """
15
 
16
  # 類別級的鎖,用於執行緒安全
 
37
 
38
  Args:
39
  csv_path_or_df: CSV 檔案路徑或 DataFrame
40
+
41
+ Expected columns:
42
+ - Trial_Type: 屬性名稱 (e.g., Water, Fire, Grass)
43
+ - rc: 控制組(速度慢)的勝場數
44
+ - nc: 控制組的總場數
45
+ - rt: 實驗組(速度快)的勝場數
46
+ - nt: 實驗組的總場數
47
  """
48
  if isinstance(csv_path_or_df, str):
49
  self.df = pd.read_csv(csv_path_or_df)
 
56
 
57
  if missing_cols:
58
  raise ValueError(f"資料缺少必要欄位: {missing_cols}")
59
+
60
+ return True
61
 
62
+ def validate_data(self):
63
+ """驗證資料有效性"""
64
+ if self.df is None:
65
+ raise ValueError("請先載入資料")
66
+
67
+ # 檢查數值欄位
68
+ for col in ['rc', 'nc', 'rt', 'nt']:
69
+ if not pd.api.types.is_numeric_dtype(self.df[col]):
70
+ raise ValueError(f"欄位 {col} 必須是數值類型")
71
+
72
+ # 檢查邏輯約束
73
+ if (self.df['rc'] > self.df['nc']).any():
74
+ raise ValueError("rc (勝場數) 不能大於 nc (總場數)")
75
+
76
+ if (self.df['rt'] > self.df['nt']).any():
77
+ raise ValueError("rt (勝場數) 不能大於 nt (總場數)")
78
+
79
+ return True
80
+
81
+ def run_analysis(self, n_samples=2000, n_tune=1000, n_chains=2, target_accept=0.95):
82
  """
83
  執行貝氏階層模型分析
84
 
 
87
  n_tune: 調整期樣本數
88
  n_chains: 鏈數
89
  target_accept: 目標接受率
 
90
 
91
  Returns:
92
  dict: 包含所有分析結果的字典
93
  """
94
  with self._lock:
95
  try:
96
+ self.validate_data()
 
 
 
 
97
 
98
  # 準備資料
99
  trial_labels = self.df['Trial_Type'].values
100
+ num_trials = len(self.df)
101
 
102
+ # 建立模型
103
+ with pm.Model() as self.model:
104
+ # --- 先驗分佈 (Priors) ---
105
  d = pm.Normal('d', mu=0, sigma=10)
106
  tau = pm.Gamma('tau', alpha=0.001, beta=0.001)
107
  sigma = pm.Deterministic('sigma', 1 / pm.math.sqrt(tau))
108
 
109
+ # --- 各屬性特定效應 (Trial-specific effects) ---
110
+ mu = pm.Normal('mu', mu=0, sigma=10, shape=num_trials)
111
+ delta = pm.Normal('delta', mu=d, sigma=1 / pm.math.sqrt(tau), shape=num_trials)
112
 
113
+ # --- 轉換與似然函數 (Logit Link & Likelihood) ---
114
  pc = pm.Deterministic('pc', pm.math.invlogit(mu))
115
  pt = pm.Deterministic('pt', pm.math.invlogit(mu + delta))
116
+
117
  rc_obs = pm.Binomial('rc_obs', n=self.df['nc'].values, p=pc, observed=self.df['rc'].values)
118
  rt_obs = pm.Binomial('rt_obs', n=self.df['nt'].values, p=pt, observed=self.df['rt'].values)
119
 
120
+ # --- 其他統計量 ---
121
  delta_new = pm.Normal('delta_new', mu=d, sigma=1 / pm.math.sqrt(tau))
122
  or_speed = pm.Deterministic('or_speed', pm.math.exp(d))
123
 
 
 
 
 
 
 
 
 
 
 
124
  # 執行 MCMC 抽樣
125
+ self.trace = pm.sample(
126
+ draws=n_samples,
127
+ tune=n_tune,
128
+ chains=n_chains,
129
+ target_accept=target_accept,
 
 
 
130
  return_inferencedata=True,
131
+ progressbar=False, # 在 Streamlit 中關閉進度條
132
+ discard_tuned_samples=False # 👈 加這行!保留 tune 樣本
133
  )
134
 
135
+ # 生成摘要統計
136
+ summary = az.summary(self.trace, var_names=['d', 'sigma', 'or_speed'], hdi_prob=0.95)
137
 
138
+ # 計算各屬性的 delta 統計量
139
+ delta_posterior = self.trace.posterior['delta'].values.reshape(-1, num_trials)
140
+ delta_mean = delta_posterior.mean(axis=0)
141
+ delta_std = delta_posterior.std(axis=0)
142
+ delta_hdi = az.hdi(self.trace, var_names=['delta'], hdi_prob=0.95)['delta'].values
143
 
144
+ # 判斷顯著性(HDI 不包含 0)
145
+ delta_significant = (delta_hdi[:, 0] > 0) | (delta_hdi[:, 1] < 0)
 
146
 
147
+ # 計算控制組和實驗組的勝率
148
+ pc_posterior = self.trace.posterior['pc'].values.reshape(-1, num_trials)
149
+ pt_posterior = self.trace.posterior['pt'].values.reshape(-1, num_trials)
150
 
151
+ pc_mean = pc_posterior.mean(axis=0)
152
+ pt_mean = pt_posterior.mean(axis=0)
 
 
 
 
 
153
 
154
  # 整理結果
155
  results = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  'timestamp': datetime.now().isoformat(),
157
+ 'n_trials': num_trials,
158
+ 'trial_labels': trial_labels.tolist(),
159
+
160
+ # 整體效應
161
+ 'overall': {
162
+ 'd_mean': float(summary.loc['d', 'mean']),
163
+ 'd_sd': float(summary.loc['d', 'sd']),
164
+ 'd_hdi_low': float(summary.loc['d', 'hdi_2.5%']),
165
+ 'd_hdi_high': float(summary.loc['d', 'hdi_97.5%']),
166
+
167
+ 'sigma_mean': float(summary.loc['sigma', 'mean']),
168
+ 'sigma_sd': float(summary.loc['sigma', 'sd']),
169
+ 'sigma_hdi_low': float(summary.loc['sigma', 'hdi_2.5%']),
170
+ 'sigma_hdi_high': float(summary.loc['sigma', 'hdi_97.5%']),
171
+
172
+ 'or_mean': float(summary.loc['or_speed', 'mean']),
173
+ 'or_sd': float(summary.loc['or_speed', 'sd']),
174
+ 'or_hdi_low': float(summary.loc['or_speed', 'hdi_2.5%']),
175
+ 'or_hdi_high': float(summary.loc['or_speed', 'hdi_97.5%']),
176
+ },
177
+
178
+ # 各屬性的效應
179
+ 'by_trial': {
180
+ 'delta_mean': delta_mean.tolist(),
181
+ 'delta_std': delta_std.tolist(),
182
+ 'delta_hdi_low': delta_hdi[:, 0].tolist(),
183
+ 'delta_hdi_high': delta_hdi[:, 1].tolist(),
184
+ 'delta_significant': delta_significant.tolist(),
185
+ 'pc_mean': pc_mean.tolist(),
186
+ 'pt_mean': pt_mean.tolist(),
187
+ },
188
+
189
+ # 原始資料
190
+ 'data': self.df.to_dict('records'),
191
+
192
+ # 模型參數
193
+ 'model_params': {
194
  'n_samples': n_samples,
195
  'n_tune': n_tune,
196
  'n_chains': n_chains,
197
  'target_accept': target_accept
198
+ },
199
+
200
+ # 收斂診斷
201
+ 'diagnostics': self._compute_diagnostics(summary),
202
+
203
+ # 解釋
204
+ 'interpretation': self._interpret_results(
205
+ summary.loc['or_speed', 'mean'],
206
+ summary.loc['or_speed', 'hdi_2.5%'],
207
+ summary.loc['or_speed', 'hdi_97.5%'],
208
+ summary.loc['sigma', 'mean']
209
+ )
210
  }
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  # 儲存到 session results
213
  self._session_results[self.session_id] = results
214
 
 
 
 
215
  return results
216
 
217
  except Exception as e:
218
  raise Exception(f"分析失敗: {str(e)}")
219
 
220
+ def _compute_diagnostics(self, summary):
221
+ """計算收斂診斷指標"""
222
  try:
223
+ # R-hat (應該接近 1.0)
224
+ rhat_d = float(summary.loc['d', 'r_hat']) if 'r_hat' in summary.columns else None
225
+ rhat_sigma = float(summary.loc['sigma', 'r_hat']) if 'r_hat' in summary.columns else None
226
+
227
+ # ESS (有效樣本數)
228
+ ess_d = float(summary.loc['d', 'ess_bulk']) if 'ess_bulk' in summary.columns else None
229
+ ess_sigma = float(summary.loc['sigma', 'ess_bulk']) if 'ess_bulk' in summary.columns else None
230
+
231
+ return {
232
+ 'rhat_d': rhat_d,
233
+ 'rhat_sigma': rhat_sigma,
234
+ 'ess_d': ess_d,
235
+ 'ess_sigma': ess_sigma,
236
+ 'converged': (rhat_d is None or rhat_d < 1.1) and (rhat_sigma is None or rhat_sigma < 1.1)
237
+ }
238
+ except:
239
+ return {
240
+ 'converged': None,
241
+ 'rhat_d': None,
242
+ 'rhat_sigma': None,
243
+ 'ess_d': None,
244
+ 'ess_sigma': None
245
+ }
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
+ def _interpret_results(self, or_mean, or_low, or_high, sigma_mean):
248
+ """解釋分析結果"""
249
+ # 整體效應顯著性
250
+ if or_low > 1:
251
+ overall_effect = "火系寶可夢相對於水系顯著更容易獲勝"
252
+ overall_significance = "顯著正效應"
253
+ elif or_high < 1:
254
+ overall_effect = "水系寶可夢相對於火系顯著更容易獲勝"
255
+ overall_significance = "顯著負效應"
256
+ else:
257
+ overall_effect = "火系與水系勝率無顯著差異"
258
+ overall_significance = "不顯著"
259
+
260
+ # 效果大小
261
+ if or_mean > 2:
262
+ effect_size = "大效果 (OR > 2) - 火系有明顯優勢"
263
+ elif or_mean > 1.5:
264
+ effect_size = "中等效果 (OR > 1.5) - 火系有一定優勢"
265
+ elif or_mean > 1:
266
+ effect_size = "小效果 (OR > 1) - 火系略有優勢"
267
+ elif or_mean == 1:
268
+ effect_size = "無差異 (OR = 1) - 火系與水系勢均力敵"
269
+ elif or_mean > 0.67:
270
+ effect_size = "小效果 (OR < 1) - 水系略有優勢"
271
+ elif or_mean > 0.5:
272
+ effect_size = "中等效果 (OR < 0.67) - 水系有一定優勢"
273
+ else:
274
+ effect_size = "大效果 (OR < 0.5) - 水系有明顯優勢"
275
+
 
 
 
 
 
 
 
 
276
 
277
+ # 異質性評估
278
+ if sigma_mean > 0.5:
279
+ heterogeneity = "高異質性 - 不同配對的勝率差異很大"
280
+ elif sigma_mean > 0.3:
281
+ heterogeneity = "中等異質性 - 不同配對的勝率有一定差異"
282
+ else:
283
+ heterogeneity = "低異質性 - 不同配對的勝率相對一致"
284
+
285
+ return {
286
+ 'overall_effect': overall_effect,
287
+ 'overall_significance': overall_significance,
288
+ 'effect_size': effect_size,
289
+ 'heterogeneity': heterogeneity
290
+ }
291
 
292
+ def get_model_graph(self):
293
+ """生成模型 DAG 圖(返回 graphviz 物件)"""
294
+ if self.model is None:
295
+ raise ValueError("請先執行分析")
 
 
 
 
 
 
 
 
 
296
 
297
+ try:
298
+ gv = pm.model_to_graphviz(self.model)
299
+ return gv
300
+ except Exception as e:
301
+ raise Exception(f"無法生成 DAG 圖: {str(e)}")
 
 
 
302
 
303
  @classmethod
304
  def get_session_results(cls, session_id):
bayesian_llm_assistant.py CHANGED
@@ -1,25 +1,45 @@
1
  import google.generativeai as genai
 
 
 
 
 
2
 
3
  class BayesianLLMAssistant:
4
  """
5
- 貝氏階層模型 LLM 問答助手
6
- 協助用戶理解貝氏分析結果
7
  """
8
-
9
- def __init__(self, api_key, session_id):
10
  """
11
  初始化 LLM 助手
12
 
13
  Args:
14
- api_key: Google Gemini API key
15
  session_id: 唯一的 session 識別碼
 
16
  """
17
- genai.configure(api_key=api_key)
18
- self.model = genai.GenerativeModel('gemini-2.0-flash-exp')
19
  self.session_id = session_id
20
  self.conversation_history = []
21
 
22
- # 系統提示詞(雙語版)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  self.system_prompt = """You are an expert Bayesian statistician specializing in hierarchical models and meta-analysis, particularly in the context of Pokémon battle statistics.
24
 
25
  **IMPORTANT - Language Instruction:**
@@ -28,65 +48,137 @@ class BayesianLLMAssistant:
28
  - If user asks in English, respond in English
29
  - Maintain language consistency throughout the conversation
30
 
31
- 你是一位精通貝氏統計和階層模型的專家,特別專注於寶可夢速度對戰分析。
32
-
33
- Your role is to help users understand Bayesian hierarchical model results for analyzing how Speed affects win rates across different Pokémon types.
34
- 你的角色是幫助使用者理解貝氏階層模型的結果,分析速度如何影響不同屬性寶可夢的勝率。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  You should:
37
- 1. Explain Bayesian concepts in simple, accessible terms (prior, posterior, credible intervals)
38
- 2. Interpret hierarchical modeling and why it's useful (borrowing strength, shrinkage)
39
- 3. Explain what parameters mean (d, delta, sigma, tau)
40
- 4. Discuss posterior distributions and HDI (Highest Density Interval)
41
- 5. Help users understand convergence diagnostics (trace plots, R-hat)
42
- 6. Explain the difference between Bayesian and frequentist approaches
43
- 7. Provide battle strategy insights based on posterior estimates
44
- 8. Discuss uncertainty quantification and practical significance
 
 
45
 
46
  你應該:
47
- 1. 用簡單易懂的方式解釋貝氏概念(先驗、後驗、可信區間)
48
- 2. 詮釋階層模型及其優勢(資訊借用收縮效應
49
- 3. 解釋參數的意義(d、delta、sigma、tau)
50
- 4. 討論後驗分佈和 HDI(最高密度區間)
51
- 5. 幫助使用者理解收斂診斷(trace plot、R-hat)
52
- 6. 解釋貝氏與頻率論方法差異
53
- 7. 根據後驗估計提供對戰策略見解
54
- 8. 不確定性量化和實際顯著性
 
 
55
 
56
  Key concepts to explain when relevant:
57
- 重要概念解釋(當相關時):
 
 
 
 
 
 
 
 
 
58
 
59
- **Bayesian Framework | 貝氏框架:**
60
- - **Prior**: Initial belief before seeing data | 先驗觀察料前的初始信念
61
- - **Likelihood**: Probability of data given parameters | 似然給定參數下資料的機率
62
- - **Posterior**: Updated belief after seeing data | 後驗觀察資料後更新的
63
- - **HDI**: 95% highest density interval (Bayesian CI) | HDI95% 最高密度區間貝氏信賴區間
64
-
65
- **Hierarchical Model Parameters | 階層模型參數:**
66
- - **d**: Overall mean effect across all types | d:所有屬性整體平均效應
67
- - **delta[i]**: Type-specific effect for type i | delta[i]第 i 個屬性的特定效應
68
- - **sigma**: Between-type variability | sigma:屬性間變異性
69
- - **tau**: Precision parameter (1/sigma²) | tau精確度參數(1/sigma²)
70
- - **or_speed**: Odds ratio = exp(d) | or_speed:勝算比 = exp(d)
71
-
72
- **Model Advantages | 模型優勢:**
73
- - Borrows information across types (partial pooling) | 跨屬性資訊借用(部分池化)
74
- - Quantifies uncertainty properly | 正確量化不確定性
75
- - Shrinks unreliable estimates toward overall mean | 將不可靠估計收縮至整體平均
76
- - Handles small sample sizes better | 更好處理小樣本
77
-
78
- **Interpretation Guidelines | 解讀指引:**
79
- - HDI not crossing 0 → significant effect | HDI 不跨越 0 → 效應顯著
80
- - or_speed > 1 → faster Pokémon more likely to win | or_speed > 1 → 速度快更容易獲勝
81
- - Large sigma → high variability between types | sigma 大 → 屬���間差異
82
- - Trace plots should look like "hairy caterpillar" | Trace 圖應該像「毛毛蟲」
83
-
84
- When discussing Pokémon battles:
85
- 討論寶可夢對戰時:
86
- - Explain why Speed matters (turn order, priority moves) | 解釋速度的重要性(回合順序、先制技能)
87
- - Connect type-specific effects to battle mechanics | 將屬性特定效應連結到對戰機制
88
- - Discuss practical implications for team building | 討論組隊的實際意涵
89
- - Consider exceptions (Trick Room, priority moves) | 考慮例外情況(戲法空間、先制招式)
90
 
91
  Always be clear, educational, and engaging. Use examples when helpful.
92
  Format responses with proper markdown for better readability.
@@ -95,14 +187,14 @@ Format responses with proper markdown for better readability.
95
 
96
  def get_response(self, user_message, analysis_results=None):
97
  """
98
- 獲取 AI 回應
99
 
100
  Args:
101
  user_message: 用戶訊息
102
  analysis_results: 分析結果字典(可選)
103
 
104
  Returns:
105
- str: AI 回應
106
  """
107
  # 準備上下文資訊
108
  context = ""
@@ -131,16 +223,32 @@ Format responses with proper markdown for better readability.
131
  # 組合最終提示詞
132
  final_prompt = full_prompt + conversation_text + f"\nUser: {user_message}\n\nAssistant:"
133
 
134
- # 調用 Gemini API
135
- response = self.model.generate_content(
136
- final_prompt,
137
- generation_config=genai.types.GenerationConfig(
138
- temperature=1.0,
139
- max_output_tokens=4000,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  )
141
- )
142
 
143
- assistant_message = response.text
 
144
 
145
  # 添加助手回應到歷史
146
  self.conversation_history.append({
@@ -148,10 +256,55 @@ Format responses with proper markdown for better readability.
148
  "content": assistant_message
149
  })
150
 
151
- return assistant_message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
  except Exception as e:
154
- return f" Error: {str(e)}\n\nPlease check your API key and try again."
 
 
155
 
156
  def _prepare_context(self, results):
157
  """準備分析結果的上下文資訊"""
@@ -159,203 +312,232 @@ Format responses with proper markdown for better readability.
159
  if not results:
160
  return "目前尚無分析結果。No analysis results available yet."
161
 
162
- # 判斷效應方向
163
- if results['d_mean'] > 0:
164
- effect_direction = "faster Pokémon have HIGHER win rates | 速度快的寶可夢有更高的勝率"
165
- else:
166
- effect_direction = "slower Pokémon have HIGHER win rates | 速度慢的寶可夢有更高的勝率"
167
 
168
- # 判斷顯著性
169
- if results['is_significant']:
170
- significance = "YES - The effect is significant | 是 - 效應顯著"
171
- else:
172
- significance = "NO - The effect is not significant | 否 - 效應不顯著"
 
173
 
174
  context = f"""
175
  ## Current Bayesian Hierarchical Model Analysis | 目前的貝氏階層模型分析
176
 
177
- ### Dataset Information | 資料集資訊
178
- - Number of Pokémon Types Analyzed | 分析的屬性: {results['n_trials']}
179
- - Types | 屬性: {', '.join(results['trial_labels'])}
180
-
181
- ### Overall Effect (All Types Combined) | 整體效應(所有屬性合併)
182
-
183
- **d (Overall Mean Effect | 整體平均效應):**
184
- - Mean | 平均: {results['d_mean']:.4f}
185
- - SD | 標準差: {results['d_sd']:.4f}
186
- - 95% HDI | 95% 最高密度區間: [{results['d_hdi_lower']:.4f}, {results['d_hdi_upper']:.4f}]
187
- - **Interpretation | 解讀**: {effect_direction}
188
- - **Is Significant? | 是否顯著?**: {significance}
189
-
190
- **sigma (Between-Type Variability | 屬性間變異):**
191
- - Mean | 平均值: {results['sigma_mean']:.4f}
192
- - SD | 標準差: {results['sigma_sd']:.4f}
193
- - **Interpretation | 解讀**: {"High variability between types | 屬性間差異大" if results['sigma_mean'] > 0.5 else "Moderate variability between types | 屬性間差異中等" if results['sigma_mean'] > 0.2 else "Low variability between types | 屬性間差異小"}
194
-
195
- **or_speed (Speed Odds Ratio | 速度勝算比):**
196
- - Mean | 平均值: {results['or_speed_mean']:.4f}
197
- - SD | 標準差: {results['or_speed_sd']:.4f}
198
- - 95% HDI | 95% 最高密度區間: [{results['or_speed_hdi_lower']:.4f}, {results['or_speed_hdi_upper']:.4f}]
199
- - **Interpretation | 解讀**: {
200
- f"Faster Pokémon are {results['or_speed_mean']:.2f} times more likely to win | 速度快的寶可夢獲勝機率是慢的 {results['or_speed_mean']:.2f} 倍"
201
- if results['or_speed_mean'] > 1
202
- else f"Slower Pokémon are {1/results['or_speed_mean']:.2f} times more likely to win | 速度慢的寶可夢獲勝機率是快的 {1/results['or_speed_mean']:.2f} 倍"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  }
204
 
205
- ### Type-Specific Effects | 屬性特定效應
206
-
207
  """
 
 
 
 
 
208
 
209
- # 添加各屬性的詳細結果
210
- for delta_result in results['delta_results']:
211
- significant_marker = "★" if delta_result['is_significant'] else " "
212
- context += f"\n**{delta_result['trial_type']} {significant_marker}:**\n"
213
- context += f" - Delta Mean | 平均效應: {delta_result['delta_mean']:.4f}\n"
214
- context += f" - 95% HDI: [{delta_result['delta_hdi_lower']:.4f}, {delta_result['delta_hdi_upper']:.4f}]\n"
215
- context += f" - Significant? | 顯著?: {'Yes 是' if delta_result['is_significant'] else 'No 否'}\n"
216
-
217
- context += f"""
218
- ### Model Fitting Information | 模型擬合資訊
219
- - Samples | 樣本數: {results['sampling_params']['n_samples']}
220
- - Tuning samples | 調整樣本數: {results['sampling_params']['n_tune']}
221
- - Chains | 鏈數: {results['sampling_params']['n_chains']}
222
- - Target accept rate | 目標接受率: {results['sampling_params']['target_accept']}
223
-
224
- ### Key Insights | 關鍵洞察
225
- 1. **Overall Pattern | 整體模式**: {effect_direction}
226
- 2. **Heterogeneity | 異質性**: {"Different types show different responses to speed" if results['sigma_mean'] > 0.3 else "Types respond similarly to speed"}
227
- 3. **Significant Types | 顯著屬性**: {sum(1 for dr in results['delta_results'] if dr['is_significant'])} out of {results['n_trials']} types show significant speed effects
228
- """
 
 
 
 
 
 
 
 
 
 
 
229
 
230
- return context
231
 
 
232
  def generate_summary(self, analysis_results):
233
  """自動生成分析結果總結"""
234
 
235
  summary_prompt = """請根據提供的貝氏階層模型分析結果生成一份完整的總結報告,包含:
236
 
237
- 1. **分析目的**:這個模型在研究什麼
238
  2. **整體發現**:
239
- - 速度對勝率整體影響(d 參數)
240
- - 是否具有統計顯著性
241
- - 勝算比告訴我們什麼?
242
  3. **屬性間差異**:
243
- - sigma 參數顯示什麼?
244
- - 哪些屬性對速度特別敏感
245
- - 哪些屬性例外?
246
- 4. **對戰意涵**:這對實戰有什麼啟示
247
- 5. **建議**:訓練師該如何運用這些資訊
 
 
 
248
 
249
  請用清楚的繁體中文 Markdown 格式撰寫,包含適當的章節標題。"""
250
 
251
- return self.get_response(summary_prompt, analysis_results)
 
252
 
253
- def explain_bayesian_concepts(self):
254
- """解釋貝氏統計基本概念"""
255
 
256
- explain_prompt = """請用簡單的方式解釋貝氏統計,特別是在這個寶可夢速度分析的情境下。
257
-
258
- 請涵蓋:
259
- 1. 什麼是貝氏統計?與傳統統計有何不同?
260
- 2. 什麼是先驗、似然、後驗?
261
- 3. 什麼是 HDI(最高密度區間)?與信賴區間有何不同?
262
- 4. 為什麼用貝氏方法分析這個問題?
263
- 5. 如何解讀後驗分佈?
264
-
265
- 請用寶可夢的實際例子讓說明更具體易懂,全程使用繁體中文。"""
266
 
267
- return self.get_response(explain_prompt, None)
268
-
269
- def explain_hierarchical_model(self):
270
- """解釋階層模型的概念"""
271
 
272
- explain_prompt = """請解釋什麼是階層模型(Hierarchical Model),以及為什麼用它來分析不同屬性寶可夢。
273
 
274
- 請涵蓋
275
- 1. 什麼是階層結構?
276
- 2. 什麼是「資訊借用」(borrowing strength)?
277
- 3. 什麼是「收縮效應」(shrinkage)為什麼這很重要?
278
- 4. 在這個分析中,階層模型如何幫助我們
279
- 5. d、delta、sigma 參分別代表什麼
 
 
280
 
281
- 請用具體的寶可夢例子說明,使用繁體中文。"""
282
 
283
- return self.get_response(explain_prompt, None)
 
284
 
285
- def explain_convergence(self):
286
- """解釋收斂診斷"""
287
 
288
- explain_prompt = """請解釋如何判斷 MCMC 抽樣是否收斂以及 Trace Plot 該如何解讀
289
 
290
  請涵蓋:
291
- 1. 什麼是 MCMC 抽樣
292
- 2. 什麼是收斂為什麼重要?
293
- 3. Trace Plot 該如何解讀
294
- 4. 什麼是「毛毛蟲圖」
295
- 5. 如果沒有收斂會怎樣
296
 
297
- 請用簡單語言解釋,使用繁體中文。"""
298
 
299
- return self.get_response(explain_prompt, None)
 
300
 
301
- def compare_types(self, analysis_results):
302
- """比較不同屬性"""
303
 
304
- compare_prompt = """根據各屬性 delta 值請分析哪些寶可夢屬性對速度最敏感,哪些最不敏感
305
 
306
- 提供
307
- 1. 速度效應最大的前 5 個屬性
308
- 2. 速度效應最小的前 5 個屬性
309
- 3. 可能的原因(從對戰機制角度)
310
- 4. 組隊建議
 
311
 
312
- 請用繁體中文回答。"""
313
 
314
- return self.get_response(compare_prompt, analysis_results)
 
315
 
316
  def battle_strategy_advice(self, analysis_results):
317
  """提供對戰策略建議"""
318
 
319
- strategy_prompt = """根據這個貝氏階層模型的分析結果,請為寶可夢訓練師提供實際的對戰策略建議。
320
 
321
  請考慮:
322
- 1. 在組建隊伍時應該多重視速度?
323
- 2. 哪些屬性的寶可夢特別需要速度?
324
- 3. 哪些屬性可以犧牲速度換取其他能力
325
- 4. 有什麼例外情況(如戲法空間隊伍)
326
- 5. 對競技對戰的影響
327
 
328
  請具體且可操作,使用繁體中文回答。"""
329
 
330
- return self.get_response(strategy_prompt, analysis_results)
 
331
 
332
- def explain_metric(self, metric_name, analysis_results):
333
- """解釋特定指標"""
334
-
335
- metric_explanations = {
336
- 'd': 'Overall Mean Effect (d) | 整體平均效應',
337
- 'sigma': 'Between-Type Variability (sigma) | 屬性間變異',
338
- 'or_speed': 'Speed Odds Ratio (or_speed) | 速度勝算比',
339
- 'delta': 'Type-Specific Effects (delta) | 屬性特定效應',
340
- 'hdi': '95% HDI (Highest Density Interval) | 95% 最高密度區間'
341
- }
342
-
343
- metric_display = metric_explanations.get(metric_name, metric_name)
344
 
345
- explain_prompt = f"""請在這次貝氏階層模型分析的脈絡下,解釋以下指標:
346
 
347
- 指標{metric_display}
348
-
349
- 請包含:
350
- 1. 這個指標一般來說測量什麼?
351
- 2. 在本次分析中得到數值是多少
352
- 3. 如何從寶可夢對戰的角度詮釋個數值
353
- 4. 這告訴我們速度的重要性如何?
354
- 5. 有什麼需要注意的限制或注意事項?
355
 
356
  請用繁體中文回答。"""
357
 
358
- return self.get_response(explain_prompt, analysis_results)
 
359
 
360
  def reset_conversation(self):
361
  """重置對話歷史"""
 
1
  import google.generativeai as genai
2
+ import json
3
+ import re
4
+ import graphviz
5
+ import io
6
+ from PIL import Image
7
 
8
  class BayesianLLMAssistant:
9
  """
10
+ 貝氏階層模型 LLM 問答助手(支援動態 DAG 生成)
11
+ 協助用戶理解貝氏分析結果,並可根據描述生成客製化 DAG 圖
12
  """
13
+
14
+ def __init__(self, api_key, session_id, api_provider="Google Gemini"):
15
  """
16
  初始化 LLM 助手
17
 
18
  Args:
19
+ api_key: API key (Gemini Claude)
20
  session_id: 唯一的 session 識別碼
21
+ api_provider: API 提供商 ("Google Gemini" 或 "Anthropic Claude")
22
  """
23
+ self.api_provider = api_provider
 
24
  self.session_id = session_id
25
  self.conversation_history = []
26
 
27
+ if api_provider == "Google Gemini":
28
+ import google.generativeai as genai
29
+ genai.configure(api_key=api_key)
30
+ self.model = genai.GenerativeModel('gemini-2.0-flash-exp')
31
+ self.client = None
32
+ else: # Anthropic Claude
33
+ import anthropic
34
+ self.client = anthropic.Anthropic(api_key=api_key)
35
+ self.model_name = "claude-sonnet-4-5-20250929"
36
+ self.model = None
37
+
38
+
39
+ # 系統提示詞(加入 DAG 生成能力)
40
+ # 完整修改後的 system_prompt
41
+ # 替換 bayesian_llm_assistant.py 第 40-181 行
42
+
43
  self.system_prompt = """You are an expert Bayesian statistician specializing in hierarchical models and meta-analysis, particularly in the context of Pokémon battle statistics.
44
 
45
  **IMPORTANT - Language Instruction:**
 
48
  - If user asks in English, respond in English
49
  - Maintain language consistency throughout the conversation
50
 
51
+ 你是一位精通貝氏階層模型和統合分析統計專家,特別專注於寶可夢對戰統計分析。
52
+
53
+ Your role is to help users understand Bayesian hierarchical model results analyzing
54
+ win rate comparisons between Fire-type and Water-type Pokémon across different matchup pairs.
55
+ 你的角色是幫助使用者理解貝氏階層模型分析結果,
56
+ 了解火系與水系寶可夢在不同配對組合下的勝率比較。
57
+
58
+ **NEW CAPABILITY: DAG Diagram Generation | 新能力:DAG 圖生成**
59
+ When users ask you to draw, create, or visualize a DAG (Directed Acyclic Graph) or model structure, you can generate Graphviz DOT code.
60
+ 當用戶要求你繪製、創建或視覺化 DAG(有向無環圖)或模型結構時,你可以生成 Graphviz DOT 代碼。
61
+
62
+ **How to generate DAG code:**
63
+ 1. Detect requests like: "draw a DAG", "show me the model structure", "visualize the relationships", "畫一個 DAG 圖", "顯示模型結構"
64
+ 2. Generate Graphviz DOT code wrapped in special tags:
65
+ ```graphviz
66
+ digraph G {
67
+ // Your DOT code here
68
+ }
69
+ ```
70
+ 3. The system will automatically render it as an image
71
+
72
+ **IMPORTANT - Font and Label Instructions for DAG:**
73
+ - NEVER use Chinese characters in node labels
74
+ - Use ONLY English labels, or use English + romanized Chinese
75
+ - DO NOT set fontname in the graph
76
+ - Example of good labels: "d (overall effect)" or "delta[i] (pair-specific)"
77
+ - Example of bad labels: "整體效應" or any Chinese text
78
+
79
+ **重要 - DAG 圖的字型和標籤指示:**
80
+ - 絕對不要在節點標籤中使用中文字
81
+ - 只使用英文標籤,或使用「英文 + 拼音」
82
+ - 不要設定 fontname
83
+ - 好的標籤範例:"d (overall effect)" 或 "delta[i] (pair-specific)"
84
+ - 不好的標籤範例:"整體效應" 或任何中文
85
+
86
+ **Example DAG code for Bayesian hierarchical model:**
87
+ ```graphviz
88
+ digraph BayesianModel {
89
+ rankdir=TB;
90
+ node [shape=ellipse, style=filled, fillcolor=lightblue];
91
+
92
+ // Priors
93
+ d [label="d\n(Fire vs Water overall)", fillcolor=lightyellow];
94
+ tau [label="tau\n(precision)", fillcolor=lightyellow];
95
+ sigma [label="sigma = 1/√tau", shape=diamond, fillcolor=lightgray];
96
+
97
+ // Hierarchy
98
+ d -> delta [label="mean"];
99
+ tau -> delta [label="precision"];
100
+ sigma -> delta [style=dashed];
101
+
102
+ delta [label="delta[i]\n(pair-specific)", fillcolor=lightgreen];
103
+ mu [label="mu[i]\n(baseline)", fillcolor=lightyellow];
104
+
105
+ // Likelihood
106
+ delta -> pt [label="effect"];
107
+ mu -> pc;
108
+ mu -> pt;
109
+
110
+ pc [label="pc[i]\n(Water win rate)", shape=diamond, fillcolor=lightgray];
111
+ pt [label="pt[i]\n(Fire win rate)", shape=diamond, fillcolor=lightgray];
112
+
113
+ pc -> rc_obs [label="probability"];
114
+ pt -> rt_obs [label="probability"];
115
+
116
+ rc_obs [label="rc_obs[i]\n(Water wins)", shape=box, fillcolor=lightcoral];
117
+ rt_obs [label="rt_obs[i]\n(Fire wins)", shape=box, fillcolor=lightcoral];
118
+ }
119
+ ```
120
 
121
  You should:
122
+ 1. Explain Bayesian concepts in simple, accessible terms
123
+ 2. Interpret posterior distributions, HDI (Highest Density Interval), and credible intervals
124
+ 3. Explain hierarchical structure and why it's useful
125
+ 4. Help users understand heterogeneity (sigma) between different matchup pairs
126
+ 5. Discuss the practical significance of Fire vs Water type advantages
127
+ 6. Provide insights about which matchup pairs favor Fire-types the most
128
+ 7. Suggest team building strategies based on the statistical findings
129
+ 8. Clarify differences between Bayesian and frequentist approaches
130
+ 9. Explain MCMC diagnostics (R-hat, ESS) when relevant
131
+ 10. **Generate custom DAG diagrams based on user descriptions**
132
 
133
  你應該:
134
+ 1. 用簡單易懂的方式解釋貝氏概念
135
+ 2. 詮釋後驗分佈HDI(最高密度區間和可信區間
136
+ 3. 解釋階層結構及其優勢
137
+ 4. 幫助使用者理解不同配對的異質性(sigma
138
+ 5. 討論火系與水系屬性優劣勢的實際意義
139
+ 6. 提供哪些配對組合中火系最具優勢見解
140
+ 7. 根據發現出組隊策略建議
141
+ 8. 說明貝氏方法與頻率方法的差異
142
+ 9. 適時解釋 MCMC 診斷指標(R-hat、ESS)
143
+ 10. **根據用戶描述生成客製化 DAG 圖**
144
 
145
  Key concepts to explain when relevant:
146
+ - **Bayesian Hierarchical Model**: Borrows strength across matchup pairs, shrinkage effect
147
+ - **Prior & Posterior**: How data updates beliefs
148
+ - **HDI (Highest Density Interval)**: 95% most credible values
149
+ - **d (overall effect)**: Average log odds ratio of Fire vs Water across all pairs
150
+ - **sigma (between-pair variation)**: How much different matchup pairs vary in Fire advantage
151
+ - **delta (pair-specific effects)**: Each matchup pair's individual Fire advantage/disadvantage
152
+ - **Odds Ratio**: exp(d) - how much more likely Fire-types are to win compared to Water-types
153
+ - **MCMC**: Markov Chain Monte Carlo sampling method
154
+ - **Convergence**: R-hat < 1.1, good ESS (effective sample size)
155
+ - **DAG (Directed Acyclic Graph)**: Visual representation of model structure
156
 
157
+ 重要概念解釋(當相關時):
158
+ - **貝氏階層模型**:跨配對借用訊,收縮效應
159
+ - **先驗與後驗**:資料如何更新信念
160
+ - **HDI(最高密度區間)**:95% 最可的數值範圍
161
+ - **d(整體效應)**:火系相對於水系的平均對數勝算比跨所有配對
162
+ - **sigma(配對間變異)**:不同配對組合的火系優勢差異程度
163
+ - **delta(配對特定效應)**:每組配對的個別火系優勢/劣勢
164
+ - **勝算比**:exp(d) - 火系相對於水系獲勝可能性倍數
165
+ - **MCMC**:馬可夫鏈蒙地卡羅抽樣方法
166
+ - **收斂性**:R-hat < 1.1,良好 ESS(有效樣本數)
167
+ - **DAG(有向無環圖)**:模型結構的視覺化表示
168
+
169
+ When discussing Pokémon type matchups:
170
+ - Connect statistical findings to type advantage mechanics (Water typically beats Fire in core games)
171
+ - Explain why Fire vs Water matchups show certain patterns
172
+ - Discuss individual matchup variations and their causes (e.g., specific Pokémon abilities, stats)
173
+ - Identify which Fire/Water Pokémon pairs show unusual results (Fire winning despite type disadvantage)
174
+ - Consider team building and type coverage implications
175
+
176
+ 討論寶可夢屬性對抗時:
177
+ - 將統計發現連結到屬性相剋機制(水系通常剋火系)
178
+ - 解釋火系對水系對戰模式為何呈現特定趨勢
179
+ - 討論個別配對的變異及其可能原因(例如特殊能力、數值差異
180
+ - 識別哪些火/水系配對顯示異常結果(火系儘管屬性不利仍獲勝)
181
+ - 考慮組隊和屬性覆蓋的影響
 
 
 
 
 
 
182
 
183
  Always be clear, educational, and engaging. Use examples when helpful.
184
  Format responses with proper markdown for better readability.
 
187
 
188
  def get_response(self, user_message, analysis_results=None):
189
  """
190
+ 獲取 AI 回應(支援 DAG 生成)
191
 
192
  Args:
193
  user_message: 用戶訊息
194
  analysis_results: 分析結果字典(可選)
195
 
196
  Returns:
197
+ tuple: (回應文字, DAG 圖片或 None)
198
  """
199
  # 準備上下文資訊
200
  context = ""
 
223
  # 組合最終提示詞
224
  final_prompt = full_prompt + conversation_text + f"\nUser: {user_message}\n\nAssistant:"
225
 
226
+
227
+ # 調用對應的 API
228
+ if self.api_provider == "Google Gemini":
229
+ response = self.model.generate_content(
230
+ final_prompt,
231
+ generation_config=genai.types.GenerationConfig(
232
+ temperature=0.7,
233
+ max_output_tokens=4000,
234
+ )
235
+ )
236
+ assistant_message = response.text
237
+
238
+ else: # Anthropic Claude
239
+ response = self.client.messages.create(
240
+ model=self.model_name,
241
+ max_tokens=4000,
242
+ temperature=0.7,
243
+ system=self.system_prompt,
244
+ messages=[
245
+ {"role": "user", "content": final_prompt}
246
+ ]
247
  )
248
+ assistant_message = response.content[0].text
249
 
250
+ # 檢查是否包含 Graphviz 代碼
251
+ dag_image = self._extract_and_render_dag(assistant_message)
252
 
253
  # 添加助手回應到歷史
254
  self.conversation_history.append({
 
256
  "content": assistant_message
257
  })
258
 
259
+ return assistant_message, dag_image
260
+
261
+ except Exception as e:
262
+ error_msg = f"❌ Error: {str(e)}\n\nPlease check your API key and try again."
263
+ return error_msg, None
264
+
265
+
266
+
267
+ def _extract_and_render_dag(self, text):
268
+ """
269
+ 從文字中提取 Graphviz 代碼並渲染成圖片
270
+
271
+ Args:
272
+ text: 包含可能的 Graphviz 代碼的文字
273
+
274
+ Returns:
275
+ PIL Image 或 None
276
+ """
277
+ # 方法 1: 嘗試提取 ```graphviz ... ``` 格式
278
+ pattern1 = r'```graphviz\s*\n(.*?)\n```'
279
+ matches = re.findall(pattern1, text, re.DOTALL)
280
+
281
+ if matches:
282
+ dot_code = matches[0]
283
+ else:
284
+ # 方法 2: 嘗試提取 digraph ... } 格式(沒有 markdown 包裹)
285
+ #pattern2 = r'(digraph\s+\w+\s*\{.*?\n\})'
286
+ pattern2 = r'(digraph\s+\w+\s*\{.*\})'
287
+ matches = re.findall(pattern2, text, re.DOTALL)
288
+
289
+ if not matches:
290
+ return None
291
+
292
+ dot_code = matches[0]
293
+
294
+ try:
295
+ # 使用 Graphviz 渲染
296
+ graph = graphviz.Source(dot_code)
297
+ png_bytes = graph.pipe(format='png')
298
+
299
+ # 轉換為 PIL Image
300
+ img = Image.open(io.BytesIO(png_bytes))
301
+
302
+ return img
303
 
304
  except Exception as e:
305
+ print(f"Failed to render DAG: {e}")
306
+ return None
307
+
308
 
309
  def _prepare_context(self, results):
310
  """準備分析結果的上下文資訊"""
 
312
  if not results:
313
  return "目前尚無分析結果。No analysis results available yet."
314
 
315
+ overall = results['overall']
316
+ interp = results['interpretation']
317
+ diag = results['diagnostics']
 
 
318
 
319
+ # 找出顯著的屬
320
+ sig_types = [
321
+ results['trial_labels'][i]
322
+ for i, sig in enumerate(results['by_trial']['delta_significant'])
323
+ if sig
324
+ ]
325
 
326
  context = f"""
327
  ## Current Bayesian Hierarchical Model Analysis | 目前的貝氏階層模型分析
328
 
329
+ ### Overall Effect | 整體效應
330
+ - **d (Log Odds Ratio) | d(對勝算比)**:
331
+ - Mean | 平均: {overall['d_mean']:.4f}
332
+ - SD | 標準差: {overall['d_sd']:.4f}
333
+ - 95% HDI: [{overall['d_hdi_low']:.4f}, {overall['d_hdi_high']:.4f}]
334
+
335
+ - **sigma (Between-type Variation) | sigma(屬性間變異)**:
336
+ - Mean | 平均: {overall['sigma_mean']:.4f}
337
+ - SD | 標準差: {overall['sigma_sd']:.4f}
338
+ - 95% HDI: [{overall['sigma_hdi_low']:.4f}, {overall['sigma_hdi_high']:.4f}]
339
+
340
+ - **Odds Ratio | 勝算比**:
341
+ - Mean | 平均: {overall['or_mean']:.4f}
342
+ - SD | 標準差: {overall['or_sd']:.4f}
343
+ - 95% HDI: [{overall['or_hdi_low']:.4f}, {overall['or_hdi_high']:.4f}]
344
+
345
+ ### Model Diagnostics | 模型診斷
346
+ - **R-hat (d)**: {f"{diag['rhat_d']:.4f}" if diag['rhat_d'] is not None else 'N/A'} {'✓' if diag['rhat_d'] and diag['rhat_d'] < 1.1 else '✗'}
347
+ - **R-hat (sigma)**: {f"{diag['rhat_sigma']:.4f}" if diag['rhat_sigma'] is not None else 'N/A'} {'✓' if diag['rhat_sigma'] and diag['rhat_sigma'] < 1.1 else '✗'}
348
+ - **ESS (d)**: {int(diag['ess_d']) if diag['ess_d'] is not None else 'N/A'}
349
+ - **ESS (sigma)**: {int(diag['ess_sigma']) if diag['ess_sigma'] is not None else 'N/A'}
350
+ - **Convergence | 收斂狀態**: {'✓ Converged 已收斂' if diag['converged'] else '✗ Not Converged 未收斂'}
351
+
352
+ ### Interpretation | 結果解釋
353
+ - **Overall Effect | 整體效應**: {interp['overall_effect']}
354
+ - **Significance | 顯著性**: {interp['overall_significance']}
355
+ - **Effect Size | 效果大小**: {interp['effect_size']}
356
+ - **Heterogeneity | 異質性**: {interp['heterogeneity']}
357
+
358
+ ### Significant Types | 顯著的屬性
359
+ {len(sig_types)} out of {results['n_trials']} types show significant speed effects:
360
+ {len(sig_types)} 個屬性(共 {results['n_trials']} 個)顯示顯著的速度效應:
361
+ {', '.join(sig_types) if sig_types else 'None 無'}
362
+
363
+ ### Number of Types Analyzed | 分析的屬性數量
364
+ {results['n_trials']} types in total 共 {results['n_trials']} 個屬性
365
+
366
+ ### Key Finding | 關鍵發現
367
+ {
368
+ f"On average, Fire-type Pokémon are {overall['or_mean']:.2f} times more likely to win compared to Water-type (95% HDI: [{overall['or_hdi_low']:.2f}, {overall['or_hdi_high']:.2f}]). 平均而言,火系寶可夢獲勝的可能性是水系的 {overall['or_mean']:.2f} 倍 (95% HDI: [{overall['or_hdi_low']:.2f}, {overall['or_hdi_high']:.2f}])。"
369
+
370
+ if overall['or_mean'] > 1
371
+ else f"Interestingly, the data suggests no clear speed advantage or even a slight disadvantage. 有趣的是,資料顯示速度並無明顯優勢,甚至可能略有劣勢。"
372
  }
373
 
374
+ The variation between types (sigma = {overall['sigma_mean']:.3f}) indicates {interp['heterogeneity'].lower()}.
375
+ 屬性間的變異(sigma = {overall['sigma_mean']:.3f})表示{interp['heterogeneity'].lower()}。
376
  """
377
+ return context
378
+
379
+ def draw_custom_dag(self, description):
380
+ """
381
+ 根據用戶描述生成客製化 DAG 圖
382
 
383
+ Args:
384
+ description: 用戶對 DAG 的描述
385
+
386
+ Returns:
387
+ tuple: (解釋文字, DAG 圖片或 None)
388
+ """
389
+ prompt = f"""Based on the following description, generate a Graphviz DOT code for a DAG diagram:
390
+
391
+ User description: {description}
392
+
393
+ Please:
394
+ 1. Create a clear and informative DAG
395
+ 2. Use appropriate node shapes (ellipse for random variables, box for observed data, diamond for deterministic nodes)
396
+ 3. Use different colors to distinguish node types
397
+ 4. **CRITICAL: Use ONLY English labels - NO Chinese characters in node labels**
398
+ 5. Add labels to explain what each node represents (in English)
399
+ 6. Wrap your DOT code in ```graphviz ``` tags
400
+ 7. Provide a brief explanation in Traditional Chinese about what the diagram shows
401
+
402
+ 根據以下描述,生成 Graphviz DOT 代碼的 DAG 圖:
403
+
404
+ 用戶描述:{description}
405
+
406
+ 請:
407
+ 1. 創建清晰且有資訊性的 DAG
408
+ 2. 使用適當的節點形狀(橢圓代表隨機變數,矩形代表觀測資料,菱形代表確定性節點)
409
+ 3. 使用不同顏色區分節點類型
410
+ 4. **重要:節點標籤必須使用英文,不能使用中文**
411
+ 5. 添加標籤說明每個節點代表什麼(用英文)
412
+ 6. 將 DOT 代碼包在 ```graphviz ``` 標籤中
413
+ 7. 用繁體中文簡要說明圖表顯示什麼"""
414
 
415
+ return self.get_response(prompt, None)
416
 
417
+ # 保留原有的所有方法...
418
  def generate_summary(self, analysis_results):
419
  """自動生成分析結果總結"""
420
 
421
  summary_prompt = """請根據提供的貝氏階層模型分析結果生成一份完整的總結報告,包含:
422
 
423
+ 1. **模型目的**:簡述這個階層模型在分析什麼
424
  2. **整體發現**:
425
+ - 速度對勝率有什麼整體影響
426
+ - d 和勝算比告訴我們什麼
427
+ - HDI 的意義是什麼?
428
  3. **屬性間差異**:
429
+ - sigma 告訴我們什麼?
430
+ - 哪些屬性特別受速度影響
431
+ 4. **模型品質**:
432
+ - 模型收斂得好嗎(R-hat、ESS)
433
+ - 結果可信嗎
434
+ 5. **實戰啟示**:
435
+ - 訓練師如何運用這些資訊?
436
+ - 哪些屬性應該優先考慮速度?
437
 
438
  請用清楚的繁體中文 Markdown 格式撰寫,包含適當的章節標題。"""
439
 
440
+ text, _ = self.get_response(summary_prompt, analysis_results)
441
+ return text
442
 
443
+ def explain_metric(self, metric_name, analysis_results):
444
+ """解釋特定指標"""
445
 
446
+ metric_explanations = {
447
+ 'd': 'd (整體對數勝算比)',
448
+ 'sigma': 'sigma (屬性間變異)',
449
+ 'or_speed': 'Odds Ratio (勝算比)',
450
+ 'hdi': '95% HDI (最高密度區間)',
451
+ 'delta': 'delta (屬性特定效應)',
452
+ 'rhat': 'R-hat (收斂診斷)',
453
+ 'ess': 'ESS (有效樣本數)'
454
+ }
 
455
 
456
+ metric_display = metric_explanations.get(metric_name, metric_name)
 
 
 
457
 
458
+ explain_prompt = f"""請在這次貝氏階層模型分析的脈絡下,解釋以下指標:
459
 
460
+ 指標{metric_display}
461
+
462
+ 請包含:
463
+ 1. 這個指標在貝氏統計中測量什麼?
464
+ 2. 在本次分析中得到的數值是多少
465
+ 3. 如何從寶可夢對戰的角度詮釋這個
466
+ 4. 與頻率論統計的對應指標有何不同?
467
+ 5. 有什麼需要注意的限制或注意事項?
468
 
469
+ 請用繁體中文回答。"""
470
 
471
+ text, _ = self.get_response(explain_prompt, analysis_results)
472
+ return text
473
 
474
+ def explain_bayesian_vs_frequentist(self):
475
+ """解釋貝氏與頻率論的差異"""
476
 
477
+ explain_prompt = """請用簡單的方式解釋貝氏統計和頻率論統計的差異特別是在寶可夢對戰分析的情境下
478
 
479
  請涵蓋:
480
+ 1. 兩者的根本哲學差異是什麼?
481
+ 2. p 值 vs HDI(可信區間)有什麼不同
482
+ 3. 為什麼我們用階層模型來分析多個屬性
483
+ 4. 貝氏方法的優勢和限制是什麼?
484
+ 5. 什麼時候該用貝氏、什麼時候該用頻率論
485
 
486
+ 請用寶可夢實際例子讓說明更具體易懂全程使用繁體中文。"""
487
 
488
+ text, _ = self.get_response(explain_prompt, None)
489
+ return text
490
 
491
+ def explain_hierarchical_model(self):
492
+ """解釋階層模型的概念"""
493
 
494
+ explain_prompt = """請用簡單方式解釋貝氏階層模型特別是在寶可夢屬性分析的情境下
495
 
496
+ 涵蓋
497
+ 1. 什麼是階層模型?為什麼要用階層結構?
498
+ 2. 「借用資訊」(borrowing strength) 是什麼意思?
499
+ 3. 收縮效應 (shrinkage) 如何運作?
500
+ 4. 為什麼階層模型適合分析多個屬性?
501
+ 5. d、sigma、delta 之間的關係是什麼?
502
 
503
+ 請用寶可夢的實際例子讓說明更具體易懂,全程使用繁體中文。"""
504
 
505
+ text, _ = self.get_response(explain_prompt, None)
506
+ return text
507
 
508
  def battle_strategy_advice(self, analysis_results):
509
  """提供對戰策略建議"""
510
 
511
+ strategy_prompt = """根據貝氏階層模型的分析結果,請為寶可夢訓練師提供實際的對戰策略建議。
512
 
513
  請考慮:
514
+ 1. 整體而言,速度對勝率的影響有多大
515
+ 2. 哪些屬性特別受益於速度?哪些不受影響?
516
+ 3. 訓練師在組建隊伍時應該如何權衡速度?
517
+ 4. 有沒有屬性可以忽略速度、專注其他數值
518
+ 5. 對競技對戰有什麼啟示
519
 
520
  請具體且可操作,使用繁體中文回答。"""
521
 
522
+ text, _ = self.get_response(strategy_prompt, analysis_results)
523
+ return text
524
 
525
+ def compare_types(self, analysis_results):
526
+ """比較不同屬性"""
 
 
 
 
 
 
 
 
 
 
527
 
528
+ compare_prompt = """請比較分析結果中不同屬性對速度反應差異。
529
 
530
+ 請說明
531
+ 1. 哪些屬性對速度最敏感?為什麼?
532
+ 2. 哪些屬性對速度不敏感?可能的原因是什麼?
533
+ 3. 屬性間的異質性(sigma)告訴我們什麼?
534
+ 4. 有沒有令人意外發現
535
+ 5. 這些差異對組隊策略有什麼啟示
 
 
536
 
537
  請用繁體中文回答。"""
538
 
539
+ text, _ = self.get_response(compare_prompt, analysis_results)
540
+ return text
541
 
542
  def reset_conversation(self):
543
  """重置對話歷史"""
bayesian_utils.py ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import plotly.graph_objects as go
2
+ import plotly.express as px
3
+ import pandas as pd
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ import matplotlib
7
+ matplotlib.use('Agg') # 使用非互動式後端
8
+ import arviz as az
9
+ import io
10
+ import base64
11
+ from PIL import Image
12
+
13
+ def plot_trace(trace, var_names=['d', 'sigma']):
14
+ """
15
+ 繪製 Trace Plot(MCMC 收斂診斷)
16
+ 包含完整的 warmup + posterior
17
+
18
+ Args:
19
+ trace: ArviZ InferenceData 物件
20
+ var_names: 要繪製的變數名稱
21
+
22
+ Returns:
23
+ PIL Image
24
+ """
25
+ fig, axes = plt.subplots(len(var_names), 2, figsize=(14, 4 * len(var_names)))
26
+ if len(var_names) == 1:
27
+ axes = axes.reshape(1, -1)
28
+
29
+ # 檢查是否有 warmup_posterior
30
+ has_warmup = hasattr(trace, 'warmup_posterior') and trace.warmup_posterior is not None
31
+
32
+ for idx, var_name in enumerate(var_names):
33
+ # 左圖: KDE 密度圖(只用 posterior, 不用 warmup)
34
+ post_data = trace.posterior[var_name].values
35
+ for chain_idx in range(post_data.shape[0]):
36
+ from scipy import stats
37
+ data = post_data[chain_idx].flatten()
38
+ density = stats.gaussian_kde(data)
39
+ xs = np.linspace(data.min(), data.max(), 200)
40
+ axes[idx, 0].plot(xs, density(xs), alpha=0.8, label=f'Chain {chain_idx+1}')
41
+ axes[idx, 0].set_xlabel(var_name, fontsize=12)
42
+ axes[idx, 0].set_ylabel('Density', fontsize=12)
43
+ axes[idx, 0].set_title(f'{var_name}', fontsize=13, fontweight='bold')
44
+ if idx == 0:
45
+ axes[idx, 0].legend()
46
+
47
+ # 右圖: Trace 圖(完整 warmup + posterior)
48
+ if has_warmup:
49
+ # 有 warmup: 合併繪製
50
+ warmup_data = trace.warmup_posterior[var_name].values
51
+ post_data = trace.posterior[var_name].values
52
+
53
+ n_warmup = warmup_data.shape[1]
54
+ n_post = post_data.shape[1]
55
+
56
+ # 定義顏色,讓每條鏈用固定顏色
57
+ colors = plt.cm.tab10.colors # 使用 matplotlib 的顏色循環
58
+
59
+ for chain_idx in range(warmup_data.shape[0]):
60
+ chain_color = colors[chain_idx % len(colors)] # 每條鏈一個固定顏色
61
+
62
+ # 繪 warmup 部分
63
+ x_warmup = np.arange(n_warmup)
64
+ axes[idx, 1].plot(x_warmup, warmup_data[chain_idx].flatten(),
65
+ color=chain_color, # 👈 指定顏色
66
+ alpha=0.7, linewidth=0.5,
67
+ label=f'Chain {chain_idx+1}' if idx == 0 else '')
68
+
69
+ # 繪 posterior 部分 (用同樣的顏色!)
70
+ x_post = np.arange(n_warmup, n_warmup + n_post)
71
+ axes[idx, 1].plot(x_post, post_data[chain_idx].flatten(),
72
+ color=chain_color, # 👈 同一個顏色
73
+ alpha=0.7, linewidth=0.5)
74
+
75
+ # 加 Tune 結束的紅線
76
+ axes[idx, 1].axvline(x=n_warmup, color='red', linestyle='--',
77
+ linewidth=2, alpha=0.7,
78
+ label='Tune結束' if idx == 0 else '')
79
+
80
+
81
+
82
+
83
+ else:
84
+ # 沒有 warmup: 只用 posterior
85
+ post_data = trace.posterior[var_name].values
86
+ for chain_idx in range(post_data.shape[0]):
87
+ axes[idx, 1].plot(post_data[chain_idx].flatten(),
88
+ alpha=0.7, linewidth=0.5,
89
+ label=f'Chain {chain_idx+1}' if idx == 0 else '')
90
+
91
+ axes[idx, 1].set_xlabel('Iteration', fontsize=12)
92
+ axes[idx, 1].set_ylabel(var_name, fontsize=12)
93
+ axes[idx, 1].set_title(f'{var_name} trace', fontsize=13, fontweight='bold')
94
+ if idx == 0:
95
+ axes[idx, 1].legend(loc='upper right', fontsize=9)
96
+ axes[idx, 1].grid(alpha=0.3)
97
+
98
+ plt.tight_layout()
99
+
100
+ # 轉換為圖片
101
+ buf = io.BytesIO()
102
+ plt.savefig(buf, format='png', dpi=300, bbox_inches='tight')
103
+ buf.seek(0)
104
+ img = Image.open(buf)
105
+ plt.close()
106
+
107
+ return img
108
+
109
+
110
+ # ============================================
111
+ # 替換說明:
112
+ # 在 bayesian_utils.py 中,把第 13-51 行的整個 plot_trace 函數
113
+ # 替換成上面這個版本
114
+ # ============================================
115
+
116
+ def plot_posterior(trace, var_names=['d', 'sigma', 'or_speed'], hdi_prob=0.95):
117
+ """
118
+ 繪製後驗分佈圖
119
+
120
+ Args:
121
+ trace: ArviZ InferenceData 物件
122
+ var_names: 要繪製的變數名稱
123
+ hdi_prob: HDI 機率
124
+
125
+ Returns:
126
+ PIL Image
127
+ """
128
+ fig = az.plot_posterior(trace, var_names=var_names, hdi_prob=hdi_prob, figsize=(14, 5))
129
+ plt.tight_layout()
130
+
131
+ # 轉換為圖片
132
+ buf = io.BytesIO()
133
+ plt.savefig(buf, format='png', dpi=300, bbox_inches='tight')
134
+ buf.seek(0)
135
+ img = Image.open(buf)
136
+ plt.close()
137
+
138
+ return img
139
+
140
+ def plot_forest(trace, trial_labels, title='Effect of Speed on Win Rate by Type'):
141
+ """
142
+ 繪製 Forest Plot(各屬性效應)
143
+
144
+ Args:
145
+ trace: ArviZ InferenceData 物件
146
+ trial_labels: 屬性標籤列表
147
+ title: 圖表標題
148
+
149
+ Returns:
150
+ PIL Image
151
+ """
152
+ num_trials = len(trial_labels)
153
+
154
+ # 計算統計量
155
+ delta_posterior = trace.posterior['delta'].values.reshape(-1, num_trials)
156
+ delta_mean = delta_posterior.mean(axis=0)
157
+ delta_hdi = az.hdi(trace, var_names=['delta'], hdi_prob=0.95)['delta'].values
158
+
159
+ # 建立圖表
160
+ fig, ax = plt.subplots(figsize=(12, max(10, num_trials * 0.4)))
161
+ y_pos = np.arange(num_trials)
162
+
163
+ # 繪製信賴區間(橫線)
164
+ ax.hlines(y_pos, delta_hdi[:, 0], delta_hdi[:, 1], color='steelblue', linewidth=3, label='95% HDI')
165
+
166
+ # 繪製平均值(點)
167
+ ax.scatter(delta_mean, y_pos, color='darkblue', s=120, zorder=3,
168
+ edgecolors='white', linewidth=1.5, label='Mean')
169
+
170
+ # 標註顯著的點
171
+ for i, (mean, hdi) in enumerate(zip(delta_mean, delta_hdi)):
172
+ if hdi[0] > 0: # 顯著正效應
173
+ ax.text(mean, i, ' ★', fontsize=15, ha='left', va='center', color='gold')
174
+ elif hdi[1] < 0: # 顯著負效應
175
+ ax.text(mean, i, ' ☆', fontsize=15, ha='left', va='center', color='red')
176
+
177
+ # 設定軸
178
+ ax.set_yticks(y_pos)
179
+ ax.set_yticklabels(trial_labels, fontsize=11)
180
+ ax.invert_yaxis()
181
+ ax.axvline(0, color='red', linestyle='--', linewidth=2, label='No Effect (δ=0)')
182
+ ax.set_xlabel('Delta (Log Odds Ratio)', fontsize=13)
183
+ ax.set_title(title, fontsize=15, fontweight='bold', pad=20)
184
+ ax.legend(loc='lower right')
185
+ ax.grid(axis='x', alpha=0.3)
186
+
187
+ plt.tight_layout()
188
+
189
+ # 轉換為圖片
190
+ buf = io.BytesIO()
191
+ plt.savefig(buf, format='png', dpi=300, bbox_inches='tight')
192
+ buf.seek(0)
193
+ img = Image.open(buf)
194
+ plt.close()
195
+
196
+ return img
197
+
198
+ def plot_model_dag(analyzer):
199
+ """
200
+ 繪製模型 DAG 圖
201
+
202
+ Args:
203
+ analyzer: BayesianHierarchicalAnalyzer 物件
204
+
205
+ Returns:
206
+ PIL Image 或 None
207
+ """
208
+ try:
209
+ gv = analyzer.get_model_graph()
210
+
211
+ # 轉換為 PNG
212
+ png_bytes = gv.pipe(format='png')
213
+
214
+ # 轉換為 PIL Image
215
+ img = Image.open(io.BytesIO(png_bytes))
216
+
217
+ return img
218
+ except Exception as e:
219
+ print(f"無法生成 DAG 圖: {e}")
220
+ return None
221
+
222
+ def create_summary_table(results):
223
+ """
224
+ 創建結果摘要表格
225
+
226
+ Args:
227
+ results: 分析結果字典
228
+
229
+ Returns:
230
+ pandas DataFrame
231
+ """
232
+ overall = results['overall']
233
+
234
+ summary_data = {
235
+ '參數': ['d (整體效應)', 'sigma (屬性間變異)', 'or_speed (勝算比)'],
236
+ '平均值': [
237
+ f"{overall['d_mean']:.4f}",
238
+ f"{overall['sigma_mean']:.4f}",
239
+ f"{overall['or_mean']:.4f}"
240
+ ],
241
+ '標準差': [
242
+ f"{overall['d_sd']:.4f}",
243
+ f"{overall['sigma_sd']:.4f}",
244
+ f"{overall['or_sd']:.4f}"
245
+ ],
246
+ '95% HDI 下界': [
247
+ f"{overall['d_hdi_low']:.4f}",
248
+ f"{overall['sigma_hdi_low']:.4f}",
249
+ f"{overall['or_hdi_low']:.4f}"
250
+ ],
251
+ '95% HDI 上界': [
252
+ f"{overall['d_hdi_high']:.4f}",
253
+ f"{overall['sigma_hdi_high']:.4f}",
254
+ f"{overall['or_hdi_high']:.4f}"
255
+ ]
256
+ }
257
+
258
+ return pd.DataFrame(summary_data)
259
+
260
+ def create_trial_results_table(results):
261
+ """
262
+ 創建各屬性結果表格
263
+
264
+ Args:
265
+ results: 分析結果字典
266
+
267
+ Returns:
268
+ pandas DataFrame
269
+ """
270
+ trial_labels = results['trial_labels']
271
+ by_trial = results['by_trial']
272
+ data = results['data']
273
+
274
+ trial_data = {
275
+ '屬性': trial_labels,
276
+ 'Delta (平均)': [f"{x:.4f}" for x in by_trial['delta_mean']],
277
+ 'Delta (標準差)': [f"{x:.4f}" for x in by_trial['delta_std']],
278
+ '95% HDI 下界': [f"{x:.4f}" for x in by_trial['delta_hdi_low']],
279
+ '95% HDI 上界': [f"{x:.4f}" for x in by_trial['delta_hdi_high']],
280
+ '顯著性': ['★ 顯著' if sig else '不顯著' for sig in by_trial['delta_significant']],
281
+ '控制組勝率': [f"{x:.2%}" for x in by_trial['pc_mean']],
282
+ '實驗組勝率': [f"{x:.2%}" for x in by_trial['pt_mean']],
283
+ '控制組 (勝/總)': [f"{d['rc']}/{d['nc']}" for d in data],
284
+ '實驗組 (勝/總)': [f"{d['rt']}/{d['nt']}" for d in data]
285
+ }
286
+
287
+ return pd.DataFrame(trial_data)
288
+
289
+ def export_results_to_text(results):
290
+ """
291
+ 匯出結果為純文字格式
292
+
293
+ Args:
294
+ results: 分析結果字典
295
+
296
+ Returns:
297
+ str: 格式化的文字報告
298
+ """
299
+ overall = results['overall']
300
+ interp = results['interpretation']
301
+ diag = results['diagnostics']
302
+
303
+ report = f"""
304
+ ==============================================
305
+ 貝氏階層模型分析報告
306
+ ==============================================
307
+
308
+ 分析時間: {results['timestamp']}
309
+ 屬性數量: {results['n_trials']}
310
+
311
+ ----------------------------------------------
312
+ 1. 整體效應摘要
313
+ ----------------------------------------------
314
+ d (整體效應 - Log OR):
315
+ - 平均值: {overall['d_mean']:.4f}
316
+ - 標準差: {overall['d_sd']:.4f}
317
+ - 95% HDI: [{overall['d_hdi_low']:.4f}, {overall['d_hdi_high']:.4f}]
318
+
319
+ sigma (屬性間變異):
320
+ - 平均值: {overall['sigma_mean']:.4f}
321
+ - 標準差: {overall['sigma_sd']:.4f}
322
+ - 95% HDI: [{overall['sigma_hdi_low']:.4f}, {overall['sigma_hdi_high']:.4f}]
323
+
324
+ or_speed (勝算比):
325
+ - 平均值: {overall['or_mean']:.4f}
326
+ - 標準差: {overall['or_sd']:.4f}
327
+ - 95% HDI: [{overall['or_hdi_low']:.4f}, {overall['or_hdi_high']:.4f}]
328
+
329
+ ----------------------------------------------
330
+ 2. 模型收斂診斷
331
+ ----------------------------------------------
332
+ R-hat (d): {f"{diag['rhat_d']:.4f}" if diag['rhat_d'] is not None else 'N/A'}
333
+ R-hat (sigma): {f"{diag['rhat_sigma']:.4f}" if diag['rhat_sigma'] is not None else 'N/A'}
334
+ ESS (d): {int(diag['ess_d']) if diag['ess_d'] is not None else 'N/A'}
335
+ ESS (sigma): {int(diag['ess_sigma']) if diag['ess_sigma'] is not None else 'N/A'}
336
+ 收斂狀態: {'✓ 已收斂' if diag['converged'] else '✗ 未收斂'}
337
+
338
+ ----------------------------------------------
339
+ 3. 結果解釋
340
+ ----------------------------------------------
341
+ 整體效應: {interp['overall_effect']}
342
+ 顯著性: {interp['overall_significance']}
343
+ 效果大小: {interp['effect_size']}
344
+ 異質性: {interp['heterogeneity']}
345
+
346
+ ----------------------------------------------
347
+ 4. 各屬性詳細結果
348
+ ----------------------------------------------
349
+ """
350
+
351
+ # 添加各屬性的詳細資訊
352
+ trial_labels = results['trial_labels']
353
+ by_trial = results['by_trial']
354
+
355
+ for i, label in enumerate(trial_labels):
356
+ sig_marker = "★" if by_trial['delta_significant'][i] else " "
357
+ report += f"""
358
+ {sig_marker} {label}:
359
+ Delta (平均): {by_trial['delta_mean'][i]:.4f}
360
+ 95% HDI: [{by_trial['delta_hdi_low'][i]:.4f}, {by_trial['delta_hdi_high'][i]:.4f}]
361
+ 控制組勝率: {by_trial['pc_mean'][i]:.2%}
362
+ 實驗組勝率: {by_trial['pt_mean'][i]:.2%}
363
+ 勝率差異: {(by_trial['pt_mean'][i] - by_trial['pc_mean'][i]):.2%}
364
+ """
365
+
366
+ report += """
367
+ ==============================================
368
+ """
369
+
370
+ return report
371
+
372
+ def plot_odds_ratio_comparison(results):
373
+ """
374
+ 繪製各屬性的勝算比比較圖(Plotly 版本)
375
+
376
+ Args:
377
+ results: 分析結果字典
378
+
379
+ Returns:
380
+ plotly figure
381
+ """
382
+ trial_labels = results['trial_labels']
383
+ delta_mean = results['by_trial']['delta_mean']
384
+
385
+ # 轉換為勝算比
386
+ or_values = [np.exp(d) for d in delta_mean]
387
+
388
+ # 排序
389
+ sorted_indices = np.argsort(or_values)[::-1]
390
+ sorted_labels = [trial_labels[i] for i in sorted_indices]
391
+ sorted_or = [or_values[i] for i in sorted_indices]
392
+ sorted_sig = [results['by_trial']['delta_significant'][i] for i in sorted_indices]
393
+
394
+ # 顏色標記
395
+ colors = ['#2ecc71' if sig else '#95a5a6' for sig in sorted_sig]
396
+
397
+ fig = go.Figure()
398
+
399
+ fig.add_trace(go.Bar(
400
+ x=sorted_or,
401
+ y=sorted_labels,
402
+ orientation='h',
403
+ marker=dict(
404
+ color=colors,
405
+ line=dict(color='white', width=1)
406
+ ),
407
+ text=[f'{or_val:.2f}' for or_val in sorted_or],
408
+ textposition='outside',
409
+ hovertemplate='%{y}<br>OR: %{x:.3f}<extra></extra>'
410
+ ))
411
+
412
+ # 參考線 (OR = 1)
413
+ fig.add_vline(x=1, line_dash="dash", line_color="red", line_width=2)
414
+
415
+ fig.update_layout(
416
+ title='各屬性速度效應(勝算比)',
417
+ xaxis_title='Odds Ratio',
418
+ yaxis_title='',
419
+ width=800,
420
+ height=max(400, len(trial_labels) * 25),
421
+ template='plotly_white',
422
+ showlegend=False
423
+ )
424
+
425
+ return fig
fire_water_converted.csv ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Trial_Type,rt,nt,rc,nc
2
+ Pair_1,122,133,22,145
3
+ Pair_2,85,132,17,135
4
+ Pair_3,52,129,41,134
5
+ Pair_4,69,126,76,132
6
+ Pair_5,50,125,98,132
7
+ Pair_6,103,122,67,131
8
+ Pair_7,37,122,18,127
9
+ Pair_8,90,119,60,127
10
+ Pair_9,81,119,25,125
11
+ Pair_10,107,117,105,125
12
+ Pair_11,102,116,55,123
13
+ Pair_12,56,116,37,123
14
+ Pair_13,91,115,65,122
15
+ Pair_14,65,115,74,122
16
+ Pair_15,78,114,56,122
17
+ Pair_16,97,112,75,121
18
+ Pair_17,76,112,33,121
19
+ Pair_18,94,112,27,121
20
+ Pair_19,102,112,61,121
21
+ Pair_20,59,111,59,121
22
+ Pair_21,91,111,97,121
23
+ Pair_22,41,110,52,120
24
+ Pair_23,9,109,56,120
25
+ Pair_24,38,109,66,119
26
+ Pair_25,31,108,66,119
27
+ Pair_26,80,108,32,119
28
+ Pair_27,52,107,83,119
29
+ Pair_28,69,107,44,119
30
+ Pair_29,51,106,72,119
31
+ Pair_30,85,106,104,119
32
+ Pair_31,74,106,65,118
33
+ Pair_32,30,105,109,118
34
+ Pair_33,59,104,77,118
35
+ Pair_34,4,104,42,117
36
+ Pair_35,19,104,76,117
37
+ Pair_36,81,103,103,117
38
+ Pair_37,50,102,31,117
39
+ Pair_38,65,102,54,116
40
+ Pair_39,53,101,20,116
41
+ Pair_40,89,101,41,116
42
+ Pair_41,24,101,66,116
43
+ Pair_42,75,98,67,115
44
+ Pair_43,44,97,27,114
45
+ Pair_44,52,96,39,114
46
+ Pair_45,83,92,76,114
47
+ Pair_46,69,89,105,114
requirements.txt CHANGED
@@ -1,10 +1,11 @@
1
- streamlit==1.31.0
2
- pandas==2.1.4
3
- numpy==1.26.3
4
- pymc==5.10.4
5
- pytensor==2.18.6
6
- arviz==0.17.1
7
- matplotlib==3.8.2
8
- scipy==1.11.4
9
  google-generativeai>=0.3.0
10
- graphviz
 
 
 
1
+ streamlit>=1.31.0
2
+ numpy<2.0,>=1.24.0
3
+ pandas>=2.1.0
4
+ plotly>=5.18.0
5
+ pymc>=5.10.0
6
+ arviz>=0.17.0
7
+ matplotlib>=3.8.0
 
8
  google-generativeai>=0.3.0
9
+ pillow>=10.0.0
10
+ graphviz>=0.20.0
11
+ anthropic>=0.18.0
runtime.txt CHANGED
@@ -1 +1 @@
1
- python-3.11
 
1
+ python-3.11