File size: 30,510 Bytes
0e84eb9
 
 
 
 
0bfb84d
 
0e84eb9
0bfb84d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08b713d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8707199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08b713d
0e84eb9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime
import pandas as pd
import seaborn as sns
from matplotlib.lines import Line2D
from matplotlib.patches import Patch

def cv_metric_horizon():
    # Data for Ridge
    ridge_rmse_means = [1.5325, 2.2207, 2.5017, 2.5879, 2.6164]
    ridge_mae_means = [1.1858, 1.7490, 1.9916, 2.0732, 2.0849]

    # Data for LGBM
    lgbm_rmse_means = [1.5587, 2.2607, 2.5045, 2.5682, 2.5987]
    lgbm_mae_means = [1.2018, 1.7782, 1.9940, 2.0520, 2.0750]

    # Horizons
    horizons = [1, 2, 3, 4, 5]

    # Colors
    ridge_color = '#d63031'  # Đỏ
    lgbm_color = '#2E86AB'   # Xanh dương
    ridge_bg = '#ffe6e6'     # Background nhạt cho Ridge
    lgbm_bg = '#e6f0ff'      # Background nhạt cho LGBM

    # Create figure với 2 subplots
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))

    # ===== PLOT 1: RMSE comparison =====
    ax1.plot(horizons, ridge_rmse_means, color=ridge_color, linewidth=2.5, marker='o', markersize=6, label='Ridge Tuned')
    ax1.plot(horizons, lgbm_rmse_means, color=lgbm_color, linewidth=2.5, marker='s', markersize=6, label='LGBM Tuned')

    # Highlight horizon 3-5
    ax1.axvspan(3, 5, color=ridge_bg, alpha=0.3)
    ax1.axvspan(3, 5, color=lgbm_bg, alpha=0.3)

    ax1.set_xlabel('Forecasting Horizon (Days)', fontsize=12, fontweight='bold')
    ax1.set_title('CV RMSE', fontsize=14, fontweight='bold')
    ax1.grid(True, alpha=0.3)
    ax1.set_xticks(horizons)

    # ===== PLOT 2: MAE comparison =====
    ax2.plot(horizons, ridge_mae_means, color=ridge_color, linewidth=2.5, marker='o', markersize=6, label='Ridge Tuned')
    ax2.plot(horizons, lgbm_mae_means, color=lgbm_color, linewidth=2.5, marker='s', markersize=6, label='LGBM Tuned')

    # Highlight horizon 3-5
    ax2.axvspan(3, 5, color=ridge_bg, alpha=0.3)
    ax2.axvspan(3, 5, color=lgbm_bg, alpha=0.3)

    ax2.set_xlabel('Forecasting Horizon (Days)', fontsize=12, fontweight='bold')
    ax2.set_title('CV MAE', fontsize=14, fontweight='bold')
    ax2.grid(True, alpha=0.3)
    ax2.set_xticks(horizons)

    # ===== LEGEND CHUNG =====
    legend_elements = [
        Line2D([0], [0], color=ridge_color, marker='o', linestyle='-', linewidth=2.5, markersize=8, label='Ridge Tuned'),
        Line2D([0], [0], color=lgbm_color, marker='s', linestyle='-', linewidth=2.5, markersize=8, label='LGBM Tuned'),
        Patch(facecolor=ridge_bg, edgecolor="#361236", alpha=0.3, label='Longer Horizon 3-5'),
    ]

    fig.legend(handles=legend_elements, loc='upper right', bbox_to_anchor=(0.98, 0.95), 
            fontsize=12, frameon=True, fancybox=True, shadow=True, ncol=1)

    # ===== KEY INSIGHTS TEXT BOX (English) =====
    insight_text = """KEY INSIGHTS:

    • Ridge performs better at short-term
    forecasting (Day 1-2)

    • LGBM performs better from horizon 3-5
    - Lower RMSE than Ridge
    - Lower MAE than Ridge  
    - Higher stability

    → LGBM is more suitable for
    longer term forecasting (3-5 days)"""

    fig.text(0.83, 0.75, insight_text, fontsize=11, 
            bbox=dict(boxstyle="round,pad=0.5", facecolor='#F8F9FA', edgecolor='#2E86AB', alpha=0.9),
            verticalalignment='top', linespacing=1.5)

    plt.tight_layout(rect=[0, 0, 0.8, 1])  # Để chỗ cho text box
    plt.savefig('figures/per_horizon_cv_performance.png', dpi=300, bbox_inches='tight')
    plt.show()
