How to calculate operations management metrics in Python

Understand the most important metrics for operations managers and learn how to calculate them in Python.

How to calculate operations management metrics in Python
Picture by Marcin Jozwiak, Unsplash.
17 minutes to read

Successful operations management is crucial to the overall growth of an ecommerce business. While those in ecommerce, marketing, or data science, can work together the sales coming in and encourage customers to buy more, it can all be for nothing if operations don’t run smoothly.

If the goods aren’t in stock, don’t go out on time, get picked in error, get damaged in transit, or the operations team’s KPIs are at odds with what customers need, then the customers the company has invested in acquiring may never return again.

Operations management metrics allow managers and workers to evaluate and control their performance, communicate how well they’re doing, identify potential areas of concern, and allow improvements to be identified and introduced. Here’s a quick guide to calculating some of the most useful.

Available inventory accuracy

Available inventory accuracy (or inventory accuracy, for short) is used to measure the proportion of discrepancies between the stock levels the warehouse management system (WMS) says the company has in stock, and what the pickers find when they get to the pick face.

Customers end up placing orders for items that are shown to be in stock, but pickers find no available inventory when they reach the pick face. They then have to either part-pick the order or liaise with CS to let the customer know. The customer, rightly, is disappointed, and their probability to churn is likely to increase unless service recovery is used.

The retailer then gets hit with extra costs due to the slowdown in picking, the liaising with CS, the possibility of order cancellation, or split shipment, and the potential loss of customer equity. As you can see, this metric is obviously quite an important one.

def available_inventory_accuracy(counted_items, 
                                 counted_items_that_match_record):
    """Return the Available Inventory Accuracy.

    Args:
        counted_items (int): Total items supposedly in the inventory according
         to the WMS.
        counted_items_that_match_record (int): Number of items were the WMS 
        count matches the actual count.

    Returns:
        Percentage of available inventory that was correctly counted in the WMS.
    """

    return (counted_items_that_match_record / counted_items) * 100
print('Available Inventory Accuracy:', available_inventory_accuracy(counted_items=901,
                                                                    counted_items_that_match_record=890))
Available Inventory Accuracy: 98.77913429522752

Service level

Service level is related to available inventory as it measures the percentage of orders placed that were successfully delivered within the period. Failure to deliver could be due to a whole range of things, from the available inventory accuracy to various supply chain failures.

There is, of course, a simple way to resolve this in most cases: hold more stock. However, there’s a trade off between opportunity costs and operational costs. Extra stock means fewer lost sales, and a higher service level, but carrying extra stock is very expensive.

According to research by Radasanu (2016), most companies aim for a level of 95% and use inventory management processes such as ABC inventory analysis to selectively manage products based on their sales impact, and XYZ inventory analysis to manage products based on their forecast accuracy.

def service_level(orders_received, 
                  orders_delivered):
    """Return the inventory management service level metric, 
    based on the percentage of received orders delivered.

    Args:
        orders_received (int): Orders received within the period.
        orders_delivered (int): Orders successfully delivered within the period.

    Returns:
        Percentage (float) of orders received that were delivered within th period.
    """

    return (orders_delivered / orders_received) * 100
print('Service level:', service_level(orders_received=2000, 
                                      orders_delivered=1900))
Service level: 95.0

Lost sales ratio

You can’t sell products you don’t have in stock (unless you take back orders, which come with their own problems). However, the problems that stock outs can cause go far beyond the potential to lose the sale of on an affected SKU.

From a customer perspective, a stock out on one SKU doesn’t always result in the customer choosing an alternative and continuing with their order. They’re more likely to bounce and exit the site, and you might lose an entire order if a specific item is unavailable, and the customer could even defect to a rival.

The other issue that stock outs cause is with paid search ads and marketplaces. Unless quickly identified, the marketing team may continue to pay to drive traffic to a page where a product is out of stock, potentially wasting a pound or so every time it happens. Similarly, rankings on marketplaces, such as eBay, which are tied to conversion rate, can be lost if an item goes out of stock, and will take time to recover, which costs money.

The lost sales ratio is a great way to examine the correlation that stock outs have upon product sales, and it should ideally be measured for every product and shared with your ecommerce team. It’s somewhat complicated to obtain the raw data. Google Analytics can be hacked to measure it, but the WMS is the most reliable source.

def lost_sales_ratio(days_out_of_stock, 
                     days_in_period):
    """Returns the lost sales ratio for a product, representing the percentage 
    of days in a period when it was OOS.

    Args:
        days_out_of_stock (int): Total days the product was out of stock.
        days_in_period (int): Total days in the period.

    Returns:
        Percentage of days in the period when the product was out of stock.
    """

    return (days_out_of_stock / days_in_period) * 100
