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)