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