print('Lost sales ratio:', lost_sales_ratio(days_out_of_stock=10, 
                                            days_in_period=30))
Lost sales ratio: 33.33333333333333

Back order rate

Back order rate is another potentially useful inventory metric, as it’s often correlated with customer satisfaction. A back order occurs when a customer requests a quantity of a SKU that the retailer is unable to supply at the time of the order, either because the stated inventory wasn’t present at the pick face, or the customer has requested more than the company ordinarily holds in stock.

Back orders, in general, are considered annoying to customers. The customer either needs to be contacted and told to wait, the order may need to be split and shipped in parts, or the customer may want to cancel altogether. When back orders are allowed, some customers don’t notice the messaging and expect their product before it’s really due.

Since lead times and supplier performance varies, safety stock is used to help minimise back orders, and the back order rate metric is used to keep things closely monitored.

def back_order_rate(total_back_orders, 
                    total_orders):
    """Return the back order rate for a period. Back orders are those that could not
     be shipped due to lack of stock.

    Args:
        total_back_orders (int): Total number of back orders.
        total_orders (int): Total number of orders.

    Returns:
        Back order rate (float).
    """

    return (total_back_orders / total_orders) * 100
print('Back order rate:', back_order_rate(total_back_orders=75, total_orders=1000))
Back order rate: 7.5
Safety stock

Safety stock is the volume of a particular SKU that a retailer needs to hold in order to satisfy typical daily demand, the occasional spikes in demand that can occur, and the typical lead time for replenishing the SKU via the supplier.

While you can determine the correct safety stock level mathematically, safety stock determinations aren’t designed to completely eliminate stock outs. It would likely be too costly for a business to meet safety stock levels for every SKU, so compromises do need to be made.

Instead, they’re often used in conjunction with ABC inventory analysis and XYZ inventory analysis, so the important SKUs, and the ones that are hardest to forecast, get the right treatment. Depending on the product class, a desired service level can be set for each SKU and used for setting the safety stock level in the most optimal manner (see Radasanu, 2016).

def safety_stock(max_units_sold_daily, 
                 avg_units_sold_daily, 
                 max_lead_time, 
                 avg_lead_time):
    """Returns the safety stock level for a given product based on sales and lead time.

    Args:
        max_units_sold_daily (int): Maximum number of units sold daily in previous period.
        avg_units_sold_daily (float): Average number of units sold daily in previous period.
        max_lead_time (int): Maximum number of days required to obtain stock.
        avg_lead_time (int): Average number of days required to obtain stock.

    Returns:
        Safety stock level for the product based on sales and lead time.
    """

    return (max_units_sold_daily * max_lead_time) - (avg_units_sold_daily * avg_lead_time)
print('Safety stock:', safety_stock(max_units_sold_daily=100,
                                    avg_units_sold_daily=5,
                                    max_lead_time=7,
                                    avg_lead_time=5))
Safety stock: 675
Sales velocity

In order to monitor product demand, operations managers typically monitor sales velocity over the preceding period. This examines the total number of units sold in the past 12 months, the number of days the product was held in stock, and the number of days over which to monitor demand (typically 30 days). Moving averages and exponential moving averages (EMAs) can also be used in monitoring sales velocity.

def sales_velocity(units_sold_last_12m, 
                   number_of_days_in_stock, 
                   velocity_days=30):
    """Return the sales velocity of a product for a given number of days.

    Args:
        units_sold_last_12m (int): Total number of units sold in the past 12 months.
        number_of_days_in_stock (int): Total number of days in the past 12 months 
        when product was in stock.
        velocity_days (int, optional): Number of days over which to measure 
        sales velocity. Default 30.

    Returns:
        Sales velocity of product
    """

    return (units_sold_last_12m / number_of_days_in_stock) * velocity_days
print('Sales velocity:', sales_velocity(units_sold_last_12m=1000,
                                        number_of_days_in_stock=250,
                                        velocity_days=30))
Sales velocity: 120.0
Accuracy of forecast demand

Most warehouse management systems will forecast the demand for a product for the forthcoming period, often based on both the recent sales, and any seasonality for the product being observed. Data science tools such as the Prophet forecasting model can be useful in setting these numbers.

However, the forecast demand for a product doesn’t always match reality. Sometimes forecasts can be thrown-off and predict sales upturns that don’t happen, or not adequately allow for seasonality or other things. Therefore, it’s important to monitor the accuracy of the forecast demand, since this directly impacts stock outs, costs, and customer satisfaction.

