{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "---\n", "title: \"Chapter 33: Predicting household spend\"\n", "---" ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "[](https://colab.research.google.com/github/sambaiga/ds-mlops-path/blob/main/tutorials/05-classical-ml-projects/33-spend-regression.ipynb) [](https://raw.githubusercontent.com/sambaiga/ds-mlops-path/main/tutorials/05-classical-ml-projects/33-spend-regression.ipynb)" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "In November 2019, a single-person household in Alabama, reporting an income between $25,000 and $49,999, bought a 65-inch Samsung 4K television, a PlayStation 4 Pro, a Microsoft Surface tablet, an Apple Watch, a GoPro camera bundle, a trampoline, and a kayak. Fifty-eight items, spread across four days. The total came to $11,709.16.\n", "\n", "For the three months before that, the same household had spent an average of $49.47 a month. Across the roughly four years this account appears in the data, its lifetime total is $16,237.77. One month accounts for 72 percent of everything it has ever bought on the platform.\n", "\n", "Nothing in this household's survey answers, age bracket, income, education, state, explains it. There's no flag for it and no obvious trigger. It's one row among 1.85 million, sitting in the data whether or not anyone thinks to look for it." ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "Chapters 28 through 32 gave you the machinery for exactly this kind of question: how to turn a vague business problem into [a five-component specification](../04-ml-intro/30-ml-workflow.qmd#translating-a-business-problem-into-a-machine-learning-problem), how [the nine-stage workflow](../04-ml-intro/30-ml-workflow.qmd#the-end-to-end-machine-learning-workflow) moves data through a model and into production, how scikit-learn's [Transformer and Predictor interfaces](../04-ml-intro/31-sklearn-core.ipynb) work, how [a Pipeline strings them together](../04-ml-intro/32-sklearn-pipeline.ipynb) without leaking information across a split. You used all of it on hotel booking data that was already clean enough to teach with.\n", "\n", "Here, you use it on a dataset nobody cleaned for teaching: five years of real Amazon purchase histories from 5,027 U.S. households, linked to a demographic and lifestyle survey each household completed once. The household above is what that data looks like up close. It's also the first of seven case studies built the same way, one real dataset and one real business question each, so the habit you build in this chapter (reason through the ask, prototype in the notebook, promote what works into a tested module) is the one the other six will follow." ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "::: {.callout-note collapse=\"true\" icon=false}\n", "## Learning objectives\n", "\n", "By the end of this chapter you will be able to:\n", "\n", "| # | Skill | Covered in |\n", "|---|---|---|\n", "| 1 | Turn a vague business ask into a five-component ML specification through your own follow-up questions | Sec. 1 |\n", "| 2 | Build a tested, cached data-download utility instead of a one-off script | Sec. 2 |\n", "| 3 | Let exploratory checks be driven by the spec card instead of habit | Sec. 3 |\n", "| 4 | Engineer temporally-safe features and catch a leakage bug with a purpose-built test | Sec. 4 |\n", "| 5 | Demonstrate the Evaluation-to-Feature-Engineering feedback loop with an honest ablation | Sec. 5 |\n", "| 6 | Diagnose a model with a learning curve and permutation importance | Sec. 6 |\n", "| 7 | Attach a back-of-envelope dollar value to a model's metric before calling it done | Sec. 7 |\n", ":::" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "## 1. Problem framing\n", "\n", "Here's the request, exactly as it would actually arrive. You're the data scientist on a retention team at a company sitting on data like this, purchase histories linked to a demographic survey, for a panel of customers. Your manager stops by your desk and says:\n", "\n", "\"We want to know which customers are worth investing more in.\"\n", "\n", "That's the whole brief. No target variable, no metric, no mention of a model. This is what a real ask sounds like, and your job right now is to figure out what it actually means, not to open a notebook." ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "### What does \"worth investing in\" even mean?\n", "\n", "Before you touch a single file, you need to pull that sentence apart. It's collapsing at least four different problems into one, and they are not interchangeable:\n", "\n", "- Customers who already spend the most. A ranking of the past, nothing predictive about it.\n", "- Customers likely to spend more going forward. A forecast.\n", "- Customers likely to leave if nobody intervenes. A churn problem.\n", "- Customers who look like existing high spenders but haven't been identified yet. A segmentation problem.\n", "\n", "Each one is a legitimate data science problem. Each one produces a different target variable, a different model, and a different action the business takes on your output. Guess wrong here and you could spend six months building the right system for the wrong question." ] }, { "cell_type": "markdown", "id": "7", "metadata": {}, "source": [ "You go back and ask a follow-up question. It turns out \"worth investing in\" means deciding who gets priority for a loyalty program with a fixed, limited budget. The team wants customers ranked by how much they're likely to spend going forward, using the demographic and behavioural data already on file, before that spending happens. Not a scoreboard of who has already spent the most.\n", "\n", "That answer resolves the first fork: this is a forecast of future value, not a description of past value, which rules out simply sorting by historical total. It also starts pinning down [Chapter 30's five-component spec](../04-ml-intro/30-ml-workflow.qmd#translating-a-business-problem-into-a-machine-learning-problem), one piece at a time, following wherever your own follow-up questions lead next:" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "
feature_window right now includes every household that ever made a purchase, but household_target only has an entry for the 4,778 with activity in months 13-24. Filter feature_window down to only the households present in household_target's index, before building a single feature from it:\n",
"feature_window = feature_window[...]\n", "
nunique() silently drops missing valuesSeries.nunique() excludes NaN by default. Section 3 found that Category is missing on about 4.8% of rows, not an edge case, a purchase with no category recorded is still a real purchase. A household whose only \"unusual\" item was one of those missing-category rows gets undercounted by exactly one category, and nothing in the output tells you it happened.\n",
"Category with a placeholder before counting, so a missing category becomes its own countable group instead of vanishing:\n",
"fixed_categories = feature_window.assign(Category=...).groupby(\"Survey ResponseID\")[\"Category\"].nunique()\n", "
R_a buys $50 and $30 in the feature window (days 0-364) and $100 in the target window (day 397). If someone later changed the window boundary and accidentally let that $100 purchase into the features, what assertion would catch it? Fill in the blank:\n",
"result = build_feature_matrix(purchases_df, survey_df, settings)\n",
"assert result.loc[\"R_a\", \"early_spend\"] == ...\n",
"top_decile_lift(y_true, y_pred): find the predicted-spend threshold marking the top 10% of predictions, then return the ratio of (actual spend of households at or above that threshold) to (actual spend of every household). A model with no predictive power at all should score close to 1.0, it can't tell the top decile from anyone else. A useful model should score well above 1.0.\n",
"def top_decile_lift(y_true, y_pred):\n",
" threshold = ...\n",
" return ...\n",
"