Gumball2k5 commited on
Commit
abfba9b
·
verified ·
1 Parent(s): 65d54f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -165
app.py CHANGED
@@ -21,118 +21,6 @@ st.set_page_config(
21
  layout="wide"
22
  )
23
 
24
- # --- START OF NEW THEME SECTION ---
25
-
26
- def load_css():
27
- """Tải CSS tùy chỉnh để tạo giao diện 'thời tiết'."""
28
- st.markdown("""
29
- <style>
30
- /* ===== FONT CHUNG ===== */
31
- /* Sử dụng font hệ thống mượt mà hơn */
32
- .stApp, .stSidebar {
33
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
34
- }
35
-
36
- /* ===== NỀN CHÍNH (MAIN BACKGROUND) ===== */
37
- /* Tạo hiệu ứng gradient "bầu trời" */
38
- [data-testid="stAppViewContainer"] {
39
- background-image: linear-gradient(to bottom, #B0E0E6, #F0F8FF); /* Từ xanh nhạt (Powder Blue) đến trắng xanh (Alice Blue) */
40
- background-attachment: fixed;
41
- background-size: cover;
42
- }
43
-
44
- /* ===== THANH SIDEBAR ===== */
45
- /* Làm sidebar có nền mờ (frosty glass) */
46
- [data-testid="stSidebar"] {
47
- background-color: rgba(255, 255, 255, 0.7) !important;
48
- backdrop-filter: blur(10px);
49
- border-right: 1px solid rgba(255, 255, 255, 0.2);
50
- }
51
-
52
- /* Chữ trong sidebar */
53
- [data-testid="stSidebar"] .st-emotion-cache-16txtl3 {
54
- color: #0E2A47; /* Xanh đậm */
55
- }
56
-
57
- /* Nút radio được chọn trong sidebar */
58
- .stRadio label[data-baseweb="radio"] div[data-checked="true"] {
59
- background-color: #005aa7 !important; /* Xanh đậm khi được chọn */
60
- }
61
-
62
- /* ===== THẺ DỰ BÁO (METRIC CARDS) ===== */
63
- /* Đây là phần quan trọng nhất */
64
- div[data-testid="stMetric"] {
65
- background-color: rgba(255, 255, 255, 0.85); /* Nền trắng mờ */
66
- border: 1px solid rgba(255, 255, 255, 0.3);
67
- border-radius: 12px; /* Bo góc */
68
- padding: 20px;
69
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); /* Đổ bóng nhẹ */
70
- backdrop-filter: blur(5px);
71
- transition: transform 0.2s ease;
72
- }
73
- /* Hiệu ứng khi di chuột vào thẻ */
74
- div[data-testid="stMetric"]:hover {
75
- transform: translateY(-3px);
76
- box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
77
- }
78
-
79
- /* Tiêu đề của thẻ (Forecast for...) */
80
- div[data-testid="stMetricLabel"] p {
81
- font-size: 1.1rem !important;
82
- font-weight: 600 !important;
83
- color: #333333; /* Màu xám đậm */
84
- }
85
-
86
- /* Giá trị nhiệt độ dự báo */
87
- div[data-testid="stMetricValue"] {
88
- font-size: 2.8rem !important;
89
- font-weight: 700 !important;
90
- color: #004080; /* Màu xanh navy đậm */
91
- }
92
-
93
- /* Giá trị "Actual" (delta) */
94
- div[data-testid="stMetricDelta"] {
95
- font-size: 1rem !important;
96
- font-weight: 600 !important;
97
- color: #555555; /* Màu xám vừa */
98
- }
99
-
100
- /* ===== Tiêu đề (H1, H2, H3) ===== */
101
- h1 {
102
- color: #004080; /* Xanh navy đậm */
103
- text-shadow: 1px 1px 3px rgba(255,255,255,0.5);
104
- }
105
- h2, h3 {
106
- color: #005aa7; /* Xanh đậm */
107
- }
108
-
109
- /* ===== EXPANDERS (Training Set Overview) ===== */
110
- div[data-testid="stExpander"] {
111
- background-color: rgba(255, 255, 255, 0.😎 !important;
112
- border-radius: 10px !important;
113
- border: 1px solid rgba(0, 0, 0, 0.1) !important;
114
- }
115
-
116
- /* ===== BIỂU ĐỒ (PLOTLY) ===== */
117
- /* Làm nền biểu đồ trong suốt để hòa vào nền gradient */
118
- .plotly-graph-div {
119
- border-radius: 8px;
120
- }
121
-
122
- /* Làm nền bảng (dataframe) đẹp hơn */
123
- .stDataFrame {
124
- border-radius: 8px;
125
- overflow: hidden;
126
- }
127
-
128
- </style>
129
- """, unsafe_allow_html=True)
130
-
131
- # Gọi hàm CSS ngay lập tức
132
- load_css()
133
-
134
- # --- END OF NEW THEME SECTION ---
135
-
136
  # --- 3. DATA & MODEL LOADING FUNCTIONS (WITH CACHING) ---
