Python & AI

Sales Forecasting with Python (Beginner Project)

Beginner sales forecasting project in Python: load monthly sales with pandas, beat a seasonal baseline with Holt-Winters, check MAPE and export a forecast.

By DashCore Hub Editorial TeamUpdated 8 min read

You can build a useful sales forecast in Python with about 40 lines of code: load monthly sales with pandas, hold back the last 12 months as a test, compare a simple "same month last year" baseline with a Holt-Winters model from statsmodels, and forecast the next six months. This beginner project walks through each step and explains how to judge whether the forecast is good enough to use.

What you need before you start

  • Python 3 with pandas, statsmodels and matplotlib installed (pip install pandas statsmodels matplotlib). A Jupyter notebook is the most comfortable place to run this.
  • A CSV with two columns: month (a date such as 2025-01-01) and sales (a number). One row per month, no gaps.
  • At least two to three years of history. Seasonal models need to see each month several times to learn the pattern. With less data, stick to simpler methods.

If your data lives in daily transactions, aggregate to monthly first. In pandas that is df.resample("MS", on="order_date")["amount"].sum(), or you can do it with a pivot table in Excel and export the result.

Tip: Forecast at the level where decisions are made. If the purchasing team orders monthly by category, forecast monthly by category, not daily by product. Coarser levels are usually more stable and easier to forecast.

Step 1: Load and check the data

import pandas as pd

df = pd.read_csv("monthly_sales.csv", parse_dates=["month"])
df = df.set_index("month").asfreq("MS")   # MS = month start

print(df.shape)
print(df["sales"].isna().sum(), "missing months")
print(df.tail())

asfreq("MS") tells pandas the data is monthly. If a month is missing in your file, it appears as a blank (NaN) row, which the check below it will count. Fix gaps before modeling: find out why a month is missing rather than inventing a number.

Plot the series too (df["sales"].plot()). Look for three things: an overall trend up or down, a repeating yearly pattern, and unusual spikes such as a one-time bulk order. Spikes that will not repeat can distort the forecast, so note them and decide with the business whether to adjust them.

Step 2: Split the data and build a baseline

The most important habit in forecasting is testing on data the model has not seen. We hide the last 12 months, build forecasts using only the earlier data, and compare them with what actually happened.

test_months = 12
train = df.iloc[:-test_months]
test = df.iloc[-test_months:]

# Seasonal naive: "this month will look like the same month last year"
naive_forecast = pd.Series(
    train["sales"].iloc[-12:].to_numpy(), index=test.index
)

The seasonal naive baseline simply repeats last year's values. It sounds too simple, but it is a fair benchmark: if a fancier model cannot beat it, the fancier model is not worth using. The .to_numpy() call drops the old dates so the values line up with the test months.

Step 3: Fit a Holt-Winters model and measure error

Holt-Winters (also called triple exponential smoothing) estimates three components: the current level, the trend and the seasonal pattern. Recent months get more weight than older ones. It is a classic choice for monthly sales with a yearly pattern, and it is the same family of methods behind Excel's FORECAST.ETS function.

from statsmodels.tsa.holtwinters import ExponentialSmoothing

model = ExponentialSmoothing(
    train["sales"],
    trend="add",
    seasonal="add",
    seasonal_periods=12,
).fit()
hw_forecast = model.forecast(test_months)

def mape(actual, predicted):
    return ((actual - predicted).abs() / actual).mean() * 100

print(f"Seasonal naive MAPE: {mape(test['sales'], naive_forecast):.1f}%")
print(f"Holt-Winters MAPE:   {mape(test['sales'], hw_forecast):.1f}%")

MAPE (mean absolute percentage error) is the average size of the error as a percentage of actual sales. Lower is better, and it is easy to explain to a manager: "on average, the forecast missed by about X%." Your results depend entirely on your data, so do not compare them with numbers from other projects; compare them with your own baseline.

SettingUse "add" when…Use "mul" when…
trendSales grow by roughly the same amount each periodSales grow by roughly the same percentage (requires positive values)
seasonalThe seasonal swing stays about the same sizeThe seasonal swing grows as sales grow (requires positive values)

Try both and keep the version with the lower test error. You can also pass trend=None if there is no clear trend.

