mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-03-27 07:09:27 +08:00
feat(timesfm): complete all three examples with quality docs
- anomaly-detection: full two-phase rewrite (context Z-score + forecast PI), 2-panel viz, Sep 2023 correctly flagged CRITICAL (z=+3.03) - covariates-forecasting: v3 rewrite with variable-shadowing bug fixed, 2x2 shared-axis viz showing actionable covariate decomposition, 108-row CSV with distinct per-store price arrays - global-temperature: output/ subfolder reorganization (all 6 output files moved, 5 scripts + shell script paths updated) - SKILL.md: added Examples table, Quality Checklist, Common Mistakes (8 items), Validation & Verification with regression assertions - .gitattributes already at repo root covering all binary types
This commit is contained in:
@@ -2,26 +2,27 @@
|
||||
"""
|
||||
TimesFM Covariates (XReg) Example
|
||||
|
||||
Demonstrates the TimesFM covariate API structure using synthetic retail
|
||||
sales data. TimesFM 1.0 does NOT support forecast_with_covariates().
|
||||
That feature requires TimesFM 2.5 + `timesfm[xreg]`.
|
||||
Demonstrates the TimesFM covariate API using synthetic retail sales data.
|
||||
TimesFM 1.0 does NOT support forecast_with_covariates(); that requires
|
||||
TimesFM 2.5 + `pip install timesfm[xreg]`.
|
||||
|
||||
This script:
|
||||
1. Generates synthetic 3-store retail data (24-week context, 12-week horizon)
|
||||
2. Visualises each covariate type (dynamic numerical, dynamic categorical, static)
|
||||
3. Prints the forecast_with_covariates() call signature for reference
|
||||
4. Exports a compact CSV (90 rows) and metadata JSON
|
||||
1. Generates synthetic 3-store weekly retail data (24-week context, 12-week horizon)
|
||||
2. Produces a 2x2 visualization showing WHAT each covariate contributes
|
||||
and WHY knowing them improves forecasts -- all panels share the same
|
||||
week x-axis (0 = first context week, 35 = last horizon week)
|
||||
3. Exports a compact CSV (108 rows) and metadata JSON
|
||||
|
||||
NOTE ON REAL DATA:
|
||||
If you want to use a real retail dataset (e.g., Kaggle Rossmann Store Sales),
|
||||
download it to a TEMP location — do NOT commit large CSVs to this repo.
|
||||
Example:
|
||||
download it to a TEMP location -- do NOT commit large CSVs to this repo.
|
||||
|
||||
import tempfile, urllib.request
|
||||
tmp = tempfile.mkdtemp(prefix="timesfm_retail_")
|
||||
# urllib.request.urlretrieve("https://...store_sales.csv", f"{tmp}/store_sales.csv")
|
||||
# df = pd.read_csv(f"{tmp}/store_sales.csv")
|
||||
Users should persist the data wherever makes sense for their workflow;
|
||||
this skills directory intentionally keeps only tiny reference datasets.
|
||||
|
||||
This skills directory intentionally keeps only tiny reference datasets.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -29,320 +30,421 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# Note: TimesFM 1.0 does not support forecast_with_covariates
|
||||
# This example demonstrates the API with TimesFM 2.5
|
||||
# Installation: pip install timesfm[xreg]
|
||||
|
||||
EXAMPLE_DIR = Path(__file__).parent
|
||||
OUTPUT_DIR = EXAMPLE_DIR / "output"
|
||||
|
||||
# Synthetic data configuration — kept SMALL (24 weeks context, 90 CSV rows)
|
||||
N_STORES = 3
|
||||
CONTEXT_LEN = 24 # weeks of history (was 48 — halved for token efficiency)
|
||||
HORIZON_LEN = 12 # weeks to forecast
|
||||
TOTAL_LEN = CONTEXT_LEN + HORIZON_LEN # 36 weeks total per store
|
||||
CONTEXT_LEN = 24
|
||||
HORIZON_LEN = 12
|
||||
TOTAL_LEN = CONTEXT_LEN + HORIZON_LEN # 36
|
||||
|
||||
|
||||
def generate_sales_data() -> dict:
|
||||
"""Generate synthetic retail sales data with covariates.
|
||||
"""Generate synthetic retail sales data with covariate components stored separately.
|
||||
|
||||
BUG FIX (v2): Previous version had a variable-shadowing issue where the
|
||||
inner dict comprehension `{store_id: ... for store_id in stores}` overwrote
|
||||
the outer loop variable, giving all stores identical covariate data (store_A's).
|
||||
Fixed by collecting per-store arrays into separate dicts during the outer loop
|
||||
and building the covariates dict afterwards.
|
||||
Returns a dict with:
|
||||
stores: {store_id: {sales, config}}
|
||||
covariates: {price, promotion, holiday, day_of_week, store_type, region}
|
||||
components: {store_id: {base, price_effect, promo_effect, holiday_effect}}
|
||||
|
||||
Components let us show 'what would sales look like without covariates?' --
|
||||
the gap between 'base' and 'sales' IS the covariate signal.
|
||||
|
||||
BUG FIX v3: Previous versions had variable-shadowing where inner dict
|
||||
comprehension `{store_id: ... for store_id in stores}` overwrote the outer
|
||||
loop variable causing all stores to get identical covariate arrays.
|
||||
Fixed by accumulating per-store arrays separately before building covariate dict.
|
||||
"""
|
||||
rng = np.random.default_rng(42)
|
||||
|
||||
# Store configurations
|
||||
stores = {
|
||||
"store_A": {"type": "premium", "region": "urban", "base_sales": 1000},
|
||||
"store_B": {"type": "standard", "region": "suburban", "base_sales": 750},
|
||||
"store_C": {"type": "discount", "region": "rural", "base_sales": 500},
|
||||
}
|
||||
base_prices = {"store_A": 12.0, "store_B": 10.0, "store_C": 7.5}
|
||||
|
||||
data: dict = {"stores": {}, "covariates": {}}
|
||||
data: dict = {"stores": {}, "covariates": {}, "components": {}}
|
||||
|
||||
# Collect per-store covariate arrays *before* building the covariates dict
|
||||
prices_by_store: dict[str, np.ndarray] = {}
|
||||
promos_by_store: dict[str, np.ndarray] = {}
|
||||
holidays_by_store: dict[str, np.ndarray] = {}
|
||||
day_of_week_by_store: dict[str, np.ndarray] = {}
|
||||
dow_by_store: dict[str, np.ndarray] = {}
|
||||
|
||||
for store_id, config in stores.items():
|
||||
bp = base_prices[store_id]
|
||||
weeks = np.arange(TOTAL_LEN)
|
||||
|
||||
trend = config["base_sales"] * (1 + 0.005 * weeks)
|
||||
seasonality = 100 * np.sin(2 * np.pi * weeks / 52)
|
||||
noise = rng.normal(0, 50, TOTAL_LEN)
|
||||
seasonality = 80 * np.sin(2 * np.pi * weeks / 52)
|
||||
noise = rng.normal(0, 40, TOTAL_LEN)
|
||||
base = (trend + seasonality + noise).astype(np.float32)
|
||||
|
||||
# Price — slightly different range per store to reflect market positioning
|
||||
base_price = {"store_A": 12.0, "store_B": 10.0, "store_C": 7.5}[store_id]
|
||||
price = base_price + rng.uniform(-0.5, 0.5, TOTAL_LEN)
|
||||
price_effect = -20 * (price - base_price)
|
||||
price = (bp + rng.uniform(-0.5, 0.5, TOTAL_LEN)).astype(np.float32)
|
||||
price_effect = (-20 * (price - bp)).astype(np.float32)
|
||||
|
||||
# Holidays (major retail weeks)
|
||||
holidays = np.zeros(TOTAL_LEN)
|
||||
holidays = np.zeros(TOTAL_LEN, dtype=np.float32)
|
||||
for hw in [0, 11, 23, 35]:
|
||||
if hw < TOTAL_LEN:
|
||||
holidays[hw] = 1.0
|
||||
holiday_effect = 200 * holidays
|
||||
holiday_effect = (200 * holidays).astype(np.float32)
|
||||
|
||||
# Promotion — random 20% of weeks
|
||||
promotion = rng.choice([0.0, 1.0], TOTAL_LEN, p=[0.8, 0.2])
|
||||
promo_effect = 150 * promotion
|
||||
promotion = rng.choice([0.0, 1.0], TOTAL_LEN, p=[0.8, 0.2]).astype(np.float32)
|
||||
promo_effect = (150 * promotion).astype(np.float32)
|
||||
|
||||
# Day-of-week proxy (weekly granularity → repeat 0-6 pattern)
|
||||
day_of_week = np.tile(np.arange(7), TOTAL_LEN // 7 + 1)[:TOTAL_LEN]
|
||||
|
||||
sales = (
|
||||
trend + seasonality + noise + price_effect + holiday_effect + promo_effect
|
||||
day_of_week = np.tile(np.arange(7), TOTAL_LEN // 7 + 1)[:TOTAL_LEN].astype(
|
||||
np.int32
|
||||
)
|
||||
sales = np.maximum(sales, 50.0).astype(np.float32)
|
||||
|
||||
sales = np.maximum(base + price_effect + holiday_effect + promo_effect, 50.0)
|
||||
|
||||
data["stores"][store_id] = {"sales": sales, "config": config}
|
||||
data["components"][store_id] = {
|
||||
"base": base,
|
||||
"price_effect": price_effect,
|
||||
"promo_effect": promo_effect,
|
||||
"holiday_effect": holiday_effect,
|
||||
}
|
||||
|
||||
prices_by_store[store_id] = price.astype(np.float32)
|
||||
promos_by_store[store_id] = promotion.astype(np.float32)
|
||||
holidays_by_store[store_id] = holidays.astype(np.float32)
|
||||
day_of_week_by_store[store_id] = day_of_week.astype(np.int32)
|
||||
prices_by_store[store_id] = price
|
||||
promos_by_store[store_id] = promotion
|
||||
holidays_by_store[store_id] = holidays
|
||||
dow_by_store[store_id] = day_of_week
|
||||
|
||||
# Build covariates dict AFTER the loop (avoids shadowing bug)
|
||||
data["covariates"] = {
|
||||
"price": prices_by_store,
|
||||
"promotion": promos_by_store,
|
||||
"holiday": holidays_by_store,
|
||||
"day_of_week": day_of_week_by_store,
|
||||
"day_of_week": dow_by_store,
|
||||
"store_type": {sid: stores[sid]["type"] for sid in stores},
|
||||
"region": {sid: stores[sid]["region"] for sid in stores},
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def demonstrate_api() -> None:
|
||||
"""Print the forecast_with_covariates API structure (TimesFM 2.5)."""
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(" TIMESFM COVARIATES API (TimesFM 2.5)")
|
||||
print("=" * 70)
|
||||
|
||||
api_code = """
|
||||
# Installation
|
||||
pip install timesfm[xreg]
|
||||
|
||||
# Import
|
||||
import timesfm
|
||||
|
||||
# Load TimesFM 2.5 (supports covariates)
|
||||
hparams = timesfm.TimesFmHparams(
|
||||
backend="cpu", # or "gpu"
|
||||
per_core_batch_size=32,
|
||||
horizon_len=12,
|
||||
)
|
||||
checkpoint = timesfm.TimesFmCheckpoint(
|
||||
huggingface_repo_id="google/timesfm-2.5-200m-pytorch"
|
||||
)
|
||||
model = timesfm.TimesFm(hparams=hparams, checkpoint=checkpoint)
|
||||
|
||||
# Prepare inputs
|
||||
inputs = [sales_store_a, sales_store_b, sales_store_c] # List of historical sales
|
||||
|
||||
# Dynamic numerical covariates (context + horizon values per series)
|
||||
dynamic_numerical_covariates = {
|
||||
"price": [
|
||||
price_history_store_a, # Shape: (context_len + horizon_len,)
|
||||
price_history_store_b,
|
||||
price_history_store_c,
|
||||
],
|
||||
"promotion": [promo_a, promo_b, promo_c],
|
||||
}
|
||||
|
||||
# Dynamic categorical covariates
|
||||
dynamic_categorical_covariates = {
|
||||
"holiday": [holiday_a, holiday_b, holiday_c], # 0 or 1 flags
|
||||
"day_of_week": [dow_a, dow_b, dow_c], # 0-6 integer values
|
||||
}
|
||||
|
||||
# Static categorical covariates (one value per series)
|
||||
static_categorical_covariates = {
|
||||
"store_type": ["premium", "standard", "discount"],
|
||||
"region": ["urban", "suburban", "rural"],
|
||||
}
|
||||
|
||||
# Forecast with covariates
|
||||
point_forecast, quantile_forecast = model.forecast_with_covariates(
|
||||
inputs=inputs,
|
||||
dynamic_numerical_covariates=dynamic_numerical_covariates,
|
||||
dynamic_categorical_covariates=dynamic_categorical_covariates,
|
||||
static_categorical_covariates=static_categorical_covariates,
|
||||
xreg_mode="xreg + timesfm", # or "timesfm + xreg"
|
||||
ridge=0.0, # Ridge regularization
|
||||
normalize_xreg_target_per_input=True,
|
||||
)
|
||||
|
||||
# Output shapes
|
||||
# point_forecast: (num_series, horizon_len)
|
||||
# quantile_forecast: (num_series, horizon_len, 10)
|
||||
"""
|
||||
print(api_code)
|
||||
|
||||
|
||||
def explain_xreg_modes() -> None:
|
||||
"""Explain the two XReg modes."""
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(" XREG MODES EXPLAINED")
|
||||
print("=" * 70)
|
||||
|
||||
print("""
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Mode 1: "xreg + timesfm" (DEFAULT) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 1. TimesFM makes baseline forecast (ignoring covariates) │
|
||||
│ 2. Calculate residuals: actual - baseline │
|
||||
│ 3. Fit linear regression: residuals ~ covariates │
|
||||
│ 4. Final forecast = TimesFM baseline + XReg adjustment │
|
||||
│ │
|
||||
│ Best for: Covariates capture residual patterns │
|
||||
│ (e.g., promotions affecting baseline sales) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Mode 2: "timesfm + xreg" │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 1. Fit linear regression: target ~ covariates │
|
||||
│ 2. Calculate residuals: actual - regression_prediction │
|
||||
│ 3. TimesFM forecasts residuals │
|
||||
│ 4. Final forecast = XReg prediction + TimesFM residual forecast │
|
||||
│ │
|
||||
│ Best for: Covariates explain main signal │
|
||||
│ (e.g., temperature driving ice cream sales) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
""")
|
||||
|
||||
|
||||
def create_visualization(data: dict) -> None:
|
||||
"""Create visualization of sales data with covariates."""
|
||||
"""
|
||||
2x2 figure -- ALL panels share x-axis = weeks 0-35.
|
||||
|
||||
(0,0) Sales by store -- context solid, horizon dashed
|
||||
(0,1) Store A: actual vs baseline (no covariates), with event overlays showing uplift
|
||||
(1,0) Price covariate for all stores -- full 36 weeks including horizon
|
||||
(1,1) Covariate effect decomposition for Store A (stacked fill_between)
|
||||
|
||||
Each panel has a conclusion annotation box explaining what the data shows.
|
||||
"""
|
||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||
|
||||
fig, axes = plt.subplots(3, 2, figsize=(16, 12))
|
||||
|
||||
store_colors = {"store_A": "#1a56db", "store_B": "#057a55", "store_C": "#c03221"}
|
||||
weeks = np.arange(TOTAL_LEN)
|
||||
context_weeks = weeks[:CONTEXT_LEN]
|
||||
|
||||
# Panel 1 — Sales by store (context only)
|
||||
ax = axes[0, 0]
|
||||
for store_id, store_data in data["stores"].items():
|
||||
ax.plot(
|
||||
context_weeks,
|
||||
store_data["sales"][:CONTEXT_LEN],
|
||||
label=f"{store_id} ({store_data['config']['type']})",
|
||||
linewidth=2,
|
||||
)
|
||||
ax.axvline(
|
||||
x=CONTEXT_LEN - 0.5, color="red", linestyle="--", label="Forecast Start →"
|
||||
fig, axes = plt.subplots(
|
||||
2,
|
||||
2,
|
||||
figsize=(16, 11),
|
||||
sharex=True,
|
||||
gridspec_kw={"hspace": 0.42, "wspace": 0.32},
|
||||
)
|
||||
ax.set_xlabel("Week")
|
||||
ax.set_ylabel("Sales")
|
||||
ax.set_title("Historical Sales by Store (24-week context)")
|
||||
ax.legend(fontsize=9)
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
# Panel 2 — Price covariate (all weeks including horizon)
|
||||
ax = axes[0, 1]
|
||||
for store_id in data["stores"]:
|
||||
ax.plot(weeks, data["covariates"]["price"][store_id], label=store_id, alpha=0.8)
|
||||
ax.axvline(x=CONTEXT_LEN - 0.5, color="red", linestyle="--")
|
||||
ax.set_xlabel("Week")
|
||||
ax.set_ylabel("Price ($)")
|
||||
ax.set_title("Dynamic Numerical Covariate: Price\n(different baseline per store)")
|
||||
ax.legend(fontsize=9)
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
# Panel 3 — Holiday flag
|
||||
ax = axes[1, 0]
|
||||
# Show all 3 stores' holidays side by side (they're the same here but could differ)
|
||||
ax.bar(weeks, data["covariates"]["holiday"]["store_A"], alpha=0.7, color="orange")
|
||||
ax.axvline(x=CONTEXT_LEN - 0.5, color="red", linestyle="--")
|
||||
ax.set_xlabel("Week")
|
||||
ax.set_ylabel("Holiday Flag")
|
||||
ax.set_title("Dynamic Categorical Covariate: Holiday")
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
# Panel 4 — Promotion (store_A example — each store differs)
|
||||
ax = axes[1, 1]
|
||||
for store_id in data["stores"]:
|
||||
ax.bar(
|
||||
weeks + {"store_A": -0.3, "store_B": 0.0, "store_C": 0.3}[store_id],
|
||||
data["covariates"]["promotion"][store_id],
|
||||
width=0.3,
|
||||
alpha=0.7,
|
||||
label=store_id,
|
||||
)
|
||||
ax.axvline(x=CONTEXT_LEN - 0.5, color="red", linestyle="--")
|
||||
ax.set_xlabel("Week")
|
||||
ax.set_ylabel("Promotion Flag")
|
||||
ax.set_title("Dynamic Categorical Covariate: Promotion\n(independent per store)")
|
||||
ax.legend(fontsize=9)
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
# Panel 5 — Store type (static)
|
||||
ax = axes[2, 0]
|
||||
store_types = [data["covariates"]["store_type"][s] for s in data["stores"]]
|
||||
store_ids = list(data["stores"].keys())
|
||||
colors = {"premium": "gold", "standard": "silver", "discount": "#cd7f32"}
|
||||
ax.bar(store_ids, [1, 1, 1], color=[colors[t] for t in store_types])
|
||||
ax.set_ylabel("Store Type")
|
||||
ax.set_title("Static Categorical Covariate: Store Type")
|
||||
ax.set_yticks([])
|
||||
for i, (sid, t) in enumerate(zip(store_ids, store_types)):
|
||||
ax.text(i, 0.5, t, ha="center", va="center", fontweight="bold", fontsize=11)
|
||||
|
||||
# Panel 6 — Data structure summary
|
||||
ax = axes[2, 1]
|
||||
ax.axis("off")
|
||||
summary_text = (
|
||||
" COVARIATE DATA STRUCTURE\n"
|
||||
" ─────────────────────────\n\n"
|
||||
" Dynamic Numerical Covariates:\n"
|
||||
" • price: array[context_len + horizon_len] per series\n"
|
||||
" • promotion: array[context_len + horizon_len] per series\n\n"
|
||||
" Dynamic Categorical Covariates:\n"
|
||||
" • holiday: array[context_len + horizon_len] per series\n"
|
||||
" • day_of_week: array[context_len + horizon_len] per series\n\n"
|
||||
" Static Categorical Covariates:\n"
|
||||
" • store_type: ['premium', 'standard', 'discount']\n"
|
||||
" • region: ['urban', 'suburban', 'rural']\n\n"
|
||||
" ⚠ Future covariate values must be KNOWN at forecast time!\n"
|
||||
" (Prices, promotion schedules, and holidays are planned.)"
|
||||
)
|
||||
ax.text(
|
||||
0.05,
|
||||
0.5,
|
||||
summary_text,
|
||||
transform=ax.transAxes,
|
||||
fontfamily="monospace",
|
||||
fontsize=9,
|
||||
verticalalignment="center",
|
||||
)
|
||||
|
||||
plt.suptitle(
|
||||
"TimesFM Covariates (XReg) — Synthetic Retail Sales Demo",
|
||||
fontsize=14,
|
||||
fig.suptitle(
|
||||
"TimesFM Covariates (XReg) -- Retail Sales with Exogenous Variables\n"
|
||||
"Shared x-axis: Week 0-23 = context (observed) | Week 24-35 = forecast horizon",
|
||||
fontsize=13,
|
||||
fontweight="bold",
|
||||
y=1.01,
|
||||
)
|
||||
plt.tight_layout()
|
||||
|
||||
def add_divider(ax, label_top=True):
|
||||
ax.axvline(CONTEXT_LEN - 0.5, color="#9ca3af", lw=1.3, ls="--", alpha=0.8)
|
||||
ax.axvspan(
|
||||
CONTEXT_LEN - 0.5, TOTAL_LEN - 0.5, alpha=0.06, color="grey", zorder=0
|
||||
)
|
||||
if label_top:
|
||||
ax.text(
|
||||
CONTEXT_LEN + 0.3,
|
||||
1.01,
|
||||
"<- horizon ->",
|
||||
transform=ax.get_xaxis_transform(),
|
||||
fontsize=7.5,
|
||||
color="#6b7280",
|
||||
style="italic",
|
||||
)
|
||||
|
||||
# -- (0,0): Sales by Store ---------------------------------------------------
|
||||
ax = axes[0, 0]
|
||||
base_price_labels = {"store_A": "$12", "store_B": "$10", "store_C": "$7.50"}
|
||||
for sid, store_data in data["stores"].items():
|
||||
sales = store_data["sales"]
|
||||
c = store_colors[sid]
|
||||
lbl = f"{sid} ({store_data['config']['type']}, {base_price_labels[sid]} base)"
|
||||
ax.plot(
|
||||
weeks[:CONTEXT_LEN],
|
||||
sales[:CONTEXT_LEN],
|
||||
color=c,
|
||||
lw=2,
|
||||
marker="o",
|
||||
ms=3,
|
||||
label=lbl,
|
||||
)
|
||||
ax.plot(
|
||||
weeks[CONTEXT_LEN:],
|
||||
sales[CONTEXT_LEN:],
|
||||
color=c,
|
||||
lw=1.5,
|
||||
ls="--",
|
||||
marker="o",
|
||||
ms=3,
|
||||
alpha=0.6,
|
||||
)
|
||||
add_divider(ax)
|
||||
ax.set_ylabel("Weekly Sales (units)", fontsize=10)
|
||||
ax.set_title("Sales by Store", fontsize=11, fontweight="bold")
|
||||
ax.legend(fontsize=7.5, loc="upper left")
|
||||
ax.grid(True, alpha=0.22)
|
||||
ratio = (
|
||||
data["stores"]["store_A"]["sales"][:CONTEXT_LEN].mean()
|
||||
/ data["stores"]["store_C"]["sales"][:CONTEXT_LEN].mean()
|
||||
)
|
||||
ax.annotate(
|
||||
f"Store A earns {ratio:.1f}x Store C\n(premium vs discount pricing)\n"
|
||||
f"-> store_type is a useful static covariate",
|
||||
xy=(0.97, 0.05),
|
||||
xycoords="axes fraction",
|
||||
ha="right",
|
||||
fontsize=8,
|
||||
bbox=dict(boxstyle="round", fc="#fffbe6", ec="#d4a017", alpha=0.95),
|
||||
)
|
||||
|
||||
# -- (0,1): Store A actual vs baseline ---------------------------------------
|
||||
ax = axes[0, 1]
|
||||
comp_A = data["components"]["store_A"]
|
||||
sales_A = data["stores"]["store_A"]["sales"]
|
||||
base_A = comp_A["base"]
|
||||
promo_A = data["covariates"]["promotion"]["store_A"]
|
||||
holiday_A = data["covariates"]["holiday"]["store_A"]
|
||||
|
||||
ax.plot(
|
||||
weeks[:CONTEXT_LEN],
|
||||
base_A[:CONTEXT_LEN],
|
||||
color="#9ca3af",
|
||||
lw=1.8,
|
||||
ls="--",
|
||||
label="Baseline (no covariates)",
|
||||
)
|
||||
ax.fill_between(
|
||||
weeks[:CONTEXT_LEN],
|
||||
base_A[:CONTEXT_LEN],
|
||||
sales_A[:CONTEXT_LEN],
|
||||
where=(sales_A[:CONTEXT_LEN] > base_A[:CONTEXT_LEN]),
|
||||
alpha=0.35,
|
||||
color="#22c55e",
|
||||
label="Covariate uplift",
|
||||
)
|
||||
ax.fill_between(
|
||||
weeks[:CONTEXT_LEN],
|
||||
sales_A[:CONTEXT_LEN],
|
||||
base_A[:CONTEXT_LEN],
|
||||
where=(sales_A[:CONTEXT_LEN] < base_A[:CONTEXT_LEN]),
|
||||
alpha=0.30,
|
||||
color="#ef4444",
|
||||
label="Price suppression",
|
||||
)
|
||||
ax.plot(
|
||||
weeks[:CONTEXT_LEN],
|
||||
sales_A[:CONTEXT_LEN],
|
||||
color=store_colors["store_A"],
|
||||
lw=2,
|
||||
label="Actual sales (Store A)",
|
||||
)
|
||||
|
||||
for w in range(CONTEXT_LEN):
|
||||
if holiday_A[w] > 0:
|
||||
ax.axvspan(w - 0.45, w + 0.45, alpha=0.22, color="darkorange", zorder=0)
|
||||
promo_weeks = [w for w in range(CONTEXT_LEN) if promo_A[w] > 0]
|
||||
if promo_weeks:
|
||||
ax.scatter(
|
||||
promo_weeks,
|
||||
sales_A[promo_weeks],
|
||||
marker="^",
|
||||
color="#16a34a",
|
||||
s=70,
|
||||
zorder=6,
|
||||
label="Promotion week",
|
||||
)
|
||||
|
||||
add_divider(ax)
|
||||
ax.set_ylabel("Weekly Sales (units)", fontsize=10)
|
||||
ax.set_title(
|
||||
"Store A -- Actual vs Baseline (No Covariates)", fontsize=11, fontweight="bold"
|
||||
)
|
||||
ax.legend(fontsize=7.5, loc="upper left", ncol=2)
|
||||
ax.grid(True, alpha=0.22)
|
||||
|
||||
hm = holiday_A[:CONTEXT_LEN] > 0
|
||||
pm = promo_A[:CONTEXT_LEN] > 0
|
||||
h_lift = (
|
||||
(sales_A[:CONTEXT_LEN][hm] - base_A[:CONTEXT_LEN][hm]).mean() if hm.any() else 0
|
||||
)
|
||||
p_lift = (
|
||||
(sales_A[:CONTEXT_LEN][pm] - base_A[:CONTEXT_LEN][pm]).mean() if pm.any() else 0
|
||||
)
|
||||
ax.annotate(
|
||||
f"Holiday weeks: +{h_lift:.0f} units avg\n"
|
||||
f"Promotion weeks: +{p_lift:.0f} units avg\n"
|
||||
f"Future event schedules must be known for XReg",
|
||||
xy=(0.97, 0.05),
|
||||
xycoords="axes fraction",
|
||||
ha="right",
|
||||
fontsize=8,
|
||||
bbox=dict(boxstyle="round", fc="#fffbe6", ec="#d4a017", alpha=0.95),
|
||||
)
|
||||
|
||||
# -- (1,0): Price covariate -- full 36 weeks ---------------------------------
|
||||
ax = axes[1, 0]
|
||||
for sid in data["stores"]:
|
||||
ax.plot(
|
||||
weeks,
|
||||
data["covariates"]["price"][sid],
|
||||
color=store_colors[sid],
|
||||
lw=2,
|
||||
label=sid,
|
||||
alpha=0.85,
|
||||
)
|
||||
add_divider(ax, label_top=False)
|
||||
ax.set_xlabel("Week", fontsize=10)
|
||||
ax.set_ylabel("Price ($)", fontsize=10)
|
||||
ax.set_title(
|
||||
"Price Covariate -- Context + Forecast Horizon", fontsize=11, fontweight="bold"
|
||||
)
|
||||
ax.legend(fontsize=8, loc="upper right")
|
||||
ax.grid(True, alpha=0.22)
|
||||
ax.annotate(
|
||||
"Prices are planned -- known for forecast horizon\n"
|
||||
"Price elasticity: -$1 increase -> -20 units sold\n"
|
||||
"Store A ($12) consistently more expensive than C ($7.50)",
|
||||
xy=(0.97, 0.05),
|
||||
xycoords="axes fraction",
|
||||
ha="right",
|
||||
fontsize=8,
|
||||
bbox=dict(boxstyle="round", fc="#fffbe6", ec="#d4a017", alpha=0.95),
|
||||
)
|
||||
|
||||
# -- (1,1): Covariate effect decomposition -----------------------------------
|
||||
ax = axes[1, 1]
|
||||
pe = comp_A["price_effect"]
|
||||
pre = comp_A["promo_effect"]
|
||||
he = comp_A["holiday_effect"]
|
||||
|
||||
ax.fill_between(
|
||||
weeks,
|
||||
0,
|
||||
pe,
|
||||
alpha=0.65,
|
||||
color="steelblue",
|
||||
step="mid",
|
||||
label=f"Price effect (max +/-{np.abs(pe).max():.0f} units)",
|
||||
)
|
||||
ax.fill_between(
|
||||
weeks,
|
||||
pe,
|
||||
pe + pre,
|
||||
alpha=0.70,
|
||||
color="#22c55e",
|
||||
step="mid",
|
||||
label="Promotion effect (+150 units)",
|
||||
)
|
||||
ax.fill_between(
|
||||
weeks,
|
||||
pe + pre,
|
||||
pe + pre + he,
|
||||
alpha=0.70,
|
||||
color="darkorange",
|
||||
step="mid",
|
||||
label="Holiday effect (+200 units)",
|
||||
)
|
||||
total = pe + pre + he
|
||||
ax.plot(weeks, total, "k-", lw=1.5, alpha=0.75, label="Total covariate effect")
|
||||
ax.axhline(0, color="black", lw=0.9, alpha=0.6)
|
||||
add_divider(ax, label_top=False)
|
||||
ax.set_xlabel("Week", fontsize=10)
|
||||
ax.set_ylabel("Effect on sales (units)", fontsize=10)
|
||||
ax.set_title(
|
||||
"Store A -- Covariate Effect Decomposition", fontsize=11, fontweight="bold"
|
||||
)
|
||||
ax.legend(fontsize=7.5, loc="upper right")
|
||||
ax.grid(True, alpha=0.22, axis="y")
|
||||
ax.annotate(
|
||||
f"Holidays (+200) and promotions (+150) dominate\n"
|
||||
f"Price effect (+/-{np.abs(pe).max():.0f} units) is minor by comparison\n"
|
||||
f"-> Time-varying covariates explain most sales spikes",
|
||||
xy=(0.97, 0.55),
|
||||
xycoords="axes fraction",
|
||||
ha="right",
|
||||
fontsize=8,
|
||||
bbox=dict(boxstyle="round", fc="#fffbe6", ec="#d4a017", alpha=0.95),
|
||||
)
|
||||
|
||||
tick_pos = list(range(0, TOTAL_LEN, 4))
|
||||
for row in [0, 1]:
|
||||
for col in [0, 1]:
|
||||
axes[row, col].set_xticks(tick_pos)
|
||||
|
||||
plt.tight_layout()
|
||||
output_path = OUTPUT_DIR / "covariates_data.png"
|
||||
plt.savefig(output_path, dpi=150, bbox_inches="tight")
|
||||
print(f"\n📊 Saved visualization: {output_path}")
|
||||
plt.close()
|
||||
print(f"\n Saved visualization: {output_path}")
|
||||
|
||||
|
||||
def demonstrate_api() -> None:
|
||||
print("\n" + "=" * 70)
|
||||
print(" TIMESFM COVARIATES API (TimesFM 2.5)")
|
||||
print("=" * 70)
|
||||
print("""
|
||||
# Installation
|
||||
pip install timesfm[xreg]
|
||||
|
||||
import timesfm
|
||||
hparams = timesfm.TimesFmHparams(backend="cpu", per_core_batch_size=32, horizon_len=12)
|
||||
ckpt = timesfm.TimesFmCheckpoint(huggingface_repo_id="google/timesfm-2.5-200m-pytorch")
|
||||
model = timesfm.TimesFm(hparams=hparams, checkpoint=ckpt)
|
||||
|
||||
point_fc, quant_fc = model.forecast_with_covariates(
|
||||
inputs=[sales_a, sales_b, sales_c],
|
||||
dynamic_numerical_covariates={"price": [price_a, price_b, price_c]},
|
||||
dynamic_categorical_covariates={"holiday": [hol_a, hol_b, hol_c]},
|
||||
static_categorical_covariates={"store_type": ["premium","standard","discount"]},
|
||||
xreg_mode="xreg + timesfm",
|
||||
normalize_xreg_target_per_input=True,
|
||||
)
|
||||
# point_fc: (num_series, horizon_len)
|
||||
# quant_fc: (num_series, horizon_len, 10)
|
||||
""")
|
||||
|
||||
|
||||
def explain_xreg_modes() -> None:
|
||||
print("\n" + "=" * 70)
|
||||
print(" XREG MODES")
|
||||
print("=" * 70)
|
||||
print("""
|
||||
"xreg + timesfm" (DEFAULT)
|
||||
1. TimesFM makes baseline forecast
|
||||
2. Fit regression on residuals (actual - baseline) ~ covariates
|
||||
3. Final = TimesFM baseline + XReg adjustment
|
||||
Best when: covariates explain residual variation (e.g. promotions)
|
||||
|
||||
"timesfm + xreg"
|
||||
1. Fit regression: target ~ covariates
|
||||
2. TimesFM forecasts the residuals
|
||||
3. Final = XReg prediction + TimesFM residual forecast
|
||||
Best when: covariates explain the main signal (e.g. temperature)
|
||||
""")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -350,27 +452,22 @@ def main() -> None:
|
||||
print(" TIMESFM COVARIATES (XREG) EXAMPLE")
|
||||
print("=" * 70)
|
||||
|
||||
# Generate synthetic data
|
||||
print("\n📊 Generating synthetic retail sales data...")
|
||||
print("\n Generating synthetic retail sales data...")
|
||||
data = generate_sales_data()
|
||||
|
||||
print(f" Stores: {list(data['stores'].keys())}")
|
||||
print(f" Context length: {CONTEXT_LEN} weeks")
|
||||
print(f" Horizon length: {HORIZON_LEN} weeks")
|
||||
print(f" Covariates: {list(data['covariates'].keys())}")
|
||||
print(f" Stores: {list(data['stores'].keys())}")
|
||||
print(f" Context length: {CONTEXT_LEN} weeks")
|
||||
print(f" Horizon length: {HORIZON_LEN} weeks")
|
||||
print(f" Covariates: {list(data['covariates'].keys())}")
|
||||
|
||||
# Show API
|
||||
demonstrate_api()
|
||||
|
||||
# Explain modes
|
||||
explain_xreg_modes()
|
||||
|
||||
# Create visualization
|
||||
print("\n📊 Creating data visualization...")
|
||||
print("\n Creating 2x2 visualization (shared x-axis)...")
|
||||
create_visualization(data)
|
||||
|
||||
# Save data
|
||||
print("\n💾 Saving synthetic data...")
|
||||
print("\n Saving output data...")
|
||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||
|
||||
records = []
|
||||
for store_id, store_data in data["stores"].items():
|
||||
@@ -381,7 +478,13 @@ def main() -> None:
|
||||
"week": i,
|
||||
"split": "context" if i < CONTEXT_LEN else "horizon",
|
||||
"sales": round(float(store_data["sales"][i]), 2),
|
||||
"base_sales": round(
|
||||
float(data["components"][store_id]["base"][i]), 2
|
||||
),
|
||||
"price": round(float(data["covariates"]["price"][store_id][i]), 4),
|
||||
"price_effect": round(
|
||||
float(data["components"][store_id]["price_effect"][i]), 2
|
||||
),
|
||||
"promotion": int(data["covariates"]["promotion"][store_id][i]),
|
||||
"holiday": int(data["covariates"]["holiday"][store_id][i]),
|
||||
"day_of_week": int(data["covariates"]["day_of_week"][store_id][i]),
|
||||
@@ -393,17 +496,23 @@ def main() -> None:
|
||||
df = pd.DataFrame(records)
|
||||
csv_path = OUTPUT_DIR / "sales_with_covariates.csv"
|
||||
df.to_csv(csv_path, index=False)
|
||||
print(f" Saved: {csv_path} ({len(df)} rows × {len(df.columns)} cols)")
|
||||
print(f" Saved: {csv_path} ({len(df)} rows x {len(df.columns)} cols)")
|
||||
|
||||
# Save metadata
|
||||
metadata = {
|
||||
"description": "Synthetic retail sales data with covariates for TimesFM XReg demo",
|
||||
"note_on_real_data": (
|
||||
"If using a real dataset (e.g., Kaggle Rossmann Store Sales), "
|
||||
"download it to a temp directory (tempfile.mkdtemp) and do NOT "
|
||||
"commit it here. This skills directory only ships tiny reference files."
|
||||
"For real datasets (e.g., Kaggle Rossmann Store Sales), download to "
|
||||
"tempfile.mkdtemp() -- do NOT commit to this repo."
|
||||
),
|
||||
"stores": {sid: sdata["config"] for sid, sdata in data["stores"].items()},
|
||||
"stores": {
|
||||
sid: {
|
||||
**sdata["config"],
|
||||
"mean_sales_context": round(
|
||||
float(sdata["sales"][:CONTEXT_LEN].mean()), 1
|
||||
),
|
||||
}
|
||||
for sid, sdata in data["stores"].items()
|
||||
},
|
||||
"dimensions": {
|
||||
"context_length": CONTEXT_LEN,
|
||||
"horizon_length": HORIZON_LEN,
|
||||
@@ -412,20 +521,23 @@ def main() -> None:
|
||||
"csv_rows": len(df),
|
||||
},
|
||||
"covariates": {
|
||||
"dynamic_numerical": ["price", "promotion"],
|
||||
"dynamic_categorical": ["holiday", "day_of_week"],
|
||||
"dynamic_numerical": ["price"],
|
||||
"dynamic_categorical": ["promotion", "holiday", "day_of_week"],
|
||||
"static_categorical": ["store_type", "region"],
|
||||
},
|
||||
"xreg_modes": {
|
||||
"xreg + timesfm": "Fit regression on residuals after TimesFM forecast",
|
||||
"timesfm + xreg": "TimesFM forecasts residuals after regression fit",
|
||||
"effect_magnitudes": {
|
||||
"holiday": "+200 units per holiday week",
|
||||
"promotion": "+150 units per promotion week",
|
||||
"price": "-20 units per $1 above base price",
|
||||
},
|
||||
"bug_fixes": [
|
||||
"v2: Fixed variable-shadowing in generate_sales_data() — inner dict "
|
||||
"comprehension `{store_id: ... for store_id in stores}` was overwriting "
|
||||
"the outer loop variable, causing all stores to get identical covariate "
|
||||
"arrays. Fixed by using separate per-store dicts during the loop.",
|
||||
"v2: Reduced CONTEXT_LEN from 48 → 24 weeks; CSV now 90 rows (was 180).",
|
||||
"xreg_modes": {
|
||||
"xreg + timesfm": "Regression on TimesFM residuals (default)",
|
||||
"timesfm + xreg": "TimesFM on regression residuals",
|
||||
},
|
||||
"bug_fixes_history": [
|
||||
"v1: Variable-shadowing -- all stores had identical covariates",
|
||||
"v2: Fixed shadowing; CONTEXT_LEN 48->24",
|
||||
"v3: Added component decomposition (base, price/promo/holiday effects); 2x2 sharex viz",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -434,38 +546,21 @@ def main() -> None:
|
||||
json.dump(metadata, f, indent=2)
|
||||
print(f" Saved: {meta_path}")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 70)
|
||||
print(" ✅ COVARIATES EXAMPLE COMPLETE")
|
||||
print(" COVARIATES EXAMPLE COMPLETE")
|
||||
print("=" * 70)
|
||||
|
||||
print("""
|
||||
💡 Key Points:
|
||||
Key points:
|
||||
1. Requires timesfm[xreg] + TimesFM 2.5+ for actual inference
|
||||
2. Dynamic covariates need values for BOTH context AND horizon (future must be known!)
|
||||
3. Static covariates: one value per series (store_type, region)
|
||||
4. All 4 visualization panels share the same week x-axis (0-35)
|
||||
5. Effect decomposition shows holidays/promotions dominate over price variation
|
||||
|
||||
1. INSTALLATION: Requires timesfm[xreg] extra
|
||||
pip install timesfm[xreg]
|
||||
|
||||
2. COVARIATE TYPES:
|
||||
• Dynamic Numerical: time-varying numeric (price, promotion)
|
||||
• Dynamic Categorical: time-varying flags (holiday, day_of_week)
|
||||
• Static Categorical: fixed per series (store_type, region)
|
||||
|
||||
3. DATA REQUIREMENTS:
|
||||
• Dynamic covariates need values for context + horizon
|
||||
• Future values must be known (prices, scheduled holidays, etc.)
|
||||
|
||||
4. XREG MODES:
|
||||
• "xreg + timesfm" (default): Regression on residuals
|
||||
• "timesfm + xreg": TimesFM on residuals after regression
|
||||
|
||||
5. LIMITATIONS:
|
||||
• Requires TimesFM 2.5+ (v1.0 does not support XReg)
|
||||
• String categoricals work but int encoding is faster
|
||||
|
||||
📁 Output Files:
|
||||
• output/covariates_data.png — visualization (6 panels)
|
||||
• output/sales_with_covariates.csv — 90-row compact dataset
|
||||
• output/covariates_metadata.json — metadata + bug-fix log
|
||||
Output files:
|
||||
output/covariates_data.png -- 2x2 visualization with conclusions
|
||||
output/sales_with_covariates.csv -- 108-row compact dataset
|
||||
output/covariates_metadata.json -- metadata + effect magnitudes
|
||||
""")
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 448 KiB |
@@ -1,21 +1,24 @@
|
||||
{
|
||||
"description": "Synthetic retail sales data with covariates for TimesFM XReg demo",
|
||||
"note_on_real_data": "If using a real dataset (e.g., Kaggle Rossmann Store Sales), download it to a temp directory (tempfile.mkdtemp) and do NOT commit it here. This skills directory only ships tiny reference files.",
|
||||
"note_on_real_data": "For real datasets (e.g., Kaggle Rossmann Store Sales), download to tempfile.mkdtemp() -- do NOT commit to this repo.",
|
||||
"stores": {
|
||||
"store_A": {
|
||||
"type": "premium",
|
||||
"region": "urban",
|
||||
"base_sales": 1000
|
||||
"base_sales": 1000,
|
||||
"mean_sales_context": 1148.7
|
||||
},
|
||||
"store_B": {
|
||||
"type": "standard",
|
||||
"region": "suburban",
|
||||
"base_sales": 750
|
||||
"base_sales": 750,
|
||||
"mean_sales_context": 907.0
|
||||
},
|
||||
"store_C": {
|
||||
"type": "discount",
|
||||
"region": "rural",
|
||||
"base_sales": 500
|
||||
"base_sales": 500,
|
||||
"mean_sales_context": 645.3
|
||||
}
|
||||
},
|
||||
"dimensions": {
|
||||
@@ -27,10 +30,10 @@
|
||||
},
|
||||
"covariates": {
|
||||
"dynamic_numerical": [
|
||||
"price",
|
||||
"promotion"
|
||||
"price"
|
||||
],
|
||||
"dynamic_categorical": [
|
||||
"promotion",
|
||||
"holiday",
|
||||
"day_of_week"
|
||||
],
|
||||
@@ -39,12 +42,18 @@
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"xreg_modes": {
|
||||
"xreg + timesfm": "Fit regression on residuals after TimesFM forecast",
|
||||
"timesfm + xreg": "TimesFM forecasts residuals after regression fit"
|
||||
"effect_magnitudes": {
|
||||
"holiday": "+200 units per holiday week",
|
||||
"promotion": "+150 units per promotion week",
|
||||
"price": "-20 units per $1 above base price"
|
||||
},
|
||||
"bug_fixes": [
|
||||
"v2: Fixed variable-shadowing in generate_sales_data() \u2014 inner dict comprehension `{store_id: ... for store_id in stores}` was overwriting the outer loop variable, causing all stores to get identical covariate arrays. Fixed by using separate per-store dicts during the loop.",
|
||||
"v2: Reduced CONTEXT_LEN from 48 \u2192 24 weeks; CSV now 90 rows (was 180)."
|
||||
"xreg_modes": {
|
||||
"xreg + timesfm": "Regression on TimesFM residuals (default)",
|
||||
"timesfm + xreg": "TimesFM on regression residuals"
|
||||
},
|
||||
"bug_fixes_history": [
|
||||
"v1: Variable-shadowing -- all stores had identical covariates",
|
||||
"v2: Fixed shadowing; CONTEXT_LEN 48->24",
|
||||
"v3: Added component decomposition (base, price/promo/holiday effects); 2x2 sharex viz"
|
||||
]
|
||||
}
|
||||
@@ -1,109 +1,109 @@
|
||||
store_id,week,split,sales,price,promotion,holiday,day_of_week,store_type,region
|
||||
store_A,0,context,1372.64,11.6299,1,1,0,premium,urban
|
||||
store_A,1,context,965.54,11.9757,0,0,1,premium,urban
|
||||
store_A,2,context,1076.92,11.7269,0,0,2,premium,urban
|
||||
store_A,3,context,1094.09,12.1698,0,0,3,premium,urban
|
||||
store_A,4,context,970.18,11.9372,0,0,4,premium,urban
|
||||
store_A,5,context,1010.04,12.3327,0,0,5,premium,urban
|
||||
store_A,6,context,1098.7,12.2003,0,0,6,premium,urban
|
||||
store_A,7,context,1097.79,11.8124,0,0,0,premium,urban
|
||||
store_A,8,context,1114.81,12.3323,0,0,1,premium,urban
|
||||
store_A,9,context,1084.8,12.3048,0,0,2,premium,urban
|
||||
store_A,10,context,1339.72,11.8875,1,0,3,premium,urban
|
||||
store_A,11,context,1395.22,11.7883,0,1,4,premium,urban
|
||||
store_A,12,context,1158.92,12.1825,0,0,5,premium,urban
|
||||
store_A,13,context,1228.57,11.6398,0,0,6,premium,urban
|
||||
store_A,14,context,1198.65,11.6999,0,0,0,premium,urban
|
||||
store_A,15,context,1138.98,11.5074,0,0,1,premium,urban
|
||||
store_A,16,context,1186.2,12.2869,0,0,2,premium,urban
|
||||
store_A,17,context,1122.3,12.1649,0,0,3,premium,urban
|
||||
store_A,18,context,1212.12,12.2052,0,0,4,premium,urban
|
||||
store_A,19,context,1161.74,12.2807,0,0,5,premium,urban
|
||||
store_A,20,context,1157.89,11.9589,0,0,6,premium,urban
|
||||
store_A,21,context,1126.39,12.0687,0,0,0,premium,urban
|
||||
store_A,22,context,1224.8,11.6398,0,0,1,premium,urban
|
||||
store_A,23,context,1350.44,11.6145,0,1,2,premium,urban
|
||||
store_A,24,horizon,1119.15,12.1684,0,0,3,premium,urban
|
||||
store_A,25,horizon,1120.03,11.9711,0,0,4,premium,urban
|
||||
store_A,26,horizon,1155.31,12.0652,0,0,5,premium,urban
|
||||
store_A,27,horizon,1285.92,12.265,1,0,6,premium,urban
|
||||
store_A,28,horizon,1284.01,12.1347,1,0,0,premium,urban
|
||||
store_A,29,horizon,1130.01,12.0536,0,0,1,premium,urban
|
||||
store_A,30,horizon,1209.43,12.0592,0,0,2,premium,urban
|
||||
store_A,31,horizon,1231.79,11.804,1,0,3,premium,urban
|
||||
store_A,32,horizon,1077.46,11.5308,0,0,4,premium,urban
|
||||
store_A,33,horizon,1050.73,11.9367,0,0,5,premium,urban
|
||||
store_A,34,horizon,1124.21,11.7146,0,0,6,premium,urban
|
||||
store_A,35,horizon,1344.73,11.9085,0,1,0,premium,urban
|
||||
store_B,0,context,1053.03,9.9735,1,1,0,standard,suburban
|
||||
store_B,1,context,903.51,9.767,1,0,1,standard,suburban
|
||||
store_B,2,context,826.82,9.8316,0,0,2,standard,suburban
|
||||
store_B,3,context,709.93,10.0207,0,0,3,standard,suburban
|
||||
store_B,4,context,834.42,9.9389,0,0,4,standard,suburban
|
||||
store_B,5,context,847.01,9.5216,0,0,5,standard,suburban
|
||||
store_B,6,context,802.58,10.3263,0,0,6,standard,suburban
|
||||
store_B,7,context,770.87,10.3962,0,0,0,standard,suburban
|
||||
store_B,8,context,873.1,9.6402,0,0,1,standard,suburban
|
||||
store_B,9,context,844.74,10.054,0,0,2,standard,suburban
|
||||
store_B,10,context,1050.46,9.6086,1,0,3,standard,suburban
|
||||
store_B,11,context,1085.99,10.1722,0,1,4,standard,suburban
|
||||
store_B,12,context,978.74,9.7812,0,0,5,standard,suburban
|
||||
store_B,13,context,1033.59,10.1594,1,0,6,standard,suburban
|
||||
store_B,14,context,846.06,10.227,0,0,0,standard,suburban
|
||||
store_B,15,context,906.93,10.2686,0,0,1,standard,suburban
|
||||
store_B,16,context,922.35,9.6077,0,0,2,standard,suburban
|
||||
store_B,17,context,1111.93,10.416,1,0,3,standard,suburban
|
||||
store_B,18,context,946.95,9.7302,0,0,4,standard,suburban
|
||||
store_B,19,context,923.2,9.5374,0,0,5,standard,suburban
|
||||
store_B,20,context,963.38,10.0549,0,0,6,standard,suburban
|
||||
store_B,21,context,978.7,9.8709,1,0,0,standard,suburban
|
||||
store_B,22,context,840.39,10.3298,0,0,1,standard,suburban
|
||||
store_B,23,context,1019.22,10.3083,0,1,2,standard,suburban
|
||||
store_B,24,horizon,848.1,9.8171,0,0,3,standard,suburban
|
||||
store_B,25,horizon,777.91,10.4529,0,0,4,standard,suburban
|
||||
store_B,26,horizon,883.44,9.7909,0,0,5,standard,suburban
|
||||
store_B,27,horizon,827.78,10.0151,0,0,6,standard,suburban
|
||||
store_B,28,horizon,762.41,9.756,0,0,0,standard,suburban
|
||||
store_B,29,horizon,763.79,10.436,0,0,1,standard,suburban
|
||||
store_B,30,horizon,838.41,9.6646,0,0,2,standard,suburban
|
||||
store_B,31,horizon,860.45,9.5449,0,0,3,standard,suburban
|
||||
store_B,32,horizon,904.82,9.9351,0,0,4,standard,suburban
|
||||
store_B,33,horizon,1084.74,10.4924,1,0,5,standard,suburban
|
||||
store_B,34,horizon,808.09,10.3917,0,0,6,standard,suburban
|
||||
store_B,35,horizon,938.26,10.2486,0,1,0,standard,suburban
|
||||
store_C,0,context,709.43,7.1053,0,1,0,discount,rural
|
||||
store_C,1,context,649.01,7.0666,1,0,1,discount,rural
|
||||
store_C,2,context,660.66,7.5944,1,0,2,discount,rural
|
||||
store_C,3,context,750.17,7.1462,1,0,3,discount,rural
|
||||
store_C,4,context,726.88,7.8247,1,0,4,discount,rural
|
||||
store_C,5,context,639.97,7.3103,0,0,5,discount,rural
|
||||
store_C,6,context,580.71,7.1439,0,0,6,discount,rural
|
||||
store_C,7,context,549.13,7.921,0,0,0,discount,rural
|
||||
store_C,8,context,597.79,7.1655,0,0,1,discount,rural
|
||||
store_C,9,context,627.48,7.2847,0,0,2,discount,rural
|
||||
store_C,10,context,634.26,7.1536,0,0,3,discount,rural
|
||||
store_C,11,context,928.07,7.1155,1,1,4,discount,rural
|
||||
store_C,12,context,643.37,7.0211,0,0,5,discount,rural
|
||||
store_C,13,context,652.8,7.0554,0,0,6,discount,rural
|
||||
store_C,14,context,766.65,7.1746,0,0,0,discount,rural
|
||||
store_C,15,context,737.37,7.0534,0,0,1,discount,rural
|
||||
store_C,16,context,589.02,7.5911,0,0,2,discount,rural
|
||||
store_C,17,context,613.06,7.6807,0,0,3,discount,rural
|
||||
store_C,18,context,556.25,7.3936,0,0,4,discount,rural
|
||||
store_C,19,context,596.46,7.318,0,0,5,discount,rural
|
||||
store_C,20,context,632.0,7.5045,0,0,6,discount,rural
|
||||
store_C,21,context,662.1,7.875,0,0,0,discount,rural
|
||||
store_C,22,context,558.0,7.8511,0,0,1,discount,rural
|
||||
store_C,23,context,769.38,7.0435,0,1,2,discount,rural
|
||||
store_C,24,horizon,482.94,7.1815,0,0,3,discount,rural
|
||||
store_C,25,horizon,571.69,7.2367,0,0,4,discount,rural
|
||||
store_C,26,horizon,666.89,7.2494,1,0,5,discount,rural
|
||||
store_C,27,horizon,677.55,7.5712,1,0,6,discount,rural
|
||||
store_C,28,horizon,503.9,7.4163,0,0,0,discount,rural
|
||||
store_C,29,horizon,541.34,7.0493,0,0,1,discount,rural
|
||||
store_C,30,horizon,443.17,7.3736,0,0,2,discount,rural
|
||||
store_C,31,horizon,596.87,7.5238,1,0,3,discount,rural
|
||||
store_C,32,horizon,628.12,7.1017,0,0,4,discount,rural
|
||||
store_C,33,horizon,586.61,7.8335,1,0,5,discount,rural
|
||||
store_C,34,horizon,456.82,7.052,0,0,6,discount,rural
|
||||
store_C,35,horizon,782.3,7.9248,0,1,0,discount,rural
|
||||
store_id,week,split,sales,base_sales,price,price_effect,promotion,holiday,day_of_week,store_type,region
|
||||
store_A,0,context,1369.59,1012.19,11.6299,7.4,1,1,0,premium,urban
|
||||
store_A,1,context,973.53,973.04,11.9757,0.49,0,0,1,premium,urban
|
||||
store_A,2,context,1064.63,1059.16,11.7269,5.46,0,0,2,premium,urban
|
||||
store_A,3,context,1077.59,1080.99,12.1698,-3.4,0,0,3,premium,urban
|
||||
store_A,4,context,980.39,979.14,11.9372,1.26,0,0,4,premium,urban
|
||||
store_A,5,context,1011.7,1018.36,12.3327,-6.65,0,0,5,premium,urban
|
||||
store_A,6,context,1084.16,1088.16,12.2003,-4.01,0,0,6,premium,urban
|
||||
store_A,7,context,1085.98,1082.23,11.8124,3.75,0,0,0,premium,urban
|
||||
store_A,8,context,1098.52,1105.17,12.3323,-6.65,0,0,1,premium,urban
|
||||
store_A,9,context,1075.62,1081.71,12.3048,-6.1,0,0,2,premium,urban
|
||||
store_A,10,context,1312.23,1159.98,11.8875,2.25,1,0,3,premium,urban
|
||||
store_A,11,context,1368.02,1163.79,11.7883,4.23,0,1,4,premium,urban
|
||||
store_A,12,context,1138.41,1142.06,12.1825,-3.65,0,0,5,premium,urban
|
||||
store_A,13,context,1197.29,1190.09,11.6398,7.2,0,0,6,premium,urban
|
||||
store_A,14,context,1174.12,1168.12,11.6999,6.0,0,0,0,premium,urban
|
||||
store_A,15,context,1128.16,1118.3,11.5074,9.85,0,0,1,premium,urban
|
||||
store_A,16,context,1163.81,1169.55,12.2869,-5.74,0,0,2,premium,urban
|
||||
store_A,17,context,1114.18,1117.48,12.1649,-3.3,0,0,3,premium,urban
|
||||
store_A,18,context,1186.87,1190.98,12.2052,-4.1,0,0,4,premium,urban
|
||||
store_A,19,context,1147.27,1152.88,12.2807,-5.61,0,0,5,premium,urban
|
||||
store_A,20,context,1146.48,1145.66,11.9589,0.82,0,0,6,premium,urban
|
||||
store_A,21,context,1121.83,1123.21,12.0687,-1.37,0,0,0,premium,urban
|
||||
store_A,22,context,1203.28,1196.08,11.6398,7.2,0,0,1,premium,urban
|
||||
store_A,23,context,1344.9,1137.19,11.6145,7.71,0,1,2,premium,urban
|
||||
store_A,24,horizon,1118.64,1122.01,12.1684,-3.37,0,0,3,premium,urban
|
||||
store_A,25,horizon,1121.14,1120.56,11.9711,0.58,0,0,4,premium,urban
|
||||
store_A,26,horizon,1149.99,1151.29,12.0652,-1.3,0,0,5,premium,urban
|
||||
store_A,27,horizon,1284.67,1139.97,12.265,-5.3,1,0,6,premium,urban
|
||||
store_A,28,horizon,1284.67,1137.36,12.1347,-2.69,1,0,0,premium,urban
|
||||
store_A,29,horizon,1132.79,1133.86,12.0536,-1.07,0,0,1,premium,urban
|
||||
store_A,30,horizon,1197.3,1198.49,12.0592,-1.18,0,0,2,premium,urban
|
||||
store_A,31,horizon,1247.22,1093.3,11.804,3.92,1,0,3,premium,urban
|
||||
store_A,32,horizon,1095.84,1086.46,11.5308,9.38,0,0,4,premium,urban
|
||||
store_A,33,horizon,1073.83,1072.57,11.9367,1.27,0,0,5,premium,urban
|
||||
store_A,34,horizon,1134.51,1128.8,11.7146,5.71,0,0,6,premium,urban
|
||||
store_A,35,horizon,1351.15,1149.32,11.9085,1.83,0,1,0,premium,urban
|
||||
store_B,0,context,1062.53,712.0,9.9735,0.53,1,1,0,standard,suburban
|
||||
store_B,1,context,904.49,749.83,9.767,4.66,1,0,1,standard,suburban
|
||||
store_B,2,context,813.63,810.26,9.8316,3.37,0,0,2,standard,suburban
|
||||
store_B,3,context,720.11,720.53,10.0207,-0.41,0,0,3,standard,suburban
|
||||
store_B,4,context,820.78,819.55,9.9389,1.22,0,0,4,standard,suburban
|
||||
store_B,5,context,833.27,823.7,9.5216,9.57,0,0,5,standard,suburban
|
||||
store_B,6,context,795.26,801.78,10.3263,-6.53,0,0,6,standard,suburban
|
||||
store_B,7,context,770.37,778.29,10.3962,-7.92,0,0,0,standard,suburban
|
||||
store_B,8,context,855.92,848.72,9.6402,7.2,0,0,1,standard,suburban
|
||||
store_B,9,context,832.33,833.41,10.054,-1.08,0,0,2,standard,suburban
|
||||
store_B,10,context,1029.44,871.61,9.6086,7.83,1,0,3,standard,suburban
|
||||
store_B,11,context,1066.35,869.8,10.1722,-3.44,0,1,4,standard,suburban
|
||||
store_B,12,context,942.86,938.49,9.7812,4.38,0,0,5,standard,suburban
|
||||
store_B,13,context,1015.99,869.18,10.1594,-3.19,1,0,6,standard,suburban
|
||||
store_B,14,context,836.44,840.98,10.227,-4.54,0,0,0,standard,suburban
|
||||
store_B,15,context,885.72,891.1,10.2686,-5.37,0,0,1,standard,suburban
|
||||
store_B,16,context,901.45,893.6,9.6077,7.85,0,0,2,standard,suburban
|
||||
store_B,17,context,1080.63,938.95,10.416,-8.32,1,0,3,standard,suburban
|
||||
store_B,18,context,922.14,916.74,9.7302,5.4,0,0,4,standard,suburban
|
||||
store_B,19,context,904.66,895.41,9.5374,9.25,0,0,5,standard,suburban
|
||||
store_B,20,context,935.48,936.58,10.0549,-1.1,0,0,6,standard,suburban
|
||||
store_B,21,context,979.23,826.64,9.8709,2.58,1,0,0,standard,suburban
|
||||
store_B,22,context,837.49,844.09,10.3298,-6.6,0,0,1,standard,suburban
|
||||
store_B,23,context,1021.39,827.56,10.3083,-6.17,0,1,2,standard,suburban
|
||||
store_B,24,horizon,847.21,843.55,9.8171,3.66,0,0,3,standard,suburban
|
||||
store_B,25,horizon,789.27,798.33,10.4529,-9.06,0,0,4,standard,suburban
|
||||
store_B,26,horizon,877.09,872.91,9.7909,4.18,0,0,5,standard,suburban
|
||||
store_B,27,horizon,832.42,832.72,10.0151,-0.3,0,0,6,standard,suburban
|
||||
store_B,28,horizon,781.9,777.02,9.756,4.88,0,0,0,standard,suburban
|
||||
store_B,29,horizon,781.04,789.76,10.436,-8.72,0,0,1,standard,suburban
|
||||
store_B,30,horizon,844.57,837.86,9.6646,6.71,0,0,2,standard,suburban
|
||||
store_B,31,horizon,863.43,854.33,9.5449,9.1,0,0,3,standard,suburban
|
||||
store_B,32,horizon,898.12,896.82,9.9351,1.3,0,0,4,standard,suburban
|
||||
store_B,33,horizon,1070.58,930.42,10.4924,-9.85,1,0,5,standard,suburban
|
||||
store_B,34,horizon,820.4,828.24,10.3917,-7.83,0,0,6,standard,suburban
|
||||
store_B,35,horizon,965.86,770.83,10.2486,-4.97,0,1,0,standard,suburban
|
||||
store_C,0,context,709.12,501.23,7.1053,7.89,0,1,0,discount,rural
|
||||
store_C,1,context,651.44,492.78,7.0666,8.67,1,0,1,discount,rural
|
||||
store_C,2,context,659.15,511.04,7.5944,-1.89,1,0,2,discount,rural
|
||||
store_C,3,context,733.06,575.98,7.1462,7.08,1,0,3,discount,rural
|
||||
store_C,4,context,712.21,568.7,7.8247,-6.49,1,0,4,discount,rural
|
||||
store_C,5,context,615.23,611.44,7.3103,3.79,0,0,5,discount,rural
|
||||
store_C,6,context,568.99,561.87,7.1439,7.12,0,0,6,discount,rural
|
||||
store_C,7,context,541.12,549.54,7.921,-8.42,0,0,0,discount,rural
|
||||
store_C,8,context,583.57,576.88,7.1655,6.69,0,0,1,discount,rural
|
||||
store_C,9,context,607.34,603.04,7.2847,4.31,0,0,2,discount,rural
|
||||
store_C,10,context,613.79,606.86,7.1536,6.93,0,0,3,discount,rural
|
||||
store_C,11,context,919.49,561.8,7.1155,7.69,1,1,4,discount,rural
|
||||
store_C,12,context,622.61,613.04,7.0211,9.58,0,0,5,discount,rural
|
||||
store_C,13,context,630.52,621.63,7.0554,8.89,0,0,6,discount,rural
|
||||
store_C,14,context,721.62,715.12,7.1746,6.51,0,0,0,discount,rural
|
||||
store_C,15,context,699.18,690.25,7.0534,8.93,0,0,1,discount,rural
|
||||
store_C,16,context,578.85,580.67,7.5911,-1.82,0,0,2,discount,rural
|
||||
store_C,17,context,598.23,601.84,7.6807,-3.61,0,0,3,discount,rural
|
||||
store_C,18,context,554.43,552.3,7.3936,2.13,0,0,4,discount,rural
|
||||
store_C,19,context,587.39,583.75,7.318,3.64,0,0,5,discount,rural
|
||||
store_C,20,context,615.58,615.67,7.5045,-0.09,0,0,6,discount,rural
|
||||
store_C,21,context,638.68,646.18,7.875,-7.5,0,0,0,discount,rural
|
||||
store_C,22,context,555.99,563.01,7.8511,-7.02,0,0,1,discount,rural
|
||||
store_C,23,context,768.83,559.7,7.0435,9.13,0,1,2,discount,rural
|
||||
store_C,24,horizon,499.62,493.25,7.1815,6.37,0,0,3,discount,rural
|
||||
store_C,25,horizon,570.9,565.64,7.2367,5.27,0,0,4,discount,rural
|
||||
store_C,26,horizon,677.52,522.5,7.2494,5.01,1,0,5,discount,rural
|
||||
store_C,27,horizon,685.25,536.68,7.5712,-1.42,1,0,6,discount,rural
|
||||
store_C,28,horizon,517.46,515.78,7.4163,1.67,0,0,0,discount,rural
|
||||
store_C,29,horizon,549.38,540.36,7.0493,9.01,0,0,1,discount,rural
|
||||
store_C,30,horizon,470.04,467.51,7.3736,2.53,0,0,2,discount,rural
|
||||
store_C,31,horizon,622.9,473.37,7.5238,-0.48,1,0,3,discount,rural
|
||||
store_C,32,horizon,620.09,612.12,7.1017,7.97,0,0,4,discount,rural
|
||||
store_C,33,horizon,614.45,471.12,7.8335,-6.67,1,0,5,discount,rural
|
||||
store_C,34,horizon,484.25,475.29,7.052,8.96,0,0,6,discount,rural
|
||||
store_C,35,horizon,781.64,590.14,7.9248,-8.5,0,1,0,discount,rural
|
||||
|
||||
|
Reference in New Issue
Block a user