def ridge_lgbm():
    # Data for tuned models
    models = ['Ridge Tuned', 'LGBM Tuned']
    rmse_values = [2.2919, 2.2992]
    mae_values = [1.8169, 1.8211]
    r2_values = [0.7814, 0.7813]

    # Create subplots
    fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))

    # Colors
    ridge_color = '#d63031'
    lgbm_color = '#2E86AB'

    # Plot 1: RMSE comparison
    x = np.arange(len(models))
    width = 0.6

    bars1 = ax1.bar(x, rmse_values, width, color=[ridge_color, lgbm_color], alpha=0.8)
    ax1.set_title('RMSE', fontsize=14, fontweight='bold')
    ax1.set_xticks(x)
    ax1.set_xticklabels(models)  # Bỏ rotation
    ax1.set_ylim(0, 3)  # Tăng y-limit đến 3
    ax1.grid(True, alpha=0.3)

    # Add values on bars
    for bar, value in zip(bars1, rmse_values):
        ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, 
                f'{value:.4f}', ha='center', va='bottom', fontweight='bold', fontsize=11)

    # Plot 2: MAE comparison
    bars2 = ax2.bar(x, mae_values, width, color=[ridge_color, lgbm_color], alpha=0.8)
    ax2.set_title('MAE', fontsize=14, fontweight='bold')
    ax2.set_xticks(x)
    ax2.set_xticklabels(models)  # Bỏ rotation
    ax2.set_ylim(0, 3)  # Tăng y-limit đến 3
    ax2.grid(True, alpha=0.3)

    # Add values on bars
    for bar, value in zip(bars2, mae_values):
        ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, 
                f'{value:.4f}', ha='center', va='bottom', fontweight='bold', fontsize=11)

    # Plot 3: R² comparison
    bars3 = ax3.bar(x, r2_values, width, color=[ridge_color, lgbm_color], alpha=0.8)
    ax3.set_title('R²', fontsize=14, fontweight='bold')
    ax3.set_xticks(x)
    ax3.set_xticklabels(models)  # Bỏ rotation
    ax3.set_ylim(0, 1)  # R² vẫn giữ 0-1
    ax3.grid(True, alpha=0.3)

    # Add values on bars
    for bar, value in zip(bars3, r2_values):
        ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, 
                f'{value:.4f}', ha='center', va='bottom', fontweight='bold', fontsize=11)

    # Bỏ tất cả xlabel và ylabel
    ax1.set_xlabel('')
    ax1.set_ylabel('')
    ax2.set_xlabel('')
    ax2.set_ylabel('')
    ax3.set_xlabel('')
    ax3.set_ylabel('')

    plt.tight_layout()
    plt.savefig('figures/tuned_models_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()

def train_test_gap():
    # --- Chart 2: Horizon-wise Gap ---
    horizons = ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5']
    baseline_gap = [0.6915, 1.0250, 1.1818, 1.3531, 1.4625]
    optimized_gap = [0.2626, 0.3331, 0.5779, 0.3747, 0.7057]

    plt.figure(figsize=(10, 5))
    x = range(len(horizons))

    plt.plot(x, baseline_gap, marker='o', label="Baseline Gap", linewidth=2, markersize=8, color='red')
    plt.plot(x, optimized_gap, marker='s', label="Optimized Gap", linewidth=2, markersize=8, color='green')

    plt.xticks(x, horizons, fontsize=11)
    plt.xlabel("Forecasting Horizon", fontsize=12, fontweight='bold')
    plt.title("Train-Test Generalization Gap: Baseline vs Optimized Model", fontsize=14, fontweight='bold')
    plt.legend(fontsize=10)
    plt.grid(True, alpha=0.3)
    plt.ylim(0, 1.6)  # Trục y bắt đầu từ 0

    # Thêm giá trị trên các điểm
    for i, (base, opt) in enumerate(zip(baseline_gap, optimized_gap)):
        plt.text(i, base + 0.05, f'{base:.3f}', ha='center', va='bottom', fontsize=9, color='red')
        plt.text(i, opt - 0.08, f'{opt:.3f}', ha='center', va='top', fontsize=9, color='green')

    plt.tight_layout()
    plt.savefig('figures/horizon_gap_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()


def cv_range():
   # Data
   metrics = ['RMSE', 'MAE', 'R²']
   cv_means = [2.2992, 1.8211, 0.7813]
   cv_stds = [0.1346, 0.1236, 0.0290]
   test_values = [2.1866, 1.7246, 0.8149]

   # Create figure
   fig, ax = plt.subplots(figsize=(10, 6))

   x_pos = np.arange(len(metrics))
   width = 0.6

   # Plot CV means with error bars (representing ±1 std)
   bars = ax.bar(x_pos, cv_means, width, yerr=cv_stds, 
               capsize=8, label='CV Mean ± Std', 
               color='lightblue', alpha=0.7, edgecolor='navy', linewidth=1.5)

   # Plot test values as points
   # ax.scatter(x_pos, test_values, color='red', s=100, zorder=3, 
   #            label='Test Value', marker='o', edgecolor='darkred', linewidth=1)

   # Customize plot
   ax.set_xlabel('Metrics', fontsize=12, fontweight='bold')
   ax.set_ylabel('Score', fontsize=12, fontweight='bold')
   ax.set_title('Generalization Check: CV Performance vs Test Set', 
               fontsize=14, fontweight='bold', pad=20)
   ax.set_xticks(x_pos)
   ax.set_xticklabels(metrics, fontsize=11)
   ax.legend(fontsize=10)

   plt.tight_layout()
   plt.savefig('figures/generalization_check.png', dpi=300, bbox_inches='tight')
   plt.show()

def metric_overall():
    # 1. Biểu đồ so sánh average metrics
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))

    # Biểu đồ 1: Average metrics comparison
    metrics = ['RMSE', 'MAE', 'R²']
    baseline_avg = [2.4121, 1.8246, 0.7642]
    optimized_avg = [2.1866, 1.7246, 0.8149]

    x = np.arange(len(metrics))
    width = 0.35

    bars1 = ax1.bar(x - width/2, baseline_avg, width, label='Baseline', color='gray', alpha=0.7)
    bars2 = ax1.bar(x + width/2, optimized_avg, width, label='Optimized', color='#2E86AB', alpha=0.8)

    ax1.set_xlabel('Metrics', fontsize=12, fontweight='bold')
    ax1.set_title('Average Performance: Optimized vs Baseline', fontsize=14, fontweight='bold', pad=20)
    ax1.set_xticks(x)
    ax1.set_xticklabels(metrics, fontsize=11)
    ax1.legend(fontsize=10)
    ax1.grid(True, alpha=0.3)

    # Thêm giá trị trên cột
    for bars in [bars1, bars2]:
        for bar in bars:
            height = bar.get_height()
            ax1.text(bar.get_x() + bar.get_width()/2., height + 0.02,
                    f'{height:.3f}', ha='center', va='bottom', fontsize=10, fontweight='bold')

    # Biểu đồ 2: Horizon-wise RMSE comparison
    horizons = ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5']
    baseline_rmse = [1.4713, 2.2180, 2.5523, 2.8270, 2.9917]
    optimized_rmse = [1.4682, 2.1102, 2.3645, 2.4761, 2.5139]

    x2 = np.arange(len(horizons))
    bars3 = ax2.bar(x2 - width/2, baseline_rmse, width, label='Baseline', color='gray', alpha=0.7)
    bars4 = ax2.bar(x2 + width/2, optimized_rmse, width, label='Optimized', color='#2E86AB', alpha=0.8)

    ax2.set_xlabel('Forecasting Horizon', fontsize=12, fontweight='bold')
    ax2.set_title('RMSE by Horizon: Optimized vs Baseline', fontsize=14, fontweight='bold', pad=20)
    ax2.set_xticks(x2)
    ax2.set_xticklabels(horizons, fontsize=11)
    ax2.legend(fontsize=10)
    ax2.grid(True, alpha=0.3)

    # Thêm giá trị trên cột
    for bars in [bars3, bars4]:
        for bar in bars:
            height = bar.get_height()
            ax2.text(bar.get_x() + bar.get_width()/2., height + 0.02,
                    f'{height:.3f}', ha='center', va='bottom', fontsize=10, fontweight='bold')

    plt.tight_layout()
    plt.savefig('figures/optimized_vs_baseline_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()

def cv_overview():
   # Dữ liệu từ output của bạn
   folds_data = {
      'fold1': {
         'train_start': '2015-10-20', 'train_end': '2017-01-19',
         'val_start': '2017-02-19', 'val_end': '2018-05-21',
         'train_samples': 458, 'val_samples': 457
      },
      'fold2': {
         'train_start': '2015-10-20', 'train_end': '2018-05-21',
         'val_start': '2018-06-21', 'val_end': '2019-09-20',
         'train_samples': 945, 'val_samples': 457
      },
      'fold3': {
         'train_start': '2015-10-20', 'train_end': '2019-09-20',
         'val_start': '2019-10-21', 'val_end': '2021-01-19',
         'train_samples': 1432, 'val_samples': 457
      },
      'fold4': {
         'train_start': '2015-10-20', 'train_end': '2021-01-19',
         'val_start': '2021-02-19', 'val_end': '2022-05-21',
         'train_samples': 1919, 'val_samples': 457
      },
      'fold5': {
         'train_start': '2015-10-20', 'train_end': '2022-05-21',
         'val_start': '2022-06-21', 'val_end': '2023-09-20',
         'train_samples': 2406, 'val_samples': 457
      }
   }

   # Chuyển đổi ngày tháng
   for fold in folds_data.values():
      for key in ['train_start', 'train_end', 'val_start', 'val_end']:
         fold[key] = datetime.strptime(fold[key], '%Y-%m-%d')

   # Màu sắc
   train_color = '#1f77b4'  # blue
   val_color = '#ff7f0e'    # orange
   gap_color = 'lightgray'

   # Vẽ biểu đồ tổng quan
   plt.figure(figsize=(14, 6))

   # Tạo timeline tổng quan
   all_dates = []
   for fold_data in folds_data.values():
      all_dates.extend([fold_data['train_start'], fold_data['train_end'], 
                        fold_data['val_start'], fold_data['val_end']])
   overall_start = min(all_dates)
   overall_end = max(all_dates)

   # Tạo danh sách các điểm mốc thời gian quan trọng cho trục X
   important_dates = [
      datetime(2016, 1, 1), datetime(2017, 1, 1), datetime(2018, 1, 1),
      datetime(2019, 1, 1), datetime(2020, 1, 1), datetime(2021, 1, 1),
      datetime(2022, 1, 1), datetime(2023, 1, 1)
   ]

   # Vẽ từng fold trên cùng một timeline
   for i, (fold_name, fold_data) in enumerate(folds_data.items()):
      y_pos = i  # Fold 1 ở trên cùng, fold 5 ở dưới cùng
      
      # Tính positions dựa trên datetime
      train_start_pos = fold_data['train_start']
      train_end_pos = fold_data['train_end']
      val_start_pos = fold_data['val_start']
      val_end_pos = fold_data['val_end']
      
      # Tính gap period
      gap_start_pos = fold_data['train_end']
      gap_end_pos = fold_data['val_start']
      
      # Vẽ train period
      plt.barh(y_pos, (train_end_pos - train_start_pos).days, 
               left=(train_start_pos - overall_start).days, 
               height=0.4, color=train_color, alpha=0.7, label='Train' if i==0 else "")
      
      # Vẽ gap period
      plt.barh(y_pos, (gap_end_pos - gap_start_pos).days, 
               left=(gap_start_pos - overall_start).days, 
               height=0.4, color=gap_color, alpha=0.5, label='Cutoff 30 days (lag/rolling)' if i==0 else "")
      
      # Vẽ validation period
      plt.barh(y_pos, (val_end_pos - val_start_pos).days, 
               left=(val_start_pos - overall_start).days, 
               height=0.4, color=val_color, alpha=0.7, label='Validation' if i==0 else "")
      
      # Thêm thông tin samples - FONT SIZE LỚN HƠN
      plt.text((train_start_pos - overall_start).days, y_pos, 
               f" {fold_data['train_samples']}", va='center', ha='left', 
               fontsize=12, fontweight='bold')  # Tăng từ 8 lên 12
      plt.text((val_start_pos - overall_start).days, y_pos, 
               f" {fold_data['val_samples']}", va='center', ha='left', 
               fontsize=12, fontweight='bold')  # Tăng từ 8 lên 12

   # Customize overview plot - FONT SIZE LỚN HƠN
   plt.yticks(range(len(folds_data)), [f'Fold {i+1}' for i in range(len(folds_data))], 
            fontsize=12)  # Tăng font size cho trục Y
   plt.ylabel('Folds', fontweight='bold', fontsize=14)  # Tăng font size

   # Thiết lập trục X với timeline - NẰM NGANG
   x_ticks = [(date - overall_start).days for date in important_dates]
   x_tick_labels = [date.strftime('%Y-%m') for date in important_dates]
   plt.xticks(x_ticks, x_tick_labels, rotation=0, fontsize=11)  # rotation=0 để nằm ngang, tăng font size
   plt.xlabel('Timeline', fontweight='bold', fontsize=14)

   plt.title('Time Series Cross-Validation - Overview of All Folds', fontsize=16, fontweight='bold')

   # LEGEND TO HƠN
   plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=12)  # Tăng font size legend

   plt.grid(True, alpha=0.3, axis='x')
   plt.tight_layout()
   plt.show()

