feat(example): add working TimesFM forecast example with global temperature data

- Add NOAA GISTEMP global temperature anomaly dataset (36 months, 2022-2024)
- Run TimesFM 1.0 PyTorch forecast for 2025 (12-month horizon)
- Generate fan chart visualization with 80%/90% confidence intervals
- Create comprehensive markdown report with findings and API notes

API Discovery:
- TimesFM 2.5 PyTorch checkpoint has file format issue (model.safetensors
  vs expected torch_model.ckpt)
- Working API uses TimesFmHparams + TimesFmCheckpoint + TimesFm() constructor
- Documented API in GitHub README differs from actual pip package

Includes:
- temperature_anomaly.csv (input data)
- forecast_output.csv (point forecast + quantiles)
- forecast_output.json (machine-readable output)
- forecast_visualization.png (LFS-tracked)
- run_forecast.py (reusable script)
- visualize_forecast.py (chart generation)
- run_example.sh (one-click runner)
- README.md (full report with findings)
This commit is contained in:
Clayton Young
2026-02-21 15:25:52 -05:00
parent 98670bcf47
commit c7c5bc21ff
9 changed files with 787 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
# TimesFM Forecast Report: Global Temperature Anomaly (2025)
**Model:** TimesFM 1.0 (200M) PyTorch
**Generated:** 2026-02-21
**Source:** NOAA GISTEMP Global Land-Ocean Temperature Index
---
## Executive Summary
TimesFM forecasts a mean temperature anomaly of **1.19°C** for 2025, slightly below the 2024 average of 1.25°C. The model predicts continued elevated temperatures with a peak of 1.30°C in March 2025 and a minimum of 1.06°C in December 2025.
---
## Input Data
### Historical Temperature Anomalies (2022-2024)
| Date | Anomaly (°C) | Date | Anomaly (°C) | Date | Anomaly (°C) |
|------|-------------|------|-------------|------|-------------|
| 2022-01 | 0.89 | 2023-01 | 0.87 | 2024-01 | 1.22 |
| 2022-02 | 0.89 | 2023-02 | 0.98 | 2024-02 | 1.35 |
| 2022-03 | 1.02 | 2023-03 | 1.21 | 2024-03 | 1.34 |
| 2022-04 | 0.88 | 2023-04 | 1.00 | 2024-04 | 1.26 |
| 2022-05 | 0.85 | 2023-05 | 0.94 | 2024-05 | 1.15 |
| 2022-06 | 0.88 | 2023-06 | 1.08 | 2024-06 | 1.20 |
| 2022-07 | 0.88 | 2023-07 | 1.18 | 2024-07 | 1.24 |
| 2022-08 | 0.90 | 2023-08 | 1.24 | 2024-08 | 1.30 |
| 2022-09 | 0.88 | 2023-09 | 1.47 | 2024-09 | 1.28 |
| 2022-10 | 0.95 | 2023-10 | 1.32 | 2024-10 | 1.27 |
| 2022-11 | 0.77 | 2023-11 | 1.18 | 2024-11 | 1.22 |
| 2022-12 | 0.78 | 2023-12 | 1.16 | 2024-12 | 1.20 |
**Statistics:**
- Total observations: 36 months
- Mean anomaly: 1.09°C
- Trend (2022→2024): +0.37°C
---
## Raw Forecast Output
### Point Forecast and Confidence Intervals
| Month | Point | 80% CI | 90% CI |
|-------|-------|--------|--------|
| 2025-01 | 1.259 | [1.141, 1.297] | [1.248, 1.324] |
| 2025-02 | 1.286 | [1.141, 1.340] | [1.277, 1.375] |
| 2025-03 | 1.295 | [1.127, 1.355] | [1.287, 1.404] |
| 2025-04 | 1.221 | [1.035, 1.290] | [1.208, 1.331] |
| 2025-05 | 1.170 | [0.969, 1.239] | [1.153, 1.289] |
| 2025-06 | 1.146 | [0.942, 1.218] | [1.128, 1.270] |
| 2025-07 | 1.170 | [0.950, 1.248] | [1.151, 1.300] |
| 2025-08 | 1.203 | [0.971, 1.284] | [1.186, 1.341] |
| 2025-09 | 1.191 | [0.959, 1.283] | [1.178, 1.335] |
| 2025-10 | 1.149 | [0.908, 1.240] | [1.126, 1.287] |
| 2025-11 | 1.080 | [0.836, 1.176] | [1.062, 1.228] |
| 2025-12 | 1.061 | [0.802, 1.153] | [1.037, 1.217] |
### JSON Output
```json
{
"model": "TimesFM 1.0 (200M) PyTorch",
"input": {
"source": "NOAA GISTEMP Global Temperature Anomaly",
"n_observations": 36,
"date_range": "2022-01 to 2024-12",
"mean_anomaly_c": 1.089
},
"forecast": {
"horizon": 12,
"dates": ["2025-01", "2025-02", "2025-03", "2025-04", "2025-05", "2025-06",
"2025-07", "2025-08", "2025-09", "2025-10", "2025-11", "2025-12"],
"point": [1.259, 1.286, 1.295, 1.221, 1.170, 1.146, 1.170, 1.203, 1.191, 1.149, 1.080, 1.061]
},
"summary": {
"forecast_mean_c": 1.186,
"forecast_max_c": 1.295,
"forecast_min_c": 1.061,
"vs_last_year_mean": -0.067
}
}
```
---
## Visualization
![Temperature Anomaly Forecast](forecast_visualization.png)
---
## Findings
### Key Observations
1. **Slight cooling trend expected**: The model forecasts a mean anomaly 0.07°C below 2024 levels, suggesting a potential stabilization after the record-breaking temperatures of 2023-2024.
2. **Seasonal pattern preserved**: The forecast shows the expected seasonal variation with higher anomalies in late winter (Feb-Mar) and lower in late fall (Nov-Dec).
3. **Widening uncertainty**: The 90% CI expands from ±0.04°C in January to ±0.08°C in December, reflecting typical forecast uncertainty growth over time.
4. **Peak temperature**: March 2025 is predicted to have the highest anomaly at 1.30°C, potentially approaching the September 2023 record of 1.47°C.
### Limitations
- TimesFM is a zero-shot forecaster without physical climate model constraints
- The 36-month training window may not capture multi-decadal climate trends
- El Niño/La Niña cycles are not explicitly modeled
### Recommendations
- Use this forecast as a baseline comparison for physics-based climate models
- Update forecast quarterly as new observations become available
- Consider ensemble approaches combining TimesFM with other methods
---
## Reproducibility
### Files
| File | Description |
|------|-------------|
| `temperature_anomaly.csv` | Input data (36 months) |
| `forecast_output.csv` | Point forecast with quantiles |
| `forecast_output.json` | Machine-readable forecast |
| `forecast_visualization.png` | Fan chart visualization |
| `run_forecast.py` | Forecasting script |
| `visualize_forecast.py` | Visualization script |
| `run_example.sh` | One-click runner |
### How to Reproduce
```bash
# Install dependencies
uv pip install "timesfm[torch]" matplotlib pandas numpy
# Run the complete example
cd scientific-skills/timesfm-forecasting/examples/global-temperature
./run_example.sh
```
---
## Technical Notes
### API Discovery
The TimesFM PyTorch API differs from the GitHub README documentation:
**Documented (GitHub README):**
```python
model = timesfm.TimesFm(
context_len=512,
horizon_len=128,
backend="gpu",
)
model.load_from_google_repo("google/timesfm-2.5-200m-pytorch")
```
**Actual Working API:**
```python
hparams = timesfm.TimesFmHparams(horizon_len=12)
checkpoint = timesfm.TimesFmCheckpoint(
huggingface_repo_id="google/timesfm-1.0-200m-pytorch"
)
model = timesfm.TimesFm(hparams=hparams, checkpoint=checkpoint)
```
### TimesFM 2.5 PyTorch Issue
The `google/timesfm-2.5-200m-pytorch` checkpoint downloads as `model.safetensors`, but the TimesFM loader expects `torch_model.ckpt`. This causes a `FileNotFoundError` at model load time. Using TimesFM 1.0 PyTorch resolves this issue.
---
*Report generated by TimesFM Forecasting Skill (claude-scientific-skills)*

