Author: Mikey Tabak, PhD
Company: Quantitative Science Consulting, LLC
Date: 8 November 2024
Website: www.QSC.earth
Efficient inventory management is crucial for any business that deals with physical products, from retail to manufacturing. The balance between too much and too little inventory is delicate: stockouts can halt production or disappoint customers, while excess stock ties up capital and adds holding costs. In this notebook, we’re developing a predictive approach to inventory management using SARIMA (Seasonal Autoregressive Integrated Moving Average) forecasting. This model helps anticipate future demand, enabling smarter, data-driven inventory decisions. By adjusting factors such as holding costs, shortage costs, and lead time, our model can help businesses decide the optimal times and quantities for reordering materials, ensuring that inventory levels are optimized for both cost efficiency and demand readiness.
This approach provides a simplified view of a complex system, illustrating a foundational technique. In practice, we incorporate far more data and layers of complexity into the models to capture the nuanced dynamics of inventory and production management more accurately.
# First load libraries
from demo.timeseries_utils import InventoryForecastEnv, evaluate_costs_over_parameter_range
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
env = InventoryForecastEnv(
shelf_life=12, # Shelf life of raw materials (in days). Impacts the timing of orders to prevent waste.
order_lead_time=7, # Lead time (in days) for receiving raw materials after placing an order.
holding_cost=10, # Cost (in USD) to store one unit of inventory per day.
shortage_cost=15, # Penalty cost (in USD) per unit of unmet demand.
seasonality_factor=5, # Scale factor to adjust the level of fluctuation in demand.
forecast_horizon=50, # Number of days into the future for which demand is forecasted.
initial_inventory=50, # Initial amount of raw materials available at the start of the forecast.
safety_stock_factor=2 # Multiplier to adjust safety stock levels based on shortage costs.
# It is a multiplier that adjusts the amount of extra inventory kept on hand to mitigate
# the risk of stockouts
)
The SARIMA model used here is designed to account for both trends and seasonality in demand patterns. For instance, in retail, demand often spikes during the holiday season, while an agricultural business might see increases in demand during harvest periods. SARIMA captures these cycles, allowing us to forecast future demand in alignment with predictable patterns. For this example, we use simplified parameters to showcase the SARIMA model’s capabilities in anticipating upcoming demand surges or declines, helping businesses better prepare for those fluctuations.
env.fit_sarima()
demand_forecast = env.forecast_demand()
# Calculate reorder points and costs
reorder_info = env.reorder_points(demand_forecast)
# Sum up holding and shortage costs
total_holding_cost = reorder_info['holding_cost'].sum()
total_shortage_cost = reorder_info['shortage_cost'].sum()
Forecasting demand allows businesses to stay ahead of fluctuations, ensuring that they’re neither overstocked nor understocked. Reorder points are calculated based on these forecasts, allowing companies to place orders with enough lead time to avoid the cost of last-minute orders or shortages. In practice, this means companies can avoid costly disruptions by restocking in advance of predicted spikes in demand—like retail stores increasing inventory ahead of the holiday season or manufacturers preparing for quarterly production cycles.
reorder_info.head()
forecast_demand | inventory | reorder_quantity | holding_cost | shortage_cost | |
---|---|---|---|---|---|
2024-12-31 | 64.918362 | 50.000000 | 92.820396 | 779.020343 | 0.000000 |
2025-01-01 | 47.758027 | 77.902034 | 27.165625 | 301.440074 | 0.000000 |
2025-01-02 | 61.378260 | 30.144007 | 104.888166 | 0.000000 | 468.513796 |
2025-01-03 | 58.909707 | 0.000000 | 129.601356 | 0.000000 | 883.645607 |
2025-01-04 | 62.376191 | 0.000000 | 137.227621 | 0.000000 | 935.642872 |
print(f"Total cost of holding inventory: ${total_holding_cost:,.2f}")
print(f"Total cost of running out of inventory (shortage cost): ${total_shortage_cost:,.2f}")
Total cost of holding inventory: $8,501.85 Total cost of running out of inventory (shortage cost): $28,557.48
In the real world, demand, costs, and lead times aren’t static—they can change due to market conditions, supply chain disruptions, or shifts in customer behavior. By simulating different parameter settings, we can better understand how various scenarios impact overall costs. For example, if lead times suddenly increase due to supply chain delays, companies can use this model to evaluate whether to raise inventory levels temporarily to avoid shortages. This flexibility lets businesses adjust their strategies on the fly, ensuring they’re prepared for unexpected changes.
Here we increase the lead time from seven to 10 days. Note how this affects shortage cost.
env = InventoryForecastEnv(
shelf_life=12, # Shelf life of raw materials (in days). Impacts the timing of orders to prevent waste.
order_lead_time=10, # Lead time (in days) for receiving raw materials after placing an order.
holding_cost=10, # Cost (in USD) to store one unit of inventory per day.
shortage_cost=15, # Penalty cost (in USD) per unit of unmet demand.
seasonality_factor=5, # Scale factor to adjust the level of fluctuation in demand.
forecast_horizon=50, # Number of days into the future for which demand is forecasted.
initial_inventory=50, # Initial amount of raw materials available at the start of the forecast.
safety_stock_factor=2 # Multiplier to adjust safety stock levels based on shortage costs.
# It is a multiplier that adjusts the amount of extra inventory kept on hand to mitigate
# the risk of stockouts
)
# fit the model and forecast demand
env.fit_sarima()
demand_forecast = env.forecast_demand()
# Calculate reorder points and costs
reorder_info = env.reorder_points(demand_forecast)
# Sum up holding and shortage costs
total_holding_cost = reorder_info['holding_cost'].sum()
total_shortage_cost = reorder_info['shortage_cost'].sum()
print(f"Total cost of holding inventory: ${total_holding_cost:,.2f}")
print(f"Total cost of running out of inventory (shortage cost): ${total_shortage_cost:,.2f}")
Total cost of holding inventory: $4,445.63 Total cost of running out of inventory (shortage cost): $34,916.52
reorder_info.reset_index(inplace=True)
# Set Seaborn theme
sns.set_theme(style="whitegrid")
sns.set_palette("muted")
# Create subplots
fig, axs = plt.subplots(3, 1, figsize=(12, 10), sharex=True)
# Inventory and Forecasted Demand
sns.lineplot(ax=axs[0], x=reorder_info.index, y=reorder_info['inventory'], label='Inventory', color="royalblue", linewidth=2)
sns.lineplot(ax=axs[0], x=reorder_info.index, y=reorder_info['forecast_demand'], label='Forecasted Demand', color="darkgreen", linewidth=2)
axs[0].set_ylabel('Units')
axs[0].legend(loc='upper right', fontsize=10)
axs[0].set_title('Inventory and Forecasted Demand', fontsize=14, fontweight='bold')
# Reorder Quantity
sns.barplot(ax=axs[1], x=reorder_info.index, y=reorder_info['reorder_quantity'], color="darkorange")
axs[1].set_ylabel('Reorder Quantity')
axs[1].set_title('Reorder Quantity Over Time', fontsize=14, fontweight='bold')
# Holding and Shortage Costs
sns.lineplot(ax=axs[2], x=reorder_info.index, y=reorder_info['holding_cost'], label='Holding Cost', color="skyblue", linewidth=2)
sns.lineplot(ax=axs[2], x=reorder_info.index, y=reorder_info['shortage_cost'], label='Shortage Cost', color="salmon", linewidth=2)
axs[2].set_ylabel('Cost')
axs[2].legend(loc='upper right', fontsize=10)
axs[2].set_title('Holding and Shortage Costs Over Time', fontsize=14, fontweight='bold')
# X-axis label
axs[2].set_xlabel('Time', fontsize=12)
# Adjust layout
plt.tight_layout()
plt.show()
How does the safety_stock_factor affect cost?
param_values = np.linspace(0.5, 8, 10) # Define range of values for safety_stock_factor
result = evaluate_costs_over_parameter_range(
'safety_stock_factor',
param_values,
shelf_life=12,
order_lead_time=7,
holding_cost=10,
shortage_cost=20,
seasonality_factor=5,
forecast_horizon=50,
initial_inventory=50
)
Modifying the safety stock factor allows us to tailor the amount of “buffer” inventory kept on hand to cover unforeseen demand surges or supply chain delays. For example, a manufacturer of seasonal goods might set a high safety stock factor before their peak season to avoid costly shortages, whereas during off-peak months, they might lower the factor to save on holding costs. This optimization step provides businesses with a dynamically calculated, cost-effective safety stock level, allowing for a smart balance between avoiding shortages and minimizing inventory expenses.
opt_result = env.optimize_safety_stock_factor()
print(f"Optimal safety_stock_factor: {opt_result.x:.5f}")
print(f"Minimized Total Holding and Shorting Costs over {env.forecast_horizon} day period of forecasting: ${opt_result.fun:,.2f}")
Optimal safety_stock_factor: 1.53490 Minimized Total Holding and Shorting Costs over 50 day period of forecasting: $39,200.35
Now that we optimized the safety_stock_factor
for these conditions, this value can be used in ongoing operations to set safety stock levels that balance shortage and holding costs more effectively. Using this optimized number, we can achieve a more cost-effective balance between maintaining sufficient stock levels to avoid costly shortages and avoiding excess inventory that leads to higher holding costs.
Importantly, as conditions change (e.g., holding costs increase), we can re-optimize the safety stock factor based on these conditions and modify the order startegy accordingly.
This demonstration shows how advanced forecasting techniques can be applied to real-world inventory challenges. Using a demand forecasting model like SARIMA, combined with an optimization of safety stock, businesses can better control inventory levels, reduce operational costs, and ensure they’re prepared to meet customer demand. At QSC, we specialize in customizing predictive models like these to meet specific business needs, helping companies reduce waste, save on costs, and operate more sustainably. Our expertise can turn your data into actionable insights, setting up an inventory management system that works with your production cycles and unique demands.