137
  # Checklist Items 1 & 2: Cache all heavy operations
138
 
@@ -174,8 +62,7 @@ def load_champion_models():
174
  "Ensure the 5 .pkl files are in the 'models/' directory.")
175
  return []
176
 
177
- @st.cache_data
178
- def load_performance_data(file_path="data/final_5_day_results_df.csv"):
179
  """Loads pre-calculated performance data for Tab 3."""
180
  try:
181
  df = pd.read_csv(file_path)
@@ -241,6 +128,11 @@ with tab1:
241
  # --- MỤC 3 TRONG CHECKLIST ---
242
  st.title("Saigon Temperature Forecasting Application 🌦️")
243
 
 
 
 
 
 
244
  st.subheader("Project Summary")
245
  st.markdown("""
246
  The goal of this project is to forecast the average daily temperature for Ho Chi Minh City for the next 5 days.
@@ -249,7 +141,8 @@ with tab1:
249
  * **Model:** We use 5 'specialist' models - each model is optimized to predict a specific future day (T+1 to T+5).
250
  """)
251
 
252
- st.subheader("Our 'Two-Stream' Strategy")
 
253
  st.markdown("""
254
  To optimize performance, we applied a "Two-Stream" strategy:
255
  1. **Stream 1 (Linear Models):** Linear models (like Linear Regression) were trained on a feature set pruned using VIF to avoid multicollinearity.
@@ -258,7 +151,8 @@ with tab1:
258
  Our Champion Model is a **Stacking** model from Stream 2, which demonstrated superior performance.
259
  """)
260
 
261
- st.subheader("Final Model Leaderboard")
 
262
  st.markdown("Model leaderboard ranked by average RMSE score (lower is better).")
263
 
264
  # Gọi hàm từ benchmark_utils.py
@@ -300,7 +194,6 @@ with tab2:
300
  )
301
  else:
302
  st.error("Test data could not be loaded.") # Đã xóa st.sidebar.
303
- # --- KẾT THÚC DI CHUYỂN ---
304
 
305
  st.divider() # Thêm đường kẻ ngang
306
 
@@ -333,7 +226,6 @@ with tab2:
333
  cols = st.columns(5)
334
 
335
  # Lấy giá trị thực tế để so sánh
336
- # --- SỬA LỖI LOGIC: Lấy 'actual_values' từ all_data_df ---
337
  actual_values = []
338
  if selected_date_ts in all_data_df.index:
339
  actual_row = all_data_df.loc[selected_date_ts]
@@ -355,6 +247,25 @@ with tab2:
355
  delta=delta_text,
356
  delta_color="off"
357
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
  # --- BIỂU ĐỒ DỮ LIỆU TRAINING ---
360
  st.subheader("Training Set Overview")
@@ -368,23 +279,13 @@ with tab2:
368
  mode='lines', name='Training Data (Actual)',
369
  line=dict(color='#005aa7', width=1)
370
  ))
