HomeWorkSlow stock

Flagship analysis · E-commerce and inventory · Public data

Finding stock that stopped selling

Which products had no sale in the last 90 days, what did they bring in, and does the busy autumn hide more stops?

7.3%

Autumn 2010 revenue from products with no sale in autumn 2011

£254K; 794 of 3,251 products, Sep 11 to Dec 9

Products with no sale in autumn 2011 brought in 7.3% of autumn 2010 revenue: 794 of 3,251 products. Against the prior 90 days instead, the share is 1.1%, because autumn is the busy season. Sales data, not stock levels.

Products with no sale in autumn 2011 brought in 7.3% of autumn 2010 revenue: 794 of 3,251 products. Against the prior 90 days instead, the share is 1.1%, because autumn is the busy season. Sales data, not stock levels.

Public data · UCI Online Retail II

Model · 4,725 products, one block each

1,567 of 4,725 products had no sale in the last 90 days.

All products4,725products, one block each; height = two-year net revenue, log scale

How to read this model

One block per product with a sale, grouped by what it did in the final 90 days. Height = two-year net revenue (Dec 2009 to Dec 2011), log scale; £10 or less, including net zero or below, is the floor. Choose a group to stack the blocks to scale. UCI Online Retail II · public data

Chart 1 · Two ways to measure the same stop

Against the same weeks a year earlier, products that stopped held 7.3% of that season's revenue; against the prior 90 days, 1.1%.

Does comparing the final 90 days with the 90 days before them flatter the products?

Share of the earlier window's revenue, by what happened next. Autumn here means Sep 11 to Dec 9. Source: UCI Online Retail II, Chen, D. (2012), UCI Machine Learning Repository · CC BY 4.0 · public data

01

Why this question

At Dasalla, data analysis identified $300,000 in non-performing inventory in a $2,000,000 product portfolio, and I presented it to leadership.

Drawn to scale.

This page answers the same kind of question, end to end, on public data. It uses none of that company's data and is not a record of that work.

02

The data

Source
UCI Online Retail IIChen, D. (2012), UCI Machine Learning Repository
Licence
CC BY 4.0Public data
Period
Dec 2009 to Dec 2011Data as of December 2011, fixed dataset
Rows
1,067,3711,021,254 after cleaning

A UK online gift retailer selling mostly to wholesale customers. The data has sales, not stock levels, and the shop's busiest season falls in the final 90 days.

The cleaning, to scale

1,067,371 rows in, 1,021,254 kept.

Cleaning steps and definitions
  1. Dropped non-product stock codes (POST, DOT, M, m, D, BANK CHARGES, AMAZONFEE, C2, PADS, CRUK, ADJUST, ADJUST2, S, B, TEST001/002, gift card vouchers) that represent postage, fees, manual adjustments, and test rows rather than real merchandise (5,931 rows).
  2. Dropped rows with price <= 0 (6,151 rows); nearly all of these have no Customer ID and are internal adjustments, not customer sales.
  3. Dropped negative-quantity rows on invoices that do not start with 'C' (none left after the step above); a true return in this dataset is a cancellation invoice (prefix 'C').
  4. Removed 34,035 exact duplicate line items (after the rules above).
  5. Counts are taken in this order, so each row is counted once. In the raw file the rules overlap: 6,207 rows have a price of zero or less and 3,457 have a negative quantity outside cancellations.
  6. A product is a stock code, upper-cased (so 85123a and 85123A are one product). One product can carry more than one code, or be relaunched under a new code, and the data cannot tell.
  7. A sale is a line on an invoice that is not a cancellation. Revenue is net of cancellations: cancellation lines (invoices starting with C) are negative and count against the date of the cancellation.
03

Method

Every product with a sale lands in one of four groups, by what it did in the final 90 days of the data.

The three windows, drawn to scale

Windows: final 90 days Sep 11 to Dec 9, 2011 (the last invoice is Dec 9 at 12:50); prior 90 days Jun 13 to Sep 10, 2011; the same weeks a year earlier Sep 11 to Dec 9, 2010.

  • Stopped1,567

    Sold earlier, no sale in the final 90 days.

  • Slowing347

    Sold in both windows and final-90 revenue under half of prior-90 revenue (prior-90 revenue above zero).

  • Steady or growing2,602

    The rest that sold in the final 90 days.

  • New209

    First sale in the final 90 days.

Products in each group, of 4,725 products with a sale. 14 stock codes have only cancellation lines (net £263.88 below zero): they have no sale, so they are in no group and not in the totals.