def plot_month_average(df):
    df0 = df.copy()
    # Tính trung bình nhiệt độ theo tháng
    df0['datetime'] = pd.to_datetime(df0['datetime'])
    monthly_avg = df0.groupby(df0['datetime'].dt.month)['temp'].mean()

    # Vẽ biểu đồ đường
    plt.figure(figsize=(14, 7))
    plt.plot(monthly_avg.index, monthly_avg.values, color='#E74C3C', marker='o', 
            markerfacecolor='#C0392B', markersize=8, linestyle='-', linewidth=3, 
            label='Average Temperature')

    # Làm nổi bật điểm peak (tháng 6) và điểm thấp nhất (tháng 1)
    max_temp_idx = monthly_avg.idxmax()
    min_temp_idx = monthly_avg.idxmin()
    
    # Highlight peak point
    plt.scatter(max_temp_idx, monthly_avg[max_temp_idx], color='red', s=150, 
                zorder=5, label=f'Hottest Month (Jun: {monthly_avg[max_temp_idx]:.1f}°C)',
                edgecolors='darkred', linewidth=2)
    
    # Highlight lowest point  
    plt.scatter(min_temp_idx, monthly_avg[min_temp_idx], color='blue', s=150,
                zorder=5, label=f'Coldest Month (Jan: {monthly_avg[min_temp_idx]:.1f}°C)',
                edgecolors='darkblue', linewidth=2)

    # Ghi giá trị nhiệt độ bên trên mỗi điểm
    for i, val in enumerate(monthly_avg.values):
        plt.text(monthly_avg.index[i], val + 0.3, f"{val:.1f}°C", 
                ha='center', fontsize=10, fontweight='bold', color='#2C3E50')

    # Trang trí biểu đồ
    plt.title("Hanoi Average Monthly Temperature (2015–2025)", fontsize=16, fontweight='bold', pad=20)
    plt.xlabel("Month", fontsize=12, fontweight='bold')
    #plt.ylabel("Temperature (°C)", fontsize=12, fontweight='bold')
    plt.grid(alpha=0.3, linestyle='--')
    plt.xticks(range(1, 13), 
              ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
                'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']) 

    # Đặt legend và key insights gần nhau hơn
    plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left')

    # Thêm Key Insights - đặt sát dưới legend
    insights_text = """KEY INSIGHTS:

• SUMMER: Jun-Aug hottest
  (June: 30.3°C peak)

• WINTER: Dec-Feb coldest  
  (Jan: 17.9°C lowest)

• RAPID TRANSITIONS: 
  - Spring (Mar-May): Fast warming
  - Autumn (Sep-Nov): Fast cooling

• STABLE: Strong 10-year pattern"""

    plt.figtext(0.83, 0.76, insights_text, fontsize=10, 
                bbox=dict(boxstyle="round,pad=0.6", facecolor="lightyellow", alpha=0.9),
                verticalalignment='top')

    plt.tight_layout()
    plt.show()


