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)