File size: 16,300 Bytes
0000826 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
import gradio as gr
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import time
class MeanAnalyzer:
def __init__(self):
self.reset()
def reset(self):
"""重置所有數據"""
self.sequence = [100.0] # 初始值
self.multipliers = []
self.arithmetic_means = [100.0]
self.geometric_means = [100.0]
self.current_step = 0
def add_step(self, min_mult=0.1, max_mult=2.1, seed=None):
"""添加一個新的步驟"""
if seed is not None:
np.random.seed(seed + self.current_step)
# 生成隨機乘數
multiplier = np.random.uniform(min_mult, max_mult)
self.multipliers.append(multiplier)
# 計算新值
new_value = self.sequence[-1] * multiplier
self.sequence.append(new_value)
# 計算算術平均
arithmetic_mean = np.mean(self.sequence)
self.arithmetic_means.append(arithmetic_mean)
# 計算幾何平均
geometric_mean = np.exp(np.mean(np.log(self.sequence)))
self.geometric_means.append(geometric_mean)
self.current_step += 1
return self.create_plot(), self.get_stats()
def create_plot(self):
"""創建四象限圖表"""
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('Sequence Values & Means', 'Mean Difference (Arithmetic - Geometric)',
'Mean Ratio (Arithmetic / Geometric)', 'Statistics Summary'),
specs=[[{'type': 'scatter'}, {'type': 'scatter'}],
[{'type': 'scatter'}, {'type': 'table'}]],
vertical_spacing=0.12,
horizontal_spacing=0.15
)
steps = list(range(len(self.sequence)))
# 左上:數列值與平均數
fig.add_trace(
go.Scatter(x=steps, y=self.sequence,
mode='lines+markers', name='Sequence',
line=dict(color='blue', width=2),
marker=dict(size=6)),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=steps, y=self.arithmetic_means,
mode='lines', name='Arithmetic Mean',
line=dict(color='red', dash='dash', width=2)),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=steps, y=self.geometric_means,
mode='lines', name='Geometric Mean',
line=dict(color='green', dash='dot', width=2)),
row=1, col=1
)
# 右上:差異
differences = [a - g for a, g in zip(self.arithmetic_means, self.geometric_means)]
fig.add_trace(
go.Scatter(x=steps, y=differences,
mode='lines+markers', name='Difference',
line=dict(color='purple', width=2),
marker=dict(size=6)),
row=1, col=2
)
fig.add_trace(
go.Scatter(x=steps, y=[0]*len(steps),
mode='lines', name='Zero Line',
line=dict(color='gray', dash='dash', width=1),
showlegend=False),
row=1, col=2
)
# 左下:比值
ratios = [a/g if g != 0 else 1 for a, g in zip(self.arithmetic_means, self.geometric_means)]
fig.add_trace(
go.Scatter(x=steps, y=ratios,
mode='lines+markers', name='Ratio',
line=dict(color='orange', width=2),
marker=dict(size=6)),
row=2, col=1
)
fig.add_trace(
go.Scatter(x=steps, y=[1]*len(steps),
mode='lines', name='Ratio=1',
line=dict(color='gray', dash='dash', width=1),
showlegend=False),
row=2, col=1
)
# 右下:統計表
stats_data = [
['Current Step', str(self.current_step)],
['Latest Value', f'{self.sequence[-1]:.2f}'],
['Arithmetic Mean', f'{self.arithmetic_means[-1]:.2f}'],
['Geometric Mean', f'{self.geometric_means[-1]:.2f}'],
['Current Difference', f'{differences[-1]:.2f}'],
['Current Ratio', f'{ratios[-1]:.3f}'],
['Max Difference', f'{max(differences):.2f}'],
['Avg Difference', f'{np.mean(differences):.2f}']
]
fig.add_trace(
go.Table(
header=dict(values=['Statistic', 'Value'],
fill_color='lightgray',
align='left',
font=dict(size=14, color='black', family='Arial Black')),
cells=dict(values=[[row[0] for row in stats_data],
[row[1] for row in stats_data]],
fill_color='white',
align='left',
font=dict(size=13, color='black', family='Arial'),
height=30)
),
row=2, col=2
)
# 更新佈局
fig.update_xaxes(title_text="Steps", row=1, col=1)
fig.update_xaxes(title_text="Steps", row=1, col=2)
fig.update_xaxes(title_text="Steps", row=2, col=1)
fig.update_yaxes(title_text="Value", row=1, col=1)
fig.update_yaxes(title_text="Difference", row=1, col=2)
fig.update_yaxes(title_text="Ratio", row=2, col=1)
fig.update_layout(
height=700,
showlegend=True,
title_text="Geometric Mean vs Arithmetic Mean - Real-time Analysis",
title_font_size=20,
hovermode='x unified'
)
return fig
def get_stats(self):
"""獲取當前統計信息"""
if len(self.sequence) == 0:
return "尚無數據"
diff = self.arithmetic_means[-1] - self.geometric_means[-1]
ratio = self.arithmetic_means[-1] / self.geometric_means[-1] if self.geometric_means[-1] != 0 else 1
stats_text = f"""
### 📊 當前狀態
- **步驟數**: {self.current_step}
- **序列最新值**: {self.sequence[-1]:.2f}
- **算術平均**: {self.arithmetic_means[-1]:.2f}
- **幾何平均**: {self.geometric_means[-1]:.2f}
- **差異 (算術-幾何)**: {diff:.2f}
- **比值 (算術/幾何)**: {ratio:.3f}
### 💡 觀察重點
- 算術平均 {'>' if diff > 0 else '='} 幾何平均 {'✓ (符合數學定理)' if diff >= 0 else ''}
- 差異程度: {'小' if diff < 5 else '中等' if diff < 20 else '大'}
"""
if len(self.multipliers) > 0:
stats_text += f"\n- 最後使用的乘數: {self.multipliers[-1]:.3f}"
return stats_text
def animate_sequence(self, seed, length, min_mult, max_mult, speed):
"""生成動畫序列"""
self.reset()
frames = []
for i in range(length):
self.add_step(min_mult, max_mult, seed)
frames.append((self.create_plot(), self.get_stats(), i+1))
return frames
# 創建全局分析器實例
analyzer = MeanAnalyzer()
def reset_sequence(initial_value):
"""重置序列"""
analyzer.reset()
analyzer.sequence[0] = initial_value
analyzer.arithmetic_means[0] = initial_value
analyzer.geometric_means[0] = initial_value
return analyzer.create_plot(), analyzer.get_stats(), "✅ 已重置序列"
def add_single_step(seed, min_mult, max_mult):
"""添加單步"""
if seed == -1:
seed = None
plot, stats = analyzer.add_step(min_mult, max_mult, seed)
return plot, stats, f"✅ 已添加步驟 {analyzer.current_step}"
def run_animation(seed, length, min_mult, max_mult, speed):
"""運行動畫"""
if seed == -1:
seed = None
analyzer.reset()
message = f"🎬 開始動畫 (共 {length} 步)..."
for i in range(length):
plot, stats = analyzer.add_step(min_mult, max_mult, seed)
progress = f"進度: {i+1}/{length} 步"
yield plot, stats, message, progress
time.sleep(1.0 / speed) # 控制動畫速度
# 最終統計
differences = [a - g for a, g in zip(analyzer.arithmetic_means, analyzer.geometric_means)]
final_message = f"""
### 🎬 動畫完成!序列分析摘要:
- **總步驟數**: {length}
- **最終序列值**: {analyzer.sequence[-1]:.2f}
- **最終算術平均**: {analyzer.arithmetic_means[-1]:.2f}
- **最終幾何平均**: {analyzer.geometric_means[-1]:.2f}
- **最大差異**: {max(differences):.2f}
- **平均差異**: {np.mean(differences):.2f}
- **最終比值**: {analyzer.arithmetic_means[-1]/analyzer.geometric_means[-1]:.3f}
### 💡 關鍵洞察
- 算術平均始終 ≥ 幾何平均 ✓
- 差異隨著數列波動而變化
- 幾何平均更適合計算成長率
"""
yield plot, stats, final_message, f"完成 {length}/{length} 步"
def create_demo():
"""創建 Gradio 界面"""
with gr.Blocks(title="幾何平均 vs 算術平均", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🔢 幾何平均數 vs 算術平均數 - 差異分析器
此介面為-國立高雄科技大學 114學年-財管系-財管系二甲 統計學(一) 上課教材
> 探索兩種平均數在隨機序列中的差異演變,理解為什麼投資報酬率要用幾何平均計算!
### 📚 數學概念
- **算術平均**: `(a₁ + a₂ + ... + aₙ) / n` - 一般的平均值
- **幾何平均**: `(a₁ × a₂ × ... × aₙ)^(1/n)` - 適合計算平均成長率
- **定理**: 算術平均 ≥ 幾何平均(AM-GM 不等式)
""")
with gr.Tabs():
# 互動模式標籤
with gr.Tab("🎮 互動模式"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 🎛️ 控制面板")
seed_input = gr.Number(
label="🎲 隨機種子 (-1 為真隨機)",
value=42,
precision=0
)
initial_value = gr.Number(
label="🏁 初始值",
value=100,
minimum=1
)
with gr.Row():
min_mult = gr.Slider(
label="📉 最小乘數",
minimum=0.1,
maximum=1.0,
value=0.5,
step=0.1
)
max_mult = gr.Slider(
label="📈 最大乘數",
minimum=1.0,
maximum=3.0,
value=1.5,
step=0.1
)
with gr.Row():
reset_btn = gr.Button("🔄 重置", variant="secondary")
add_btn = gr.Button("➕ 添加步驟", variant="primary")
status_text = gr.Textbox(
label="狀態訊息",
value="✅ 就緒",
interactive=False
)
stats_display = gr.Markdown(analyzer.get_stats())
with gr.Column(scale=3):
gr.Markdown("### 📈 實時比較圖表")
plot_display = gr.Plot(value=analyzer.create_plot())
# 動畫模式標籤
with gr.Tab("🎬 動畫模式"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 🎥 動畫設置")
anim_seed = gr.Number(
label="🎲 隨機種子 (-1 為真隨機)",
value=42,
precision=0
)
anim_length = gr.Slider(
label="📏 序列長度",
minimum=10,
maximum=100,
value=30,
step=5
)
with gr.Row():
anim_min_mult = gr.Slider(
label="📉 最小乘數",
minimum=0.1,
maximum=1.0,
value=0.5,
step=0.1
)
anim_max_mult = gr.Slider(
label="📈 最大乘數",
minimum=1.0,
maximum=3.0,
value=1.5,
step=0.1
)
anim_speed = gr.Slider(
label="⚡ 播放速度",
minimum=1,
maximum=10,
value=3,
step=1
)
play_btn = gr.Button("▶️ 播放完整動畫", variant="primary", size="lg")
anim_progress = gr.Textbox(
label="進度",
value="等待開始...",
interactive=False
)
anim_message = gr.Markdown("### 準備就緒")
with gr.Column(scale=3):
gr.Markdown("### 🎬 動畫圖表")
anim_plot = gr.Plot(value=analyzer.create_plot())
anim_stats = gr.Markdown("等待動畫開始...")
gr.Markdown("""
---
### 🎓 學習指南
1. **互動模式**:手動控制每一步,仔細觀察差異變化
2. **動畫模式**:自動播放完整序列,看整體趨勢
3. **實驗建議**:
- 嘗試不同的乘數範圍,看差異如何變化
- 使用相同種子,改變參數對比結果
- 觀察極端情況(很小或很大的乘數範圍)
### 💡 重要發現
- 當數列波動越大,兩種平均數的差異越明顯
- 幾何平均對極端值(特別是接近 0 的值)更敏感
- 這就是為什麼計算投資報酬率要用幾何平均!
""")
# 事件綁定 - 互動模式
reset_btn.click(
fn=reset_sequence,
inputs=[initial_value],
outputs=[plot_display, stats_display, status_text]
)
add_btn.click(
fn=add_single_step,
inputs=[seed_input, min_mult, max_mult],
outputs=[plot_display, stats_display, status_text]
)
# 事件綁定 - 動畫模式
play_btn.click(
fn=run_animation,
inputs=[anim_seed, anim_length, anim_min_mult, anim_max_mult, anim_speed],
outputs=[anim_plot, anim_stats, anim_message, anim_progress]
)
return demo
# 啟動應用
if __name__ == "__main__":
demo = create_demo()
demo.launch(share=True) |