jeff7522553
init
0000826
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)