371
-
372
- # --- CẬP NHẬT LAYOUT ĐỂ THÊM SLIDER VÀ KHÓA TRỤC Y ---
373
  fig_train.update_layout(
374
  title="Actual Temperature - Full Training Set",
375
- xaxis_title="Date",
376
- yaxis_title="Temperature (°C)",
377
  template="plotly_white",
378
-
379
- # 1. Thêm thanh trượt (slider) cho trục X
380
- xaxis_rangeslider_visible=True,
381
-
382
- # 2. Khóa trục Y (không cho zoom/thay đổi)
383
- # Điều này giữ nguyên range nhiệt độ khi bạn trượt/zoom trục X
384
- yaxis_fixedrange=True
385
  )
386
- # --- KẾT THÚC CẬP NHẬT ---
387
-
388
  st.plotly_chart(fig_train, use_container_width=True)
389
 
390
  # 4. Biểu đồ Context
@@ -417,29 +318,37 @@ with tab2:
417
  )
418
  st.plotly_chart(fig, use_container_width=True)
419
 
420
- # --- Biểu đồ so sánh Actual vs Forecast ---
421
  st.subheader("5-Day Forecast vs. Actual Comparison")
422
- if is_partial_forecast:
423
- st.info("Cannot draw the Actual vs. Forecast comparison chart because "
424
- "the selected date is too close to the end of the test set (missing 'actual' data).")
425
- else:
426
- fig_comp = go.Figure()
427
- fig_comp.add_trace(go.Scatter(
428
- x=forecast_dates, y=predictions,
429
- mode='lines+markers', name='5-Day Forecast',
430
- line=dict(color='red', dash='dot')
431
- ))
 
 
432
  fig_comp.add_trace(go.Scatter(
433
  x=forecast_dates, y=actual_values,
434
  mode='lines+markers', name='5-Day Actual',
435
  line=dict(color='blue')
436
  ))
437
- fig_comp.update_layout(
438
- title="5-Day Forecast vs. Actual Values",
439
- xaxis_title="Date", yaxis_title="Temperature (°C)",
440
- template="plotly_white", legend=dict(x=0.01, y=0.99)
441
- )
442
- st.plotly_chart(fig_comp, use_container_width=True)
 
 
 
 
 
 
443
 
444
  else:
445
  # Điều chỉnh lại cảnh báo này
@@ -484,9 +393,16 @@ with tab3:
484
  )
485
  st.plotly_chart(fig_r2, use_container_width=True)
486
 
487
- # 2. Biểu đồ Dự báo vs. Thực tế
488
- st.subheader("Forecast vs. Actual Comparison (on entire test set)")
 
 
 
 
 
 
489
 
 
490
  @st.cache_data
491
  def get_full_test_predictions(_models, _X_test):
492
  """Run predictions on the entire test set and cache the results."""
@@ -500,21 +416,19 @@ with tab3:
500
  with st.spinner("Running predictions on entire test set... (This is cached for next time)"):
501
  y_pred_test = get_full_test_predictions(models, X_test)
502
 
503
- col1, col2 = st.columns(2)
504
- with col1:
505
- fig_d1 = diag.plot_forecast_vs_actual(
506
- y_true=y_test['Day 1'],
507
- y_pred=y_pred_test['Day 1'],
508
- day_ahead_title="Day 1 Forecast"
509
- )
510
- st.plotly_chart(fig_d1, use_container_width=True)
511
- with col2:
512
- fig_d5 = diag.plot_forecast_vs_actual(
513
- y_true=y_test['Day 5'],
514
- y_pred=y_pred_test['Day 5'],
515
- day_ahead_title="Day 5 Forecast"
516
- )
517
- st.plotly_chart(fig_d5, use_container_width=True)
518
 
519
  # 3. Mục Tùy chọn: Deep Dive Expander
520
  with st.expander("Champion Model Diagnostics (Deep Dive)"):
 
21
  layout="wide"
22
  )
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # --- 3. DATA & MODEL LOADING FUNCTIONS (WITH CACHING) ---
25
  # Checklist Items 1 & 2: Cache all heavy operations
26
 
 
62
  "Ensure the 5 .pkl files are in the 'models/' directory.")
63
  return []
64
 
65
+ @st.cache_datadef load_performance_data(file_path="data/final_5_day_results_df.csv"):
 