def plot_volatility_customized(df1):
    df = df1.copy()
    df["datetime"] = pd.to_datetime(df["datetime"], errors="coerce")
    df = df.sort_values("datetime").set_index("datetime")
    df_2024 = df[df.index.year == 2024].copy()
    df_2024['temp_rolling_std_5'] = df_2024['temp'].rolling(window=5).std()
    
    # Tạo figure với layout tốt hơn
    plt.figure(figsize=(15, 8))
    
    # Tạo vùng màu theo mùa với độ nổi bật khác nhau
    winter_mask = (df_2024.index.month >= 1) & (df_2024.index.month <= 3)
    spring_mask = (df_2024.index.month >= 4) & (df_2024.index.month <= 5)
    summer_mask = (df_2024.index.month >= 6) & (df_2024.index.month <= 8)
    autumn_mask = (df_2024.index.month >= 9) & (df_2024.index.month <= 12)
    
    plt.fill_between(df_2024[winter_mask].index, 
                    df_2024[winter_mask]['temp'] - df_2024[winter_mask]['temp_rolling_std_5'], 
                    df_2024[winter_mask]['temp'] + df_2024[winter_mask]['temp_rolling_std_5'],
                    alpha=0.7, color='#FF8A65', label='High Volatility (Winter)')  # Cam đậm
    
    # Spring - Medium-High volatility
    plt.fill_between(df_2024[spring_mask].index, 
                    df_2024[spring_mask]['temp'] - df_2024[spring_mask]['temp_rolling_std_5'], 
                    df_2024[spring_mask]['temp'] + df_2024[spring_mask]['temp_rolling_std_5'],
                    alpha=0.6, color='#FF9800', label='Medium-High Volatility (Spring)')  # Cam vừa
    
    # Autumn - Medium volatility  
    plt.fill_between(df_2024[autumn_mask].index, 
                    df_2024[autumn_mask]['temp'] - df_2024[autumn_mask]['temp_rolling_std_5'], 
                    df_2024[autumn_mask]['temp'] + df_2024[autumn_mask]['temp_rolling_std_5'],
                    alpha=0.5, color='#FFB74D', label='Medium Volatility (Autumn)')  # Cam nhạt
    
    # Summer - Lowest volatility - màu nhạt nhất
    plt.fill_between(df_2024[summer_mask].index, 
                    df_2024[summer_mask]['temp'] - df_2024[summer_mask]['temp_rolling_std_5'], 
                    df_2024[summer_mask]['temp'] + df_2024[summer_mask]['temp_rolling_std_5'],
                    alpha=0.4, color='#FFE0B2', label='Low Volatility (Summer)')  # Cam rất nhạt
    
    
    # Vẽ đường nhiệt độ
    plt.plot(df_2024.index, df_2024['temp'], color='darkred', linewidth=2, 
             label='Daily Temperature', marker='o', markersize=2, alpha=0.8)
    
    # Đánh dấu các điểm volatility cao nhất
    high_vol_threshold = df_2024['temp_rolling_std_5'].quantile(0.95)
    high_vol_points = df_2024[df_2024['temp_rolling_std_5'] > high_vol_threshold]
    plt.scatter(high_vol_points.index, high_vol_points['temp'], 
                color='darkblue', s=30, zorder=5, label='Peak Volatility Days')
    
    # Customize biểu đồ
    # plt.ylabel('Temperature (°C)', fontsize=12, fontweight='bold')
    plt.xlabel('Date', fontsize=12, fontweight='bold')
    plt.title('Winter Shows Highest Volatility (2024)', 
              fontsize=18, fontweight='bold', pad=20)
    plt.grid(True, alpha=0.3)
    
    # Đặt legend bên ngoài
    plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
    
    # Thêm text box với key insights - đặt ngay dưới legend
    insights_text = """KEY INSIGHTS:
• WINTER (Jan-Mar): Highest volatility
  - Frequent cold fronts & temperature swings
  - Hardest to forecast accurately
  
• SUMMER (Jun-Aug): Most stable period
  - Consistent high temperatures
  - Easiest for 5-day forecasting
  
• SPRING/AUTUMN: Moderate volatility
  - Transition seasons with mixed patterns
  
• ⚠️ PEAK VOLATILITY: 
  - Sudden weather events like 'Gio Mua Dong Bac'"""
    
    
    plt.figtext(1.05, 0.7, insights_text, fontsize=10, 
                bbox=dict(boxstyle="round,pad=0.65", facecolor="lightyellow", alpha=0.9),
                verticalalignment='top',
                transform=plt.gca().transAxes)
    
    plt.tight_layout()
    plt.show()