View File

@@ -0,0 +1,13 @@
date,point_forecast,q10,q20,q30,q40,q50,q60,q70,q80,q90,q99
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-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-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-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-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-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-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-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-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-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-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-12-01,1.0613453,1.0366092,0.80220693,0.89521873,0.9593707,1.0152239,1.0613453,1.1032857,1.15315,1.216908,1.2959521
1 date point_forecast q10 q20 q30 q40 q50 q60 q70 q80 q90 q99
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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

View File

@@ -0,0 +1,188 @@
{
"model": "TimesFM 1.0 (200M) PyTorch",
"input": {
"source": "NOAA GISTEMP Global Temperature Anomaly",
"n_observations": 36,
"date_range": "2022-01 to 2024-12",
"mean_anomaly_c": 1.09
},
"forecast": {
"horizon": 12,
"dates": [
"2025-01",
"2025-02",
"2025-03",
"2025-04",
"2025-05",
"2025-06",
"2025-07",
"2025-08",
"2025-09",
"2025-10",
"2025-11",
"2025-12"
],
"point": [
1.25933837890625,
1.285666823387146,
1.2950127124786377,
1.2207623720169067,
1.170255422592163,
1.1455552577972412,
1.1702347993850708,
1.2026824951171875,
1.1909748315811157,
1.1490840911865234,
1.080478549003601,
1.0613453388214111
],
"quantiles": {
"10%": [
1.2481880187988281,
1.2773758172988892,
1.286991834640503,
1.2084007263183594,
1.1533130407333374,
1.1275498867034912,
1.1510555744171143,
1.1859495639801025,
1.1784849166870117,
1.1264795064926147,
1.0624356269836426,
1.036609172821045
],
"20%": [
1.1407020092010498,
1.1406043767929077,
1.126852035522461,
1.0352504253387451,
0.9691494703292847,
0.9420379400253296,
0.9503718018531799,
0.970925509929657,
0.9594371318817139,
0.9079477190971375,
0.8361266255378723,
0.8022069334983826
],
"30%": [
1.1880751848220825,
1.1960833072662354,
1.187617301940918,
1.104191780090332,
1.0431063175201416,
1.01105535030365,
1.0347577333450317,
1.0594383478164673,
1.040370225906372,
0.9952926635742188,
0.9259791970252991,
0.8952187299728394
],
"40%": [
1.2137157917022705,
1.232267141342163,
1.2349879741668701,
1.151865005493164,
1.0932612419128418,
1.0658776760101318,
1.084773302078247,
1.1106674671173096,
1.1036059856414795,
1.0548235177993774,
0.9882403016090393,
0.9593706727027893
],
"50%": [
1.2394564151763916,
1.2593891620635986,
1.267505168914795,
1.1853008270263672,
1.127617597579956,
1.1061187982559204,
1.128767728805542,
1.1579902172088623,
1.1511956453323364,
1.1052223443984985,
1.03863525390625,
1.0152238607406616
],
"60%": [
1.25933837890625,
1.285666823387146,
1.2950127124786377,
1.2207623720169067,
1.170255422592163,
1.1455552577972412,
1.1702347993850708,
1.2026824951171875,
1.1909748315811157,
1.1490840911865234,
1.080478549003601,
1.0613453388214111
],
"70%": [
1.27677321434021,
1.3110136985778809,
1.3284480571746826,
1.2566629648208618,
1.2019660472869873,
1.1806211471557617,
1.2114834785461426,
1.2399210929870605,
1.2390201091766357,
1.1897773742675781,
1.1281580924987793,
1.1032856702804565
],
"80%": [
1.2971320152282715,
1.3400218486785889,
1.3547290563583374,
1.2898554801940918,
1.2390310764312744,
1.2180578708648682,
1.248227596282959,
1.2842004299163818,
1.2832940816879272,
1.240414023399353,
1.175971508026123,
1.153149962425232
],
"90%": [
1.3239599466323853,
1.3751201629638672,
1.403548240661621,
1.3310348987579346,
1.2891905307769775,
1.2702757120132446,
1.2997852563858032,
1.3408125638961792,
1.3354730606079102,
1.286876916885376,
1.2283769845962524,
1.2169079780578613
],
"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": {
"forecast_mean_c": 1.186,
"forecast_max_c": 1.295,
"forecast_min_c": 1.061,
"vs_last_year_mean": -0.067
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -0,0 +1,53 @@
#!/bin/bash
# run_example.sh - Run the TimesFM temperature anomaly forecasting example
#
# This script:
# 1. Runs the preflight system check
# 2. Runs the TimesFM forecast
# 3. Generates the visualization
#
# Usage:
# ./run_example.sh
#
# Prerequisites:
# - Python 3.10+
# - timesfm[torch] installed: uv pip install "timesfm[torch]"
# - matplotlib, pandas, numpy
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
echo "============================================================"
echo " TimesFM Example: Global Temperature Anomaly Forecast"
echo "============================================================"
# Step 1: Preflight check
echo ""
echo "🔍 Step 1: Running preflight system check..."
python3 "$SKILL_ROOT/scripts/check_system.py" || {
echo "❌ Preflight check failed. Please fix the issues above before continuing."
exit 1
}
# Step 2: Run forecast
echo ""
echo "📊 Step 2: Running TimesFM forecast..."
cd "$SCRIPT_DIR"
python3 run_forecast.py
# Step 3: Generate visualization
echo ""
echo "📈 Step 3: Generating visualization..."
python3 visualize_forecast.py
echo ""
echo "============================================================"
echo " ✅ Example complete!"
echo "============================================================"
echo ""
echo "Output files:"
echo " - $SCRIPT_DIR/forecast_output.csv"
echo " - $SCRIPT_DIR/forecast_output.json"
echo " - $SCRIPT_DIR/forecast_visualization.png"

View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""
Run TimesFM forecast on global temperature anomaly data.
Generates forecast output CSV and JSON for the example.
"""
from __future__ import annotations
import json
from pathlib import Path
import numpy as np
import pandas as pd
# Preflight check
print("=" * 60)
print(" TIMeSFM FORECAST - Global Temperature Anomaly Example")
print("=" * 60)
# Load data
data_path = Path(__file__).parent / "temperature_anomaly.csv"
df = pd.read_csv(data_path, parse_dates=["date"])
df = df.sort_values("date").reset_index(drop=True)
print(f"\n📊 Input Data: {len(df)} months of temperature anomalies")
print(
f" Date range: {df['date'].min().strftime('%Y-%m')} to {df['date'].max().strftime('%Y-%m')}"
)
print(f" Mean anomaly: {df['anomaly_c'].mean():.2f}°C")
print(
f" Trend: {df['anomaly_c'].iloc[-12:].mean() - df['anomaly_c'].iloc[:12].mean():.2f}°C change (first to last year)"
)
# Prepare input for TimesFM
# TimesFM expects a list of 1D numpy arrays
input_series = df["anomaly_c"].values.astype(np.float32)
# Load TimesFM 1.0 (PyTorch)
# NOTE: TimesFM 2.5 PyTorch checkpoint has a file format issue at time of writing.
# 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)
checkpoint = timesfm.TimesFmCheckpoint(
huggingface_repo_id="google/timesfm-1.0-200m-pytorch"
)
model = timesfm.TimesFm(hparams=hparams, checkpoint=checkpoint)
# Forecast
print("\n📈 Running forecast (12 months ahead)...")
forecast_input = [input_series]
frequency_input = [0] # Monthly data
point_forecast, experimental_quantile_forecast = model.forecast(
forecast_input,
freq=frequency_input,
)
print(f" Point forecast shape: {point_forecast.shape}")
print(f" Quantile forecast shape: {experimental_quantile_forecast.shape}")
# Extract results
point = point_forecast[0] # Shape: (horizon,)
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]
# Index mapping: 0=10%, 1=20%, ..., 4=50% (median), ..., 9=99%
quantile_labels = ["10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "99%"]
# Create forecast dates (2025 monthly)
last_date = df["date"].max()
forecast_dates = pd.date_range(
start=last_date + pd.DateOffset(months=1), periods=12, freq="MS"
)
# Build output DataFrame
output_df = pd.DataFrame(
{
"date": forecast_dates.strftime("%Y-%m-%d"),
"point_forecast": point,
"q10": quantiles[:, 0],
"q20": quantiles[:, 1],
"q30": quantiles[:, 2],
"q40": quantiles[:, 3],
"q50": quantiles[:, 4], # Median
"q60": quantiles[:, 5],
"q70": quantiles[:, 6],
"q80": quantiles[:, 7],
"q90": quantiles[:, 8],
"q99": quantiles[:, 9],
}
)
# Save outputs
output_dir = Path(__file__).parent
output_df.to_csv(output_dir / "forecast_output.csv", index=False)
# JSON output for the report
output_json = {
"model": "TimesFM 1.0 (200M) PyTorch",
"input": {
"source": "NOAA GISTEMP Global Temperature Anomaly",
"n_observations": len(df),
"date_range": f"{df['date'].min().strftime('%Y-%m')} to {df['date'].max().strftime('%Y-%m')}",
"mean_anomaly_c": round(df["anomaly_c"].mean(), 3),
},
"forecast": {
"horizon": 12,
"dates": forecast_dates.strftime("%Y-%m").tolist(),
"point": point.tolist(),
"quantiles": {
label: quantiles[:, i].tolist() for i, label in enumerate(quantile_labels)
},
},
"summary": {
"forecast_mean_c": round(float(point.mean()), 3),
"forecast_max_c": round(float(point.max()), 3),
"forecast_min_c": round(float(point.min()), 3),
"vs_last_year_mean": round(
float(point.mean() - df["anomaly_c"].iloc[-12:].mean()), 3
),
},
}
with open(output_dir / "forecast_output.json", "w") as f:
json.dump(output_json, f, indent=2)
# Print summary
print("\n" + "=" * 60)
print(" FORECAST RESULTS")
print("=" * 60)
print(
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(f"\n {'Month':<10} {'Point':>8} {'80% CI':>15} {'90% CI':>15}")
print(f" {'-' * 10} {'-' * 8} {'-' * 15} {'-' * 15}")
for i, (date, pt, q10, q90, q05, q95) in enumerate(
zip(
forecast_dates.strftime("%Y-%m"),
point,
quantiles[:, 1], # 20%
quantiles[:, 7], # 80%
quantiles[:, 0], # 10%
quantiles[:, 8], # 90%
)
):
print(
f" {date:<10} {pt:>8.3f} [{q10:>6.3f}, {q90:>6.3f}] [{q05:>6.3f}, {q95:>6.3f}]"
)
print(f"\n📊 Summary Statistics:")
print(f" Mean forecast: {point.mean():.3f}°C")
print(
f" Max forecast: {point.max():.3f}°C (Month: {forecast_dates[point.argmax()].strftime('%Y-%m')})"
)
print(
f" Min forecast: {point.min():.3f}°C (Month: {forecast_dates[point.argmin()].strftime('%Y-%m')})"
)
print(f" vs 2024 mean: {point.mean() - df['anomaly_c'].iloc[-12:].mean():+.3f}°C")
print(f"\n✅ Output saved to:")
print(f" {output_dir / 'forecast_output.csv'}")
print(f" {output_dir / 'forecast_output.json'}")

View File

@@ -0,0 +1,37 @@
date,anomaly_c
2022-01-01,0.89
2022-02-01,0.89
2022-03-01,1.02
2022-04-01,0.88
2022-05-01,0.85
2022-06-01,0.88
2022-07-01,0.88
2022-08-01,0.90
2022-09-01,0.88
2022-10-01,0.95
2022-11-01,0.77
2022-12-01,0.78
2023-01-01,0.87
2023-02-01,0.98
2023-03-01,1.21
2023-04-01,1.00
2023-05-01,0.94
2023-06-01,1.08
2023-07-01,1.18
2023-08-01,1.24
2023-09-01,1.47
2023-10-01,1.32
2023-11-01,1.18
2023-12-01,1.16
2024-01-01,1.22
2024-02-01,1.35
2024-03-01,1.34
2024-04-01,1.26
2024-05-01,1.15
2024-06-01,1.20
2024-07-01,1.24
2024-08-01,1.30
2024-09-01,1.28
2024-10-01,1.27
2024-11-01,1.22
2024-12-01,1.20
1 date anomaly_c
2 2022-01-01 0.89
3 2022-02-01 0.89
4 2022-03-01 1.02
5 2022-04-01 0.88
6 2022-05-01 0.85
7 2022-06-01 0.88
8 2022-07-01 0.88
9 2022-08-01 0.90
10 2022-09-01 0.88
11 2022-10-01 0.95
12 2022-11-01 0.77
13 2022-12-01 0.78
14 2023-01-01 0.87
15 2023-02-01 0.98
16 2023-03-01 1.21
17 2023-04-01 1.00
18 2023-05-01 0.94
19 2023-06-01 1.08
20 2023-07-01 1.18
21 2023-08-01 1.24
22 2023-09-01 1.47
23 2023-10-01 1.32
24 2023-11-01 1.18
25 2023-12-01 1.16
26 2024-01-01 1.22
27 2024-02-01 1.35
28 2024-03-01 1.34
29 2024-04-01 1.26
30 2024-05-01 1.15
31 2024-06-01 1.20
32 2024-07-01 1.24
33 2024-08-01 1.30
34 2024-09-01 1.28
35 2024-10-01 1.27
36 2024-11-01 1.22
37 2024-12-01 1.20

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Visualize TimesFM forecast results for global temperature anomaly.
Generates a publication-quality figure showing:
- Historical data (2022-2024)
- Point forecast (2025)
- 80% and 90% confidence intervals (fan chart)
Usage:
python visualize_forecast.py
"""
from __future__ import annotations
import json
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Configuration
EXAMPLE_DIR = Path(__file__).parent
INPUT_FILE = EXAMPLE_DIR / "temperature_anomaly.csv"
FORECAST_FILE = EXAMPLE_DIR / "forecast_output.json"
OUTPUT_FILE = EXAMPLE_DIR / "forecast_visualization.png"
def main() -> None:
# Load historical data
df = pd.read_csv(INPUT_FILE, parse_dates=["date"])
# Load forecast results
with open(FORECAST_FILE) as f:
forecast = json.load(f)
# Extract forecast data
dates = pd.to_datetime(forecast["forecast"]["dates"])
point = np.array(forecast["forecast"]["point"])
q10 = np.array(forecast["forecast"]["quantiles"]["10%"])
q20 = np.array(forecast["forecast"]["quantiles"]["20%"])
q80 = np.array(forecast["forecast"]["quantiles"]["80%"])
q90 = np.array(forecast["forecast"]["quantiles"]["90%"])
# Create figure
fig, ax = plt.subplots(figsize=(12, 6))
# Plot historical data
ax.plot(
df["date"],
df["anomaly_c"],
color="#2563eb",
linewidth=1.5,
marker="o",
markersize=3,
label="Historical (NOAA GISTEMP)",
)
# Plot 90% CI (outer band)
ax.fill_between(dates, q10, q90, alpha=0.2, color="#dc2626", label="90% CI")
# Plot 80% CI (inner band)
ax.fill_between(dates, q20, q80, alpha=0.3, color="#dc2626", label="80% CI")
# Plot point forecast
ax.plot(
dates,
point,
color="#dc2626",
linewidth=2,
marker="s",
markersize=4,
label="TimesFM Forecast",
)
# Add vertical line at forecast boundary
ax.axvline(
x=df["date"].max(), color="#6b7280", linestyle="--", linewidth=1, alpha=0.7
)
# Formatting
ax.set_xlabel("Date", fontsize=12)
ax.set_ylabel("Temperature Anomaly (°C)", fontsize=12)
ax.set_title(
"TimesFM Forecast: Global Temperature Anomaly (2025)\nAbove 1951-1980 Baseline",
fontsize=14,
fontweight="bold",
)
# Add annotations
ax.annotate(
f"Mean forecast: {forecast['summary']['forecast_mean_c']:.2f}°C\n"
f"vs 2024: {forecast['summary']['vs_last_year_mean']:+.2f}°C",
xy=(dates[6], point[6]),
xytext=(dates[6], point[6] + 0.15),
fontsize=10,
arrowprops=dict(arrowstyle="->", color="#6b7280", lw=1),
bbox=dict(boxstyle="round,pad=0.3", facecolor="white", edgecolor="#6b7280"),
)
# Grid and legend
ax.grid(True, alpha=0.3)
ax.legend(loc="upper left", fontsize=10)
# Set y-axis limits
ax.set_ylim(0.7, 1.5)
# Rotate x-axis labels
plt.xticks(rotation=45, ha="right")
# Tight layout
plt.tight_layout()
# Save
fig.savefig(OUTPUT_FILE, dpi=150, bbox_inches="tight")
print(f"✅ Saved visualization to: {OUTPUT_FILE}")
plt.close()
if __name__ == "__main__":
main()