Anjali's family runs a sweet shop in Karol Bagh. They threw away ₹40,000 of unsold mithai every month — and ran out of besan ladoo on Diwali two years running. Anjali built a forecasting system using Prophet (with Indian holidays) and N-BEATS.
Eight months later: waste down to ₹3,000/month, zero stockouts on festivals, and her father uses the dashboard daily. Total saving: ~₹3 lakhs/year.
Every time series can be decomposed into three signals:
- Trend: The long-term direction. Anjali's shop grew 8% YoY.
- Seasonality: Repeating patterns. Weekly (weekend spike), yearly (Diwali, Holi, Karva Chauth), and even hourly.
- Residual: What's left after removing trend + seasonality. Often modelled as noise or with autoregression.
| Method | When to Use | Pros | Cons |
|---|---|---|---|
| ARIMA | Short series, simple seasonality | Interpretable, classic | Manual order tuning |
| Prophet | Daily data with holidays | Auto-tunes, handles holidays | Limited features |
| N-BEATS | Long series, complex patterns | SOTA accuracy, no feature engineering | Less interpretable |
| TFT (Temporal Fusion Transformer) | Multi-variate, multi-horizon | Handles many features + attention explanations | Compute heavy |
| Chronos / TimeGPT (foundation models) | Zero-shot forecasting | No training needed | Black box, costly API |
!pip install -q prophet
import pandas as pd
from prophet import Prophet
from prophet.make_holidays import make_holidays_df
# Load 3 years of daily ladoo sales
df = pd.read_csv("ladoo_sales.csv") # columns: ds, y
df["ds"] = pd.to_datetime(df["ds"])
# Indian holidays — built-in support
holidays = make_holidays_df(year_list=[2023, 2024, 2025, 2026], country="IN")
# Add custom mithai-relevant events
custom = pd.DataFrame({
"holiday": "raksha_bandhan_week",
"ds": pd.to_datetime(["2024-08-19", "2025-08-09", "2026-08-28"]),
"lower_window": -3, "upper_window": 1, # spike starts 3 days before
})
holidays = pd.concat([holidays, custom])
m = Prophet(
holidays=holidays,
yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=False,
seasonality_mode="multiplicative", # spikes scale with trend
changepoint_prior_scale=0.05, # default; raise to fit faster trend changes
)
m.fit(df)
future = m.make_future_dataframe(periods=60) # 60-day forecast
forecast = m.predict(future)
# yhat = point estimate, yhat_lower / yhat_upper = 80% confidence interval
print(forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]].tail(10))
m.plot(forecast)
m.plot_components(forecast) # trend / weekly / yearly / holidays
!pip install -q neuralforecast
from neuralforecast import NeuralForecast
from neuralforecast.models import NBEATS
# Same dataframe but renamed to neuralforecast convention
df_nf = df.rename(columns={"ds": "ds", "y": "y"})
df_nf["unique_id"] = "ladoo" # one series — could be many if multi-shop
model = NBEATS(
h=60, # forecast horizon = 60 days
input_size=180, # use last 180 days as input
max_steps=2000,
stack_types=["trend", "seasonality"],
n_blocks=[3, 3],
mlp_units=[[256, 256], [256, 256]],
learning_rate=1e-3,
early_stop_patience_steps=20,
)
nf = NeuralForecast(models=[model], freq="D")
nf.fit(df_nf)
predictions = nf.predict()
print(predictions.head())
N-BEATS often beats Prophet on long, noisy series because it learns trend and seasonality basis functions directly from data rather than assuming a fixed parametric form.
Right metrics:
- MAPE (Mean Absolute Percentage Error): Most intuitive. "Forecast was off by 12% on average."
- MASE (Mean Absolute Scaled Error): Compares to a naive baseline (yesterday=today). MASE < 1 means you beat the baseline.
- Pinball loss (quantile loss): If you forecast a confidence interval, this scores it.
Drift handling: Retail patterns shift (new metro line opens, competitor closes). Anjali retrains weekly and runs a drift detector — if MAPE jumps 30% above baseline for 3 consecutive days, alert.
From forecast to inventory: Raw forecasts mislead. Anjali uses the upper 80% confidence interval for perishable items (better to slightly over-stock than stockout) and the lower 60% for non-perishable packaging (cash-flow optimisation). The forecast is one input — final order quantity also factors in: shelf life, safety stock, supplier lead time, cash on hand.