def plot_yearly_temperature(df):
    """
    Plot yearly temperature trend for Hanoi with detailed annotations
    
    Parameters:
    -----------
    df : pandas.DataFrame
        DataFrame containing 'datetime' and 'temp' columns
    """
    # Prepare data
    df["datetime"] = pd.to_datetime(df["datetime"])
    yearly_temp = df.groupby(df['datetime'].dt.year)['temp'].mean()

    plt.figure(figsize=(14, 7))

    # Plot main line
    plt.plot(yearly_temp.index, yearly_temp.values, 
             linewidth=3, color='#2E86AB', marker='o', 
             markersize=8, markerfacecolor='#A23B72', 
             markeredgecolor='white', markeredgewidth=2,
             label='Yearly Average Temperature')

    # Find hottest year and significant dip year (2022)
    hottest_year = yearly_temp.idxmax()
    hottest_temp = yearly_temp.max()
    dip_year = 2022
    dip_temp = yearly_temp.loc[dip_year]

    # Highlight hottest year
    plt.annotate(f'HOTTEST: {hottest_temp:.1f}°C', 
                 xy=(hottest_year, hottest_temp), 
                 xytext=(hottest_year-0.5, hottest_temp+0.3),
                 fontsize=11, fontweight='bold', color='#F24236',
                 arrowprops=dict(arrowstyle='->', color='#F24236', lw=1.5),
                 bbox=dict(boxstyle="round,pad=0.3", facecolor='#FFE5E5', edgecolor='#F24236'))

    # Highlight significant dip year (2022)
    plt.annotate(f'SHARP DROP\n{dip_temp:.1f}°C', 
                 xy=(dip_year, dip_temp), 
                 xytext=(dip_year-0.8, dip_temp-0.5),
                 fontsize=10, fontweight='bold', color='#5D6D7E',
                 arrowprops=dict(arrowstyle='->', color='#5D6D7E', lw=1.5),
                 bbox=dict(boxstyle="round,pad=0.3", facecolor='#F8F9F9', edgecolor='#5D6D7E'))

    # Fill area under the line
    plt.fill_between(yearly_temp.index, yearly_temp.values, alpha=0.2, color='#2E86AB')

    # Customize title and labels
    plt.title("HANOI YEARLY AVERAGE TEMPERATURE TREND (2015-2025)", 
              fontsize=16, fontweight='bold', pad=20, color='#2C3E50')

    # X-axis (không rotate)
    plt.xlabel("YEAR", fontsize=12, fontweight='bold', color='#2C3E50', labelpad=10)
    plt.xticks(range(2015, 2026), fontsize=11)
    plt.xlim(2014.5, 2025.5)

    # Y-axis
    plt.ylim(23.2, 25.8)
    plt.yticks(np.arange(23.5, 26.0, 0.5), fontsize=11)

    # Background
    plt.gca().set_facecolor('#FDFEFE')

    # Add trend line to show clear trend
    z = np.polyfit(yearly_temp.index, yearly_temp.values, 1)
    p = np.poly1d(z)
    plt.plot(yearly_temp.index, p(yearly_temp.index), "--", 
             color="#E74C3C", alpha=0.7, linewidth=2, 
             label=f'Trend Line')

    # Add analysis text box (KEY INSIGHTS)
    analysis_text = """KEY INSIGHTS:
• 2019: Hottest year (25.3°C)
• 2022: Significant temperature drop
• Trend: +{:.1f}°C per decade
• 2023-2025: Stabilized at high level""".format(z[0]*10)

    plt.text(0.02, 0.98, analysis_text, transform=plt.gca().transAxes,
             fontsize=10, verticalalignment='top',
             bbox=dict(boxstyle='round', facecolor='#EBF5FB', alpha=0.8, edgecolor='#3498DB'))

    # Add legend ngay dưới KEY INSIGHTS
    plt.legend(loc='upper left', bbox_to_anchor=(0.01, 0.8), fontsize=11, framealpha=0.9)

    # Display values on each point
    for year, temp in yearly_temp.items():
        plt.text(year, temp-0.15, f'{temp:.1f}°C', 
                 ha='center', va='top', fontsize=9, fontweight='bold',
                 bbox=dict(boxstyle="round,pad=0.2", facecolor='white', alpha=0.8))

    # Adjust layout
    plt.tight_layout()
    plt.show()