The SQL: products by what happened in the last 90 days
-- Products by what happened in the last 90 days (Live SQL table: Dec 2010 to Dec 2011)
WITH p AS (
    SELECT upper(stock_code) AS code,
           min(invoice_date) FILTER (WHERE NOT is_cancelled) AS first_sale,
           count(*) FILTER (WHERE NOT is_cancelled AND invoice_date BETWEEN DATE '2011-06-13' AND DATE '2011-09-10') AS sales_prior,
           count(*) FILTER (WHERE NOT is_cancelled AND invoice_date >= DATE '2011-09-11') AS sales_final,
           sum(line_revenue) FILTER (WHERE invoice_date BETWEEN DATE '2011-06-13' AND DATE '2011-09-10') AS rev_prior,
           sum(line_revenue) FILTER (WHERE invoice_date >= DATE '2011-09-11') AS rev_final,
           sum(line_revenue) AS rev_total
    FROM retail_orders
    GROUP BY 1
    HAVING count(*) FILTER (WHERE NOT is_cancelled) > 0          -- at least one sale
), g AS (
    SELECT *,
           CASE WHEN sales_final = 0 THEN '1 Stopped'
                WHEN first_sale >= DATE '2011-09-11' THEN '4 New'
                WHEN sales_prior > 0 AND rev_prior > 0 AND rev_final < 0.5 * rev_prior THEN '2 Slowing'
                ELSE '3 Steady or growing' END AS grp
    FROM p
)
SELECT grp AS product_group,
       count(*) AS products,
       round(sum(rev_total)) AS revenue_gbp,
       round(100.0 * sum(rev_total) / sum(sum(rev_total)) OVER (), 1) AS pct_of_revenue
FROM g
GROUP BY 1
ORDER BY 1

The query runs on the Live SQL table (13 months, Dec 2010 to Dec 2011), so its counts differ from this page's 25-month figures. It returns:

Open in Live SQL
product_groupproductsrevenue_gbppct_of_revenue
1 Stopped640186,2051.9
2 Slowing347737,1437.5
3 Steady or growing25838,542,61987.4
4 New228305,5613.1
04

Findings

Chart 2 · Products against revenue

33.2% of products stopped selling, and they brought in 8.3% of two-year revenue.

How many products stopped selling, and how much did they bring in?

4,725 products with at least one sale, £18.93M of net revenue in all, Dec 2009 to Dec 2011. UCI Online Retail II · public data

Chart 3 · Stopped products by month of last sale

50.6% of the revenue from stopped products came from ones that last sold in 2011.

When did the stopped products last sell, and what did they bring in?

Each bar is the two-year revenue of the stopped products whose last sale fell in that month. The data starts in December 2009, so for products that last sold early on, it shows only the end of their sales. UCI Online Retail II · public data

The ten stopped products with the most past revenue brought in £159,766; the biggest was the tea time cake stand in gift box.

Which stopped products used to bring in the most?

The ten stopped products with the most past revenue, £159,766 in all.
ProductStock codeLast saleRevenue, two years (£)
Tea time cake stand in gift box37503Aug 1, 2010£24,913
Red retrospot traditional teapot21527May 15, 2011£23,635
English rose design quilted throw84467Jul 27, 2010£17,398
Set of 3 coloured flying ducks35004CMar 7, 2011£15,791
Set/3 russian doll stacking tins85232BFeb 11, 2011£14,084
Grow your own plant in a can22444May 19, 2011£13,714
Tea time tea towels47556BApr 18, 2011£13,312
Paper bunting coloured lace22088Jul 6, 2011£12,879
Large popcorn holder22198Feb 21, 2011£12,412
White cherry lights79323WSep 16, 2010£11,628

Revenue to the nearest pound. A product relaunched under another code would show here as stopped. UCI Online Retail II · public data

05

What I would do next with this

  • Add stock levels and costs, so each stopped product shows what is on hand and what it cost, not only what it used to bring in.
  • Match stopped codes against new codes with the same description, so a relaunch is not counted as a stop.
  • Run the same-season comparison on a third autumn before calling a product gone.
  • Refresh the workbook each month as a list of stopped and slowing products, ranked by past revenue, to go through one by one.
06

Limits

How much was on hand
This data has sales, not stock levels. It cannot say what was on a shelf, what was bought in, or what a product cost.
Why a product stopped
A stop can be a choice, a supplier problem, the end of a season or a new code for the same thing. Nothing here records which.
Whether it is one product or two
One product can carry more than one stock code, and a relaunch under a new code looks like a stop.
Costs, margins and returns
Revenue is what customers paid, net of cancellations. There are no costs, no margins and no returns to a supplier, so the pounds are not profit.
A season or an ending
Two years cannot tell a product that rests every winter from one that is gone. Chart 1 compares the same weeks a year earlier, which helps and does not settle it.

Checks: every number on this page was recomputed by separate code (verify_slow.py: plain Python reading the original UCI zip, with no DuckDB and no intermediate files) and matches, product by product.

07

My role

  • Directed the project and made the product and design decisions.
  • Approved design changes as mockups before they shipped.
  • Checked the facts in the claims about me.

How this site is made

Contact

Have channels or inventory to sort out? Let's talk.

Open to e-commerce, data and operations roles. Los Angeles or remote.