66
  """Loads pre-calculated performance data for Tab 3."""
67
  try:
68
  df = pd.read_csv(file_path)
 
128
  # --- MỤC 3 TRONG CHECKLIST ---
129
  st.title("Saigon Temperature Forecasting Application 🌦️")
130
 
131
+ # --- NÂNG CẤP: Thêm hình ảnh ---
132
+ st.image("https://images.unsplash.com/photo-1583496661160-fb58ceab5245?q=80&w=2070",
133
+ caption="Ho Chi Minh City. Credit: Unsplash", use_column_width=True)
134
+ # Bạn có thể thay thế URL trên bằng URL của riêng bạn, hoặc
135
+
136
  st.subheader("Project Summary")
137
  st.markdown("""
138
  The goal of this project is to forecast the average daily temperature for Ho Chi Minh City for the next 5 days.
 
141
  * **Model:** We use 5 'specialist' models - each model is optimized to predict a specific future day (T+1 to T+5).
142
  """)
143
 
144
+ # --- NÂNG CẤP: Thêm emoji ---
145
+ st.subheader("🚀 Our 'Two-Stream' Strategy")
146
  st.markdown("""
147
  To optimize performance, we applied a "Two-Stream" strategy:
148
  1. **Stream 1 (Linear Models):** Linear models (like Linear Regression) were trained on a feature set pruned using VIF to avoid multicollinearity.
 
151
  Our Champion Model is a **Stacking** model from Stream 2, which demonstrated superior performance.
152
  """)
153
 
154
+ # --- NÂNG CẤP: Thêm emoji ---
155
+ st.subheader("🏆 Final Model Leaderboard")
156
  st.markdown("Model leaderboard ranked by average RMSE score (lower is better).")
157
 
158
  # Gọi hàm từ benchmark_utils.py
 
194
  )
195
  else:
196
  st.error("Test data could not be loaded.") # Đã xóa st.sidebar.
 
197
 
198
  st.divider() # Thêm đường kẻ ngang
199
 
 
226
  cols = st.columns(5)
227
 
228
  # Lấy giá trị thực tế để so sánh
 
229
  actual_values = []
230
  if selected_date_ts in all_data_df.index:
231
  actual_row = all_data_df.loc[selected_date_ts]
 
247
  delta=delta_text,
248
  delta_color="off"
249
  )
250
+
251
+ # --- NÂNG CẤP: Thêm "Why" Insights ---
252
+ st.subheader("Forecast Insights (Why?)")
253
+
254
+ # Lấy 2 features từ input_features (đã được xác nhận tồn tại)
255
+ temp_lag_1 = input_features['temp_lag_1'].iloc[0]
256
+ precip_today = input_features['precip'].iloc[0]
257
+
258
+ # Hiển thị insight dựa trên giá trị
259
+ if temp_lag_1 > 30: # Giả định 30°C là "rất nóng"
260
+ st.info(f"💡 Insight: Yesterday was very hot ({temp_lag_1:.1f}°C). The model is using this strong 'persistence' signal for tomorrow's forecast.")
261
+ elif temp_lag_1 < 25: # Giả định 25°C là "mát mẻ"
262
+ st.info(f"💡 Insight: Yesterday was cool ({temp_lag_1:.1f}°C). This is likely pulling the initial forecast down.")
263
+
264
+ if precip_today > 10: # Giả định 10mm là "ngày mưa"
265
+ st.info(f"💡 Insight: The selected day had {precip_today:.1f}mm of rain. This humidity and cloud cover is factored into the forecast.")
266
+ elif 'temp_lag_1' not in locals() or (temp_lag_1 >= 25 and temp_lag_1 <= 30):
267
+ st.info("💡 Insight: Weather conditions appear stable. The forecast is primarily driven by seasonal trends and recent temperature history.")
268
+ # --- KẾT THÚC NÂNG CẤP ---
269
 
270
  # --- BIỂU ĐỒ DỮ LIỆU TRAINING ---
271
  st.subheader("Training Set Overview")
 