def get_temperature_statistics(df):
    """
    Get detailed temperature statistics
    
    Parameters:
    -----------
    df : pandas.DataFrame
        DataFrame containing 'datetime' and 'temp' columns
        
    Returns:
    --------
    dict : Dictionary containing temperature statistics
    """
    df["datetime"] = pd.to_datetime(df["datetime"])
    yearly_temp = df.groupby(df['datetime'].dt.year)['temp'].mean()
    
    z = np.polyfit(yearly_temp.index, yearly_temp.values, 1)
    trend_per_decade = z[0] * 10
    
    stats = {
        'yearly_temperatures': yearly_temp.to_dict(),
        'hottest_year': yearly_temp.idxmax(),
        'hottest_temp': yearly_temp.max(),
        'coolest_year': yearly_temp.idxmin(),
        'coolest_temp': yearly_temp.min(),
        'trend_per_decade': trend_per_decade,
        'average_temperature': yearly_temp.mean()
    }
    
    return stats


def create_data_types_table(df):
    """
    Create a styled table for data types overview
    """
    # Analyze data types
    dtype_counts = df.dtypes.value_counts()
    dtype_details = []
    
    for dtype, count in dtype_counts.items():
        if 'datetime' in str(dtype):
            dtype_details.append({'Data Type': 'datetime', 'Count': count, 'Examples': 'DateTime index'})
        elif 'float' in str(dtype) or 'int' in str(dtype):
            examples = df.select_dtypes(include=[dtype]).columns[:3].tolist()
            dtype_details.append({'Data Type': 'numeric', 'Count': count, 'Examples': ', '.join(examples)})
        elif 'object' in str(dtype):
            examples = df.select_dtypes(include=[dtype]).columns[:3].tolist()
            dtype_details.append({'Data Type': 'object', 'Count': count, 'Examples': ', '.join(examples)})
        else:
            examples = df.select_dtypes(include=[dtype]).columns[:3].tolist()
            dtype_details.append({'Data Type': str(dtype), 'Count': count, 'Examples': ', '.join(examples)})
    
    # Create DataFrame
    dtype_df = pd.DataFrame(dtype_details)
    
    # Apply styling
    styled_dtype_table = dtype_df.style\
        .set_properties(**{
            'border': '1px solid #dee2e6',
            'text-align': 'left',
            'padding': '8px'
        })\
        .set_table_styles([
            {'selector': 'th', 'props': [('background-color', '#6c757d'), 
                                       ('color', 'white'),
                                       ('font-weight', 'bold'),
                                       ('padding', '12px')]},
            {'selector': 'tr:hover', 'props': [('background-color', '#f8f9fa')]}
        ])\
        .set_caption('<h3>📋 Data Types Overview</h3>')\
        .hide(axis='index')
    
    return styled_dtype_table

