{
 "id": "customer_segments",
 "kind": "analysis R4",
 "title": "Which customers bring the money, and which are slipping away?",
 "question": "If every customer is scored on how recently they bought, how often and how much they spent, how many sit in each segment, how much revenue does each bring, and what would I do with each?",
 "source": {
  "name": "UCI Online Retail II",
  "publisher": "Chen, D. (2012), UCI Machine Learning Repository",
  "url": "https://archive.ics.uci.edu/dataset/502/online+retail+ii",
  "license": "CC BY 4.0",
  "as_of": "December 2011",
  "rows_raw": 1067371,
  "rows_used": 1021254,
  "caveat": "Scores are fifths of this retailer's own customers over the full Dec 2009 to Dec 2011 history, scored as of Dec 10, 2011. Mostly wholesale buyers, so order counts and spend run high. Customers without a Customer ID (22.2% of clean rows) are in no segment."
 },
 "snapshot": "2011-12-10",
 "cuts": {
  "recency_days": [
   20,
   59,
   187,
   409
  ],
  "orders": [
   1,
   2,
   4,
   8
  ],
  "net_spend_gbp": [
   271.75,
   590.44,
   1178.71,
   2828.94
  ]
 },
 "segments_def": [
  {
   "name": "Champions",
   "rule": "R 5, F 4 or 5"
  },
  {
   "name": "Loyal customers",
   "rule": "R 3 or 4, F 4 or 5"
  },
  {
   "name": "Potential loyalists",
   "rule": "R 4 or 5, F 2 or 3"
  },
  {
   "name": "New customers",
   "rule": "R 5, F 1"
  },
  {
   "name": "Promising",
   "rule": "R 4, F 1"
  },
  {
   "name": "Need attention",
   "rule": "R 3, F 3"
  },
  {
   "name": "About to sleep",
   "rule": "R 3, F 1 or 2"
  },
  {
   "name": "At risk",
   "rule": "R 1 or 2, F 3 or 4"
  },
  {
   "name": "Cannot lose them",
   "rule": "R 1 or 2, F 5"
  },
  {
   "name": "Hibernating",
   "rule": "R 1 or 2, F 1 or 2"
  }
 ],
 "kpis": [
  {
   "label": "Customers scored",
   "value": "5,852",
   "note": "identified, as of Dec 10, 2011"
  },
  {
   "label": "Champions and loyal customers",
   "value": "79.3%",
   "note": "of net spend, from 31.7% of customers"
  },
  {
   "label": "Champions",
   "value": "823",
   "note": "14.1% of customers, 52.7% of net spend"
  },
  {
   "label": "At risk or cannot lose them",
   "value": "£1.37M",
   "note": "net spend of 709 customers with 3 or more orders and no order in over six months"
  },
  {
   "label": "Hibernating",
   "value": "27.9%",
   "note": "of customers, 4.1% of net spend"
  }
 ],
 "finding": "Champions and loyal customers are 31.7% of the 5,852 scored customers and bring 79.3% of their net spend. Hibernating customers are the largest group, 27.9%, and bring 4.1%. 709 customers with 3 or more orders have not ordered in over six months (at risk, or cannot lose them); together they spent £1.37M, the clearest win-back list in the data.",
 "recommendation": "Protect champions and loyal customers first with service, stock and early access rather than discounts; win back the at-risk and cannot-lose groups with a direct, personal offer; spend little on hibernating customers beyond a low-cost reactivation email.",
 "segments": [
  [
   "Champions",
   14.1,
   52.7,
   823,
   8624952.28
  ],
  [
   "Loyal customers",
   17.6,
   26.6,
   1032,
   4343673.41
  ],
  [
   "At risk",
   11.0,
   5.5,
   643,
   901816.06
  ],
  [
   "Potential loyalists",
   12.2,
   4.2,
   716,
   680804.62
  ],
  [
   "Hibernating",
   27.9,
   4.1,
   1630,
   674231.11
  ],
  [
   "Cannot lose them",
   1.1,
   2.8,
   66,
   464359.49
  ],
  [
   "Need attention",
   4.8,
   2.2,
   282,
   367766.92
  ],
  [
   "About to sleep",
   7.2,
   1.4,
   421,
   223723.47
  ],
  [
   "Promising",
   2.8,
   0.3,
   161,
   52898.75
  ],
  [
   "New customers",
   1.3,
   0.2,
   78,
   25809.31
  ]
 ],
 "per_customer": [
  [
   "Champions",
   10479.89,
   12,
   9,
   823
  ],
  [
   "Loyal customers",
   4208.99,
   8.0,
   53.5,
   1032
  ],
  [
   "At risk",
   1402.51,
   4,
   369,
   643
  ],
  [
   "Potential loyalists",
   950.84,
   3.0,
   24.0,
   716
  ],
  [
   "Hibernating",
   413.64,
   1.0,
   431.0,
   1630
  ],
  [
   "Cannot lose them",
   7035.75,
   12.0,
   315.5,
   66
  ],
  [
   "Need attention",
   1304.14,
   3.0,
   102.5,
   282
  ],
  [
   "About to sleep",
   531.41,
   1,
   96,
   421
  ],
  [
   "Promising",
   328.56,
   1,
   38,
   161
  ],
  [
   "New customers",
   330.89,
   1.0,
   12.0,
   78
  ]
 ],
 "grid": [
  [
   "R5, most recent fifth",
   6.4,
   10.4,
   15.5,
   21.3,
   46.4,
   1215
  ],
  [
   "R4, second fifth",
   14.2,
   13.0,
   22.5,
   23.7,
   26.7,
   1135
  ],
  [
   "R3, middle fifth",
   21.2,
   15.0,
   24.2,
   23.8,
   15.7,
   1163
  ],
  [
   "R2, fourth fifth",
   35.8,
   22.4,
   23.3,
   14.3,
   4.3,
   1171
  ],
  [
   "R1, least recent fifth",
   61.1,
   20.1,
   12.8,
   4.5,
   1.4,
   1168
  ]
 ],
 "m_mix": [
  [
   "Champions",
   0.0,
   0.9,
   9.0,
   26.9,
   63.3,
   823
  ],
  [
   "Loyal customers",
   0.4,
   1.0,
   11.5,
   39.1,
   48.0,
   1032
  ],
  [
   "At risk",
   2.5,
   17.7,
   39.0,
   32.5,
   8.2,
   643
  ],
  [
   "Potential loyalists",
   8.0,
   27.8,
   41.3,
   19.8,
   3.1,
   716
  ],
  [
   "Hibernating",
   48.8,
   33.9,
   13.1,
   3.4,
   0.8,
   1630
  ],
  [
   "Cannot lose them",
   0.0,
   0.0,
   3.0,
   34.8,
   62.1,
   66
  ],
  [
   "Need attention",
   5.0,
   20.6,
   37.9,
   29.8,
   6.7,
   282
  ],
  [
   "About to sleep",
   35.9,
   35.6,
   20.9,
   6.4,
   1.2,
   421
  ],
  [
   "Promising",
   57.1,
   29.8,
   10.6,
   2.5,
   0.0,
   161
  ],
  [
   "New customers",
   53.8,
   39.7,
   5.1,
   0.0,
   1.3,
   78
  ]
 ],
 "recency_bands": [
  [
   "Under 1 month",
   1627,
   27.8,
   63.3
  ],
  [
   "1 to 3 months",
   1258,
   21.5,
   17.7
  ],
  [
   "3 to 6 months",
   586,
   10.0,
   6.0
  ],
  [
   "6 to 12 months",
   790,
   13.5,
   5.8
  ],
  [
   "12 to 18 months",
   1052,
   18.0,
   5.2
  ],
  [
   "Over 18 months",
   539,
   9.2,
   2.0
  ]
 ],
 "numbers": {
  "customers": 5852,
  "only_cancellations_not_scored": 23,
  "net_spend_zero_or_less": 20,
  "net_spend_total": 16360035.42,
  "segments": {
   "Champions": {
    "customers": 823,
    "net_spend": 8624952.28
   },
   "Loyal customers": {
    "customers": 1032,
    "net_spend": 4343673.41
   },
   "Potential loyalists": {
    "customers": 716,
    "net_spend": 680804.62
   },
   "New customers": {
    "customers": 78,
    "net_spend": 25809.31
   },
   "Promising": {
    "customers": 161,
    "net_spend": 52898.75
   },
   "Need attention": {
    "customers": 282,
    "net_spend": 367766.92
   },
   "About to sleep": {
    "customers": 421,
    "net_spend": 223723.47
   },
   "At risk": {
    "customers": 643,
    "net_spend": 901816.06
   },
   "Cannot lose them": {
    "customers": 66,
    "net_spend": 464359.49
   },
   "Hibernating": {
    "customers": 1630,
    "net_spend": 674231.11
   }
  },
  "grid_counts_r1_to_r5_by_f1_to_f5": [
   [
    714,
    235,
    150,
    53,
    16
   ],
   [
    419,
    262,
    273,
    167,
    50
   ],
   [
    246,
    175,
    282,
    277,
    183
   ],
   [
    161,
    147,
    255,
    269,
    303
   ],
   [
    78,
    126,
    188,
    259,
    564
   ]
  ]
 },
 "sample_queries": [
  {
   "label": "RFM scores and segments (DuckDB, the full cleaned history)",
   "sql": "-- RFM: score each customer 1 to 5 on recency, frequency and spend, then name the segment\n-- The cut points are the customers' own fifths (nearest rank), computed once and written in.\nWITH customers AS (\n    SELECT customer_id,\n           DATE '2011-12-10' - max(invoice_date::DATE) FILTER (WHERE NOT is_cancelled) AS recency_days,\n           count(DISTINCT invoice) FILTER (WHERE NOT is_cancelled) AS orders,\n           round(sum(line_revenue), 2) AS net_spend -- in pounds and pence; cancellations count as negative\n    FROM retail_lines\n    WHERE customer_id IS NOT NULL\n    GROUP BY 1\n    HAVING count(*) FILTER (WHERE NOT is_cancelled) > 0\n),\nscored AS (\n    SELECT *,\n           CASE WHEN recency_days <= 20 THEN 5 WHEN recency_days <= 59 THEN 4 WHEN recency_days <= 187 THEN 3\n                WHEN recency_days <= 409 THEN 2 ELSE 1 END AS r,\n           CASE WHEN orders <= 1 THEN 1 WHEN orders <= 2 THEN 2 WHEN orders <= 4 THEN 3\n                WHEN orders <= 8 THEN 4 ELSE 5 END AS f,\n           CASE WHEN net_spend <= 271.75 THEN 1 WHEN net_spend <= 590.44 THEN 2 WHEN net_spend <= 1178.71 THEN 3\n                WHEN net_spend <= 2828.94 THEN 4 ELSE 5 END AS m\n    FROM customers\n)\nSELECT CASE\n           WHEN r = 5 AND f >= 4 THEN 'Champions'\n           WHEN r BETWEEN 3 AND 4 AND f >= 4 THEN 'Loyal customers'\n           WHEN r >= 4 AND f BETWEEN 2 AND 3 THEN 'Potential loyalists'\n           WHEN r = 5 AND f = 1 THEN 'New customers'\n           WHEN r = 4 AND f = 1 THEN 'Promising'\n           WHEN r = 3 AND f = 3 THEN 'Need attention'\n           WHEN r = 3 AND f <= 2 THEN 'About to sleep'\n           WHEN r <= 2 AND f BETWEEN 3 AND 4 THEN 'At risk'\n           WHEN r <= 2 AND f = 5 THEN 'Cannot lose them'\n           WHEN r <= 2 AND f <= 2 THEN 'Hibernating'\n       END AS segment,\n       count(*) AS customers,\n       round(sum(net_spend), 2) AS net_spend,\n       round(100.0 * sum(net_spend) / sum(sum(net_spend)) OVER (), 1) AS share_of_spend_pct\nFROM scored\nGROUP BY 1\nORDER BY net_spend DESC"
  }
 ],
 "cleaning": [
  "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.",
  "Dropped rows with price <= 0 (6,207 rows); nearly all of these have no Customer ID and are internal stock corrections, not customer sales.",
  "Dropped negative-quantity rows on invoices that do not start with 'C' (3,457 rows); a true return in this dataset is a cancellation invoice (prefix 'C'), and these unlabeled negative-quantity rows all have null Customer ID and mostly null description, so they read as inventory write-offs rather than customer returns.",
  "Removed 34,035 exact duplicate line items (after the rules above).",
  "Kept rows with missing Customer ID (22.2% of clean rows) for revenue and product analysis since they are real sales; excluded them from the cohort retention analysis, which requires a known customer.",
  "One row per identified customer with at least one invoice that is not a cancellation (5,852). Recency: days from the last purchase day to Dec 10, 2011, the day after the last invoice. Frequency: distinct invoices that are not cancellations. Spend: net revenue, with cancellations counted as negative.",
  "Each score is the customer's fifth of this base on that measure, by the nearest-rank rule, so tied values share a score and the fifths are not exactly equal in size."
 ],
 "checks": [
  "The SQL below, run in DuckDB 1.5.5, returns the same customers, net spend and share per segment as the scoring code, for all ten segments; every cell of the 5 by 5 recency and frequency grid falls in exactly one segment.",
  "Every number on this page was recomputed by separate code (verify_r4.py: plain Python reading the original UCI zip) and matches.",
  "The segments add up to all 5,852 customers and to their total net spend, £16.36M."
 ]
}