279
  mode='lines', name='Training Data (Actual)',
280
  line=dict(color='#005aa7', width=1)
281
  ))
 
 
282
  fig_train.update_layout(
283
  title="Actual Temperature - Full Training Set",
284
+ xaxis_title="Date", yaxis_title="Temperature (°C)",
 
285
  template="plotly_white",
286
+ xaxis_rangeslider_visible=True, # Thêm slider
287
+ yaxis_fixedrange=True # Khóa trục Y
 
 
 
 
 
288
  )
 
 
289
  st.plotly_chart(fig_train, use_container_width=True)
290
 
291
  # 4. Biểu đồ Context
 
318
  )
319
  st.plotly_chart(fig, use_container_width=True)
320
 
321
+ # --- NÂNG CẤP: Biểu đồ thông minh hơn ---
322
  st.subheader("5-Day Forecast vs. Actual Comparison")
323
+
324
+ fig_comp = go.Figure()
325
+
326
+ # 1. Luôn thêm đường Dự báo
327
+ fig_comp.add_trace(go.Scatter(
328
+ x=forecast_dates, y=predictions,
329
+ mode='lines+markers', name='5-Day Forecast',
330
+ line=dict(color='red', dash='dot')
331
+ ))
332
+
333
+ # 2. Chỉ thêm đường Thực tế nếu có đủ 5 ngày dữ liệu
334
+ if not is_partial_forecast:
335
  fig_comp.add_trace(go.Scatter(
336
  x=forecast_dates, y=actual_values,
337
  mode='lines+markers', name='5-Day Actual',
338
  line=dict(color='blue')
339
  ))
340
+ fig_comp.update_layout(title="5-Day Forecast vs. Actual Values")
341
+ else:
342
+ # Nếu không, chỉ hiển thị dự báo
343
+ fig_comp.update_layout(title="5-Day Forecast (Actual data not yet available)")
344
+
345
+ # Luôn hiển thị biểu đồ
346
+ fig_comp.update_layout(
347
+ xaxis_title="Date", yaxis_title="Temperature (°C)",
348
+ template="plotly_white", legend=dict(x=0.01, y=0.99)
349
+ )
350
+ st.plotly_chart(fig_comp, use_container_width=True)
351
+ # --- KẾT THÚC NÂNG CẤP ---
352
 
353
  else:
354
  # Điều chỉnh lại cảnh báo này
 
393
  )
394
  st.plotly_chart(fig_r2, use_container_width=True)
395
 
396
+ # --- NÂNG CẤP: Biểu đồ tương tác với Slider ---
397
+ st.subheader("Interactive Forecast vs. Actual Comparison")
398
+
399
+ # 1. Thêm slider
400
+ selected_horizon = st.slider(
401
+ "Select Forecast Horizon (Day) to inspect:",
402
+ 1, 5, 1
403
+ )
404
 
405
+ # 2. Lấy dữ liệu dự đoán (đã được cache)
406
  @st.cache_data
407
  def get_full_test_predictions(_models, _X_test):
408
  """Run predictions on the entire test set and cache the results."""
 
416
  with st.spinner("Running predictions on entire test set... (This is cached for next time)"):
417
  y_pred_test = get_full_test_predictions(models, X_test)
418
 
419
+ # 3. Chọn dữ liệu dựa trên slider
420
+ y_true_selected = y_test[f'Day {selected_horizon}']
421
+ y_pred_selected = y_pred_test[f'Day {selected_horizon}']
422
+
423
+ # 4. Vẽ 1 biểu đồ duy nhất
424
+ fig_interactive = diag.plot_forecast_vs_actual(
425
+ y_true=y_true_selected,
426
+ y_pred=y_pred_selected,
427
+ day_ahead_title=f"Day {selected_horizon} Forecast"
428
+ )
429
+ st.plotly_chart(fig_interactive, use_container_width=True)
430
+ # --- KẾT THÚC NÂNG CẤP ---
431
+
 
 
432
 
433
  # 3. Mục Tùy chọn: Deep Dive Expander
434
  with st.expander("Champion Model Diagnostics (Deep Dive)"):