TimesFM Forecasting
Overview
TimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model developed by Google Research for time-series forecasting. It works zero-shot — feed it any univariate time series and it returns point forecasts with calibrated quantile prediction intervals, no training required.
This skill wraps TimesFM for safe, agent-friendly local inference. It includes a mandatory preflight system checker that verifies RAM, GPU memory, and disk space before the model is ever loaded so the agent never crashes a user's machine.
Key numbers: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on CPU, ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM. Always run the system checker first.
When to Use This Skill
Use this skill when:
- Forecasting any univariate time series (sales, demand, sensor, vitals, price, weather)
- You need zero-shot forecasting without training a custom model
- You want probabilistic forecasts with calibrated prediction intervals (quantiles)
- You have time series of any length (the model handles 1–16,384 context points)
- You need to batch-forecast hundreds or thousands of series efficiently
- You want a foundation model approach instead of hand-tuning ARIMA/ETS parameters
Do not use this skill when:
- You need classical statistical models with coefficient interpretation → use
statsmodels - You need time series classification or clustering → use
aeon - You need multivariate vector autoregression or Granger causality → use
statsmodels - Your data is tabular (not temporal) → use
scikit-learn
Note on Anomaly Detection: TimesFM does not have built-in anomaly detection, but you can use the quantile forecasts as prediction intervals — values outside the 90% CI (q10–q90) are statistically unusual. See the
examples/anomaly-detection/directory for a full example.
⚠️ Mandatory Preflight: System Requirements Check
CRITICAL — ALWAYS run the system checker before loading the model for the first time.
bashpython scripts/check_system.py
This script checks:
- Available RAM — warns if below 4 GB, blocks if below 2 GB
- GPU availability — detects CUDA/MPS devices and VRAM
- Disk space — verifies room for the ~800 MB model download
- Python version — requires 3.10+
- Existing installation — checks if
timesfmandtorchare installed
Note: Model weights are NOT stored in this repository. TimesFM weights (~800 MB) download on-demand from HuggingFace on first use and cache in
~/.cache/huggingface/. The preflight checker ensures sufficient resources before any download begins.
mermaidflowchart TD accTitle: Preflight System Check accDescr: Decision flowchart showing the system requirement checks that must pass before loading TimesFM. start["🚀 Run check_system.py"] --> ram{"RAM ≥ 4 GB?"} ram -->|"Yes"| gpu{"GPU available?"} ram -->|"No (2-4 GB)"| warn_ram["⚠️ Warning: tight RAM<br/>CPU-only, small batches"] ram -->|"No (< 2 GB)"| block["🛑 BLOCKED<br/>Insufficient memory"] warn_ram --> disk gpu -->|"CUDA / MPS"| vram{"VRAM ≥ 2 GB?"} gpu -->|"CPU only"| cpu_ok["✅ CPU mode<br/>Slower but works"] vram -->|"Yes"| gpu_ok["✅ GPU mode<br/>Fast inference"] vram -->|"No"| cpu_ok gpu_ok --> disk{"Disk ≥ 2 GB free?"} cpu_ok --> disk disk -->|"Yes"| ready["✅ READY<br/>Safe to load model"] disk -->|"No"| block_disk["🛑 BLOCKED<br/>Need space for weights"] classDef ok fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d classDef warn fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12 classDef block fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d classDef neutral fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#1f2937 class ready,gpu_ok,cpu_ok ok class warn_ram warn class block,block_disk block class start,ram,gpu,vram,disk neutral
Hardware Requirements by Model Version
| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |
|---|---|---|---|---|---|
| TimesFM 2.5 (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |
| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |
| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |
Recommendation: Always use TimesFM 2.5 unless you have a specific reason to use an older checkpoint. It is smaller, faster, and supports 8× longer context.
🔧 Installation
Step 1: Verify System (always first)
bashpython scripts/check_system.py
Step 2: Install TimesFM
bash# Using uv (recommended by this repo) uv pip install timesfm[torch] # Or using pip pip install timesfm[torch] # For JAX/Flax backend (faster on TPU/GPU) uv pip install timesfm[flax]
Step 3: Install PyTorch for Your Hardware
bash# CUDA 12.1 (NVIDIA GPU) pip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121 # CPU only pip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu # Apple Silicon (MPS) pip install torch>=2.0.0 # MPS support is built-in
Step 4: Verify Installation
pythonimport timesfm import numpy as np print(f"TimesFM version: {timesfm.__version__}") print("Installation OK")
🎯 Quick Start
Minimal Example (5 Lines)
pythonimport torch, numpy as np, timesfm torch.set_float32_matmul_precision("high") model = timesfm.TimesFM_2p5_200M_torch.from_pretrained( "google/timesfm-2.5-200m-pytorch" ) model.compile(timesfm.ForecastConfig( max_context=1024, max_horizon=256, normalize_inputs=True, use_continuous_quantile_head=True, force_flip_invariance=True, infer_is_positive=True, fix_quantile_crossing=True, )) point, quantiles = model.forecast(horizon=24, inputs=[ np.sin(np.linspace(0, 20, 200)), # any 1-D array ]) # point.shape == (1, 24) — median forecast # quantiles.shape == (1, 24, 10) — 10th–90th percentile bands
Forecast from CSV
pythonimport pandas as pd, numpy as np df = pd.read_csv("monthly_sales.csv", parse_dates=["date"], index_col="date") # Convert each column to a list of arrays inputs = [df[col].dropna().values.astype(np.float32) for col in df.columns] point, quantiles = model.forecast(horizon=12, inputs=inputs) # Build a results DataFrame for i, col in enumerate(df.columns): last_date = df[col].dropna().index[-1] future_dates = pd.date_range(last_date, periods=13, freq="MS")[1:] forecast_df = pd.DataFrame({ "date": future_dates, "forecast": point[i], "lower_80": quantiles[i, :, 2], # 20th percentile "upper_80": quantiles[i, :, 8], # 80th percentile }) print(f"\n--- {col} ---") print(forecast_df.to_string(index=False))
Forecast with Covariates (XReg)
TimesFM 2.5+ supports exogenous variables through forecast_with_covariates(). Requires timesfm[xreg].
python# Requires: uv pip install timesfm[xreg] point, quantiles = model.forecast_with_covariates( inputs=inputs, dynamic_numerical_covariates={"price": price_arrays}, dynamic_categorical_covariates={"holiday": holiday_arrays}, static_categorical_covariates={"region": region_labels}, xreg_mode="xreg + timesfm", # or "timesfm + xreg" )
| Covariate Type | Description | Example |
|---|---|---|
dynamic_numerical | Time-varying numeric | price, temperature, promotion spend |
dynamic_categorical | Time-varying categorical | holiday flag, day of week |
static_numerical | Per-series numeric | store size, account age |
static_categorical | Per-series categorical | store type, region, product category |
XReg Modes:
"xreg + timesfm"(default): TimesFM forecasts first, then XReg adjusts residuals"timesfm + xreg": XReg fits first, then TimesFM forecasts residuals
See
examples/covariates-forecasting/for a complete example with synthetic retail data.
Anomaly Detection (via Quantile Intervals)
TimesFM does not have built-in anomaly detection, but the quantile forecasts naturally provide prediction intervals that can detect anomalies:
pythonpoint, q = model.forecast(horizon=H, inputs=[values]) # 90% prediction interval lower_90 = q[0, :, 1] # 10th percentile upper_90 = q[0, :, 9] # 90th percentile # Detect anomalies: values outside the 90% CI actual = test_values # your holdout data anomalies = (actual < lower_90) | (actual > upper_90) # Severity levels is_warning = (actual < q[0, :, 2]) | (actual > q[0, :, 8]) # outside 80% CI is_critical = anomalies # outside 90% CI
| Severity | Condition | Interpretation |
|---|---|---|
| Normal | Inside 80% CI | Expected behavior |
| Warning | Outside 80% CI | Unusual but possible |
| Critical | Outside 90% CI | Statistically rare (< 10% probability) |
See
examples/anomaly-detection/for a complete example with visualization.
python# Requires: uv pip install timesfm[xreg] point, quantiles = model.forecast_with_covariates( inputs=inputs, dynamic_numerical_covariates={"temperature": temp_arrays}, dynamic_categorical_covariates={"day_of_week": dow_arrays}, static_categorical_covariates={"region": region_labels}, xreg_mode="xreg + timesfm", # or "timesfm + xreg" )
📊 Understanding the Output
Quantile Forecast Structure
TimesFM returns (point_forecast, quantile_forecast):
point_forecast: shape(batch, horizon)— the median (0.5 quantile)quantile_forecast: shape(batch, horizon, 10)— ten slices:
| Index | Quantile | Use |
|---|---|---|
| 0 | Mean | Average prediction |
| 1 | 0.1 | Lower bound of 80% PI |
| 2 | 0.2 | Lower bound of 60% PI |
| 3 | 0.3 | — |
| 4 | 0.4 | — |
| 5 | 0.5 | Median (= point_forecast) |
| 6 | 0.6 | — |
| 7 | 0.7 | — |
| 8 | 0.8 | Upper bound of 60% PI |
| 9 | 0.9 | Upper bound of 80% PI |
Extracting Prediction Intervals
pythonpoint, q = model.forecast(horizon=H, inputs=data) # 80% prediction interval (most common) lower_80 = q[:, :, 1] # 10th percentile upper_80 = q[:, :, 9] # 90th percentile # 60% prediction interval (tighter) lower_60 = q[:, :, 2] # 20th percentile upper_60 = q[:, :, 8] # 80th percentile # Median (same as point forecast) median = q[:, :, 5]
mermaidflowchart LR accTitle: Quantile Forecast Anatomy accDescr: Diagram showing how the 10-element quantile vector maps to prediction intervals. input["📈 Input Series<br/>1-D array"] --> model["🤖 TimesFM<br/>compile + forecast"] model --> point["📍 Point Forecast<br/>(batch, horizon)"] model --> quant["📊 Quantile Forecast<br/>(batch, horizon, 10)"] quant --> pi80["80% PI<br/>q[:,:,1] – q[:,:,9]"] quant --> pi60["60% PI<br/>q[:,:,2] – q[:,:,8]"] quant --> median["Median<br/>q[:,:,5]"] classDef data fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f classDef model fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87 classDef output fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d class input data class model model class point,quant,pi80,pi60,median output
🔧 ForecastConfig Reference
All forecasting behavior is controlled by timesfm.ForecastConfig:
pythontimesfm.ForecastConfig( max_context=1024, # Max context window (truncates longer series) max_horizon=256, # Max forecast horizon normalize_inputs=True, # Normalize inputs (RECOMMENDED for stability) per_core_batch_size=32, # Batch size per device (tune for memory) use_continuous_quantile_head=True, # Better quantile accuracy for long horizons force_flip_invariance=True, # Ensures f(-x) = -f(x) (mathematical consistency) infer_is_positive=True, # Clamp forecasts ≥ 0 when all inputs > 0 fix_quantile_crossing=True, # Ensure q10 ≤ q20 ≤ ... ≤ q90 return_backcast=False, # Return backcast (for covariate workflows) )
| Parameter | Default | When to Change |
|---|---|---|
max_context | 0 | Set to match your longest historical window (e.g., 512, 1024, 4096) |
max_horizon | 0 | Set to your maximum forecast length |
normalize_inputs | False | Always set True — prevents scale-dependent instability |
per_core_batch_size | 1 | Increase for throughput; decrease if OOM |
use_continuous_quantile_head | False | Set True for calibrated prediction intervals |
force_flip_invariance | True | Keep True unless profiling shows it hurts |
infer_is_positive | True | Set False for series that can be negative (temperature, returns) |
fix_quantile_crossing | False | Set True to guarantee monotonic quantiles |
📋 Common Workflows
Workflow 1: Single Series Forecast
mermaidflowchart TD accTitle: Single Series Forecast Workflow accDescr: Step-by-step workflow for forecasting a single time series with system checking. check["1. Run check_system.py"] --> load["2. Load model<br/>from_pretrained()"] load --> compile["3. Compile with ForecastConfig"] compile --> prep["4. Prepare data<br/>pd.read_csv → np.array"] prep --> forecast["5. model.forecast()<br/>horizon=N"] forecast --> extract["6. Extract point + PI"] extract --> plot["7. Plot or export results"] classDef step fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#1f2937 class check,load,compile,prep,forecast,extract,plot step
pythonimport torch, numpy as np, pandas as pd, timesfm # 1. System check (run once) # python scripts/check_system.py # 2-3. Load and compile torch.set_float32_matmul_precision("high") model = timesfm.TimesFM_2p5_200M_torch.from_pretrained( "google/timesfm-2.5-200m-pytorch" ) model.compile(timesfm.ForecastConfig( max_context=512, max_horizon=52, normalize_inputs=True, use_continuous_quantile_head=True, fix_quantile_crossing=True, )) # 4. Prepare data df = pd.read_csv("weekly_demand.csv", parse_dates=["week"]) values = df["demand"].values.astype(np.float32) # 5. Forecast point, quantiles = model.forecast(horizon=52, inputs=[values]) # 6. Extract prediction intervals forecast_df = pd.DataFrame({ "forecast": point[0], "lower_80": quantiles[0, :, 1], "upper_80": quantiles[0, :, 9], }) # 7. Plot import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(values[-104:], label="Historical") x_fc = range(len(values[-104:]), len(values[-104:]) + 52) ax.plot(x_fc, forecast_df["forecast"], label="Forecast", color="tab:orange") ax.fill_between(x_fc, forecast_df["lower_80"], forecast_df["upper_80"], alpha=0.2, color="tab:orange", label="80% PI") ax.legend() ax.set_title("52-Week Demand Forecast") plt.tight_layout() plt.savefig("forecast.png", dpi=150) print("Saved forecast.png")
Workflow 2: Batch Forecasting (Many Series)
pythonimport pandas as pd, numpy as np # Load wide-format CSV (one column per series) df = pd.read_csv("all_stores.csv", parse_dates=["date"], index_col="date") inputs = [df[col].dropna().values.astype(np.float32) for col in df.columns] # Forecast all series at once (batched internally) point, quantiles = model.forecast(horizon=30, inputs=inputs) # Collect results results = {} for i, col in enumerate(df.columns): results[col] = { "forecast": point[i].tolist(), "lower_80": quantiles[i, :, 1].tolist(), "upper_80": quantiles[i, :, 9].tolist(), } # Export import json with open("batch_forecasts.json", "w") as f: json.dump(results, f, indent=2) print(f"Forecasted {len(results)} series → batch_forecasts.json")
Workflow 3: Evaluate Forecast Accuracy
pythonimport numpy as np # Hold out the last H points for evaluation H = 24 train = values[:-H] actual = values[-H:] point, quantiles = model.forecast(horizon=H, inputs=[train]) pred = point[0] # Metrics mae = np.mean(np.abs(actual - pred)) rmse = np.sqrt(np.mean((actual - pred) ** 2)) mape = np.mean(np.abs((actual - pred) / actual)) * 100 # Prediction interval coverage lower = quantiles[0, :, 1] upper = quantiles[0, :, 9] coverage = np.mean((actual >= lower) & (actual <= upper)) * 100 print(f"MAE: {mae:.2f}") print(f"RMSE: {rmse:.2f}") print(f"MAPE: {mape:.1f}%") print(f"80% PI Coverage: {coverage:.1f}% (target: 80%)")
⚙️ Performance Tuning
GPU Acceleration
pythonimport torch # Check GPU availability if torch.cuda.is_available(): print(f"GPU: {torch.cuda.get_device_name(0)}") print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1e9:.1f} GB") elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): print("Apple Silicon MPS available") else: print("CPU only — inference will be slower but still works") # Always set this for Ampere+ GPUs (A100, RTX 3090, etc.) torch.set_float32_matmul_precision("high")
Batch Size Tuning
python# Start conservative, increase until OOM # GPU with 8 GB VRAM: per_core_batch_size=64 # GPU with 16 GB VRAM: per_core_batch_size=128 # GPU with 24 GB VRAM: per_core_batch_size=256 # CPU with 8 GB RAM: per_core_batch_size=8 # CPU with 16 GB RAM: per_core_batch_size=32 # CPU with 32 GB RAM: per_core_batch_size=64 model.compile(timesfm.ForecastConfig( max_context=1024, max_horizon=256, per_core_batch_size=32, # <-- tune this normalize_inputs=True, use_continuous_quantile_head=True, fix_quantile_crossing=True, ))
Memory-Constrained Environments
pythonimport gc, torch # Force garbage collection before loading gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() # Load model model = timesfm.TimesFM_2p5_200M_torch.from_pretrained( "google/timesfm-2.5-200m-pytorch" ) # Use small batch size on low-memory machines model.compile(timesfm.ForecastConfig( max_context=512, # Reduce context if needed max_horizon=128, # Reduce horizon if needed per_core_batch_size=4, # Small batches normalize_inputs=True, use_continuous_quantile_head=True, fix_quantile_crossing=True, )) # Process series in chunks to avoid OOM CHUNK = 50 all_results = [] for i in range(0, len(inputs), CHUNK): chunk = inputs[i:i+CHUNK] p, q = model.forecast(horizon=H, inputs=chunk) all_results.append((p, q)) gc.collect() # Clean up between chunks
🔗 Integration with Other Skills
With statsmodels
Use statsmodels for classical models (ARIMA, SARIMAX) as a comparison baseline:
python# TimesFM forecast tfm_point, tfm_q = model.forecast(horizon=H, inputs=[values]) # statsmodels ARIMA forecast from statsmodels.tsa.arima.model import ARIMA arima = ARIMA(values, order=(1,1,1)).fit() arima_forecast = arima.forecast(steps=H) # Compare print(f"TimesFM MAE: {np.mean(np.abs(actual - tfm_point[0])):.2f}") print(f"ARIMA MAE: {np.mean(np.abs(actual - arima_forecast)):.2f}")
With matplotlib / scientific-visualization
Plot forecasts with prediction intervals as publication-quality figures.
With exploratory-data-analysis
Run EDA on the time series before forecasting to understand trends, seasonality, and stationarity.
📚 Available Scripts
scripts/check_system.py
Mandatory preflight checker. Run before first model load.
bashpython scripts/check_system.py
Output example:
=== TimesFM System Requirements Check === [RAM] Total: 32.0 GB | Available: 24.3 GB ✅ PASS [GPU] NVIDIA RTX 4090 | VRAM: 24.0 GB ✅ PASS [Disk] Free: 142.5 GB ✅ PASS [Python] 3.12.1 ✅ PASS [timesfm] Installed (2.5.0) ✅ PASS [torch] Installed (2.4.1+cu121) ✅ PASS VERDICT: ✅ System is ready for TimesFM 2.5 (GPU mode) Recommended: per_core_batch_size=128
scripts/forecast_csv.py
End-to-end CSV forecasting with automatic system check.
bashpython scripts/forecast_csv.py input.csv \ --horizon 24 \ --date-col date \ --value-cols sales,revenue \ --output forecasts.csv
📖 Reference Documentation
Detailed guides in references/:
| File | Contents |
|---|---|
references/system_requirements.md | Hardware tiers, GPU/CPU selection, memory estimation formulas |
references/api_reference.md | Full ForecastConfig docs, from_pretrained options, output shapes |
references/data_preparation.md | Input formats, NaN handling, CSV loading, covariate setup |
Common Pitfalls
- Not running system check → model load crashes on low-RAM machines. Always run
check_system.pyfirst. - Forgetting
model.compile()→RuntimeError: Model is not compiled. Must callcompile()beforeforecast(). - Not setting
normalize_inputs=True→ unstable forecasts for series with large values. - Using v1/v2 on machines with < 32 GB RAM → use TimesFM 2.5 (200M params) instead.
- Not setting
fix_quantile_crossing=True→ quantiles may not be monotonic (q10 > q50). - Huge
per_core_batch_sizeon small GPU → CUDA OOM. Start small, increase. - Passing 2-D arrays → TimesFM expects a list of 1-D arrays, not a 2-D matrix.
- Forgetting
torch.set_float32_matmul_precision("high")→ slower inference on Ampere+ GPUs. - Not handling NaN in output → edge cases with very short series. Always check
np.isnan(point).any(). - Using
infer_is_positive=Truefor series that can be negative → clamps forecasts at zero. Set False for temperature, returns, etc.
Model Versions
mermaidtimeline accTitle: TimesFM Version History accDescr: Timeline of TimesFM model releases showing parameter counts and key improvements. section 2024 TimesFM 1.0 : 200M params, 2K context, JAX only TimesFM 2.0 : 500M params, 2K context, PyTorch + JAX section 2025 TimesFM 2.5 : 200M params, 16K context, quantile head, no frequency indicator
| Version | Params | Context | Quantile Head | Frequency Flag | Status |
|---|---|---|---|---|---|
| 2.5 | 200M | 16,384 | ✅ Continuous (30M) | ❌ Removed | Latest |
| 2.0 | 500M | 2,048 | ✅ Fixed buckets | ✅ Required | Archived |
| 1.0 | 200M | 2,048 | ✅ Fixed buckets | ✅ Required | Archived |
Hugging Face checkpoints:
google/timesfm-2.5-200m-pytorch(recommended)google/timesfm-2.5-200m-flaxgoogle/timesfm-2.0-500m-pytorch(archived)google/timesfm-1.0-200m-pytorch(archived)
Resources
- Paper: A Decoder-Only Foundation Model for Time-Series Forecasting (ICML 2024)
- Repository: https://github.com/google-research/timesfm
- Hugging Face: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6
- Google Blog: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/
- BigQuery Integration: https://cloud.google.com/bigquery/docs/timesfm-model
Examples
Three fully-working reference examples live in examples/. Use them as ground truth for correct API usage and expected output shape.
| Example | Directory | What It Demonstrates | When To Use It |
|---|---|---|---|
| Global Temperature Forecast | examples/global-temperature/ | Basic model.forecast() call, CSV -> PNG -> GIF pipeline, 36-month NOAA context | Starting point; copy-paste baseline for any univariate series |
| Anomaly Detection | examples/anomaly-detection/ | Two-phase detection: linear detrend + Z-score on context, quantile PI on forecast; 2-panel viz | Any task requiring outlier detection on historical + forecasted data |
| Covariates (XReg) | examples/covariates-forecasting/ | forecast_with_covariates() API (TimesFM 2.5), covariate decomposition, 2x2 shared-axis viz | Retail, energy, or any series with known exogenous drivers |
Running the Examples
bash# Global temperature (no TimesFM 2.5 needed) cd examples/global-temperature && python run_forecast.py && python visualize_forecast.py # Anomaly detection (uses TimesFM 1.0) cd examples/anomaly-detection && python detect_anomalies.py # Covariates (API demo -- requires TimesFM 2.5 + timesfm[xreg] for real inference) cd examples/covariates-forecasting && python demo_covariates.py
Expected Outputs
| Example | Key output files | Acceptance criteria |
|---|---|---|
| global-temperature | output/forecast_output.json, output/forecast_visualization.png | point_forecast has 12 values; PNG shows context + forecast + PI bands |
| anomaly-detection | output/anomaly_detection.json, output/anomaly_detection.png | Sep 2023 flagged CRITICAL (z >= 3.0); >= 2 forecast CRITICAL from injected anomalies |
| covariates-forecasting | output/sales_with_covariates.csv, output/covariates_data.png | CSV has 108 rows (3 stores x 36 weeks); stores have distinct price arrays |
Quality Checklist
Run this checklist after every TimesFM task before declaring success:
- Output shape correct --
point_fcshape is(n_series, horizon),quant_fcis(n_series, horizon, 10) - Quantile indices -- index 0 = mean, 1 = q10, 2 = q20 ... 9 = q90. NOT 0 = q0, 1 = q10.
- Frequency flag -- TimesFM 1.0/2.0: pass
freq=[0]for monthly data. TimesFM 2.5: no freq flag. - Series length -- context must be >= 32 data points (model minimum). Warn if shorter.
- No NaN --
np.isnan(point_fc).any()should be False. Check input series for gaps first. - Visualization axes -- if multiple panels share data, use
sharex=True. All time axes must cover the same span. - Binary outputs in Git LFS -- PNG and GIF files must be tracked via
.gitattributes(repo root already configured). - No large datasets committed -- any real dataset > 1 MB should be downloaded to
tempfile.mkdtemp()and annotated in code. -
matplotlib.use('Agg')-- must appear before any pyplot import when running headless. -
infer_is_positive-- setFalsefor temperature anomalies, financial returns, or any series that can be negative.
Common Mistakes
These bugs have appeared in this skill's examples. Learn from them:
-
Quantile index off-by-one -- The most common mistake.
quant_fc[..., 0]is the mean, not q0. q10 = index 1, q90 = index 9. Always define named constants:IDX_Q10, IDX_Q20, IDX_Q80, IDX_Q90 = 1, 2, 8, 9. -
Variable shadowing in comprehensions -- If you build per-series covariate dicts inside a loop, do NOT use the loop variable as the comprehension variable. Accumulate into separate
dict[str, ndarray]outside the loop, then assign.python# WRONG -- outer `store_id` gets shadowed: covariates = {store_id: arr[store_id] for store_id in stores} # inside outer loop over store_id # CORRECT -- use a different name or accumulate beforehand: prices_by_store: dict[str, np.ndarray] = {} for store_id, config in stores.items(): prices_by_store[store_id] = compute_price(config) -
Wrong CSV column name -- The global-temperature CSV uses
anomaly_c, notanomaly. Alwaysprint(df.columns)before accessing. -
tight_layout()warning withsharex=True-- Harmless; suppress withplt.tight_layout(rect=[0, 0, 1, 0.97])or ignore. -
TimesFM 2.5 required for
forecast_with_covariates()-- TimesFM 1.0 does NOT have this method. Installpip install timesfm[xreg]and use checkpointgoogle/timesfm-2.5-200m-pytorch. -
Future covariates must span the full horizon -- Dynamic covariates (price, promotions, holidays) must have values for BOTH the context AND the forecast horizon. You cannot pass context-only arrays.
-
Anomaly thresholds must be defined once -- Define
CRITICAL_Z = 3.0,WARNING_Z = 2.0as module-level constants. Never hardcode3or2inline. -
Context anomaly detection uses residuals, not raw values -- Always detrend first (
np.polyfitlinear, or seasonal decomposition), then Z-score the residuals. Raw-value Z-scores are misleading on trending data.
Validation & Verification
Use the example outputs as regression baselines. If you change forecasting logic, verify:
bash# Anomaly detection regression check: python -c " import json d = json.load(open('examples/anomaly-detection/output/anomaly_detection.json')) ctx = d['context_summary'] assert ctx['critical'] >= 1, 'Sep 2023 must be CRITICAL' assert any(r['date'] == '2023-09' and r['severity'] == 'CRITICAL' for r in d['context_detections']), 'Sep 2023 not found' print('Anomaly detection regression: PASS')" # Covariates regression check: python -c " import pandas as pd df = pd.read_csv('examples/covariates-forecasting/output/sales_with_covariates.csv') assert len(df) == 108, f'Expected 108 rows, got {len(df)}' prices = df.groupby('store_id')['price'].mean() assert prices['store_A'] > prices['store_B'] > prices['store_C'], 'Store price ordering wrong' print('Covariates regression: PASS')"

