Free data template

Simple forecast

A trend plus weekly pattern fitted to daily sales, the next 4 weeks forecast and a check against the last weeks.

It opens with this template loaded and runs in your browser. You don't need an account to use it; a free account saves your work and exports it.

The job it does

For planning stock, staff or budget: a transparent baseline forecast before anyone reaches for a complex model. It comes with its own checks, so after you change it you can see at a glance that it still does the job.

How to use it with your own data

Load your daily numbers as a CSV called daily (columns date, value).

What's inside

This is the template exactly as the Data Lab opens it, read-only. Open it to run it, change it and export it.

  • 3 notebook cells
  • Data: the retail sample database (Mangrove Market: stores, customers, orders, payments)
The brief: Simple forecast

Planning stock, staff or a budget needs a forecast, and a transparent baseline comes first: a straight-line trend times a factor for each day of the week. The notebook checks itself on the last 4 weeks it was not fitted on (the average daily error, mape_pct), then forecasts the next 4 weeks with a chart.

Your own data: load a CSV file called daily with the columns date and value, one row per day, and Run.

Done when
  • The notebook runs top to bottom without errors
  • forecast has 28 rows
  • forecast.forecast has no empty values
  • A line chart plots forecast
Cell 1 · SQL into sales_daily
-- Revenue per day from June to November 2025 (delivered orders)
select o.ordered_at::date as date,
       (sum(i.qty * i.unit_price_cents) / 100.0)::float8 as value
from orders o
join order_items i on i.order_id = o.id
where o.status = 'delivered'
  and o.ordered_at >= '2025-06-01' and o.ordered_at < '2025-12-01'
group by 1
order by 1
Cell 2 · Python
import numpy as np
# Your own data: load a CSV file called "daily" (columns date, value) and it is used instead.
src = (daily if "daily" in globals() else sales_daily).copy()
src.columns = ["date", "value"]
src["date"] = pd.to_datetime(src["date"])
src = src.sort_values("date").reset_index(drop=True)
src["t"] = np.arange(len(src))
src["dow"] = src["date"].dt.dayofweek

def fit(df):
    """A straight-line trend, times a factor per day of the week."""
    slope, intercept = np.polyfit(df["t"], df["value"], 1)
    season = (df["value"] / (intercept + slope * df["t"])).groupby(df["dow"]).mean()
    return slope, intercept, season

def predict(model, t, dow):
    slope, intercept, season = model
    return (intercept + slope * np.asarray(t)) * season.reindex(np.asarray(dow)).to_numpy()

# check it first: fit on all but the last 4 weeks, forecast them, and measure the error
train, test = src.iloc[:-28], src.iloc[-28:]
check = predict(fit(train), test["t"], test["dow"])
mape_pct = round(float((abs(test["value"] - check) / test["value"]).mean() * 100), 2)
print(f"On the last 4 weeks the forecast was off by {mape_pct}% a day on average.")
Cell 3 · Python
# the next 4 weeks, from a model fitted on all the data
model = fit(src)
days = pd.date_range(src["date"].iloc[-1] + pd.Timedelta(days=1), periods=28)
t = np.arange(len(src), len(src) + 28)
forecast = pd.DataFrame({"date": days.strftime("%Y-%m-%d"), "forecast": predict(model, t, days.dayofweek).round(2)})

recent = src.tail(56)
plot = pd.concat([
    pd.DataFrame({"date": recent["date"].dt.strftime("%Y-%m-%d"), "actual": recent["value"].round(2), "forecast": np.nan}),
    forecast.assign(actual=np.nan),
], ignore_index=True)
chart(plot, x="date", y=["actual", "forecast"], kind="line", title="Revenue per day: the last 8 weeks and the next 4")
forecast

The lesson behind it

Learn why it works, with a podcast and a quiz for each lesson.

  • KPI dashboard notebook

    Weekly revenue, orders, average basket and active customers, with a chart per KPI.

  • A/B test readout

    Conversion per variant, the lift, a confidence interval and a plain-language verdict.

All data templates · Every template