Data AnalyticsDomain analytics and business cases

Stockout analysis with incomplete availability data

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 20 September 2026 · 3 min read
Technically reviewed by Ishaan Sharma
In this article (5 sections)

Zero sales do not prove a stockout. Customers may not have wanted the item, the store may have been closed, or the sales feed may be incomplete. Likewise, a missing availability record is not evidence that stock was available.

Keep observed availability, observed stockouts and unknown coverage separate. Report the rate your data can support before estimating duration or lost demand.

Define the observation unit

The synthetic availability fixture contains two products over four dates, giving eight scheduled observations. Each row represents one consistent daily availability snapshot. Six snapshots have known available units; two are unknown.

Daily sales are included for comparison, but they cover the day rather than the exact snapshot moment. A morning stockout followed by replenishment can coexist with sales later in the day. This dataset cannot reconstruct that path.

Among the six known snapshots, two show zero available units. The observed-snapshot stockout rate is therefore 2/6 = 33.3%. Coverage is 6/8 = 75%.

Preserve the unknown category

sql
SELECT COUNT(*) AS scheduled_snapshots,
 SUM(coverage='known') AS known_snapshots,
 SUM(coverage='unknown') AS unknown_snapshots,
 SUM(coverage='known' AND available_units=0) AS observed_stockouts,
 SUM(sold_units=0) AS zero_sales_days
FROM availability;

The query returns eight scheduled snapshots, six known, two unknown, two observed stockouts and six zero-sales days. Treating all zero-sales rows as stockouts would triple the observed stockout count.

python
from build_and_verify import database

db = database()
rows = db.execute('SELECT * FROM availability').fetchall()
db.close()
known = [r for r in rows if r[2]=='known']
unknown = [r for r in rows if r[2]=='unknown']
assert all(r[3] is not None and r[3]>=0 for r in known)
assert all(r[3] is None for r in unknown)
stockouts = [r for r in known if r[3]==0]
n, missing, out = len(rows),len(unknown),len(stockouts)
assert (n,len(known),missing,out)==(8,6,2,2)
lower, upper = out/n, (out+missing)/n
assert lower == .25 and upper == .5
assert sum(r[4]==0 for r in rows)==6
print({'coverage':len(known)/n,'known_snapshot_stockout_rate':out/len(known),
       'all_snapshot_lower_bound':lower,'all_snapshot_upper_bound':upper})

Without further assumptions, the all-snapshot rate lies between 25% and 50%: the lower bound treats both unknown snapshots as available, and the upper bound treats both as stockouts. This is a missing-data bound for these eight scheduled observations, not a statistical confidence interval.

Do not assume missingness is harmless

If availability telemetry tends to fail during inventory-system incidents, the known snapshots may underrepresent stockouts. Reporting 33.3% as the unquestioned rate for all scheduled observations would rely on an unsupported representativeness assumption.

Break coverage down by product, location and time when those fields exist. A high overall coverage percentage can hide a complete gap for one important store. Keep an expected observation grid so entirely missing rows are detected; counting only rows that arrived cannot reveal absent scheduled observations.

Distinguish incidence from duration and lost sales

A daily snapshot rate is not the share of trading hours spent out of stock. Duration requires stock-state transitions or sufficiently detailed observations with a justified interpolation rule. One unavailable snapshot does not establish a full day of unavailability.

Lost sales require an estimate of demand that would have occurred with stock available. Observed sales during stockouts are constrained by availability and cannot supply that counterfactual directly. Promotions, substitutes, seasonality and customer switching complicate the estimate.

Before building a demand model, verify that the operational definition uses sellable available stock rather than physical stock that is reserved, damaged or blocked. The distinction should match the customer's ability to buy.

Exercise: remove one row entirely from the CSV. Build the expected product-date grid and show that coverage falls instead of letting the denominator shrink silently. Then describe what additional events would be needed to estimate stockout hours.

NeuraPath's Data Analytics with Generative AI course connects missing-data handling with operational metrics. A trustworthy stockout report distinguishes what was observed from what the business still needs to measure.

Continue learning

This article is part of the Domain analytics and business cases sequence. Use the neighbouring tasks when you need the prerequisite or the next application.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.

Explore Data Analytics with Generative AI
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.