def create_detailed_data_types_table(df):
    """
    Create a more detailed table showing each column's data type
    """
    dtype_info = []
    
    for col in df.columns:
        dtype = df[col].dtype
        if 'datetime' in str(dtype):
            category = 'datetime'
        elif 'float' in str(dtype) or 'int' in str(dtype):
            category = 'numeric'
        elif 'object' in str(dtype):
            category = 'object'
        else:
            category = str(dtype)
            
        dtype_info.append({
            'Column': col,
            'Data Type': category,
            'Specific Type': str(dtype),
            'Null Count': df[col].isnull().sum(),
            'Unique Values': df[col].nunique()
        })
    
    detailed_dtype_df = pd.DataFrame(dtype_info)
    
    # Color coding for data types
    def color_data_type(val):
        if val == 'datetime':
            return 'background-color: #e8f5e8; color: #2e7d32; font-weight: bold'
        elif val == 'numeric':
            return 'background-color: #e3f2fd; color: #1565c0; font-weight: bold'
        elif val == 'object':
            return 'background-color: #f3e5f5; color: #7b1fa2; font-weight: bold'
        else:
            return 'background-color: #fff3e0; color: #ef6c00; font-weight: bold'
    
    styled_detailed_table = detailed_dtype_df.style\
        .applymap(color_data_type, subset=['Data Type'])\
        .background_gradient(subset=['Null Count'], cmap='Reds')\
        .background_gradient(subset=['Unique Values'], cmap='Blues')\
        .set_properties(**{
            'border': '1px solid #dee2e6',
            'text-align': 'left',
            'padding': '8px'
        })\
        .set_table_styles([
            {'selector': 'th', 'props': [('background-color', '#495057'), 
                                       ('color', 'white'),
                                       ('font-weight', 'bold'),
                                       ('padding', '12px')]},
            {'selector': 'tr:hover', 'props': [('background-color', '#f8f9fa')]}
        ])\
        .set_caption('<h3>📋 Detailed Data Types Information</h3>')
    
    return styled_detailed_table