Update global-temperature example to use TimesFM 2.5 API

This commit is contained in:
Rajat Sen
2026-06-08 17:31:07 +00:00
parent e56854bc9e
commit 6ed1d8a7a2
5 changed files with 196 additions and 193 deletions
@@ -1,13 +1,13 @@
date,point_forecast,q10,q20,q30,q40,q50,q60,q70,q80,q90,q99 date,point_forecast,mean,q10,q20,q30,q40,q50,q60,q70,q80,q90
2025-01-01,1.2593384,1.248188,1.140702,1.1880752,1.2137158,1.2394564,1.2593384,1.2767732,1.297132,1.32396,1.367888 2025-01-01,1.2223774,1.2215943,1.1230627,1.1613995,1.1832488,1.2030286,1.2223774,1.240989,1.2626991,1.2927711,1.339603
2025-02-01,1.2856668,1.2773758,1.1406044,1.1960833,1.2322671,1.2593892,1.2856668,1.3110137,1.3400218,1.3751202,1.4253658 2025-02-01,1.2563584,1.2502017,1.1482248,1.1891649,1.2134029,1.2355918,1.2563584,1.2787917,1.3061507,1.3358022,1.3880283
2025-03-01,1.2950127,1.2869918,1.126852,1.1876173,1.234988,1.2675052,1.2950127,1.328448,1.354729,1.4035482,1.4642649 2025-03-01,1.286477,1.2816916,1.1694773,1.2141376,1.2430503,1.2636719,1.286477,1.3101407,1.3365126,1.3725822,1.4265702
2025-04-01,1.2207624,1.2084007,1.0352504,1.1041918,1.151865,1.1853008,1.2207624,1.256663,1.2898555,1.3310349,1.4016538 2025-04-01,1.240488,1.2406754,1.119298,1.1689228,1.1957527,1.2162383,1.240488,1.265359,1.2880774,1.3237736,1.3806401
2025-05-01,1.1702554,1.153313,0.9691495,1.0431063,1.0932612,1.1276176,1.1702554,1.201966,1.2390311,1.2891905,1.3632389 2025-05-01,1.2026378,1.1969143,1.0776879,1.1280149,1.1554759,1.1801765,1.2026378,1.2274072,1.2546852,1.2890899,1.3469675
2025-06-01,1.1455553,1.1275499,0.94203794,1.0110554,1.0658777,1.1061188,1.1455553,1.1806211,1.2180579,1.2702757,1.345366 2025-06-01,1.21002,1.1963896,1.0811386,1.1352499,1.1687461,1.1853771,1.21002,1.2333429,1.255994,1.2938039,1.3532466
2025-07-01,1.1702348,1.1510556,0.9503718,1.0347577,1.0847733,1.1287677,1.1702348,1.2114835,1.2482276,1.2997853,1.3807325 2025-07-01,1.2253109,1.2151253,1.0917634,1.1474797,1.176413,1.2029413,1.2253109,1.250956,1.2764856,1.3113455,1.3728349
2025-08-01,1.2026825,1.1859496,0.9709255,1.0594383,1.1106675,1.1579902,1.2026825,1.2399211,1.2842004,1.3408126,1.419526 2025-08-01,1.2421811,1.2292916,1.1043029,1.1598811,1.1929151,1.2164187,1.2421811,1.266998,1.2924675,1.3304923,1.394975
2025-09-01,1.1909748,1.1784849,0.95943713,1.0403702,1.103606,1.1511956,1.1909748,1.2390201,1.2832941,1.3354731,1.416972 2025-09-01,1.269735,1.2603163,1.1239274,1.1872942,1.2162873,1.242951,1.269735,1.2976494,1.3236798,1.3580028,1.4252181
2025-10-01,1.1490841,1.1264795,0.9079477,0.99529266,1.0548235,1.1052223,1.1490841,1.1897774,1.240414,1.2868769,1.3775467 2025-10-01,1.2496669,1.2436218,1.0962446,1.1630417,1.1938806,1.221712,1.2496669,1.2757387,1.3041382,1.3376691,1.409816
2025-11-01,1.0804785,1.0624356,0.8361266,0.9259792,0.9882403,1.0386353,1.0804785,1.1281581,1.1759715,1.228377,1.3122478 2025-11-01,1.2135266,1.2031629,1.0545524,1.1223581,1.157211,1.1864623,1.2135266,1.2447275,1.2706528,1.3091388,1.3801678
2025-12-01,1.0613453,1.0366092,0.80220693,0.89521873,0.9593707,1.0152239,1.0613453,1.1032857,1.15315,1.216908,1.2959521 2025-12-01,1.2034141,1.1867243,1.0412307,1.1113251,1.1433371,1.1752325,1.2034141,1.2308054,1.2621957,1.2912495,1.370078
1 date point_forecast mean q10 q20 q30 q40 q50 q60 q70 q80 q99 q90
2 2025-01-01 1.2593384 1.2223774 1.2215943 1.248188 1.1230627 1.140702 1.1613995 1.1880752 1.1832488 1.2137158 1.2030286 1.2394564 1.2223774 1.2593384 1.240989 1.2767732 1.2626991 1.297132 1.2927711 1.367888 1.32396 1.339603
3 2025-02-01 1.2856668 1.2563584 1.2502017 1.2773758 1.1482248 1.1406044 1.1891649 1.1960833 1.2134029 1.2322671 1.2355918 1.2593892 1.2563584 1.2856668 1.2787917 1.3110137 1.3061507 1.3400218 1.3358022 1.4253658 1.3751202 1.3880283
4 2025-03-01 1.2950127 1.286477 1.2816916 1.2869918 1.1694773 1.126852 1.2141376 1.1876173 1.2430503 1.234988 1.2636719 1.2675052 1.286477 1.2950127 1.3101407 1.328448 1.3365126 1.354729 1.3725822 1.4642649 1.4035482 1.4265702
5 2025-04-01 1.2207624 1.240488 1.2406754 1.2084007 1.119298 1.0352504 1.1689228 1.1041918 1.1957527 1.151865 1.2162383 1.1853008 1.240488 1.2207624 1.265359 1.256663 1.2880774 1.2898555 1.3237736 1.4016538 1.3310349 1.3806401
6 2025-05-01 1.1702554 1.2026378 1.1969143 1.153313 1.0776879 0.9691495 1.1280149 1.0431063 1.1554759 1.0932612 1.1801765 1.1276176 1.2026378 1.1702554 1.2274072 1.201966 1.2546852 1.2390311 1.2890899 1.3632389 1.2891905 1.3469675
7 2025-06-01 1.1455553 1.21002 1.1963896 1.1275499 1.0811386 0.94203794 1.1352499 1.0110554 1.1687461 1.0658777 1.1853771 1.1061188 1.21002 1.1455553 1.2333429 1.1806211 1.255994 1.2180579 1.2938039 1.345366 1.2702757 1.3532466
8 2025-07-01 1.1702348 1.2253109 1.2151253 1.1510556 1.0917634 0.9503718 1.1474797 1.0347577 1.176413 1.0847733 1.2029413 1.1287677 1.2253109 1.1702348 1.250956 1.2114835 1.2764856 1.2482276 1.3113455 1.3807325 1.2997853 1.3728349
9 2025-08-01 1.2026825 1.2421811 1.2292916 1.1859496 1.1043029 0.9709255 1.1598811 1.0594383 1.1929151 1.1106675 1.2164187 1.1579902 1.2421811 1.2026825 1.266998 1.2399211 1.2924675 1.2842004 1.3304923 1.419526 1.3408126 1.394975
10 2025-09-01 1.1909748 1.269735 1.2603163 1.1784849 1.1239274 0.95943713 1.1872942 1.0403702 1.2162873 1.103606 1.242951 1.1511956 1.269735 1.1909748 1.2976494 1.2390201 1.3236798 1.2832941 1.3580028 1.416972 1.3354731 1.4252181
11 2025-10-01 1.1490841 1.2496669 1.2436218 1.1264795 1.0962446 0.9079477 1.1630417 0.99529266 1.1938806 1.0548235 1.221712 1.1052223 1.2496669 1.1490841 1.2757387 1.1897774 1.3041382 1.240414 1.3376691 1.3775467 1.2868769 1.409816
12 2025-11-01 1.0804785 1.2135266 1.2031629 1.0624356 1.0545524 0.8361266 1.1223581 0.9259792 1.157211 0.9882403 1.1864623 1.0386353 1.2135266 1.0804785 1.2447275 1.1281581 1.2706528 1.1759715 1.3091388 1.3122478 1.228377 1.3801678
13 2025-12-01 1.0613453 1.2034141 1.1867243 1.0366092 1.0412307 0.80220693 1.1113251 0.89521873 1.1433371 0.9593707 1.1752325 1.0152239 1.2034141 1.0613453 1.2308054 1.1032857 1.2621957 1.15315 1.2912495 1.2959521 1.216908 1.370078
@@ -1,5 +1,5 @@
{ {
"model": "TimesFM 1.0 (200M) PyTorch", "model": "TimesFM 2.5 (200M) PyTorch",
"input": { "input": {
"source": "NOAA GISTEMP Global Temperature Anomaly", "source": "NOAA GISTEMP Global Temperature Anomaly",
"n_observations": 36, "n_observations": 36,
@@ -23,166 +23,166 @@
"2025-12" "2025-12"
], ],
"point": [ "point": [
1.25933837890625, 1.2223774194717407,
1.285666823387146, 1.2563583850860596,
1.2950127124786377, 1.286476969718933,
1.2207623720169067, 1.240488052368164,
1.170255422592163, 1.202637791633606,
1.1455552577972412, 1.2100199460983276,
1.1702347993850708, 1.2253109216690063,
1.2026824951171875, 1.2421810626983643,
1.1909748315811157, 1.2697349786758423,
1.1490840911865234, 1.2496669292449951,
1.080478549003601, 1.2135266065597534,
1.0613453388214111 1.2034140825271606
], ],
"quantiles": { "quantiles": {
"mean": [
1.2215943336486816,
1.25020170211792,
1.281691551208496,
1.240675449371338,
1.1969143152236938,
1.1963895559310913,
1.215125322341919,
1.229291558265686,
1.260316252708435,
1.243621826171875,
1.2031629085540771,
1.186724305152893
],
"10%": [ "10%": [
1.2481880187988281, 1.1230627298355103,
1.2773758172988892, 1.1482248306274414,
1.286991834640503, 1.1694773435592651,
1.2084007263183594, 1.119297981262207,
1.1533130407333374, 1.0776878595352173,
1.1275498867034912, 1.0811386108398438,
1.1510555744171143, 1.0917633771896362,
1.1859495639801025, 1.1043028831481934,
1.1784849166870117, 1.123927354812622,
1.1264795064926147, 1.0962445735931396,
1.0624356269836426, 1.054552435874939,
1.036609172821045 1.0412306785583496
], ],
"20%": [ "20%": [
1.1407020092010498, 1.161399483680725,
1.1406043767929077, 1.1891648769378662,
1.126852035522461, 1.2141375541687012,
1.0352504253387451, 1.168922781944275,
0.9691494703292847, 1.1280149221420288,
0.9420379400253296, 1.1352498531341553,
0.9503718018531799, 1.1474796533584595,
0.970925509929657, 1.1598811149597168,
0.9594371318817139, 1.1872942447662354,
0.9079477190971375, 1.1630417108535767,
0.8361266255378723, 1.1223580837249756,
0.8022069334983826 1.1113251447677612
], ],
"30%": [ "30%": [
1.1880751848220825, 1.18324875831604,
1.1960833072662354, 1.2134028673171997,
1.187617301940918, 1.2430503368377686,
1.104191780090332, 1.195752739906311,
1.0431063175201416, 1.1554758548736572,
1.01105535030365, 1.1687461137771606,
1.0347577333450317, 1.1764130592346191,
1.0594383478164673, 1.1929150819778442,
1.040370225906372, 1.2162872552871704,
0.9952926635742188, 1.193880558013916,
0.9259791970252991, 1.1572109460830688,
0.8952187299728394 1.1433371305465698
], ],
"40%": [ "40%": [
1.2137157917022705, 1.2030285596847534,
1.232267141342163, 1.2355917692184448,
1.2349879741668701, 1.263671875,
1.151865005493164, 1.216238260269165,
1.0932612419128418, 1.1801764965057373,
1.0658776760101318, 1.1853771209716797,
1.084773302078247, 1.2029412984848022,
1.1106674671173096, 1.216418743133545,
1.1036059856414795, 1.2429510354995728,
1.0548235177993774, 1.2217119932174683,
0.9882403016090393, 1.1864622831344604,
0.9593706727027893 1.1752325296401978
], ],
"50%": [ "50%": [
1.2394564151763916, 1.2223774194717407,
1.2593891620635986, 1.2563583850860596,
1.267505168914795, 1.286476969718933,
1.1853008270263672, 1.240488052368164,
1.127617597579956, 1.202637791633606,
1.1061187982559204, 1.2100199460983276,
1.128767728805542, 1.2253109216690063,
1.1579902172088623, 1.2421810626983643,
1.1511956453323364, 1.2697349786758423,
1.1052223443984985, 1.2496669292449951,
1.03863525390625, 1.2135266065597534,
1.0152238607406616 1.2034140825271606
], ],
"60%": [ "60%": [
1.25933837890625, 1.2409889698028564,
1.285666823387146, 1.2787916660308838,
1.2950127124786377, 1.3101407289505005,
1.2207623720169067, 1.2653590440750122,
1.170255422592163, 1.2274072170257568,
1.1455552577972412, 1.2333428859710693,
1.1702347993850708, 1.2509560585021973,
1.2026824951171875, 1.266998052597046,
1.1909748315811157, 1.2976493835449219,
1.1490840911865234, 1.2757387161254883,
1.080478549003601, 1.2447274923324585,
1.0613453388214111 1.2308053970336914
], ],
"70%": [ "70%": [
1.27677321434021, 1.2626991271972656,
1.3110136985778809, 1.3061506748199463,
1.3284480571746826, 1.336512565612793,
1.2566629648208618, 1.2880773544311523,
1.2019660472869873, 1.2546851634979248,
1.1806211471557617, 1.2559939622879028,
1.2114834785461426, 1.276485562324524,
1.2399210929870605, 1.292467474937439,
1.2390201091766357, 1.323679804801941,
1.1897773742675781, 1.30413818359375,
1.1281580924987793, 1.2706527709960938,
1.1032856702804565 1.2621957063674927
], ],
"80%": [ "80%": [
1.2971320152282715, 1.2927711009979248,
1.3400218486785889, 1.3358021974563599,
1.3547290563583374, 1.372582197189331,
1.2898554801940918, 1.3237736225128174,
1.2390310764312744, 1.2890899181365967,
1.2180578708648682, 1.2938039302825928,
1.248227596282959, 1.3113454580307007,
1.2842004299163818, 1.3304922580718994,
1.2832940816879272, 1.358002781867981,
1.240414023399353, 1.3376691341400146,
1.175971508026123, 1.3091387748718262,
1.153149962425232 1.2912495136260986
], ],
"90%": [ "90%": [
1.3239599466323853, 1.3396029472351074,
1.3751201629638672, 1.3880282640457153,
1.403548240661621, 1.426570177078247,
1.3310348987579346, 1.3806401491165161,
1.2891905307769775, 1.3469674587249756,
1.2702757120132446, 1.3532465696334839,
1.2997852563858032, 1.3728349208831787,
1.3408125638961792, 1.394974946975708,
1.3354730606079102, 1.425218105316162,
1.286876916885376, 1.409816026687622,
1.2283769845962524, 1.380167841911316,
1.2169079780578613 1.3700779676437378
],
"99%": [
1.3678879737854004,
1.4253658056259155,
1.4642648696899414,
1.40165376663208,
1.3632389307022095,
1.3453660011291504,
1.380732536315918,
1.4195259809494019,
1.416972041130066,
1.3775466680526733,
1.3122477531433105,
1.2959520816802979
] ]
} }
}, },
"summary": { "summary": {
"forecast_mean_c": 1.186, "forecast_mean_c": 1.235,
"forecast_max_c": 1.295, "forecast_max_c": 1.286,
"forecast_min_c": 1.061, "forecast_min_c": 1.203,
"vs_last_year_mean": -0.067 "vs_last_year_mean": -0.017
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 147 KiB

@@ -11,6 +11,7 @@ from pathlib import Path
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import timesfm
# Preflight check # Preflight check
print("=" * 60) print("=" * 60)
@@ -35,27 +36,28 @@ print(
# TimesFM expects a list of 1D numpy arrays # TimesFM expects a list of 1D numpy arrays
input_series = df["anomaly_c"].values.astype(np.float32) input_series = df["anomaly_c"].values.astype(np.float32)
# Load TimesFM 1.0 (PyTorch) # Load TimesFM 2.5 (PyTorch)
# NOTE: TimesFM 2.5 PyTorch checkpoint has a file format issue at time of writing. print("\n🤖 Loading TimesFM 2.5 (200M) PyTorch...")
# The model.safetensors file is not loadable via torch.load().
# Using TimesFM 1.0 PyTorch which works correctly.
print("\n🤖 Loading TimesFM 1.0 (200M) PyTorch...")
import timesfm
hparams = timesfm.TimesFmHparams(horizon_len=12) model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
checkpoint = timesfm.TimesFmCheckpoint( "google/timesfm-2.5-200m-pytorch",
huggingface_repo_id="google/timesfm-1.0-200m-pytorch" torch_compile=False,
) )
model = timesfm.TimesFm(hparams=hparams, checkpoint=checkpoint) model.compile(timesfm.ForecastConfig(
max_context=512,
max_horizon=12,
normalize_inputs=True,
use_continuous_quantile_head=True,
fix_quantile_crossing=True,
))
# Forecast # Forecast
print("\n📈 Running forecast (12 months ahead)...") print("\n📈 Running forecast (12 months ahead)...")
forecast_input = [input_series] forecast_input = [input_series]
frequency_input = [0] # Monthly data
point_forecast, experimental_quantile_forecast = model.forecast( point_forecast, experimental_quantile_forecast = model.forecast(
forecast_input, horizon=12,
freq=frequency_input, inputs=forecast_input,
) )
print(f" Point forecast shape: {point_forecast.shape}") print(f" Point forecast shape: {point_forecast.shape}")
@@ -65,9 +67,8 @@ print(f" Quantile forecast shape: {experimental_quantile_forecast.shape}")
point = point_forecast[0] # Shape: (horizon,) point = point_forecast[0] # Shape: (horizon,)
quantiles = experimental_quantile_forecast[0] # Shape: (horizon, num_quantiles) quantiles = experimental_quantile_forecast[0] # Shape: (horizon, num_quantiles)
# TimesFM quantiles: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.99] # TimesFM 2.5 columns: 0=mean, 1=10%, 2=20%, ..., 5=50% (median), ..., 9=90%
# Index mapping: 0=10%, 1=20%, ..., 4=50% (median), ..., 9=99% quantile_labels = ["mean", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%"]
quantile_labels = ["10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "99%"]
# Create forecast dates (2025 monthly) # Create forecast dates (2025 monthly)
last_date = df["date"].max() last_date = df["date"].max()
@@ -80,16 +81,16 @@ output_df = pd.DataFrame(
{ {
"date": forecast_dates.strftime("%Y-%m-%d"), "date": forecast_dates.strftime("%Y-%m-%d"),
"point_forecast": point, "point_forecast": point,
"q10": quantiles[:, 0], "mean": quantiles[:, 0],
"q20": quantiles[:, 1], "q10": quantiles[:, 1],
"q30": quantiles[:, 2], "q20": quantiles[:, 2],
"q40": quantiles[:, 3], "q30": quantiles[:, 3],
"q50": quantiles[:, 4], # Median "q40": quantiles[:, 4],
"q60": quantiles[:, 5], "q50": quantiles[:, 5], # Median
"q70": quantiles[:, 6], "q60": quantiles[:, 6],
"q80": quantiles[:, 7], "q70": quantiles[:, 7],
"q90": quantiles[:, 8], "q80": quantiles[:, 8],
"q99": quantiles[:, 9], "q90": quantiles[:, 9],
} }
) )
@@ -100,7 +101,7 @@ output_df.to_csv(output_dir / "forecast_output.csv", index=False)
# JSON output for the report # JSON output for the report
output_json = { output_json = {
"model": "TimesFM 1.0 (200M) PyTorch", "model": "TimesFM 2.5 (200M) PyTorch",
"input": { "input": {
"source": "NOAA GISTEMP Global Temperature Anomaly", "source": "NOAA GISTEMP Global Temperature Anomaly",
"n_observations": len(df), "n_observations": len(df),
@@ -135,24 +136,24 @@ print("=" * 60)
print( print(
f"\n📅 Forecast period: {forecast_dates[0].strftime('%Y-%m')} to {forecast_dates[-1].strftime('%Y-%m')}" f"\n📅 Forecast period: {forecast_dates[0].strftime('%Y-%m')} to {forecast_dates[-1].strftime('%Y-%m')}"
) )
print(f"\n🌡️ Temperature Anomaly Forecast (°C above 1951-1980 baseline):") print("\n🌡️ Temperature Anomaly Forecast (°C above 1951-1980 baseline):")
print(f"\n {'Month':<10} {'Point':>8} {'80% CI':>15} {'90% CI':>15}") print(f"\n {'Month':<10} {'Point':>8} {'60% CI':>15} {'80% CI':>15}")
print(f" {'-' * 10} {'-' * 8} {'-' * 15} {'-' * 15}") print(f" {'-' * 10} {'-' * 8} {'-' * 15} {'-' * 15}")
for i, (date, pt, q10, q90, q05, q95) in enumerate( for i, (date, pt, q20, q80, q10, q90) in enumerate(
zip( zip(
forecast_dates.strftime("%Y-%m"), forecast_dates.strftime("%Y-%m"),
point, point,
quantiles[:, 1], # 20% quantiles[:, 2], # 20%
quantiles[:, 7], # 80% quantiles[:, 8], # 80%
quantiles[:, 0], # 10% quantiles[:, 1], # 10%
quantiles[:, 8], # 90% quantiles[:, 9], # 90%
) )
): ):
print( print(
f" {date:<10} {pt:>8.3f} [{q10:>6.3f}, {q90:>6.3f}] [{q05:>6.3f}, {q95:>6.3f}]" f" {date:<10} {pt:>8.3f} [{q20:>6.3f}, {q80:>6.3f}] [{q10:>6.3f}, {q90:>6.3f}]"
) )
print(f"\n📊 Summary Statistics:") print("\n📊 Summary Statistics:")
print(f" Mean forecast: {point.mean():.3f}°C") print(f" Mean forecast: {point.mean():.3f}°C")
print( print(
f" Max forecast: {point.max():.3f}°C (Month: {forecast_dates[point.argmax()].strftime('%Y-%m')})" f" Max forecast: {point.max():.3f}°C (Month: {forecast_dates[point.argmax()].strftime('%Y-%m')})"
@@ -162,6 +163,6 @@ print(
) )
print(f" vs 2024 mean: {point.mean() - df['anomaly_c'].iloc[-12:].mean():+.3f}°C") print(f" vs 2024 mean: {point.mean() - df['anomaly_c'].iloc[-12:].mean():+.3f}°C")
print(f"\n✅ Output saved to:") print("\n✅ Output saved to:")
print(f" {output_dir / 'forecast_output.csv'}") print(f" {output_dir / 'forecast_output.csv'}")
print(f" {output_dir / 'forecast_output.json'}") print(f" {output_dir / 'forecast_output.json'}")
@@ -16,6 +16,8 @@ from __future__ import annotations
import json import json
from pathlib import Path from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@@ -57,11 +59,11 @@ def main() -> None:
label="Historical (NOAA GISTEMP)", label="Historical (NOAA GISTEMP)",
) )
# Plot 90% CI (outer band) # Plot 80% CI (outer band)
ax.fill_between(dates, q10, q90, alpha=0.2, color="#dc2626", label="90% CI") ax.fill_between(dates, q10, q90, alpha=0.2, color="#dc2626", label="80% CI")
# Plot 80% CI (inner band) # Plot 60% CI (inner band)
ax.fill_between(dates, q20, q80, alpha=0.3, color="#dc2626", label="80% CI") ax.fill_between(dates, q20, q80, alpha=0.3, color="#dc2626", label="60% CI")
# Plot point forecast # Plot point forecast
ax.plot( ax.plot(