diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d5aff20 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,29 @@ +# Git LFS tracking for binary files + +# Images +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text + +# Model weights and checkpoints +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text + +# Data files +*.parquet filter=lfs diff=lfs merge=lfs -text +*.feather filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text + +# Archives +*.zip filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tar.gz filter=lfs diff=lfs merge=lfs -text diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/README.md b/scientific-skills/timesfm-forecasting/examples/global-temperature/README.md new file mode 100644 index 0000000..035f636 --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/README.md @@ -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)* diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_output.csv b/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_output.csv new file mode 100644 index 0000000..c24104c --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_output.csv @@ -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 diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_output.json b/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_output.json new file mode 100644 index 0000000..d1bd036 --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_output.json @@ -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 + } +} \ No newline at end of file diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_visualization.png b/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_visualization.png new file mode 100644 index 0000000..27a623d Binary files /dev/null and b/scientific-skills/timesfm-forecasting/examples/global-temperature/forecast_visualization.png differ diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/run_example.sh b/scientific-skills/timesfm-forecasting/examples/global-temperature/run_example.sh new file mode 100755 index 0000000..89c22bb --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/run_example.sh @@ -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" diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/run_forecast.py b/scientific-skills/timesfm-forecasting/examples/global-temperature/run_forecast.py new file mode 100644 index 0000000..88b1ed5 --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/run_forecast.py @@ -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'}") diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/temperature_anomaly.csv b/scientific-skills/timesfm-forecasting/examples/global-temperature/temperature_anomaly.csv new file mode 100644 index 0000000..82eed54 --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/temperature_anomaly.csv @@ -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 diff --git a/scientific-skills/timesfm-forecasting/examples/global-temperature/visualize_forecast.py b/scientific-skills/timesfm-forecasting/examples/global-temperature/visualize_forecast.py new file mode 100644 index 0000000..07f54bb --- /dev/null +++ b/scientific-skills/timesfm-forecasting/examples/global-temperature/visualize_forecast.py @@ -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()