AI Demand Forecasting: How Retailers Are Cutting Overstock by 30% with ML
Building machine learning demand forecasting systems that outperform traditional statistical methods
AI Demand Forecasting: How Retailers Are Cutting Overstock by 30% with ML
Building machine learning demand forecasting systems that outperform traditional statistical methods
Learn how major retailers and manufacturers are using machine learning to forecast demand with greater accuracy than traditional methods — reducing stockouts, cutting excess inventory, and optimizing replenishment cycles.
AI Demand Forecasting: Cutting Inventory Costs with Machine Learning
Inventory is one of the largest working capital costs in retail and manufacturing. Getting demand wrong means either too much (carrying costs, markdowns) or too little (stockouts, lost sales). AI is dramatically improving forecast accuracy.
The Cost of Bad Forecasting
A typical retailer with $500M revenue:
Improving forecast accuracy by even 10% can unlock millions in working capital.
Why Traditional Methods Fall Short
Simple moving average: Assumes future = average of past. Misses trends and seasonality. Exponential smoothing: Better for trends, but can't incorporate external signals. ARIMA: Statistically sophisticated but requires manual tuning per SKU, doesn't scale to 100K+ products.
ML approaches learn patterns across all products simultaneously and can incorporate:
Building an ML Demand Forecasting System
python
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import mean_absolute_percentage_error
import warnings
warnings.filterwarnings('ignore')class DemandForecastingModel:
"""
Multi-step demand forecasting using gradient boosted trees.
Handles multiple products, seasonal patterns, and external signals.
"""
def __init__(self, forecast_horizon_days: int = 28):
self.horizon = forecast_horizon_days
self.model = GradientBoostingRegressor(
n_estimators=300,
max_depth=5,
learning_rate=0.05,
subsample=0.8,
random_state=42
)
self.encoders = {}
def create_lag_features(self, df: pd.DataFrame,
target_col: str = 'sales_units') -> pd.DataFrame:
"""
Create lag and rolling window features.
The core of time series ML: using past values to predict future.
"""
features = df.copy()
# Lag features (past sales values)
for lag in [1, 2, 3, 7, 14, 21, 28]:
features[f'lag_{lag}d'] = features.groupby('sku_id')[target_col].shift(lag)
# Rolling statistics (smoothed historical demand)
for window in [7, 14, 28, 90]:
roll = features.groupby('sku_id')[target_col].shift(1).rolling(window)
features[f'rolling_mean_{window}d'] = roll.mean().reset_index(0, drop=True)
features[f'rolling_std_{window}d'] = roll.std().reset_index(0, drop=True)
features[f'rolling_max_{window}d'] = roll.max().reset_index(0, drop=True)
# Year-over-year (same week last year)
features['lag_364d'] = features.groupby('sku_id')[target_col].shift(364)
features['lag_371d'] = features.groupby('sku_id')[target_col].shift(371)
features['yoy_average'] = (features['lag_364d'] + features['lag_371d']) / 2
# Trend feature: is demand growing or shrinking?
features['demand_trend'] = (
features['rolling_mean_14d'] - features['rolling_mean_28d']
) / (features['rolling_mean_28d'] + 1)
return features
def create_calendar_features(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Extract calendar-based features.
Captures weekly, monthly, and annual seasonality.
"""
features = df.copy()
date_col = pd.to_datetime(features['date'])
# Basic calendar features
features['day_of_week'] = date_col.dt.dayofweek # 0=Monday
features['day_of_month'] = date_col.dt.day
features['week_of_year'] = date_col.dt.isocalendar().week.astype(int)
features['month'] = date_col.dt.month
features['quarter'] = date_col.dt.quarter
features['year'] = date_col.dt.year
# Cyclical encoding (Monday=0 and Sunday=6 are adjacent)
features['dow_sin'] = np.sin(2 * np.pi * features['day_of_week'] / 7)
features['dow_cos'] = np.cos(2 * np.pi * features['day_of_week'] / 7)
features['month_sin'] = np.sin(2 * np.pi * features['month'] / 12)
features['month_cos'] = np.cos(2 * np.pi * features['month'] / 12)
# Key shopping periods
features['is_weekend'] = (features['day_of_week'] >= 5).astype(int)
features['is_month_start'] = (features['day_of_month'] <= 5).astype(int)
features['is_month_end'] = (features['day_of_month'] >= 26).astype(int)
# Holiday indicators (add your own)
features['days_to_holiday'] = self._days_to_next_holiday(date_col)
features['days_after_holiday'] = self._days_after_last_holiday(date_col)
return features
def create_product_features(self, df: pd.DataFrame) -> pd.DataFrame:
"""Features specific to each product."""
features = df.copy()
# Encode categorical variables
for col in ['sku_id', 'category', 'subcategory', 'brand']:
if col in features.columns:
if col not in self.encoders:
self.encoders[col] = LabelEncoder()
features[f'{col}_encoded'] = self.encoders[col].fit_transform(
features[col].astype(str)
)
else:
features[f'{col}_encoded'] = self.encoders[col].transform(
features[col].astype(str)
)
# Price features
if 'price' in features.columns:
features['price_log'] = np.log1p(features['price'])
features['price_vs_category_avg'] = (
features['price'] /
features.groupby('category')['price'].transform('mean')
)
# Promotion features
if 'is_on_promotion' in features.columns:
features['promotion_lift'] = (
features.groupby('sku_id')['is_on_promotion'].shift(1) *
features.groupby('sku_id')['sales_units'].transform('std')
).fillna(0)
return features
def train(self, historical_data: pd.DataFrame) -> dict:
"""Train the forecasting model."""
# Feature engineering pipeline
df = self.create_lag_features(historical_data)
df = self.create_calendar_features(df)
df = self.create_product_features(df)
# Drop rows with NaN from lag features
df = df.dropna()
# Define features to use
feature_cols = [c for c in df.columns if c not in
['date', 'sku_id', 'sales_units', 'category',
'subcategory', 'brand']]
self.feature_columns = feature_cols
X = df[feature_cols].fillna(0)
y = df['sales_units']
# Train/test split (time-based)
split_date = df['date'].quantile(0.8)
train_mask = df['date'] < split_date
X_train, X_test = X[train_mask], X[~train_mask]
y_train, y_test = y[train_mask], y[~train_mask]
self.model.fit(X_train, y_train)
# Evaluate
y_pred = self.model.predict(X_test)
y_pred = np.maximum(y_pred, 0) # No negative forecasts
mape = mean_absolute_percentage_error(y_test + 1, y_pred + 1)
print(f"Model MAPE: {mape:.1%}")
print(f"Training on {len(X_train):,} observations, testing on {len(X_test):,}")
return {'mape': mape, 'n_train': len(X_train), 'n_test': len(X_test)}
def forecast(self, recent_data: pd.DataFrame,
forecast_date: str,
sku_ids: list[str] = None) -> pd.DataFrame:
"""
Generate {horizon}-day demand forecast.
Returns DataFrame with sku_id, date, forecasted_demand.
"""
if sku_ids:
data = recent_data[recent_data['sku_id'].isin(sku_ids)]
else:
data = recent_data
# Build feature matrix for forecast date
forecast_df = pd.DataFrame()
for sku_id in data['sku_id'].unique():
sku_data = data[data['sku_id'] == sku_id].copy()
# Create future dates
future_dates = pd.date_range(
start=forecast_date,
periods=self.horizon,
freq='D'
)
sku_future = pd.DataFrame({
'date': future_dates,
'sku_id': sku_id,
'sales_units': np.nan # Unknown future values
})
# Combine with history for feature creation
combined = pd.concat([sku_data.tail(400), sku_future], ignore_index=True)
combined = self.create_lag_features(combined)
combined = self.create_calendar_features(combined)
combined = self.create_product_features(combined)
# Get only future rows
future_rows = combined[combined['date'].isin(future_dates)]
if len(future_rows) > 0:
X_future = future_rows[self.feature_columns].fillna(0)
predictions = self.model.predict(X_future)
sku_forecasts = pd.DataFrame({
'sku_id': sku_id,
'date': future_dates[:len(predictions)],
'forecasted_demand': np.maximum(predictions, 0).round(0).astype(int)
})
forecast_df = pd.concat([forecast_df, sku_forecasts], ignore_index=True)
return forecast_df
def _days_to_next_holiday(self, dates: pd.Series) -> pd.Series:
"""Calculate days to next major holiday."""
# Simplified - would have full holiday calendar in production
return pd.Series([0] * len(dates))
def _days_after_last_holiday(self, dates: pd.Series) -> pd.Series:
return pd.Series([0] * len(dates))
Calculate reorder recommendations
def generate_reorder_plan(forecasts: pd.DataFrame,
inventory_levels: pd.DataFrame,
supplier_lead_times: dict) -> pd.DataFrame:
"""
Generate purchase order recommendations based on forecasts.
"""
reorder_plan = []
for sku_id in forecasts['sku_id'].unique():
sku_forecast = forecasts[forecasts['sku_id'] == sku_id]
current_stock = inventory_levels[
inventory_levels['sku_id'] == sku_id
]['current_stock'].values[0] if len(inventory_levels[inventory_levels['sku_id'] == sku_id]) > 0 else 0
lead_time = supplier_lead_times.get(sku_id, 14) # Default 14 days
safety_stock_days = 7 # Hold 7 days safety stock
# Demand during lead time + safety stock
demand_window = sku_forecast['forecasted_demand'].sum()
reorder_point = (demand_window / len(sku_forecast)) * (lead_time + safety_stock_days)
if current_stock < reorder_point:
# How much to order?
# Order enough for lead time + 30 days of demand
order_quantity = max(0, demand_window - current_stock + safety_stock_days)
reorder_plan.append({
'sku_id': sku_id,
'current_stock': current_stock,
'reorder_point': round(reorder_point, 0),
'recommended_order_qty': round(order_quantity, 0),
'days_of_stock_remaining': current_stock / max(demand_window / 28, 1),
'urgency': 'CRITICAL' if current_stock < reorder_point * 0.5 else 'NORMAL'
})
return pd.DataFrame(reorder_plan).sort_values('urgency', ascending=False)
Results Companies Are Achieving
Walmart's AI demand forecasting:
Target's supply chain AI:
Smaller retailer (10,000 SKUs, $200M revenue):
The ROI calculation is compelling: improving MAPE by 10 percentage points typically unlocks 15-25% inventory reduction.
相关教程
How companies are using ML and NLP to monitor supplier health and predict supply disruptions
How warehouses are using AI to reduce picking time by 35% without full robotics investment
Building intelligent routing systems that balance time, cost, and capacity constraints