You have daily sales data and need to know what happens next. How many units will you sell next Tuesday? Is demand trending up or down? Which day of the week consistently outperforms? This module takes your daily sales history, decomposes it into trend, seasonality, and noise, then generates a 30-day forecast with confidence intervals, all from a single CSV upload.
Worked Example: Day of Week and Trend
Question: Do sales change by day of the week? Does a model that uses both a time trend and day of week do better than one that uses day of week alone?
Inputs: The script makes made-up daily sales (seed 42) with a slow rise, day-of-week shifts, and random noise. It records 180 days. Day 0 is called Monday by assumption, since the script has no calendar. The first 150 days fit the models. The last 30 test them.
Method: Fit a straight line and day-of-week shifts with least squares. Compare the mean absolute error (MAE), the mean size of the misses, with a simple rule: use the past average for each day of the week. Then refit the line-plus-day model on all 180 days and make single-value forecasts for the next seven days.
import numpy as np
SEED = 42
rng = np.random.default_rng(SEED)
n = 180
t = np.arange(n)
# day 0 is called Monday
weekday_effects = np.array([0, -5, -2, 0, 2, 8, 20], dtype=float)
weekdays = t % 7
sales = (100 + 0.15 * t + weekday_effects[weekdays]
+ rng.normal(0, 5, n))
train_n = 150
train_t, test_t = t[:train_n], t[train_n:]
train_y, test_y = sales[:train_n], sales[train_n:]
def design(rows):
days = [(rows % 7 == d).astype(float) for d in range(1, 7)]
return np.column_stack([np.ones(len(rows)), rows, *days])
beta = np.linalg.lstsq(design(train_t), train_y, rcond=None)[0]
pred_trend_weekday = design(test_t) @ beta
weekday_means = np.array(
[train_y[train_t % 7 == d].mean() for d in range(7)])
pred_weekday = weekday_means[test_t % 7]
mae = lambda actual, pred: np.mean(np.abs(actual - pred))
beta_all = np.linalg.lstsq(design(t), sales, rcond=None)[0]
future_t = np.arange(n, n + 7)
future_prediction = design(future_t) @ beta_all
print(f"Simulated data, seed {SEED}; day 0 is labelled Monday")
print(f"Daily observations: {n}; training: {train_n}; "
f"held-out test: {len(test_t)}")
print("Built in: trend +0.15 units per day; "
"Saturday is 13 units above Tuesday")
print(f"Fitted trend: {beta[1]:.3f} units per day")
print("Held-out MAE (trend + weekday): "
f"{mae(test_y, pred_trend_weekday):.2f} units")
print("Held-out MAE (weekday averages): "
f"{mae(test_y, pred_weekday):.2f} units")
print("Next 7 daily point forecasts (units),")
print("refit on all 180 days: "
+ ", ".join(f"{x:.1f}" for x in future_prediction))
print("Saturday minus Tuesday, fitted weekday terms: "
f"{beta_all[6] - beta_all[2]:.1f} units")
Printed output:
Simulated data, seed 42; day 0 is labelled Monday
Daily observations: 180; training: 150; held-out test: 30
Built in: trend +0.15 units per day; Saturday is 13 units above Tuesday
Fitted trend: 0.144 units per day
Held-out MAE (trend + weekday): 3.82 units
Held-out MAE (weekday averages): 13.34 units
Next 7 daily point forecasts (units),
refit on all 180 days: 134.2, 147.5, 126.7, 122.2, 124.3, 127.6, 128.2
Saturday minus Tuesday, fitted weekday terms: 12.5 units
Result: On the last 30 days, the line-plus-day model misses by 3.82 units on average. The weekday-only rule misses by 13.34. The data were made with a rising trend of 0.15 units per day, and the weekday-only rule has no trend term, so it had to lose: the test days sit about 90 days after the training average, which is about 13.5 units of trend. The fit recovers the trend (0.144 against 0.15 built in). In the fitted weekday terms, Saturday sits about 12.5 units above Tuesday; a gap of 13 was built in. The seven forecasts start on a Saturday in this labelling and come from the model refit on all 180 days.
Limit: These made-up sales have a simple pattern by design, so both findings are built in. They do not show how a model will work for a real store. The forecasts also lack a range of likely values.
What Is Retail Demand Forecasting?
Demand forecasting uses historical sales patterns to predict future demand. The core insight is that retail sales are not random. They follow patterns. Weekends typically differ from weekdays. Holidays create spikes. Seasonal products follow predictable arcs. A good forecasting model separates these recurring patterns from the underlying trend and the random noise, then projects forward.
This module uses time series decomposition and multiple forecasting models to analyze your daily sales data. STL decomposition (Seasonal and Trend decomposition using Loess) breaks your sales into three components: the long-term trend (is demand growing or shrinking?), the seasonal pattern (which days or months consistently over- or under-perform?), and the remainder (random variation that no model can predict). Understanding these components is valuable even before you look at the forecast itself.
For example, a store might find a strong weekly pattern, such as busier Saturdays than Tuesdays (the worked example above builds in a gap like this). That information can inform staffing decisions, even without a forward-looking forecast. The forecast then takes it a step further, projecting ahead with confidence intervals so the operations team can plan inventory and labor with a stated range of uncertainty.
The module compares multiple forecasting models, including exponential smoothing and ARIMA-family models, and reports accuracy metrics for each. This model comparison ensures you are not stuck with a single approach that might not fit your data. The report shows which model performed best on your historical data using a train/test split, so you can see how each model did on days it was not fitted to.
When to Use Retail Demand Forecasting
Use this module whenever you have daily sales data and need to plan for the future. The most common use cases fall into three categories.
Inventory planning: Order too much and you tie up capital in unsold stock. Order too little and you lose sales. A demand forecast with confidence intervals gives you a range. A 95% range shows how wide the model says the forecast may be. It does not set a stock level or promise that stock will meet demand. Pick a service goal, then decide how you turn the sales forecast into a stock target for reorder points, safety stock, and purchase order quantities.
Staffing and operations: Retail labor is often the largest controllable cost. If your own sales show that Saturdays are busier than Tuesdays (a pattern the weekly seasonality analysis can reveal), check the size of the gap in your data before you schedule around it. The forecast extends this to specific upcoming dates, if next Saturday is also near a holiday, demand may spike further.
Financial planning: Revenue forecasts feed budget planning, cash flow projections, and investor reporting. The confidence intervals let you present best-case and worst-case scenarios backed by statistical rigor rather than gut feel. A CFO who can say "we forecast $420K in sales next month with a 95% confidence interval of $380K to $460K" is far more credible than one who says "I think we will do about $420K."
This module works for any domain with daily time series data, not just retail. Restaurant covers, website visits, app downloads, support ticket volumes, energy consumption, or any other metric measured daily can be analyzed with the same tool. The "retail demand" framing simply reflects the most common use case.
What Data Do You Need?
You need a CSV with at least two columns:
Required: date_col: a date column in any standard format (YYYY-MM-DD, MM/DD/YYYY, etc.). Each row should represent one day. value: a numeric column containing the measurement you want to forecast (sales units, revenue, visitor count, etc.).
Optional: group_id: a grouping variable like store ID, location, or channel. If provided, the module can compare performance across groups (e.g., Store A vs. Store B) and highlight which locations are trending differently. item_id: a product or item identifier. If provided, the module can rank items by demand and identify which products drive the most volume.
You need enough historical data for the model to detect patterns. Use enough past sales to spot the pattern you want to study, and be careful with results from a short run of data. A longer history makes monthly and yearly patterns easier to see. A 7-day (weekly) seasonal period fits the most common retail pattern, and you can change it to match your business cycle.
You can set the forecast horizon and the confidence level. A shorter horizon usually produces tighter confidence intervals for operational planning. A longer horizon is useful for strategic planning but will naturally have wider uncertainty bands.
How to Read the Report
The report opens with the Analysis Overview and Data Preprocessing slides, showing dataset dimensions, date range, and any data quality steps applied (handling of missing days, aggregate computations, train/test split).
The Historical Trends slide plots your raw daily data with 7-day and 30-day moving averages overlaid. The 7-day moving average smooths out day-to-day noise to reveal the weekly rhythm. The 30-day moving average shows the broader trend. If these two lines are diverging, it signals a recent shift in momentum that the longer average has not yet caught up to.
The STL Decomposition slide separates your time series into three layers: the observed data, the trend component, the seasonal component, and the remainder. The trend shows the underlying trajectory stripped of seasonality. The seasonal component shows the repeating pattern, in daily retail data, this is typically a 7-day cycle. The remainder is what is left after trend and seasonality are removed. Large spikes in the remainder indicate unusual events (promotions, weather, outages) that broke the normal pattern.
The Weekly Seasonality slide shows box plots of sales by day of week. This immediately reveals which days are strongest and weakest. Overlapping boxes mean the difference is not pronounced; well-separated boxes mean the day-of-week effect is substantial. The Monthly Patterns slide extends this to year-over-year monthly comparisons: useful for spotting seasonal trends like holiday ramps or summer dips.
The ACF/PACF Diagnostics slide shows autocorrelation and partial autocorrelation plots. These technical diagnostics confirm whether the seasonal period is correctly detected (you will see spikes at lag 7 for weekly data) and inform model selection. Spikes that decay slowly suggest a trend; spikes at seasonal lags suggest periodicity.
The Forecast slide shows the 30-day (or custom horizon) projection with confidence bands. The blue line is the point forecast; the shaded region is the 95% confidence interval. Narrow bands mean the model is confident; wide bands mean uncertainty is high. The Model Comparison slide shows accuracy metrics (RMSE, MAE, MAPE) for each model tested, so you can see which one earned the right to generate the final forecast.
If you provided a group_id column, the Store Comparison and Demand Heatmap slides break down performance by group, revealing which locations are growing, declining, or exhibiting unusual patterns.
When to Use Something Else
If your data is not daily (for example, monthly revenue over several years) use the ARIMA module, which handles various time frequencies and provides more granular model diagnostics including stationarity tests and differencing.
If you want a simpler trend analysis without the full forecasting machinery, the Simple Trend module provides linear and polynomial trend fitting with change-point detection.
If your forecasting needs are complex (multiple external regressors, holiday effects, or multiple seasonalities (weekly + yearly)) consider the Prophet module, which is designed for business time series with these characteristics.
If you are comparing a metric across groups rather than forecasting it forward, use ANOVA to test whether the group means are statistically different, or Kruskal-Wallis if the data is skewed.
The R Code Behind the Analysis
Every report names its method and includes the R code that produced it, so you can reproduce, audit and cite it. The method code is fixed, not rewritten for each run, so the same data gives the same analysis every time.
The analysis uses stl() for Seasonal and Trend decomposition using Loess, forecast::auto.arima() and forecast::ets() for model fitting and comparison, and forecast() for generating predictions with confidence intervals. Weekly seasonality uses day-of-week aggregation with box plots via base R. ACF and PACF diagnostics use acf() and pacf(). Moving averages use zoo::rollmean(). Every step is visible in the code tab of your report, so you or a statistician can verify exactly what was done.
Your turn
Bring your own data and the question you actually need answered.
MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai