Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- README.md +261 -0
- app.py +657 -0
- bayesian_core.py +264 -0
- llm_assistant.py +278 -0
- requirements.txt +8 -0
- utils.py +409 -0
README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Pokemon Speed Bayesian Analysis System
|
| 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 |
+
# ⚡ Pokemon Speed Bayesian Analysis System
|
| 13 |
+
|
| 14 |
+
A comprehensive web-based system for analyzing the impact of speed on Pokemon win rates using Bayesian hierarchical meta-analysis, powered by AI assistant.
|
| 15 |
+
|
| 16 |
+
## ✨ Features
|
| 17 |
+
|
| 18 |
+
### 🔬 **Bayesian Hierarchical Modeling**
|
| 19 |
+
- PyMC-based MCMC sampling
|
| 20 |
+
- Hierarchical structure to borrow strength across Pokemon types
|
| 21 |
+
- Type-specific and overall effect estimation
|
| 22 |
+
|
| 23 |
+
### 📊 **Interactive Visualizations**
|
| 24 |
+
- **Trace Plots**: Check MCMC convergence
|
| 25 |
+
- **Posterior Distributions**: Visualize parameter uncertainty with HDI
|
| 26 |
+
- **Forest Plots**: Compare effects across Pokemon types
|
| 27 |
+
- **Win Rate Comparisons**: See actual win rate differences
|
| 28 |
+
- **Heterogeneity Analysis**: Understand between-type variation
|
| 29 |
+
|
| 30 |
+
### 🤖 **AI-Powered Assistant**
|
| 31 |
+
- GPT-4 integration for result interpretation
|
| 32 |
+
- Natural language Q&A about analysis results
|
| 33 |
+
- Automatic summary generation
|
| 34 |
+
- Statistical concept explanations
|
| 35 |
+
- Type-specific insights
|
| 36 |
+
|
| 37 |
+
### 📥 **Export Capabilities**
|
| 38 |
+
- JSON format for full results
|
| 39 |
+
- CSV format for type-specific data
|
| 40 |
+
- Downloadable reports
|
| 41 |
+
|
| 42 |
+
## 🚀 Quick Start
|
| 43 |
+
|
| 44 |
+
### Installation
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
# Install dependencies
|
| 48 |
+
pip install -r requirements.txt
|
| 49 |
+
|
| 50 |
+
# Run the application
|
| 51 |
+
streamlit run app.py
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### Usage
|
| 55 |
+
|
| 56 |
+
1. **Configure Settings** (Sidebar)
|
| 57 |
+
- Enter your OpenAI API Key for AI features
|
| 58 |
+
- Upload your data CSV or use example data
|
| 59 |
+
- Adjust MCMC parameters if needed
|
| 60 |
+
|
| 61 |
+
2. **Run Analysis** (Data & Analysis tab)
|
| 62 |
+
- Click "🚀 Run Analysis"
|
| 63 |
+
- Wait for MCMC sampling to complete (2-5 minutes)
|
| 64 |
+
- View results and convergence diagnostics
|
| 65 |
+
|
| 66 |
+
3. **Explore Visualizations** (Visualizations tab)
|
| 67 |
+
- Trace plots for convergence checking
|
| 68 |
+
- Posterior distributions with HDI
|
| 69 |
+
- Forest plots for type comparisons
|
| 70 |
+
- Win rate comparisons
|
| 71 |
+
|
| 72 |
+
4. **Ask Questions** (AI Assistant tab)
|
| 73 |
+
- Use quick question buttons
|
| 74 |
+
- Chat with AI about results
|
| 75 |
+
- Get concept explanations
|
| 76 |
+
- Request improvement suggestions
|
| 77 |
+
|
| 78 |
+
5. **Export Results** (Export Results tab)
|
| 79 |
+
- Download as JSON or CSV
|
| 80 |
+
- Review export preview
|
| 81 |
+
|
| 82 |
+
## 📁 Data Format
|
| 83 |
+
|
| 84 |
+
Your CSV file should contain the following columns:
|
| 85 |
+
|
| 86 |
+
| Column | Description |
|
| 87 |
+
|--------|-------------|
|
| 88 |
+
| `Trial_Type` | Pokemon type name (e.g., "Fire", "Water") |
|
| 89 |
+
| `rc` | Control group (slow) win count |
|
| 90 |
+
| `nc` | Control group total battles |
|
| 91 |
+
| `rt` | Treatment group (fast) win count |
|
| 92 |
+
| `nt` | Treatment group total battles |
|
| 93 |
+
|
| 94 |
+
**Example:**
|
| 95 |
+
|
| 96 |
+
```csv
|
| 97 |
+
Trial_Type,rc,nc,rt,nt
|
| 98 |
+
Fire,45,100,58,100
|
| 99 |
+
Water,52,110,63,105
|
| 100 |
+
Electric,48,95,61,98
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
## 🔬 Statistical Model
|
| 104 |
+
|
| 105 |
+
### Hierarchical Structure
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
Overall Effect (d, τ)
|
| 109 |
+
↓
|
| 110 |
+
Type-Specific Effects (δᵢ, μᵢ)
|
| 111 |
+
↓
|
| 112 |
+
Observed Win Rates (rc, rt)
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### Key Parameters
|
| 116 |
+
|
| 117 |
+
- **d**: Overall log odds ratio of speed effect
|
| 118 |
+
- **OR (Odds Ratio)**: exp(d) - multiplicative effect on odds
|
| 119 |
+
- **σ (sigma)**: Between-type heterogeneity
|
| 120 |
+
- **δᵢ (delta)**: Type-specific speed effects
|
| 121 |
+
- **μᵢ (mu)**: Type-specific baseline win rates
|
| 122 |
+
|
| 123 |
+
### Priors
|
| 124 |
+
|
| 125 |
+
```python
|
| 126 |
+
d ~ Normal(0, 10) # Overall effect
|
| 127 |
+
τ ~ Gamma(0.001, 0.001) # Precision
|
| 128 |
+
σ = 1/√τ # Heterogeneity
|
| 129 |
+
μᵢ ~ Normal(0, 10) # Baseline rates
|
| 130 |
+
δᵢ ~ Normal(d, σ) # Type effects
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## 📊 Interpreting Results
|
| 134 |
+
|
| 135 |
+
### Log Odds Ratio (d)
|
| 136 |
+
- **d > 0**: Speed increases win probability
|
| 137 |
+
- **d < 0**: Speed decreases win probability
|
| 138 |
+
- **d ≈ 0**: No effect
|
| 139 |
+
|
| 140 |
+
### Odds Ratio (OR)
|
| 141 |
+
- **OR = 1.5**: Faster Pokemon have 1.5x the odds of winning
|
| 142 |
+
- **OR = 2.0**: Faster Pokemon have 2x the odds (twice as likely)
|
| 143 |
+
|
| 144 |
+
### 95% HDI (Highest Density Interval)
|
| 145 |
+
- Bayesian credible interval
|
| 146 |
+
- 95% probability the true value falls within this range
|
| 147 |
+
- **HDI excludes 0**: Effect is "statistically credible"
|
| 148 |
+
|
| 149 |
+
### Convergence Diagnostics
|
| 150 |
+
|
| 151 |
+
**R-hat (Gelman-Rubin)**
|
| 152 |
+
- ✅ < 1.01: Excellent convergence
|
| 153 |
+
- ⚠️ 1.01-1.05: Acceptable but check
|
| 154 |
+
- ❌ > 1.05: Poor convergence, resample
|
| 155 |
+
|
| 156 |
+
**ESS (Effective Sample Size)**
|
| 157 |
+
- ✅ > 400: Good
|
| 158 |
+
- ⚠️ 100-400: Marginal
|
| 159 |
+
- ❌ < 100: Insufficient, increase samples
|
| 160 |
+
|
| 161 |
+
## 🤖 AI Assistant Features
|
| 162 |
+
|
| 163 |
+
### Quick Actions
|
| 164 |
+
- **Generate Summary**: Comprehensive analysis overview
|
| 165 |
+
- **Explain Results**: Simple interpretation
|
| 166 |
+
- **Suggest Improvements**: Data and model enhancements
|
| 167 |
+
|
| 168 |
+
### Concept Explanations
|
| 169 |
+
- Log Odds Ratio
|
| 170 |
+
- Odds Ratio
|
| 171 |
+
- HDI (Highest Density Interval)
|
| 172 |
+
- Heterogeneity
|
| 173 |
+
- Hierarchical Model
|
| 174 |
+
- Convergence Diagnostics
|
| 175 |
+
|
| 176 |
+
### Custom Questions
|
| 177 |
+
Ask anything about your analysis:
|
| 178 |
+
- "Which Pokemon type benefits most from speed?"
|
| 179 |
+
- "Is the heterogeneity high in my analysis?"
|
| 180 |
+
- "Should I trust these results based on R-hat?"
|
| 181 |
+
- "What does an odds ratio of 1.6 mean practically?"
|
| 182 |
+
|
| 183 |
+
## 🛠️ Technical Stack
|
| 184 |
+
|
| 185 |
+
- **Backend**: Python 3.8+
|
| 186 |
+
- **Bayesian Inference**: PyMC 5.x
|
| 187 |
+
- **Diagnostics**: ArviZ
|
| 188 |
+
- **Visualization**: Plotly
|
| 189 |
+
- **Web Framework**: Streamlit
|
| 190 |
+
- **AI**: OpenAI GPT-4o-mini
|
| 191 |
+
|
| 192 |
+
## ⚙️ Configuration
|
| 193 |
+
|
| 194 |
+
### MCMC Parameters
|
| 195 |
+
|
| 196 |
+
**Samples** (default: 2000)
|
| 197 |
+
- More samples = more accurate but slower
|
| 198 |
+
- Recommended: 2000-5000 for production
|
| 199 |
+
|
| 200 |
+
**Tuning** (default: 1000)
|
| 201 |
+
- Warm-up iterations discarded
|
| 202 |
+
- Recommended: 500-1500
|
| 203 |
+
|
| 204 |
+
**Target Accept** (default: 0.95)
|
| 205 |
+
- Higher = more accurate but slower
|
| 206 |
+
- Recommended: 0.90-0.98
|
| 207 |
+
|
| 208 |
+
## 🔍 Example Analysis
|
| 209 |
+
|
| 210 |
+
Using the example dataset (18 Pokemon types):
|
| 211 |
+
|
| 212 |
+
**Typical Results:**
|
| 213 |
+
- **Overall Effect (d)**: ~0.35 (95% HDI: [0.18, 0.52])
|
| 214 |
+
- **Odds Ratio**: ~1.42 (faster Pokemon have 42% higher odds)
|
| 215 |
+
- **Heterogeneity (σ)**: ~0.15 (low, effects are consistent across types)
|
| 216 |
+
- **Win Rate Increase**: ~7% on average
|
| 217 |
+
|
| 218 |
+
**Interpretation:**
|
| 219 |
+
> Across all Pokemon types, faster Pokemon have approximately 1.4x the odds of winning compared to slower Pokemon. This translates to an average win rate increase of about 7 percentage points. The effect is relatively consistent across types (low heterogeneity).
|
| 220 |
+
|
| 221 |
+
## ⚠️ Limitations
|
| 222 |
+
|
| 223 |
+
1. **Computational Time**: MCMC can take several minutes
|
| 224 |
+
2. **API Costs**: AI features require OpenAI API credits
|
| 225 |
+
3. **Data Requirements**: Need sufficient sample sizes per type
|
| 226 |
+
4. **Causality**: Analysis shows association, not causation
|
| 227 |
+
5. **Assumptions**: Binary outcomes, independent battles
|
| 228 |
+
|
| 229 |
+
## 📚 References
|
| 230 |
+
|
| 231 |
+
### Statistical Methods
|
| 232 |
+
- Gelman, A. et al. (2013). *Bayesian Data Analysis*
|
| 233 |
+
- Kruschke, J. (2014). *Doing Bayesian Data Analysis*
|
| 234 |
+
|
| 235 |
+
### Software
|
| 236 |
+
- [PyMC Documentation](https://www.pymc.io/)
|
| 237 |
+
- [ArviZ Documentation](https://arviz-devs.github.io/)
|
| 238 |
+
- [Streamlit Documentation](https://docs.streamlit.io/)
|
| 239 |
+
|
| 240 |
+
## 🤝 Contributing
|
| 241 |
+
|
| 242 |
+
Suggestions and improvements welcome! Consider:
|
| 243 |
+
- Adding more visualization types
|
| 244 |
+
- Implementing model comparison (DIC, WAIC)
|
| 245 |
+
- Supporting multiple outcome types
|
| 246 |
+
- Adding more AI assistant features
|
| 247 |
+
|
| 248 |
+
## 📄 License
|
| 249 |
+
|
| 250 |
+
MIT License - feel free to use and modify
|
| 251 |
+
|
| 252 |
+
## 🙏 Acknowledgments
|
| 253 |
+
|
| 254 |
+
- **PyMC Team** for excellent Bayesian modeling tools
|
| 255 |
+
- **OpenAI** for GPT-4 API
|
| 256 |
+
- **Streamlit** for the web framework
|
| 257 |
+
- **Pokemon Community** for inspiring this analysis
|
| 258 |
+
|
| 259 |
+
---
|
| 260 |
+
|
| 261 |
+
**Made with ⚡ for Pokemon trainers who love statistics**
|
app.py
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pokemon Speed Bayesian Analysis System with LLM Assistant
|
| 3 |
+
A comprehensive web application for analyzing speed effects on win rates
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import streamlit as st
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import numpy as np
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
import io
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
# 導入自定義模組
|
| 14 |
+
from bayesian_core import BayesianSpeedAnalyzer
|
| 15 |
+
from llm_assistant import LLMAssistant
|
| 16 |
+
from utils import (
|
| 17 |
+
plot_trace, plot_posterior, plot_forest,
|
| 18 |
+
plot_win_rate_comparison, plot_heterogeneity,
|
| 19 |
+
create_results_table, create_type_results_table
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
# ===== 頁面配置 =====
|
| 23 |
+
st.set_page_config(
|
| 24 |
+
page_title="Pokemon Speed Analysis",
|
| 25 |
+
page_icon="⚡",
|
| 26 |
+
layout="wide",
|
| 27 |
+
initial_sidebar_state="expanded"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# ===== 自定義 CSS =====
|
| 31 |
+
st.markdown("""
|
| 32 |
+
<style>
|
| 33 |
+
.main-header {
|
| 34 |
+
font-size: 2.5rem;
|
| 35 |
+
font-weight: bold;
|
| 36 |
+
color: #2d6ca2;
|
| 37 |
+
text-align: center;
|
| 38 |
+
margin-bottom: 1rem;
|
| 39 |
+
}
|
| 40 |
+
.sub-header {
|
| 41 |
+
font-size: 1.2rem;
|
| 42 |
+
color: #666;
|
| 43 |
+
text-align: center;
|
| 44 |
+
margin-bottom: 2rem;
|
| 45 |
+
}
|
| 46 |
+
.metric-card {
|
| 47 |
+
background-color: #f0f2f6;
|
| 48 |
+
padding: 1rem;
|
| 49 |
+
border-radius: 0.5rem;
|
| 50 |
+
border-left: 4px solid #2d6ca2;
|
| 51 |
+
}
|
| 52 |
+
.stAlert {
|
| 53 |
+
margin-top: 1rem;
|
| 54 |
+
}
|
| 55 |
+
</style>
|
| 56 |
+
""", unsafe_allow_html=True)
|
| 57 |
+
|
| 58 |
+
# ===== Session State 初始化 =====
|
| 59 |
+
if 'analyzer' not in st.session_state:
|
| 60 |
+
st.session_state.analyzer = None
|
| 61 |
+
if 'results' not in st.session_state:
|
| 62 |
+
st.session_state.results = None
|
| 63 |
+
if 'trace' not in st.session_state:
|
| 64 |
+
st.session_state.trace = None
|
| 65 |
+
if 'llm_assistant' not in st.session_state:
|
| 66 |
+
st.session_state.llm_assistant = None
|
| 67 |
+
if 'chat_history' not in st.session_state:
|
| 68 |
+
st.session_state.chat_history = []
|
| 69 |
+
if 'data' not in st.session_state:
|
| 70 |
+
st.session_state.data = None
|
| 71 |
+
|
| 72 |
+
# ===== 側邊欄 =====
|
| 73 |
+
with st.sidebar:
|
| 74 |
+
st.markdown("### ⚙️ Configuration")
|
| 75 |
+
|
| 76 |
+
# OpenAI API Key
|
| 77 |
+
api_key = st.text_input(
|
| 78 |
+
"OpenAI API Key",
|
| 79 |
+
type="password",
|
| 80 |
+
help="Required for AI Assistant features"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
if api_key:
|
| 84 |
+
st.success("✅ API Key provided")
|
| 85 |
+
# 初始化 LLM Assistant
|
| 86 |
+
if st.session_state.llm_assistant is None:
|
| 87 |
+
session_id = f"pokemon_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
| 88 |
+
st.session_state.llm_assistant = LLMAssistant(api_key, session_id)
|
| 89 |
+
else:
|
| 90 |
+
st.warning("⚠️ Enter API Key to enable AI features")
|
| 91 |
+
|
| 92 |
+
st.markdown("---")
|
| 93 |
+
|
| 94 |
+
# 資料上傳
|
| 95 |
+
st.markdown("### 📁 Data Upload")
|
| 96 |
+
|
| 97 |
+
uploaded_file = st.file_uploader(
|
| 98 |
+
"Upload CSV file",
|
| 99 |
+
type=['csv'],
|
| 100 |
+
help="CSV should contain: Trial_Type, rc, nc, rt, nt"
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# 使用範例資料
|
| 104 |
+
use_example = st.checkbox("Use example data", value=True)
|
| 105 |
+
|
| 106 |
+
st.markdown("---")
|
| 107 |
+
|
| 108 |
+
# 分析參數
|
| 109 |
+
st.markdown("### 🔧 Analysis Parameters")
|
| 110 |
+
|
| 111 |
+
n_samples = st.slider(
|
| 112 |
+
"MCMC Samples",
|
| 113 |
+
min_value=500,
|
| 114 |
+
max_value=5000,
|
| 115 |
+
value=2000,
|
| 116 |
+
step=500,
|
| 117 |
+
help="Number of posterior samples to draw"
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
n_tune = st.slider(
|
| 121 |
+
"Tuning Steps",
|
| 122 |
+
min_value=500,
|
| 123 |
+
max_value=3000,
|
| 124 |
+
value=1000,
|
| 125 |
+
step=500,
|
| 126 |
+
help="Number of warm-up iterations"
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
target_accept = st.slider(
|
| 130 |
+
"Target Accept Rate",
|
| 131 |
+
min_value=0.80,
|
| 132 |
+
max_value=0.99,
|
| 133 |
+
value=0.95,
|
| 134 |
+
step=0.01,
|
| 135 |
+
help="MCMC acceptance rate (higher = more accurate but slower)"
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
st.markdown("---")
|
| 139 |
+
|
| 140 |
+
# 關於
|
| 141 |
+
with st.expander("ℹ️ About"):
|
| 142 |
+
st.markdown("""
|
| 143 |
+
**Pokemon Speed Bayesian Analysis**
|
| 144 |
+
|
| 145 |
+
A hierarchical Bayesian meta-analysis system to evaluate
|
| 146 |
+
whether faster Pokemon have higher win rates across different types.
|
| 147 |
+
|
| 148 |
+
**Features:**
|
| 149 |
+
- Bayesian hierarchical modeling
|
| 150 |
+
- MCMC convergence diagnostics
|
| 151 |
+
- Interactive visualizations
|
| 152 |
+
- AI-powered result interpretation
|
| 153 |
+
|
| 154 |
+
**Powered by:**
|
| 155 |
+
- PyMC (Bayesian inference)
|
| 156 |
+
- ArviZ (diagnostics)
|
| 157 |
+
- GPT-4 (AI assistant)
|
| 158 |
+
- Streamlit (web interface)
|
| 159 |
+
""")
|
| 160 |
+
|
| 161 |
+
# ===== 主標題 =====
|
| 162 |
+
st.markdown('<div class="main-header">⚡ Pokemon Speed Bayesian Analysis System</div>', unsafe_allow_html=True)
|
| 163 |
+
st.markdown('<div class="sub-header">Hierarchical Bayesian Meta-Analysis with AI Assistant</div>', unsafe_allow_html=True)
|
| 164 |
+
|
| 165 |
+
# ===== 資料載入 =====
|
| 166 |
+
def load_data():
|
| 167 |
+
"""載入或生成資料"""
|
| 168 |
+
|
| 169 |
+
if uploaded_file is not None:
|
| 170 |
+
try:
|
| 171 |
+
df = pd.read_csv(uploaded_file)
|
| 172 |
+
|
| 173 |
+
# 驗證必要欄位
|
| 174 |
+
required_cols = ['Trial_Type', 'rc', 'nc', 'rt', 'nt']
|
| 175 |
+
missing_cols = [col for col in required_cols if col not in df.columns]
|
| 176 |
+
|
| 177 |
+
if missing_cols:
|
| 178 |
+
st.error(f"❌ Missing required columns: {', '.join(missing_cols)}")
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
st.success(f"✅ Loaded {len(df)} Pokemon types from uploaded file")
|
| 182 |
+
return df
|
| 183 |
+
|
| 184 |
+
except Exception as e:
|
| 185 |
+
st.error(f"❌ Error loading file: {str(e)}")
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
+
elif use_example:
|
| 189 |
+
# 生成範例資料 (18種屬性)
|
| 190 |
+
types = [
|
| 191 |
+
'Normal', 'Fire', 'Water', 'Electric', 'Grass', 'Ice',
|
| 192 |
+
'Fighting', 'Poison', 'Ground', 'Flying', 'Psychic', 'Bug',
|
| 193 |
+
'Rock', 'Ghost', 'Dragon', 'Dark', 'Steel', 'Fairy'
|
| 194 |
+
]
|
| 195 |
+
|
| 196 |
+
np.random.seed(42)
|
| 197 |
+
|
| 198 |
+
data = []
|
| 199 |
+
for ptype in types:
|
| 200 |
+
# 模擬數據:快速寶可夢通常有更高勝率
|
| 201 |
+
base_win_rate = 0.50
|
| 202 |
+
speed_effect = np.random.normal(0.08, 0.03) # 平均 8% 提升,變異 3%
|
| 203 |
+
|
| 204 |
+
nc = np.random.randint(80, 120) # 控制組樣本數
|
| 205 |
+
nt = np.random.randint(80, 120) # 實驗組樣本數
|
| 206 |
+
|
| 207 |
+
pc = np.clip(base_win_rate + np.random.normal(0, 0.05), 0.3, 0.7)
|
| 208 |
+
pt = np.clip(pc + speed_effect, 0.3, 0.7)
|
| 209 |
+
|
| 210 |
+
rc = int(nc * pc)
|
| 211 |
+
rt = int(nt * pt)
|
| 212 |
+
|
| 213 |
+
data.append({
|
| 214 |
+
'Trial_Type': ptype,
|
| 215 |
+
'rc': rc,
|
| 216 |
+
'nc': nc,
|
| 217 |
+
'rt': rt,
|
| 218 |
+
'nt': nt
|
| 219 |
+
})
|
| 220 |
+
|
| 221 |
+
df = pd.DataFrame(data)
|
| 222 |
+
st.info("ℹ️ Using example data (18 Pokemon types)")
|
| 223 |
+
return df
|
| 224 |
+
|
| 225 |
+
return None
|
| 226 |
+
|
| 227 |
+
# 載入資料
|
| 228 |
+
if st.session_state.data is None:
|
| 229 |
+
st.session_state.data = load_data()
|
| 230 |
+
|
| 231 |
+
# ===== 分頁 =====
|
| 232 |
+
tab1, tab2, tab3, tab4 = st.tabs([
|
| 233 |
+
"📊 Data & Analysis",
|
| 234 |
+
"📈 Visualizations",
|
| 235 |
+
"🤖 AI Assistant",
|
| 236 |
+
"📥 Export Results"
|
| 237 |
+
])
|
| 238 |
+
|
| 239 |
+
# ===== Tab 1: 資料與分析 =====
|
| 240 |
+
with tab1:
|
| 241 |
+
if st.session_state.data is not None:
|
| 242 |
+
st.markdown("### 📋 Data Preview")
|
| 243 |
+
|
| 244 |
+
# 顯示資料
|
| 245 |
+
col1, col2 = st.columns([2, 1])
|
| 246 |
+
|
| 247 |
+
with col1:
|
| 248 |
+
st.dataframe(st.session_state.data, use_container_width=True)
|
| 249 |
+
|
| 250 |
+
with col2:
|
| 251 |
+
st.markdown("**Data Summary**")
|
| 252 |
+
st.metric("Total Types", len(st.session_state.data))
|
| 253 |
+
st.metric("Total Battles (Control)", st.session_state.data['nc'].sum())
|
| 254 |
+
st.metric("Total Battles (Treatment)", st.session_state.data['nt'].sum())
|
| 255 |
+
|
| 256 |
+
st.markdown("---")
|
| 257 |
+
|
| 258 |
+
# 執行分析按鈕
|
| 259 |
+
col1, col2, col3 = st.columns([1, 1, 2])
|
| 260 |
+
|
| 261 |
+
with col1:
|
| 262 |
+
if st.button("🚀 Run Analysis", type="primary", use_container_width=True):
|
| 263 |
+
with st.spinner("Running Bayesian MCMC sampling... This may take a few minutes."):
|
| 264 |
+
try:
|
| 265 |
+
# 創建分析器
|
| 266 |
+
analyzer = BayesianSpeedAnalyzer(st.session_state.data)
|
| 267 |
+
|
| 268 |
+
# 建立模型
|
| 269 |
+
analyzer.build_model()
|
| 270 |
+
|
| 271 |
+
# 執行 MCMC
|
| 272 |
+
progress_bar = st.progress(0)
|
| 273 |
+
status_text = st.empty()
|
| 274 |
+
|
| 275 |
+
status_text.text("Building model...")
|
| 276 |
+
progress_bar.progress(20)
|
| 277 |
+
|
| 278 |
+
status_text.text(f"Sampling {n_samples} iterations...")
|
| 279 |
+
trace = analyzer.run_analysis(
|
| 280 |
+
samples=n_samples,
|
| 281 |
+
tune=n_tune,
|
| 282 |
+
target_accept=target_accept
|
| 283 |
+
)
|
| 284 |
+
progress_bar.progress(80)
|
| 285 |
+
|
| 286 |
+
status_text.text("Generating results...")
|
| 287 |
+
|
| 288 |
+
# 儲存結果
|
| 289 |
+
st.session_state.analyzer = analyzer
|
| 290 |
+
st.session_state.trace = trace
|
| 291 |
+
st.session_state.results = analyzer.results
|
| 292 |
+
|
| 293 |
+
progress_bar.progress(100)
|
| 294 |
+
status_text.empty()
|
| 295 |
+
progress_bar.empty()
|
| 296 |
+
|
| 297 |
+
st.success("✅ Analysis completed successfully!")
|
| 298 |
+
st.rerun()
|
| 299 |
+
|
| 300 |
+
except Exception as e:
|
| 301 |
+
st.error(f"❌ Analysis failed: {str(e)}")
|
| 302 |
+
|
| 303 |
+
with col2:
|
| 304 |
+
if st.session_state.results is not None:
|
| 305 |
+
if st.button("🔄 Reset Analysis", use_container_width=True):
|
| 306 |
+
st.session_state.analyzer = None
|
| 307 |
+
st.session_state.results = None
|
| 308 |
+
st.session_state.trace = None
|
| 309 |
+
st.rerun()
|
| 310 |
+
|
| 311 |
+
# 顯示結果
|
| 312 |
+
if st.session_state.results is not None:
|
| 313 |
+
st.markdown("---")
|
| 314 |
+
st.markdown("### 📊 Analysis Results")
|
| 315 |
+
|
| 316 |
+
# 關鍵指標
|
| 317 |
+
stats = st.session_state.results['statistics']
|
| 318 |
+
|
| 319 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 320 |
+
|
| 321 |
+
with col1:
|
| 322 |
+
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
| 323 |
+
st.metric(
|
| 324 |
+
"Log Odds Ratio (d)",
|
| 325 |
+
f"{stats['d_mean']:.3f}",
|
| 326 |
+
delta=f"HDI: [{stats['d_hdi_lower']:.3f}, {stats['d_hdi_upper']:.3f}]"
|
| 327 |
+
)
|
| 328 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 329 |
+
|
| 330 |
+
with col2:
|
| 331 |
+
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
| 332 |
+
st.metric(
|
| 333 |
+
"Odds Ratio (OR)",
|
| 334 |
+
f"{stats['or_mean']:.3f}",
|
| 335 |
+
delta=f"HDI: [{stats['or_hdi_lower']:.3f}, {stats['or_hdi_upper']:.3f}]"
|
| 336 |
+
)
|
| 337 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 338 |
+
|
| 339 |
+
with col3:
|
| 340 |
+
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
| 341 |
+
st.metric(
|
| 342 |
+
"Heterogeneity (σ)",
|
| 343 |
+
f"{stats['sigma_mean']:.3f}",
|
| 344 |
+
delta="Between-type variation"
|
| 345 |
+
)
|
| 346 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 347 |
+
|
| 348 |
+
with col4:
|
| 349 |
+
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
| 350 |
+
st.metric(
|
| 351 |
+
"Avg Win Rate Increase",
|
| 352 |
+
f"{stats['win_rate_increase'].mean():.1f}%",
|
| 353 |
+
delta="Percentage points"
|
| 354 |
+
)
|
| 355 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 356 |
+
|
| 357 |
+
# 解釋
|
| 358 |
+
st.markdown("### 💡 Interpretation")
|
| 359 |
+
interpretation = st.session_state.analyzer.interpret_results()
|
| 360 |
+
st.markdown(interpretation)
|
| 361 |
+
|
| 362 |
+
# 詳細結果表
|
| 363 |
+
st.markdown("### 📋 Detailed Results")
|
| 364 |
+
|
| 365 |
+
col1, col2 = st.columns(2)
|
| 366 |
+
|
| 367 |
+
with col1:
|
| 368 |
+
st.markdown("**Overall Effect Summary**")
|
| 369 |
+
fig_summary = create_results_table(st.session_state.results['summary'])
|
| 370 |
+
st.plotly_chart(fig_summary, use_container_width=True)
|
| 371 |
+
|
| 372 |
+
with col2:
|
| 373 |
+
st.markdown("**Type-Specific Results**")
|
| 374 |
+
trial_results = st.session_state.analyzer.get_trial_specific_results()
|
| 375 |
+
fig_trial = create_type_results_table(trial_results)
|
| 376 |
+
st.plotly_chart(fig_trial, use_container_width=True)
|
| 377 |
+
|
| 378 |
+
# 收斂診斷
|
| 379 |
+
st.markdown("### 🔍 Convergence Diagnostics")
|
| 380 |
+
diagnostics = st.session_state.analyzer.get_convergence_diagnostics()
|
| 381 |
+
|
| 382 |
+
if diagnostics:
|
| 383 |
+
col1, col2 = st.columns(2)
|
| 384 |
+
|
| 385 |
+
with col1:
|
| 386 |
+
st.markdown("**R-hat (Convergence)**")
|
| 387 |
+
st.write("✅ Good: < 1.01, ⚠️ Check: 1.01-1.05, ❌ Poor: > 1.05")
|
| 388 |
+
for param, value in diagnostics['r_hat'].items():
|
| 389 |
+
status = "✅" if value < 1.01 else "⚠️" if value < 1.05 else "❌"
|
| 390 |
+
st.write(f"{status} {param}: {value:.4f}")
|
| 391 |
+
|
| 392 |
+
with col2:
|
| 393 |
+
st.markdown("**ESS (Effective Sample Size)**")
|
| 394 |
+
st.write("✅ Good: > 400, ⚠️ Check: 100-400, ❌ Poor: < 100")
|
| 395 |
+
for param, value in diagnostics['ess_bulk'].items():
|
| 396 |
+
status = "✅" if value > 400 else "⚠️" if value > 100 else "❌"
|
| 397 |
+
st.write(f"{status} {param}: {value:.0f}")
|
| 398 |
+
|
| 399 |
+
else:
|
| 400 |
+
st.warning("⚠️ Please upload data or enable example data in the sidebar")
|
| 401 |
+
|
| 402 |
+
# ===== Tab 2: 視覺化 =====
|
| 403 |
+
with tab2:
|
| 404 |
+
if st.session_state.trace is not None and st.session_state.results is not None:
|
| 405 |
+
st.markdown("### 📈 Visualization Gallery")
|
| 406 |
+
|
| 407 |
+
# Trace Plot
|
| 408 |
+
with st.expander("🔍 Trace Plot (Convergence Check)", expanded=True):
|
| 409 |
+
st.markdown("""
|
| 410 |
+
**How to read:**
|
| 411 |
+
- Left: Sampling trace should look like a "hairy caterpillar" (stationary)
|
| 412 |
+
- Right: Posterior distribution shape
|
| 413 |
+
""")
|
| 414 |
+
fig_trace = plot_trace(st.session_state.trace, var_names=['d', 'sigma'])
|
| 415 |
+
st.plotly_chart(fig_trace, use_container_width=True)
|
| 416 |
+
|
| 417 |
+
# Posterior Plot
|
| 418 |
+
with st.expander("📊 Posterior Distributions", expanded=True):
|
| 419 |
+
st.markdown("""
|
| 420 |
+
**How to read:**
|
| 421 |
+
- Shaded area: 95% Highest Density Interval (credible interval)
|
| 422 |
+
- Red line: Posterior mean
|
| 423 |
+
""")
|
| 424 |
+
fig_posterior = plot_posterior(st.session_state.trace)
|
| 425 |
+
st.plotly_chart(fig_posterior, use_container_width=True)
|
| 426 |
+
|
| 427 |
+
# Forest Plot
|
| 428 |
+
with st.expander("🌲 Forest Plot (Type-Specific Effects)", expanded=True):
|
| 429 |
+
st.markdown("""
|
| 430 |
+
**How to read:**
|
| 431 |
+
- Each row = one Pokemon type
|
| 432 |
+
- Point = mean effect, line = 95% credible interval
|
| 433 |
+
- Red dashed line = no effect (δ=0)
|
| 434 |
+
- Right of line = speed helps, left = speed hurts
|
| 435 |
+
""")
|
| 436 |
+
fig_forest = plot_forest(
|
| 437 |
+
st.session_state.trace,
|
| 438 |
+
st.session_state.results['trial_labels']
|
| 439 |
+
)
|
| 440 |
+
st.plotly_chart(fig_forest, use_container_width=True)
|
| 441 |
+
|
| 442 |
+
# Win Rate Comparison
|
| 443 |
+
with st.expander("🏆 Win Rate Comparison", expanded=True):
|
| 444 |
+
stats = st.session_state.results['statistics']
|
| 445 |
+
fig_winrate = plot_win_rate_comparison(
|
| 446 |
+
st.session_state.results['trial_labels'],
|
| 447 |
+
stats['pc_mean'],
|
| 448 |
+
stats['pt_mean']
|
| 449 |
+
)
|
| 450 |
+
st.plotly_chart(fig_winrate, use_container_width=True)
|
| 451 |
+
|
| 452 |
+
# Heterogeneity
|
| 453 |
+
with st.expander("📉 Heterogeneity Analysis"):
|
| 454 |
+
st.markdown("""
|
| 455 |
+
**Sigma (σ):** Measures variation in speed effects across types
|
| 456 |
+
- Low (< 0.2): Effects are similar across types
|
| 457 |
+
- Moderate (0.2-0.5): Some type-specific differences
|
| 458 |
+
- High (> 0.5): Large differences between types
|
| 459 |
+
""")
|
| 460 |
+
fig_hetero = plot_heterogeneity(st.session_state.trace)
|
| 461 |
+
st.plotly_chart(fig_hetero, use_container_width=True)
|
| 462 |
+
|
| 463 |
+
else:
|
| 464 |
+
st.info("ℹ️ Run analysis first to view visualizations")
|
| 465 |
+
|
| 466 |
+
# ===== Tab 3: AI 助手 =====
|
| 467 |
+
with tab3:
|
| 468 |
+
st.markdown("### 🤖 AI Assistant")
|
| 469 |
+
|
| 470 |
+
if not api_key:
|
| 471 |
+
st.warning("⚠️ Please enter your OpenAI API Key in the sidebar to use AI features")
|
| 472 |
+
|
| 473 |
+
elif st.session_state.llm_assistant is not None:
|
| 474 |
+
# 快捷問題按鈕
|
| 475 |
+
st.markdown("**Quick Questions:**")
|
| 476 |
+
|
| 477 |
+
col1, col2, col3 = st.columns(3)
|
| 478 |
+
|
| 479 |
+
with col1:
|
| 480 |
+
if st.button("📝 Generate Summary", use_container_width=True):
|
| 481 |
+
if st.session_state.results:
|
| 482 |
+
with st.spinner("Generating summary..."):
|
| 483 |
+
response = st.session_state.llm_assistant.generate_summary(
|
| 484 |
+
st.session_state.results
|
| 485 |
+
)
|
| 486 |
+
st.session_state.chat_history.append({
|
| 487 |
+
'role': 'assistant',
|
| 488 |
+
'content': response
|
| 489 |
+
})
|
| 490 |
+
else:
|
| 491 |
+
st.warning("Run analysis first")
|
| 492 |
+
|
| 493 |
+
with col2:
|
| 494 |
+
if st.button("📊 Explain Results", use_container_width=True):
|
| 495 |
+
if st.session_state.results:
|
| 496 |
+
with st.spinner("Explaining..."):
|
| 497 |
+
response = st.session_state.llm_assistant.get_response(
|
| 498 |
+
"Please explain the key findings from this analysis in simple terms.",
|
| 499 |
+
st.session_state.results
|
| 500 |
+
)
|
| 501 |
+
st.session_state.chat_history.append({
|
| 502 |
+
'role': 'assistant',
|
| 503 |
+
'content': response
|
| 504 |
+
})
|
| 505 |
+
else:
|
| 506 |
+
st.warning("Run analysis first")
|
| 507 |
+
|
| 508 |
+
with col3:
|
| 509 |
+
if st.button("💡 Suggest Improvements", use_container_width=True):
|
| 510 |
+
if st.session_state.results:
|
| 511 |
+
with st.spinner("Thinking..."):
|
| 512 |
+
response = st.session_state.llm_assistant.suggest_improvements(
|
| 513 |
+
st.session_state.results
|
| 514 |
+
)
|
| 515 |
+
st.session_state.chat_history.append({
|
| 516 |
+
'role': 'assistant',
|
| 517 |
+
'content': response
|
| 518 |
+
})
|
| 519 |
+
else:
|
| 520 |
+
st.warning("Run analysis first")
|
| 521 |
+
|
| 522 |
+
# 概念解釋按鈕
|
| 523 |
+
st.markdown("**Explain Concepts:**")
|
| 524 |
+
|
| 525 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 526 |
+
|
| 527 |
+
concepts = [
|
| 528 |
+
('Log Odds Ratio', 'log_odds_ratio'),
|
| 529 |
+
('Odds Ratio', 'odds_ratio'),
|
| 530 |
+
('HDI', 'hdi'),
|
| 531 |
+
('Heterogeneity', 'heterogeneity')
|
| 532 |
+
]
|
| 533 |
+
|
| 534 |
+
for i, (label, concept_key) in enumerate(concepts):
|
| 535 |
+
with [col1, col2, col3, col4][i]:
|
| 536 |
+
if st.button(label, use_container_width=True):
|
| 537 |
+
with st.spinner(f"Explaining {label}..."):
|
| 538 |
+
response = st.session_state.llm_assistant.explain_concept(
|
| 539 |
+
concept_key,
|
| 540 |
+
st.session_state.results
|
| 541 |
+
)
|
| 542 |
+
st.session_state.chat_history.append({
|
| 543 |
+
'role': 'assistant',
|
| 544 |
+
'content': response
|
| 545 |
+
})
|
| 546 |
+
|
| 547 |
+
st.markdown("---")
|
| 548 |
+
|
| 549 |
+
# 聊天介面
|
| 550 |
+
st.markdown("**Chat with AI Assistant:**")
|
| 551 |
+
|
| 552 |
+
# 顯示歷史訊息
|
| 553 |
+
for msg in st.session_state.chat_history:
|
| 554 |
+
if msg['role'] == 'user':
|
| 555 |
+
st.markdown(f"**You:** {msg['content']}")
|
| 556 |
+
else:
|
| 557 |
+
st.markdown(f"**AI:** {msg['content']}")
|
| 558 |
+
st.markdown("---")
|
| 559 |
+
|
| 560 |
+
# 輸入框
|
| 561 |
+
user_input = st.text_area(
|
| 562 |
+
"Ask a question about the analysis:",
|
| 563 |
+
height=100,
|
| 564 |
+
placeholder="e.g., Which Pokemon type benefits most from speed?"
|
| 565 |
+
)
|
| 566 |
+
|
| 567 |
+
col1, col2 = st.columns([1, 5])
|
| 568 |
+
|
| 569 |
+
with col1:
|
| 570 |
+
if st.button("Send", type="primary"):
|
| 571 |
+
if user_input:
|
| 572 |
+
# 添加用戶訊息
|
| 573 |
+
st.session_state.chat_history.append({
|
| 574 |
+
'role': 'user',
|
| 575 |
+
'content': user_input
|
| 576 |
+
})
|
| 577 |
+
|
| 578 |
+
# 獲取 AI 回應
|
| 579 |
+
with st.spinner("Thinking..."):
|
| 580 |
+
response = st.session_state.llm_assistant.get_response(
|
| 581 |
+
user_input,
|
| 582 |
+
st.session_state.results
|
| 583 |
+
)
|
| 584 |
+
st.session_state.chat_history.append({
|
| 585 |
+
'role': 'assistant',
|
| 586 |
+
'content': response
|
| 587 |
+
})
|
| 588 |
+
|
| 589 |
+
st.rerun()
|
| 590 |
+
|
| 591 |
+
with col2:
|
| 592 |
+
if st.button("Clear Chat"):
|
| 593 |
+
st.session_state.chat_history = []
|
| 594 |
+
st.session_state.llm_assistant.reset_conversation()
|
| 595 |
+
st.rerun()
|
| 596 |
+
|
| 597 |
+
# ===== Tab 4: 匯出結果 =====
|
| 598 |
+
with tab4:
|
| 599 |
+
st.markdown("### 📥 Export Results")
|
| 600 |
+
|
| 601 |
+
if st.session_state.results is not None:
|
| 602 |
+
# 準備匯出資料
|
| 603 |
+
export_data = {
|
| 604 |
+
'timestamp': st.session_state.results['timestamp'],
|
| 605 |
+
'overall_statistics': {
|
| 606 |
+
'd_mean': float(st.session_state.results['statistics']['d_mean']),
|
| 607 |
+
'd_hdi': [
|
| 608 |
+
float(st.session_state.results['statistics']['d_hdi_lower']),
|
| 609 |
+
float(st.session_state.results['statistics']['d_hdi_upper'])
|
| 610 |
+
],
|
| 611 |
+
'or_mean': float(st.session_state.results['statistics']['or_mean']),
|
| 612 |
+
'or_hdi': [
|
| 613 |
+
float(st.session_state.results['statistics']['or_hdi_lower']),
|
| 614 |
+
float(st.session_state.results['statistics']['or_hdi_upper'])
|
| 615 |
+
],
|
| 616 |
+
'sigma_mean': float(st.session_state.results['statistics']['sigma_mean'])
|
| 617 |
+
},
|
| 618 |
+
'type_results': st.session_state.analyzer.get_trial_specific_results().to_dict('records')
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
# JSON 下載
|
| 622 |
+
st.markdown("**Download as JSON:**")
|
| 623 |
+
json_str = json.dumps(export_data, indent=2)
|
| 624 |
+
st.download_button(
|
| 625 |
+
label="📄 Download JSON",
|
| 626 |
+
data=json_str,
|
| 627 |
+
file_name=f"pokemon_speed_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
| 628 |
+
mime="application/json"
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
# CSV 下載
|
| 632 |
+
st.markdown("**Download Type Results as CSV:**")
|
| 633 |
+
csv_buffer = io.StringIO()
|
| 634 |
+
st.session_state.analyzer.get_trial_specific_results().to_csv(csv_buffer, index=False)
|
| 635 |
+
st.download_button(
|
| 636 |
+
label="📊 Download CSV",
|
| 637 |
+
data=csv_buffer.getvalue(),
|
| 638 |
+
file_name=f"pokemon_type_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
| 639 |
+
mime="text/csv"
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
# 顯示摘要
|
| 643 |
+
st.markdown("---")
|
| 644 |
+
st.markdown("### 📋 Export Preview")
|
| 645 |
+
st.json(export_data)
|
| 646 |
+
|
| 647 |
+
else:
|
| 648 |
+
st.info("ℹ️ Run analysis first to export results")
|
| 649 |
+
|
| 650 |
+
# ===== Footer =====
|
| 651 |
+
st.markdown("---")
|
| 652 |
+
st.markdown("""
|
| 653 |
+
<div style='text-align: center; color: #666; font-size: 0.9rem;'>
|
| 654 |
+
<p>Pokemon Speed Bayesian Analysis System | Powered by PyMC, ArviZ, GPT-4, and Streamlit</p>
|
| 655 |
+
<p>⚡ Analyzing the impact of speed on win rates across Pokemon types ⚡</p>
|
| 656 |
+
</div>
|
| 657 |
+
""", unsafe_allow_html=True)
|
bayesian_core.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Bayesian Meta-Analysis Core for Pokemon Speed Analysis
|
| 3 |
+
Using PyMC for hierarchical Bayesian modeling
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import pymc as pm
|
| 7 |
+
import numpy as np
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import arviz as az
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class BayesianSpeedAnalyzer:
|
| 14 |
+
"""
|
| 15 |
+
貝葉斯階層式分析器
|
| 16 |
+
分析速度對不同屬性寶可夢勝率的影響
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self, data):
|
| 20 |
+
"""
|
| 21 |
+
初始化分析器
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
data: DataFrame 包含欄位:
|
| 25 |
+
- Trial_Type: 屬性名稱
|
| 26 |
+
- rc: 控制組勝場數
|
| 27 |
+
- nc: 控制組總場數
|
| 28 |
+
- rt: 實驗組勝場數
|
| 29 |
+
- nt: 實驗組總場數
|
| 30 |
+
"""
|
| 31 |
+
self.data = data
|
| 32 |
+
self.trial_labels = data['Trial_Type'].values
|
| 33 |
+
self.num_trials = len(data)
|
| 34 |
+
self.model = None
|
| 35 |
+
self.trace = None
|
| 36 |
+
self.results = None
|
| 37 |
+
|
| 38 |
+
def build_model(self):
|
| 39 |
+
"""建立貝葉斯階層式模型"""
|
| 40 |
+
|
| 41 |
+
with pm.Model() as model:
|
| 42 |
+
# ===== 先驗分佈 (Priors) =====
|
| 43 |
+
# d: 整體速度效應 (log odds ratio)
|
| 44 |
+
d = pm.Normal('d', mu=0, sigma=10)
|
| 45 |
+
|
| 46 |
+
# tau: 精度參數 (控制屬性間變異)
|
| 47 |
+
tau = pm.Gamma('tau', alpha=0.001, beta=0.001)
|
| 48 |
+
|
| 49 |
+
# sigma: 標準差 (由 tau 導出)
|
| 50 |
+
sigma = pm.Deterministic('sigma', 1 / pm.math.sqrt(tau))
|
| 51 |
+
|
| 52 |
+
# ===== 各屬性特定參數 =====
|
| 53 |
+
# mu: 各屬性基準勝率 (logit scale)
|
| 54 |
+
mu = pm.Normal('mu', mu=0, sigma=10, shape=self.num_trials)
|
| 55 |
+
|
| 56 |
+
# delta: 各屬性的速度效應
|
| 57 |
+
delta = pm.Normal(
|
| 58 |
+
'delta',
|
| 59 |
+
mu=d,
|
| 60 |
+
sigma=1 / pm.math.sqrt(tau),
|
| 61 |
+
shape=self.num_trials
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# ===== 轉換與似然函數 =====
|
| 65 |
+
# pc: 控制組(慢速)勝率
|
| 66 |
+
pc = pm.Deterministic('pc', pm.math.invlogit(mu))
|
| 67 |
+
|
| 68 |
+
# pt: 實驗組(快速)勝率
|
| 69 |
+
pt = pm.Deterministic('pt', pm.math.invlogit(mu + delta))
|
| 70 |
+
|
| 71 |
+
# 觀測資料的似然函數
|
| 72 |
+
rc_obs = pm.Binomial(
|
| 73 |
+
'rc_obs',
|
| 74 |
+
n=self.data['nc'].values,
|
| 75 |
+
p=pc,
|
| 76 |
+
observed=self.data['rc'].values
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
rt_obs = pm.Binomial(
|
| 80 |
+
'rt_obs',
|
| 81 |
+
n=self.data['nt'].values,
|
| 82 |
+
p=pt,
|
| 83 |
+
observed=self.data['rt'].values
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# ===== 導出統計量 =====
|
| 87 |
+
# 預測新屬性的效應
|
| 88 |
+
delta_new = pm.Normal('delta_new', mu=d, sigma=1 / pm.math.sqrt(tau))
|
| 89 |
+
|
| 90 |
+
# 勝率比 (Odds Ratio)
|
| 91 |
+
or_speed = pm.Deterministic('or_speed', pm.math.exp(d))
|
| 92 |
+
|
| 93 |
+
self.model = model
|
| 94 |
+
return model
|
| 95 |
+
|
| 96 |
+
def run_analysis(self, samples=2000, tune=1000, chains=1, target_accept=0.95, progress_callback=None):
|
| 97 |
+
"""
|
| 98 |
+
執行 MCMC 抽樣
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
samples: 抽樣次數
|
| 102 |
+
tune: 暖身迭代次數
|
| 103 |
+
chains: 鏈數量
|
| 104 |
+
target_accept: 目標接受率
|
| 105 |
+
progress_callback: 進度回調函數 (可選)
|
| 106 |
+
|
| 107 |
+
Returns:
|
| 108 |
+
trace: InferenceData 物件
|
| 109 |
+
"""
|
| 110 |
+
if self.model is None:
|
| 111 |
+
self.build_model()
|
| 112 |
+
|
| 113 |
+
with self.model:
|
| 114 |
+
self.trace = pm.sample(
|
| 115 |
+
samples,
|
| 116 |
+
tune=tune,
|
| 117 |
+
chains=chains,
|
| 118 |
+
target_accept=target_accept,
|
| 119 |
+
return_inferencedata=True,
|
| 120 |
+
progressbar=False # Streamlit 中關閉進度條
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# 生成分析結果
|
| 124 |
+
self._generate_results()
|
| 125 |
+
|
| 126 |
+
return self.trace
|
| 127 |
+
|
| 128 |
+
def _generate_results(self):
|
| 129 |
+
"""生成分析結果摘要"""
|
| 130 |
+
|
| 131 |
+
# 主要參數摘要
|
| 132 |
+
summary = az.summary(
|
| 133 |
+
self.trace,
|
| 134 |
+
var_names=['d', 'sigma', 'or_speed'],
|
| 135 |
+
hdi_prob=0.95
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
# 各屬性效應摘要
|
| 139 |
+
delta_summary = az.summary(
|
| 140 |
+
self.trace,
|
| 141 |
+
var_names=['delta'],
|
| 142 |
+
hdi_prob=0.95
|
| 143 |
+
)
|
| 144 |
+
delta_summary['Trial_Type'] = self.trial_labels
|
| 145 |
+
|
| 146 |
+
# 提取關鍵統計量
|
| 147 |
+
d_mean = summary.loc['d', 'mean']
|
| 148 |
+
d_hdi_lower = summary.loc['d', 'hdi_2.5%']
|
| 149 |
+
d_hdi_upper = summary.loc['d', 'hdi_97.5%']
|
| 150 |
+
|
| 151 |
+
or_mean = summary.loc['or_speed', 'mean']
|
| 152 |
+
or_hdi_lower = summary.loc['or_speed', 'hdi_2.5%']
|
| 153 |
+
or_hdi_upper = summary.loc['or_speed', 'hdi_97.5%']
|
| 154 |
+
|
| 155 |
+
sigma_mean = summary.loc['sigma', 'mean']
|
| 156 |
+
|
| 157 |
+
# 計算各屬性勝率變化
|
| 158 |
+
delta_values = self.trace.posterior['delta'].values.reshape(-1, self.num_trials)
|
| 159 |
+
mu_values = self.trace.posterior['mu'].values.reshape(-1, self.num_trials)
|
| 160 |
+
|
| 161 |
+
pc_mean = 1 / (1 + np.exp(-mu_values.mean(axis=0))) # 控制組平均勝率
|
| 162 |
+
pt_mean = 1 / (1 + np.exp(-(mu_values.mean(axis=0) + delta_values.mean(axis=0)))) # 實驗組平均勝率
|
| 163 |
+
|
| 164 |
+
win_rate_increase = (pt_mean - pc_mean) * 100 # 勝率提升百分點
|
| 165 |
+
|
| 166 |
+
self.results = {
|
| 167 |
+
'summary': summary,
|
| 168 |
+
'delta_summary': delta_summary,
|
| 169 |
+
'statistics': {
|
| 170 |
+
'd_mean': d_mean,
|
| 171 |
+
'd_hdi_lower': d_hdi_lower,
|
| 172 |
+
'd_hdi_upper': d_hdi_upper,
|
| 173 |
+
'or_mean': or_mean,
|
| 174 |
+
'or_hdi_lower': or_hdi_lower,
|
| 175 |
+
'or_hdi_upper': or_hdi_upper,
|
| 176 |
+
'sigma_mean': sigma_mean,
|
| 177 |
+
'pc_mean': pc_mean,
|
| 178 |
+
'pt_mean': pt_mean,
|
| 179 |
+
'win_rate_increase': win_rate_increase
|
| 180 |
+
},
|
| 181 |
+
'trial_labels': self.trial_labels,
|
| 182 |
+
'num_trials': self.num_trials,
|
| 183 |
+
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
def get_convergence_diagnostics(self):
|
| 187 |
+
"""獲取收斂診斷指標"""
|
| 188 |
+
|
| 189 |
+
if self.trace is None:
|
| 190 |
+
return None
|
| 191 |
+
|
| 192 |
+
summary = az.summary(self.trace, var_names=['d', 'sigma', 'or_speed'])
|
| 193 |
+
|
| 194 |
+
diagnostics = {
|
| 195 |
+
'r_hat': {
|
| 196 |
+
'd': summary.loc['d', 'r_hat'] if 'r_hat' in summary.columns else 1.0,
|
| 197 |
+
'sigma': summary.loc['sigma', 'r_hat'] if 'r_hat' in summary.columns else 1.0,
|
| 198 |
+
'or_speed': summary.loc['or_speed', 'r_hat'] if 'r_hat' in summary.columns else 1.0
|
| 199 |
+
},
|
| 200 |
+
'ess_bulk': {
|
| 201 |
+
'd': summary.loc['d', 'ess_bulk'] if 'ess_bulk' in summary.columns else 2000,
|
| 202 |
+
'sigma': summary.loc['sigma', 'ess_bulk'] if 'ess_bulk' in summary.columns else 2000,
|
| 203 |
+
'or_speed': summary.loc['or_speed', 'ess_bulk'] if 'ess_bulk' in summary.columns else 2000
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
return diagnostics
|
| 208 |
+
|
| 209 |
+
def interpret_results(self):
|
| 210 |
+
"""解釋分析結果"""
|
| 211 |
+
|
| 212 |
+
if self.results is None:
|
| 213 |
+
return "尚未執行分析"
|
| 214 |
+
|
| 215 |
+
stats = self.results['statistics']
|
| 216 |
+
|
| 217 |
+
# 判斷速度效應顯著性
|
| 218 |
+
if stats['d_hdi_lower'] > 0:
|
| 219 |
+
significance = "顯著正向"
|
| 220 |
+
direction = "速度快明顯提升勝率"
|
| 221 |
+
elif stats['d_hdi_upper'] < 0:
|
| 222 |
+
significance = "顯著負向"
|
| 223 |
+
direction = "速度快反而降低勝率"
|
| 224 |
+
else:
|
| 225 |
+
significance = "不顯著"
|
| 226 |
+
direction = "速度效應不明確"
|
| 227 |
+
|
| 228 |
+
interpretation = f"""
|
| 229 |
+
### 🎯 整體結論
|
| 230 |
+
|
| 231 |
+
**速度效應**: {significance} ({direction})
|
| 232 |
+
|
| 233 |
+
- **對數勝率比 (d)**: {stats['d_mean']:.3f} (95% HDI: [{stats['d_hdi_lower']:.3f}, {stats['d_hdi_upper']:.3f}])
|
| 234 |
+
- **勝率比 (OR)**: {stats['or_mean']:.3f} (95% HDI: [{stats['or_hdi_lower']:.3f}, {stats['or_hdi_upper']:.3f}])
|
| 235 |
+
- **異質性 (σ)**: {stats['sigma_mean']:.3f}
|
| 236 |
+
|
| 237 |
+
### 📊 實際意義
|
| 238 |
+
|
| 239 |
+
速度快的寶可夢勝率約為速度慢的 **{stats['or_mean']:.2f} 倍**。
|
| 240 |
+
|
| 241 |
+
平均而言,速度快可使勝率提升約 **{stats['win_rate_increase'].mean():.1f} 個百分點**。
|
| 242 |
+
"""
|
| 243 |
+
|
| 244 |
+
return interpretation
|
| 245 |
+
|
| 246 |
+
def get_trial_specific_results(self):
|
| 247 |
+
"""獲取各屬性的詳細結果"""
|
| 248 |
+
|
| 249 |
+
if self.results is None:
|
| 250 |
+
return None
|
| 251 |
+
|
| 252 |
+
stats = self.results['statistics']
|
| 253 |
+
|
| 254 |
+
trial_results = []
|
| 255 |
+
for i, trial in enumerate(self.trial_labels):
|
| 256 |
+
trial_results.append({
|
| 257 |
+
'Trial_Type': trial,
|
| 258 |
+
'Control_Win_Rate': f"{stats['pc_mean'][i]:.1%}",
|
| 259 |
+
'Treatment_Win_Rate': f"{stats['pt_mean'][i]:.1%}",
|
| 260 |
+
'Win_Rate_Increase': f"{stats['win_rate_increase'][i]:+.1f}%",
|
| 261 |
+
'Effect_Size': self.results['delta_summary'].iloc[i]['mean']
|
| 262 |
+
})
|
| 263 |
+
|
| 264 |
+
return pd.DataFrame(trial_results)
|
llm_assistant.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
LLM Assistant for Pokemon Speed Bayesian Analysis
|
| 3 |
+
Powered by GPT-4 to explain statistical results
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from openai import OpenAI
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class LLMAssistant:
|
| 11 |
+
"""
|
| 12 |
+
LLM 問答助手
|
| 13 |
+
協助用戶理解貝葉斯分析結果
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __init__(self, api_key, session_id):
|
| 17 |
+
"""
|
| 18 |
+
初始化 LLM 助手
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
api_key: OpenAI API key
|
| 22 |
+
session_id: 唯一的 session 識別碼
|
| 23 |
+
"""
|
| 24 |
+
self.client = OpenAI(api_key=api_key)
|
| 25 |
+
self.session_id = session_id
|
| 26 |
+
self.conversation_history = []
|
| 27 |
+
|
| 28 |
+
# 系統提示詞
|
| 29 |
+
self.system_prompt = """You are an expert statistician and data scientist specializing in Bayesian hierarchical modeling and meta-analysis.
|
| 30 |
+
|
| 31 |
+
Your role is to help users understand their Pokemon speed analysis results, which uses a Bayesian hierarchical model to analyze whether faster Pokemon have higher win rates across different types.
|
| 32 |
+
|
| 33 |
+
**Key Statistical Concepts You Should Explain:**
|
| 34 |
+
1. **Bayesian Hierarchical Model**: Allows borrowing strength across Pokemon types while estimating type-specific effects
|
| 35 |
+
2. **Log Odds Ratio (d)**: The overall effect of speed on win rate (log scale)
|
| 36 |
+
3. **Odds Ratio (OR)**: Exponential of d, easier to interpret (e.g., OR=1.5 means 1.5x higher odds)
|
| 37 |
+
4. **Heterogeneity (σ)**: Variation in speed effects across different Pokemon types
|
| 38 |
+
5. **95% HDI (Highest Density Interval)**: Bayesian credible interval - range where the true value likely falls
|
| 39 |
+
6. **Convergence Diagnostics**: R-hat and ESS to check MCMC quality
|
| 40 |
+
7. **Delta (δ)**: Type-specific speed effects
|
| 41 |
+
|
| 42 |
+
**When Answering Questions:**
|
| 43 |
+
- Use clear, accessible language (avoid jargon when possible)
|
| 44 |
+
- Provide concrete examples from the analysis
|
| 45 |
+
- Explain uncertainty using HDI intervals
|
| 46 |
+
- Distinguish between statistical significance and practical importance
|
| 47 |
+
- Help users understand what the results mean for Pokemon battles
|
| 48 |
+
|
| 49 |
+
**Tone:**
|
| 50 |
+
- Professional but friendly
|
| 51 |
+
- Educational and clear
|
| 52 |
+
- Patient with statistical concepts
|
| 53 |
+
- Use Pokemon terminology naturally
|
| 54 |
+
|
| 55 |
+
Always format responses with proper markdown for readability."""
|
| 56 |
+
|
| 57 |
+
def get_response(self, user_message, analysis_results=None):
|
| 58 |
+
"""
|
| 59 |
+
獲取 AI 回應
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
user_message: 用戶訊息
|
| 63 |
+
analysis_results: 分析結果字典 (可選)
|
| 64 |
+
|
| 65 |
+
Returns:
|
| 66 |
+
str: AI 回應
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
# 準備上下文資訊
|
| 70 |
+
context = self._prepare_context(analysis_results) if analysis_results else ""
|
| 71 |
+
|
| 72 |
+
# 添加用戶訊息到歷史
|
| 73 |
+
self.conversation_history.append({
|
| 74 |
+
"role": "user",
|
| 75 |
+
"content": user_message
|
| 76 |
+
})
|
| 77 |
+
|
| 78 |
+
# 構建訊息列表
|
| 79 |
+
messages = [
|
| 80 |
+
{"role": "system", "content": self.system_prompt}
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
if context:
|
| 84 |
+
messages.append({"role": "system", "content": f"Analysis Context:\n{context}"})
|
| 85 |
+
|
| 86 |
+
messages.extend(self.conversation_history)
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
# 調用 OpenAI API
|
| 90 |
+
response = self.client.chat.completions.create(
|
| 91 |
+
model="gpt-4o-mini",
|
| 92 |
+
messages=messages,
|
| 93 |
+
temperature=0.7,
|
| 94 |
+
max_tokens=1500
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
assistant_message = response.choices[0].message.content
|
| 98 |
+
|
| 99 |
+
# 添加助手回應到歷史
|
| 100 |
+
self.conversation_history.append({
|
| 101 |
+
"role": "assistant",
|
| 102 |
+
"content": assistant_message
|
| 103 |
+
})
|
| 104 |
+
|
| 105 |
+
return assistant_message
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
return f"❌ Error: {str(e)}\n\nPlease check your API key and try again."
|
| 109 |
+
|
| 110 |
+
def _prepare_context(self, results):
|
| 111 |
+
"""準備分析結果的上下文資訊"""
|
| 112 |
+
|
| 113 |
+
if not results or 'statistics' not in results:
|
| 114 |
+
return "No analysis results available yet."
|
| 115 |
+
|
| 116 |
+
stats = results['statistics']
|
| 117 |
+
|
| 118 |
+
# 格式化各屬性結果
|
| 119 |
+
trial_results_text = ""
|
| 120 |
+
if 'trial_labels' in results and len(results['trial_labels']) > 0:
|
| 121 |
+
trial_results_text = "\n## Type-Specific Results\n"
|
| 122 |
+
for i, trial in enumerate(results['trial_labels']):
|
| 123 |
+
control_wr = stats['pc_mean'][i] * 100
|
| 124 |
+
treatment_wr = stats['pt_mean'][i] * 100
|
| 125 |
+
increase = stats['win_rate_increase'][i]
|
| 126 |
+
trial_results_text += f"- **{trial}**: {control_wr:.1f}% → {treatment_wr:.1f}% ({increase:+.1f}%)\n"
|
| 127 |
+
|
| 128 |
+
context = f"""
|
| 129 |
+
## Overall Speed Effect Analysis
|
| 130 |
+
|
| 131 |
+
### Model Summary
|
| 132 |
+
- **Number of Pokemon Types Analyzed**: {results.get('num_trials', 'N/A')}
|
| 133 |
+
- **Analysis Timestamp**: {results.get('timestamp', 'N/A')}
|
| 134 |
+
|
| 135 |
+
### Key Findings
|
| 136 |
+
|
| 137 |
+
**Overall Effect (d - Log Odds Ratio)**
|
| 138 |
+
- Mean: {stats['d_mean']:.3f}
|
| 139 |
+
- 95% HDI: [{stats['d_hdi_lower']:.3f}, {stats['d_hdi_upper']:.3f}]
|
| 140 |
+
|
| 141 |
+
**Odds Ratio (OR - Speed Effect)**
|
| 142 |
+
- Mean: {stats['or_mean']:.3f}
|
| 143 |
+
- 95% HDI: [{stats['or_hdi_lower']:.3f}, {stats['or_hdi_upper']:.3f}]
|
| 144 |
+
- *Interpretation*: Faster Pokemon have about {stats['or_mean']:.2f}x the odds of winning compared to slower Pokemon
|
| 145 |
+
|
| 146 |
+
**Heterogeneity (σ)**
|
| 147 |
+
- Mean: {stats['sigma_mean']:.3f}
|
| 148 |
+
- *Interpretation*: {'Low' if stats['sigma_mean'] < 0.2 else 'Moderate' if stats['sigma_mean'] < 0.5 else 'High'} variation across types
|
| 149 |
+
|
| 150 |
+
**Average Win Rate Changes**
|
| 151 |
+
- Control Group (Slower): {stats['pc_mean'].mean()*100:.1f}%
|
| 152 |
+
- Treatment Group (Faster): {stats['pt_mean'].mean()*100:.1f}%
|
| 153 |
+
- Average Increase: {stats['win_rate_increase'].mean():.1f} percentage points
|
| 154 |
+
|
| 155 |
+
{trial_results_text}
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
return context
|
| 159 |
+
|
| 160 |
+
def generate_summary(self, analysis_results):
|
| 161 |
+
"""自動生成分析結果總結"""
|
| 162 |
+
|
| 163 |
+
summary_prompt = """Based on the Bayesian hierarchical analysis results provided, please generate a comprehensive summary that includes:
|
| 164 |
+
|
| 165 |
+
1. **Executive Summary**: High-level conclusion about speed's impact on win rates
|
| 166 |
+
2. **Statistical Findings**:
|
| 167 |
+
- Overall effect size and credible intervals
|
| 168 |
+
- Interpretation of odds ratio
|
| 169 |
+
- Assessment of heterogeneity across types
|
| 170 |
+
3. **Practical Implications**: What this means for Pokemon battles and team composition
|
| 171 |
+
4. **Type-Specific Insights**: Which types benefit most/least from speed
|
| 172 |
+
5. **Limitations**: Important caveats and assumptions
|
| 173 |
+
|
| 174 |
+
Format the summary with clear sections and use Pokemon battle terminology naturally."""
|
| 175 |
+
|
| 176 |
+
return self.get_response(summary_prompt, analysis_results)
|
| 177 |
+
|
| 178 |
+
def explain_concept(self, concept_name, analysis_results=None):
|
| 179 |
+
"""解釋統計概念"""
|
| 180 |
+
|
| 181 |
+
concept_prompts = {
|
| 182 |
+
'log_odds_ratio': "What is a log odds ratio (d) and how should I interpret it in this Pokemon speed analysis?",
|
| 183 |
+
'odds_ratio': "Explain the odds ratio (OR) and what it means when OR > 1 in the context of Pokemon speed.",
|
| 184 |
+
'hdi': "What is a 95% HDI (Highest Density Interval) and how is it different from a confidence interval?",
|
| 185 |
+
'heterogeneity': "What does heterogeneity (sigma) tell us about differences across Pokemon types?",
|
| 186 |
+
'convergence': "How can I check if the MCMC sampling converged properly? What are R-hat and ESS?",
|
| 187 |
+
'hierarchical_model': "Explain the Bayesian hierarchical model structure used in this analysis."
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
if concept_name in concept_prompts:
|
| 191 |
+
prompt = concept_prompts[concept_name]
|
| 192 |
+
else:
|
| 193 |
+
prompt = f"Please explain the concept of '{concept_name}' in the context of Bayesian meta-analysis."
|
| 194 |
+
|
| 195 |
+
return self.get_response(prompt, analysis_results)
|
| 196 |
+
|
| 197 |
+
def interpret_type_results(self, type_name, analysis_results):
|
| 198 |
+
"""解釋特定屬性的結果"""
|
| 199 |
+
|
| 200 |
+
interpret_prompt = f"""Please provide a detailed interpretation of the speed effect for {type_name} Pokemon:
|
| 201 |
+
|
| 202 |
+
1. How does this type's speed effect compare to the overall average?
|
| 203 |
+
2. What is the practical win rate difference?
|
| 204 |
+
3. Is the effect statistically credible (based on HDI)?
|
| 205 |
+
4. What might explain this type's specific pattern?
|
| 206 |
+
5. Strategic recommendations for using {type_name} Pokemon in battles
|
| 207 |
+
|
| 208 |
+
Be specific and use the numerical results from the analysis."""
|
| 209 |
+
|
| 210 |
+
return self.get_response(interpret_prompt, analysis_results)
|
| 211 |
+
|
| 212 |
+
def suggest_improvements(self, analysis_results):
|
| 213 |
+
"""提供分析改進建議"""
|
| 214 |
+
|
| 215 |
+
improve_prompt = """Based on the current analysis results, please suggest potential improvements or extensions:
|
| 216 |
+
|
| 217 |
+
1. **Data Quality**: What additional data might improve the analysis?
|
| 218 |
+
2. **Model Extensions**: How could the model be enhanced?
|
| 219 |
+
3. **Alternative Analyses**: What complementary analyses would be valuable?
|
| 220 |
+
4. **Robustness Checks**: What sensitivity analyses should be performed?
|
| 221 |
+
5. **Practical Applications**: How could these findings be validated in actual battles?
|
| 222 |
+
|
| 223 |
+
Prioritize suggestions by feasibility and potential impact."""
|
| 224 |
+
|
| 225 |
+
return self.get_response(improve_prompt, analysis_results)
|
| 226 |
+
|
| 227 |
+
def compare_to_literature(self, analysis_results):
|
| 228 |
+
"""與現有文獻比較 (基於訓練知識)"""
|
| 229 |
+
|
| 230 |
+
compare_prompt = """Based on your knowledge of Pokemon competitive analysis and speed mechanics:
|
| 231 |
+
|
| 232 |
+
1. How do these results compare to general understanding of speed's importance?
|
| 233 |
+
2. Are there any surprising findings?
|
| 234 |
+
3. What does competitive Pokemon literature say about speed tiers?
|
| 235 |
+
4. How might these findings apply to different battle formats (Singles vs Doubles)?
|
| 236 |
+
5. What other factors interact with speed that weren't captured in this analysis?
|
| 237 |
+
|
| 238 |
+
Provide context from Pokemon competitive knowledge while being clear about the limitations of this specific analysis."""
|
| 239 |
+
|
| 240 |
+
return self.get_response(compare_prompt, analysis_results)
|
| 241 |
+
|
| 242 |
+
def assess_convergence(self, diagnostics):
|
| 243 |
+
"""評估 MCMC 收斂狀況"""
|
| 244 |
+
|
| 245 |
+
if not diagnostics:
|
| 246 |
+
return "No convergence diagnostics available."
|
| 247 |
+
|
| 248 |
+
diag_text = f"""
|
| 249 |
+
**Convergence Diagnostics:**
|
| 250 |
+
|
| 251 |
+
R-hat values:
|
| 252 |
+
- d: {diagnostics['r_hat']['d']:.4f}
|
| 253 |
+
- sigma: {diagnostics['r_hat']['sigma']:.4f}
|
| 254 |
+
- or_speed: {diagnostics['r_hat']['or_speed']:.4f}
|
| 255 |
+
|
| 256 |
+
ESS (Effective Sample Size):
|
| 257 |
+
- d: {diagnostics['ess_bulk']['d']:.0f}
|
| 258 |
+
- sigma: {diagnostics['ess_bulk']['sigma']:.0f}
|
| 259 |
+
- or_speed: {diagnostics['ess_bulk']['or_speed']:.0f}
|
| 260 |
+
"""
|
| 261 |
+
|
| 262 |
+
assess_prompt = f"""Based on these MCMC convergence diagnostics:
|
| 263 |
+
|
| 264 |
+
{diag_text}
|
| 265 |
+
|
| 266 |
+
Please:
|
| 267 |
+
1. Assess whether the sampling has converged properly
|
| 268 |
+
2. Explain what R-hat and ESS values indicate
|
| 269 |
+
3. Recommend whether the results are trustworthy or if resampling is needed
|
| 270 |
+
4. Suggest specific actions if convergence issues are detected
|
| 271 |
+
|
| 272 |
+
Use clear criteria (e.g., R-hat < 1.01, ESS > 400)."""
|
| 273 |
+
|
| 274 |
+
return self.get_response(assess_prompt)
|
| 275 |
+
|
| 276 |
+
def reset_conversation(self):
|
| 277 |
+
"""重置對話歷史"""
|
| 278 |
+
self.conversation_history = []
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.32.0
|
| 2 |
+
pandas>=2.2.0
|
| 3 |
+
numpy>=1.26.0
|
| 4 |
+
plotly>=5.18.0
|
| 5 |
+
pymc>=5.10.0
|
| 6 |
+
arviz>=0.17.0
|
| 7 |
+
openai>=1.30.0
|
| 8 |
+
scipy>=1.11.0
|
utils.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visualization utilities for Bayesian Pokemon Speed Analysis
|
| 3 |
+
Using Plotly for interactive charts
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import plotly.graph_objects as go
|
| 7 |
+
import plotly.express as px
|
| 8 |
+
from plotly.subplots import make_subplots
|
| 9 |
+
import numpy as np
|
| 10 |
+
import pandas as pd
|
| 11 |
+
import arviz as az
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def plot_trace(trace, var_names=['d', 'sigma']):
|
| 15 |
+
"""
|
| 16 |
+
繪製 Trace Plot (檢查收斂性)
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
trace: ArviZ InferenceData
|
| 20 |
+
var_names: 要顯示的變數名稱列表
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
plotly figure
|
| 24 |
+
"""
|
| 25 |
+
n_vars = len(var_names)
|
| 26 |
+
|
| 27 |
+
fig = make_subplots(
|
| 28 |
+
rows=n_vars, cols=2,
|
| 29 |
+
subplot_titles=[f'{var} - Trace' if i % 2 == 0 else f'{var} - Distribution'
|
| 30 |
+
for var in var_names for i in range(2)],
|
| 31 |
+
horizontal_spacing=0.1,
|
| 32 |
+
vertical_spacing=0.15
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
for i, var_name in enumerate(var_names):
|
| 36 |
+
row = i + 1
|
| 37 |
+
|
| 38 |
+
# 提取樣本
|
| 39 |
+
samples = trace.posterior[var_name].values.flatten()
|
| 40 |
+
iterations = np.arange(len(samples))
|
| 41 |
+
|
| 42 |
+
# 左圖: Trace (軌跡圖)
|
| 43 |
+
fig.add_trace(
|
| 44 |
+
go.Scatter(
|
| 45 |
+
x=iterations,
|
| 46 |
+
y=samples,
|
| 47 |
+
mode='lines',
|
| 48 |
+
line=dict(color='steelblue', width=0.8),
|
| 49 |
+
name=f'{var_name} trace'
|
| 50 |
+
),
|
| 51 |
+
row=row, col=1
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
# 右圖: Distribution (密度圖)
|
| 55 |
+
fig.add_trace(
|
| 56 |
+
go.Histogram(
|
| 57 |
+
x=samples,
|
| 58 |
+
nbinsx=50,
|
| 59 |
+
name=f'{var_name} dist',
|
| 60 |
+
marker=dict(color='lightcoral', line=dict(color='darkred', width=1)),
|
| 61 |
+
histnorm='probability density'
|
| 62 |
+
),
|
| 63 |
+
row=row, col=2
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
fig.update_layout(
|
| 67 |
+
height=300 * n_vars,
|
| 68 |
+
title_text="MCMC Trace Plot & Posterior Distribution",
|
| 69 |
+
showlegend=False,
|
| 70 |
+
template='plotly_white'
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
fig.update_xaxes(title_text="Iteration", row=n_vars, col=1)
|
| 74 |
+
fig.update_xaxes(title_text="Value", row=n_vars, col=2)
|
| 75 |
+
|
| 76 |
+
return fig
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def plot_posterior(trace, var_names=['d', 'sigma', 'or_speed'], hdi_prob=0.95):
|
| 80 |
+
"""
|
| 81 |
+
繪製後驗分佈圖 (含 HDI)
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
trace: ArviZ InferenceData
|
| 85 |
+
var_names: 變數名稱列表
|
| 86 |
+
hdi_prob: HDI 信賴水準
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
plotly figure
|
| 90 |
+
"""
|
| 91 |
+
n_vars = len(var_names)
|
| 92 |
+
|
| 93 |
+
fig = make_subplots(
|
| 94 |
+
rows=1, cols=n_vars,
|
| 95 |
+
subplot_titles=[f'{var}' for var in var_names],
|
| 96 |
+
horizontal_spacing=0.1
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
for i, var_name in enumerate(var_names):
|
| 100 |
+
col = i + 1
|
| 101 |
+
|
| 102 |
+
# 提取樣本
|
| 103 |
+
samples = trace.posterior[var_name].values.flatten()
|
| 104 |
+
|
| 105 |
+
# 計算 HDI
|
| 106 |
+
hdi = az.hdi(trace, var_names=[var_name], hdi_prob=hdi_prob)
|
| 107 |
+
hdi_lower = float(hdi[var_name].values[0])
|
| 108 |
+
hdi_upper = float(hdi[var_name].values[1])
|
| 109 |
+
mean_val = samples.mean()
|
| 110 |
+
|
| 111 |
+
# 繪製分佈
|
| 112 |
+
fig.add_trace(
|
| 113 |
+
go.Histogram(
|
| 114 |
+
x=samples,
|
| 115 |
+
nbinsx=50,
|
| 116 |
+
name=var_name,
|
| 117 |
+
marker=dict(
|
| 118 |
+
color='lightblue',
|
| 119 |
+
line=dict(color='steelblue', width=1)
|
| 120 |
+
),
|
| 121 |
+
histnorm='probability density',
|
| 122 |
+
showlegend=False
|
| 123 |
+
),
|
| 124 |
+
row=1, col=col
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# 添加平均值線
|
| 128 |
+
fig.add_vline(
|
| 129 |
+
x=mean_val,
|
| 130 |
+
line=dict(color='red', width=2, dash='dash'),
|
| 131 |
+
row=1, col=col,
|
| 132 |
+
annotation_text=f"Mean: {mean_val:.3f}",
|
| 133 |
+
annotation_position="top"
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
# 添加 HDI 陰影
|
| 137 |
+
fig.add_vrect(
|
| 138 |
+
x0=hdi_lower, x1=hdi_upper,
|
| 139 |
+
fillcolor="green", opacity=0.2,
|
| 140 |
+
line_width=0,
|
| 141 |
+
row=1, col=col,
|
| 142 |
+
annotation_text=f"{int(hdi_prob*100)}% HDI",
|
| 143 |
+
annotation_position="bottom"
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
fig.update_layout(
|
| 147 |
+
height=400,
|
| 148 |
+
title_text=f"Posterior Distributions with {int(hdi_prob*100)}% HDI",
|
| 149 |
+
template='plotly_white'
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
return fig
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def plot_forest(trace, trial_labels, hdi_prob=0.95):
|
| 156 |
+
"""
|
| 157 |
+
繪製 Forest Plot (各屬性效應比較)
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
trace: ArviZ InferenceData
|
| 161 |
+
trial_labels: 屬性名稱列表
|
| 162 |
+
hdi_prob: HDI 信賴水準
|
| 163 |
+
|
| 164 |
+
Returns:
|
| 165 |
+
plotly figure
|
| 166 |
+
"""
|
| 167 |
+
# 提取 delta 後驗樣本
|
| 168 |
+
delta_samples = trace.posterior['delta'].values.reshape(-1, len(trial_labels))
|
| 169 |
+
|
| 170 |
+
# 計算統計量
|
| 171 |
+
delta_mean = delta_samples.mean(axis=0)
|
| 172 |
+
hdi = az.hdi(trace, var_names=['delta'], hdi_prob=hdi_prob)
|
| 173 |
+
hdi_lower = hdi['delta'].values[:, 0]
|
| 174 |
+
hdi_upper = hdi['delta'].values[:, 1]
|
| 175 |
+
|
| 176 |
+
# 按效應大小排序
|
| 177 |
+
sorted_indices = np.argsort(delta_mean)
|
| 178 |
+
|
| 179 |
+
fig = go.Figure()
|
| 180 |
+
|
| 181 |
+
# 繪製信賴區間 (橫線)
|
| 182 |
+
for i, idx in enumerate(sorted_indices):
|
| 183 |
+
fig.add_trace(go.Scatter(
|
| 184 |
+
x=[hdi_lower[idx], hdi_upper[idx]],
|
| 185 |
+
y=[i, i],
|
| 186 |
+
mode='lines',
|
| 187 |
+
line=dict(color='steelblue', width=3),
|
| 188 |
+
showlegend=False
|
| 189 |
+
))
|
| 190 |
+
|
| 191 |
+
# 繪製平均值 (點)
|
| 192 |
+
fig.add_trace(go.Scatter(
|
| 193 |
+
x=delta_mean[sorted_indices],
|
| 194 |
+
y=np.arange(len(trial_labels)),
|
| 195 |
+
mode='markers',
|
| 196 |
+
marker=dict(
|
| 197 |
+
color='darkblue',
|
| 198 |
+
size=10,
|
| 199 |
+
line=dict(color='white', width=2)
|
| 200 |
+
),
|
| 201 |
+
name='Mean Effect',
|
| 202 |
+
text=trial_labels[sorted_indices],
|
| 203 |
+
hovertemplate='<b>%{text}</b><br>Effect: %{x:.3f}<extra></extra>'
|
| 204 |
+
))
|
| 205 |
+
|
| 206 |
+
# 添加無效應參考線
|
| 207 |
+
fig.add_vline(
|
| 208 |
+
x=0,
|
| 209 |
+
line=dict(color='red', width=2, dash='dash'),
|
| 210 |
+
annotation_text="No Effect (δ=0)",
|
| 211 |
+
annotation_position="top right"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
fig.update_layout(
|
| 215 |
+
title=f"Speed Effect by Pokemon Type ({int(hdi_prob*100)}% HDI)",
|
| 216 |
+
xaxis_title="Delta (Log Odds Ratio)",
|
| 217 |
+
yaxis_title="Pokemon Type",
|
| 218 |
+
yaxis=dict(
|
| 219 |
+
tickmode='array',
|
| 220 |
+
tickvals=np.arange(len(trial_labels)),
|
| 221 |
+
ticktext=trial_labels[sorted_indices]
|
| 222 |
+
),
|
| 223 |
+
height=max(500, len(trial_labels) * 30),
|
| 224 |
+
width=800,
|
| 225 |
+
template='plotly_white',
|
| 226 |
+
hovermode='closest'
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
return fig
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def plot_win_rate_comparison(trial_labels, pc_mean, pt_mean):
|
| 233 |
+
"""
|
| 234 |
+
繪製勝率比較圖
|
| 235 |
+
|
| 236 |
+
Args:
|
| 237 |
+
trial_labels: 屬性名稱列表
|
| 238 |
+
pc_mean: 控制組平均勝率
|
| 239 |
+
pt_mean: 實驗組平均勝率
|
| 240 |
+
|
| 241 |
+
Returns:
|
| 242 |
+
plotly figure
|
| 243 |
+
"""
|
| 244 |
+
df = pd.DataFrame({
|
| 245 |
+
'Type': trial_labels,
|
| 246 |
+
'Slow (Control)': pc_mean * 100,
|
| 247 |
+
'Fast (Treatment)': pt_mean * 100
|
| 248 |
+
})
|
| 249 |
+
|
| 250 |
+
# 按實驗組勝率排序
|
| 251 |
+
df = df.sort_values('Fast (Treatment)', ascending=True)
|
| 252 |
+
|
| 253 |
+
fig = go.Figure()
|
| 254 |
+
|
| 255 |
+
# 控制組
|
| 256 |
+
fig.add_trace(go.Bar(
|
| 257 |
+
y=df['Type'],
|
| 258 |
+
x=df['Slow (Control)'],
|
| 259 |
+
name='Slow Pokemon',
|
| 260 |
+
orientation='h',
|
| 261 |
+
marker=dict(color='lightcoral')
|
| 262 |
+
))
|
| 263 |
+
|
| 264 |
+
# 實驗組
|
| 265 |
+
fig.add_trace(go.Bar(
|
| 266 |
+
y=df['Type'],
|
| 267 |
+
x=df['Fast (Treatment)'],
|
| 268 |
+
name='Fast Pokemon',
|
| 269 |
+
orientation='h',
|
| 270 |
+
marker=dict(color='lightgreen')
|
| 271 |
+
))
|
| 272 |
+
|
| 273 |
+
fig.update_layout(
|
| 274 |
+
title="Win Rate Comparison: Slow vs Fast Pokemon",
|
| 275 |
+
xaxis_title="Win Rate (%)",
|
| 276 |
+
yaxis_title="Pokemon Type",
|
| 277 |
+
barmode='group',
|
| 278 |
+
height=max(500, len(trial_labels) * 30),
|
| 279 |
+
width=800,
|
| 280 |
+
template='plotly_white',
|
| 281 |
+
legend=dict(x=0.7, y=0.05)
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
return fig
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def plot_heterogeneity(trace):
|
| 288 |
+
"""
|
| 289 |
+
繪製異質性分析圖
|
| 290 |
+
|
| 291 |
+
Args:
|
| 292 |
+
trace: ArviZ InferenceData
|
| 293 |
+
|
| 294 |
+
Returns:
|
| 295 |
+
plotly figure
|
| 296 |
+
"""
|
| 297 |
+
# 提取 sigma 和 tau
|
| 298 |
+
sigma_samples = trace.posterior['sigma'].values.flatten()
|
| 299 |
+
tau_samples = trace.posterior['tau'].values.flatten()
|
| 300 |
+
|
| 301 |
+
fig = make_subplots(
|
| 302 |
+
rows=1, cols=2,
|
| 303 |
+
subplot_titles=['Sigma (Standard Deviation)', 'Tau (Precision)']
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
# Sigma 分佈
|
| 307 |
+
fig.add_trace(
|
| 308 |
+
go.Histogram(
|
| 309 |
+
x=sigma_samples,
|
| 310 |
+
nbinsx=50,
|
| 311 |
+
name='Sigma',
|
| 312 |
+
marker=dict(color='lightblue'),
|
| 313 |
+
histnorm='probability density'
|
| 314 |
+
),
|
| 315 |
+
row=1, col=1
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
# Tau 分佈
|
| 319 |
+
fig.add_trace(
|
| 320 |
+
go.Histogram(
|
| 321 |
+
x=tau_samples,
|
| 322 |
+
nbinsx=50,
|
| 323 |
+
name='Tau',
|
| 324 |
+
marker=dict(color='lightcoral'),
|
| 325 |
+
histnorm='probability density'
|
| 326 |
+
),
|
| 327 |
+
row=1, col=2
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
fig.update_layout(
|
| 331 |
+
height=400,
|
| 332 |
+
title_text="Heterogeneity Parameters (Between-Type Variation)",
|
| 333 |
+
showlegend=False,
|
| 334 |
+
template='plotly_white'
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
return fig
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
def create_results_table(summary_df):
|
| 341 |
+
"""
|
| 342 |
+
創建結果摘要表格
|
| 343 |
+
|
| 344 |
+
Args:
|
| 345 |
+
summary_df: ArviZ summary DataFrame
|
| 346 |
+
|
| 347 |
+
Returns:
|
| 348 |
+
plotly table figure
|
| 349 |
+
"""
|
| 350 |
+
# 格式化數據
|
| 351 |
+
display_df = summary_df.copy()
|
| 352 |
+
display_df = display_df.round(4)
|
| 353 |
+
|
| 354 |
+
fig = go.Figure(data=[go.Table(
|
| 355 |
+
header=dict(
|
| 356 |
+
values=['Parameter'] + list(display_df.columns),
|
| 357 |
+
fill_color='steelblue',
|
| 358 |
+
font=dict(color='white', size=12),
|
| 359 |
+
align='left'
|
| 360 |
+
),
|
| 361 |
+
cells=dict(
|
| 362 |
+
values=[display_df.index] + [display_df[col] for col in display_df.columns],
|
| 363 |
+
fill_color='lavender',
|
| 364 |
+
align='left',
|
| 365 |
+
font=dict(size=11)
|
| 366 |
+
)
|
| 367 |
+
)])
|
| 368 |
+
|
| 369 |
+
fig.update_layout(
|
| 370 |
+
title="Analysis Results Summary",
|
| 371 |
+
height=200 + len(display_df) * 30,
|
| 372 |
+
margin=dict(l=0, r=0, t=40, b=0)
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
return fig
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def create_type_results_table(trial_results_df):
|
| 379 |
+
"""
|
| 380 |
+
創建各屬���結果表格
|
| 381 |
+
|
| 382 |
+
Args:
|
| 383 |
+
trial_results_df: 各屬性結果 DataFrame
|
| 384 |
+
|
| 385 |
+
Returns:
|
| 386 |
+
plotly table figure
|
| 387 |
+
"""
|
| 388 |
+
fig = go.Figure(data=[go.Table(
|
| 389 |
+
header=dict(
|
| 390 |
+
values=list(trial_results_df.columns),
|
| 391 |
+
fill_color='steelblue',
|
| 392 |
+
font=dict(color='white', size=12),
|
| 393 |
+
align='left'
|
| 394 |
+
),
|
| 395 |
+
cells=dict(
|
| 396 |
+
values=[trial_results_df[col] for col in trial_results_df.columns],
|
| 397 |
+
fill_color='lavender',
|
| 398 |
+
align='left',
|
| 399 |
+
font=dict(size=11)
|
| 400 |
+
)
|
| 401 |
+
)])
|
| 402 |
+
|
| 403 |
+
fig.update_layout(
|
| 404 |
+
title="Type-Specific Results",
|
| 405 |
+
height=200 + len(trial_results_df) * 30,
|
| 406 |
+
margin=dict(l=0, r=0, t=40, b=0)
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
return fig
|