AI shortcut: Paste your MAPE results and plot description into an assistant and ask: "My Holt-Winters model has a test MAPE of [X]% and the seasonal naive has [Y]%. Suggest three reasons the model might be missing and three specific experiments to try next, with the pandas or statsmodels code for each."

Step 4: Forecast the future and share it

Once you are happy with the test results, refit the model on all the data (so it learns from the most recent months) and forecast forward.

import matplotlib.pyplot as plt

final_model = ExponentialSmoothing(
    df["sales"], trend="add", seasonal="add", seasonal_periods=12
).fit()
future = final_model.forecast(6)

ax = df["sales"].plot(label="Actual", figsize=(10, 4))
future.plot(ax=ax, label="Forecast", linestyle="--")
ax.set_title("Monthly sales and 6-month forecast")
ax.legend()
plt.tight_layout()
plt.show()

future.round(2).rename("forecast").rename_axis("month").to_csv("sales_forecast.csv")

The CSV can go straight into Excel or Power BI, where you can show actuals and forecast on the same line chart. Present the forecast as a planning estimate, not a promise, and refresh it every month as new actuals arrive.

Common beginner mistakes

  • Testing on training data. A model always looks great on data it has already seen. Always keep a holdout period.
  • Ignoring one-off events. Promotions, stockouts and system outages need to be noted, and sometimes adjusted, before modeling.
  • Forecasting too far ahead. Uncertainty grows with every month. Six to twelve months is a reasonable horizon for this kind of model.
  • Skipping the baseline. Without a benchmark, you cannot tell whether the model adds value.

Step 5: Forecast every category in one loop

A single total forecast is a good start, but most teams plan by category, region or store. Once the method works on one series, you can repeat it for each group with a short loop. This example assumes a file with three columns: month, category and sales, with every category having the same full history.

import pandas as pd
from statsmodels.tsa.holtwinters import ExponentialSmoothing

sales = pd.read_csv("sales_by_category.csv", parse_dates=["month"])
results = []

for category, group in sales.groupby("category"):
    series = group.set_index("month")["sales"].asfreq("MS")
    fit = ExponentialSmoothing(
        series, trend="add", seasonal="add", seasonal_periods=12
    ).fit()
    fc = fit.forecast(6).rename("forecast").rename_axis("month").reset_index()
    fc["category"] = category
    results.append(fc)

all_forecasts = pd.concat(results, ignore_index=True)
all_forecasts.to_csv("forecast_by_category.csv", index=False)
print(all_forecasts.head())

The result is a long table with one row per category and month, which is exactly the shape Power BI and Excel pivot tables like. Before you trust it, repeat the holdout test from Step 3 for each category. Some categories will forecast well; others, especially small or new ones, may not beat the seasonal naive baseline. For those, it is perfectly reasonable to use the baseline or a simple average instead. A forecast process that mixes methods by category, chosen by test error, is more honest than forcing one model onto everything.

Keep an eye on categories with very few sales or many zero months. Exponential smoothing works best on series with a stable level, so intermittent demand often needs different methods or a higher level of aggregation.

Next steps

Run this project on your own sales data, then try forecasting by region or product category. If Python is new to you, start with Python for data analysis and our pandas tutorial for Excel users. To compare with a no-code approach, read forecasting in Excel with FORECAST.ETS, and when you are ready for more advanced models, see machine learning basics for BI analysts.

Watch: video lessons

Time Series Forecasting with XGBoost - Predict Store Revenue Using Python and Machine Learningby Onur Baltaci · Watch on YouTube ↗

Frequently asked questions

How much data do I need to forecast sales in Python?

For a seasonal monthly model like Holt-Winters, aim for at least two to three full years. With less history, a simple trend or moving average is safer.

What is a good MAPE for a sales forecast?

There is no universal number; it depends on how volatile your sales are. The practical test is whether your model beats a simple baseline such as repeating last year's values.

Is Holt-Winters the same as Excel's FORECAST.ETS?

They belong to the same family of exponential smoothing methods, so results are often similar but not identical. Python gives you more control over settings and testing.

Can I use this forecast in Power BI?

Yes. Export the forecast to CSV as shown, or run the script in Power BI's Python integration, and combine it with your actuals in a line chart.

Learning with the Academy?
Mark this lesson complete to track your rank progress.
Take the quiz