The accuracy of forecast demand metric is very simple and compares the percentage difference between what was forecast and what actually happened. XYZ inventory analysis can be really useful here, since it’s specifically designed for segmenting products by their ability to be forecast accurately.

def accuracy_of_forecast_demand(actual_demand, 
                                forecast_demand):
    """Return the accuracy of forecast demand.

    Args:
        actual_demand (int): Actual number of units of product sold within the period.
        forecast_demand (int): Number of units forecast to be demanded within the period.

    Returns:
        Accuracy of forecast demand.
    """

    return ((actual_demand - forecast_demand) / actual_demand) * 100
print('Accuracy of forecast demand:', accuracy_of_forecast_demand(actual_demand=90,
                                                                  forecast_demand=80))
Accuracy of forecast demand: 11.11111111111111
Reorder point

The reorder point is the date at which the supplier order needs to be placed in order to have the product shipped and replenished with sufficient time to avoid a stock out, based on a combination of typical demand and demand spikes, typical lead times and lead time delays.

If the procurement team is not correctly calculating reorder points, then the probability for stock outs is greatly increased, which can cost the business money.

def reorder_point(max_units_sold_daily, 
                  avg_units_sold_daily, 
                  max_lead_time, 
                  avg_lead_time, 
                  lead_time):
    """Returns the reorder point for a given product based on sales and lead time.

    The reorder point is the stock level at which a new order should be placed in order to
     avoid stock outs.

    Args:
        max_units_sold_daily (int): Maximum number of units sold daily in previous period.
        avg_units_sold_daily (float): Average number of units sold daily in previous period.
        max_lead_time (int): Maximum number of days required to obtain stock.
        avg_lead_time (int): Average number of days required to obtain stock.
        lead_time (int): Number of days required to obtain stock.

    Returns:
        Safety stock level for the product based on sales and lead time.
    """

    safety = safety_stock(max_units_sold_daily, 
                          avg_units_sold_daily, 
                          max_lead_time, 
                          avg_lead_time)
    return (lead_time * avg_units_sold_daily) + safety
print('Reorder point:', reorder_point(max_units_sold_daily=100,
                                      avg_units_sold_daily=5,
                                      max_lead_time=7,
                                      avg_lead_time=5,
                                      lead_time=5))
Reorder point: 700
Days of Inventory Outstanding (DIO)

The metrics above mostly look at having sufficient stock in the inventory to meet the demand, so sales are not lost and customers are less likely to churn. However, since there’s a trade off between holding stock to satisfy demand and the costs that get tied up in stock, another metric is needed.

Days of Inventory Outstanding or DIO (sometimes called Days Sales of Inventory or DSI) is an operations efficiency metric used to measure how long inventory is held before it is sold. While the DIO target used will vary between businesses, if it is very high it suggests the company is sitting on aged or dead stock that isn’t selling quickly.

By monitoring DIO over time, you can see how quickly products are turning over. However, as it’s impacted by new product development (where new products can take time to get going), segmentation by product type and other features can be advantageous for more informative monitoring.

def dio(average_inventory_cost, 
        cost_of_goods_sold):
    """Return the DIO or Days of Inventory Outstanding over the previous 365 days.

    Args:
        average_inventory_cost (float): Average cost of inventory.
        cost_of_goods_sold (float): Cost of goods sold.

    Returns:
        Days of Inventory Outstanding (float).
    """

    return (average_inventory_cost / cost_of_goods_sold) * 365
print('Days of Inventory Outstanding:', dio(average_inventory_cost=50, 
                                            cost_of_goods_sold=1000))
Days of Inventory Outstanding: 18.25

Further reading

  • Lewis, M. and Slack, N., 2014. Operations strategy. Pearson Education.

  • Melnyk, S.A., Stewart, D.M. and Swink, M., 2004. Metrics and performance measurement in operations management: dealing with the metrics maze. Journal of operations management, 22(3), pp.209-218.

  • Radasanu, A.C., 2016. Inventory management, service level and safety stock. Journal of Public Administration, Finance and Law, (09), pp.145-153.

  • Slack, N., Chambers, S. and Johnston, R., 2010. Operations management. Pearson education.

Matt Clarke, Saturday, March 13, 2021

Matt Clarke Matt is an Ecommerce and Marketing Director who uses data science to help in his work. Matt has a Master's degree in Internet Retailing (plus two other Master's degrees in different fields) and specialises in the technical side of ecommerce and marketing.