learn-rfm-modeling-with-phoebe / Session 6 of 6
Learn RFM Modeling with Phoebe · Session 6 of 6

Keep it alive

An RFM table is a photograph with a date on it. It earns its keep only if it is retaken on a cadence, compared with the last one, and written down precisely enough that the next analyst produces the same table from the description alone. And it should know when its job is done: three digits answer "who, right now", and the moment the question becomes "how much next year" or "who is about to leave", the honest move is to hand the table to a model built for that question. Tonight we take the second photograph, read the drift between the two, write the one-page spec, and run the table through the gate it has to clear before anyone acts on it.

🟡 Core Analysts, CRM and data leads Python, pandas 45 min
0-3 · Welcome 3-18 · Cadence, drift, the spec, the handoff 18-40 · Build-along: two snapshots and the one-page spec 40-45 · Q&A
Part 0

Where we start

Five sessions built one table: three columns on 93,358 real customers, a knife whose position decides who is called best, eight named corners with an action each, and a holdout behind every action. Every one of those numbers was measured from 1 September 2018. That date is the table's expiry stamp. Most RFM projects die not because the model was wrong but because it was built once, presented once, and then quietly re-run against a moving "today" by someone who had never read the original decisions. The last session is about the part after the build: the second photograph, the drift between the two, the page that makes the table reproducible, and the question of when RFM should stop being the model.

🎫 Clear all three quiz questions to stamp your passport Live = presented in the 45 minutes Self-study = the full depth, after class ★ Build-along = run the code yourself

★ What you walk out with tonight. Two dated RFM tables and the code that compares their knives, their group sizes and their movers; a ten-field spec written as a JSON file a colleague could rebuild the table from; a check that fails loudly if the snapshot ever stops being a literal; and an eight-row gate that says whether the table is allowed to leave the notebook.

Part 1 · covers cadence and drift

Two photographs, three months apart

5 min live

Recompute on a declared cadence: monthly for a marketplace, weekly for a live-commerce room where a customer's whole relationship can run in a fortnight, quarterly for a small shop whose customers buy a few times a year. Each run is saved with its snapshot date in the filename, rfm_2018-09-01.csv, never rfm.csv. Between any two dated snapshots three things drift, and the one everyone forgets is the knife itself.

TWO PHOTOGRAPHS, THREE MONTHS APART same marketplace, same code, a different date on the frame High value Key win-back Key develop General develop Low value rfm_2018-06-01.csv High value Key win-back Key develop General develop Low value rfm_2018-09-01.csv customers move corner to corner and the knife under both tables moves too the tangerine arrow: the move that costs money if nobody sees it Frequent, big and gone quiet in June. Quiet, once-equivalent and small by September. Nobody sent the win-back message in between.
🔍 Click to zoom - the crosstab between two dated tables is the only place this arrow is visible, and it needs both dates on both frames
LiveThree things drift, and the knife is the one people forget3 min▶

First, the knife. With the cut at the mean, "high spend" means above the mean of M in that snapshot. New customers arrive, a few large baskets land, and the mean moves. A customer who spent R$ 160 has not changed, but in one snapshot she is below the line and in the next she is above it. The groups look different and nobody bought anything. The same happens to the frequency mean and to the recency mean, which drifts on its own as the whole base ages against a later snapshot date.

Second, the group sizes. Even with the knife frozen, the shares move because the base moves: a marketplace that acquires 6,000 customers a month adds them almost entirely to General develop and Low value, so the Key groups shrink as a share without losing a single member.

Third, individual customers moving corner to corner. This is the drift that pays for the cadence. The migration crosstab between two snapshots, one row per June group and one column per September group, is built step by step in the retention course's analyst session 4 and is described here in one paragraph rather than rebuilt: join the two tables on the person id, cross-tabulate the old group against the new, and read the off-diagonal cells. The cell that matters most is Key win-back to Low value, because it is the customer who was frequent and big, went quiet, and was not reached before "quiet" became "gone".

Self-studyFix the knife or let it move: recomputing the mean each snapshot versus freezing the threshold from the first3 min▶

There are two defensible policies and they answer different questions. A frozen threshold, the mean of M from the first snapshot written into the spec as a number, keeps the groups comparable across time: a customer only changes group when she changes. That is what you want when the question is "are we moving people up", because then the movement is the signal. A moving knife, the mean recomputed each run, keeps the groups honest to the current base: "high" means high among the customers you have now. That is what you want when the question is "who are the top people to call this month", because a threshold frozen in a year when baskets were smaller will eventually call half the base "high".

The trade-off is real and neither side wins it. What is not defensible is not knowing which one you are running. A team that recomputes the mean each month and then reports "Key win-back grew 20 percent" is reporting knife movement as customer movement. Write the policy into field 6 of the spec, and if it is the moving knife, print the knife's value in every run so a reader can see how much of the change is the line moving.

Real world

Why the Olist knife is so easy to move. On the 93,358 customers the mean spend is R$ 165.20 and the median is R$ 107.78. When the mean sits that far above the median, the spend column is dragged right by a few large baskets, and only 29.3 percent of customers clear the mean line. One quarter with a handful of unusually large orders lifts the mean for everyone, and a customer who has not bought since spring slips from Key potential to Low value without doing anything. A median knife would move far less between snapshots, at the cost of calling half the base "high" by construction. Whichever you choose, the reason it moved has to be readable in the run log, not reconstructed in a meeting.

Part 2 · covers the one-page RFM spec

Ten fields on one page

5 min live

Session 1 opened with six decisions that every RFM build makes and most tutorials make silently. Sessions 2 to 5 added four more. Written down, the ten fit on one page, and that page is the difference between a table someone can rebuild and a table that lives in one analyst's notebook until she leaves. If any field is missing, two analysts will disagree about the table and both will be right.

THE ONE-PAGE RFM SPEC ten fields, one page, no adjectives 1 person id 2 which orders count 3 spend, total or avg 4 window 5 snapshot date + its rule 6 the cut, and why 7 taxonomy + actions 8 holdout share 9 cadence 10 owner WHAT IT IS NOT not a methodology document not a slide with segment names one page, literal values only IF ONE FIELD IS BLANK two analysts build two tables and both of them are right FIELD 5, CIRCLED the date and the rule that chose it: end of the last complete month, 2018-09-01 A spec is finished when a colleague who has never seen the notebook rebuilds the table from the page alone and gets the same counts.
🔍 Click to zoom - field 5 is circled because it is the one that silently changes when nobody writes it down
LiveTen fields, one page, no adjectives3 min▶

Each field is a literal value, not a description. 1, person id: the column name, customer_unique_id, and one line on why it is not customer_id. 2, which orders count: order_status == "delivered". 3, spend: the column, payment_value summed per order, and the word total or average. 4, window: all time, or the trailing number of months. 5, snapshot date and the rule that chose it: 2018-09-01, end of the last complete month, found by plotting orders per month. 6, the cut and why: mean split, because the tutorial the team learned from used it, or median split because the spend column is skewed, or Klaviyo's thresholds because the tool decides. The why is what a successor needs when she wants to change it. 7, the taxonomy and the action per group: the eight names from session 1 and the action table from session 5, as a table, not a paragraph. 8, holdout share: the fraction of every audience that is not messaged, so the action can be proved. 9, cadence: monthly, weekly or quarterly, and the day of the run. 10, owner: a name.

Adjectives are the failure mode. "Recent customers with meaningful spend" is a sentence two analysts read two ways. "R below 239.5 days, M above R$ 165.20" is a sentence that produces one table.

Self-studyThe spec as a data contract: a change to any field arrives as a diff to review, not as a surprise in the segment counts2 min▶

Once the spec is a file, it can be put under version control next to the code, and then a change to the model is a change to the file. Someone moves the knife from the mean to the median: that is a one-line diff in field 6, and the reviewer asks "why" before it runs, instead of asking "why did Key win-back double" after the segment counts land in a dashboard. Someone shortens the window to twelve months: a diff in field 4, and the reviewer knows to expect the frequency column to fall. The eight-group taxonomy from session 1 lives in field 7 as a dictionary, so swapping it for Klaviyo's six or Putler's eleven is also a diff, and the reviewer can pull up the finding from sessions 2 and 3 that under those two maps nine in ten "Champions" on a one-time-buyer base bought exactly once.

This is the same discipline any shared dataset gets from a schema: the consumers of the table are protected from silent change because the definition is a reviewable artefact. RFM tables rarely get it because they start life in a notebook, and the spec is how they leave it.

Real world

When the tool is the spec. A shop that runs its segments inside Klaviyo has field 6 filled in by the vendor: recency scored 3 within 180 days and 2 within 365, frequency and monetary by thirds, six groups with published code lists, as the help centre states them. That is a legitimate spec, and it is a good one to copy into the page precisely because it is a vendor default: the day the shop exports its customers to a warehouse and an analyst rebuilds the segments in pandas, she needs to know that "Champions" meant those thresholds and not the mean split. Without the page, the two systems disagree, the marketing team trusts the one with the prettier dashboard, and both were right.

Part 3 · covers the handoff

Three questions RFM cannot answer, and one it usually can

5 min live

RFM answers "who, right now, by three coordinates". It is a description of the present. Three questions sound similar and are not: what is this customer worth over the next year, which customers are about to leave, and are there natural groups I have not drawn. Each has a model built for it, each takes the RFM table as an input, and none of them should be reached for until the question is actually being asked.

WHICH MODEL ANSWERS THE QUESTION I AM ACTUALLY ASKING Do I need a money forecast per customer, for next year? Do I need a probability that this customer is leaving? Do I suspect groups in the data that I have not drawn? RFM is enough three digits, eight actions, one holdout YES YES YES NO NO NO, USUALLY Lifetime value BG/NBD + Gamma-Gamma, retention a6 Churn model a classifier with a label, retention a7 Clustering k-means on the raw three, Unsupervised wants R, F and tenure per customer wants a definition of "left", then R, F, M wants the three columns scaled, not scored Each handoff takes the RFM table as its input. None of them replaces the ten-field spec; they each add fields to it.
🔍 Click to zoom - the tangerine box is the common exit, and on a 97 percent one-time-buyer base it is also the honest one
LiveThree questions RFM cannot answer, and one it usually can3 min▶

"What is this customer worth over the next year?" is a forecast, and RFM has no time axis in its output. The buy-till-you-die family, BG/NBD for how many purchases and Gamma-Gamma for how much each one is worth, answers it, and it takes recency, frequency and tenure per customer at the snapshot as its inputs. It is built in the retention course's analyst session 6.

"Which customers are about to leave?" is a probability per person, and it needs a label: a definition of "left" that can be checked on past customers. RFM's "quiet" corner is a heuristic for this, not a model of it. The churn model in the retention course's analyst session 7 is the tool, and R, F and M are usually three of its strongest features.

"Are there natural groups I have not drawn?" is the one question the eight corners cannot answer by construction, because the corners were drawn first. Clustering lets the data propose the groups, and the Unsupervised Learning course uses the three-number RFM portrait as exactly those features.

And the one it usually can: "who do I message this month, and with what". For a shop with one channel and a monthly send, three digits and eight actions are the whole model, and reaching for anything heavier is a way of avoiding the holdout question from session 5. Say so plainly in field 6 of the spec: "RFM is enough, revisit when the question changes".

Self-studyWhat each handoff needs from the RFM table2 min▶

Lifetime value wants three columns per customer measured at the snapshot: recency (in the buy-till-you-die convention, the age of the customer at her last purchase), frequency as repeat purchases, and tenure, the time from first purchase to the snapshot. Tenure is not in the standard RFM table, so the handoff means adding one aggregation, first=("order_purchase_timestamp", "min"), to the session 1 pipeline. Everything else transfers as is, including the frozen snapshot, which the model needs for exactly the same reason RFM does.

Clustering wants the three raw columns, not the scores. The 0 and 1 digits are the answer to a question already asked; feeding them to k-means asks the algorithm to rediscover the corners you drew. Feed it R, F and M scaled, usually log-transformed first because M and F are heavily right-skewed on any retail base, and let it propose groups the taxonomy has no name for. Then compare its clusters against the eight corners with the same crosstab from Part 1.

A churn model wants a label before it wants features. The label is the part RFM cannot provide: "did not buy within N days after the snapshot" is one honest definition, and it requires a later snapshot to exist. The dated files from Part 1 are therefore also the training data for the handoff, which is another reason to save every one of them.

Real world

A lifetime-value model on a base that mostly bought once. On Olist 97.0 percent of the 93,358 customers have exactly one delivered order, and the 2,801 repeat buyers hold 5.6 percent of revenue. A buy-till-you-die model learns the repeat rhythm of a base from the customers who have one, and here that is 2,801 people out of 93,358. The model will fit; its forecast for the other 90,557 will lean almost entirely on the population prior, because each of them has contributed one event. That is not a reason never to build it. It is the reason the handoff decision should be made after looking at the frequency column, and why "RFM is enough" is the honest exit for most marketplaces with this shape.

Part 4 · the final scorecard

Eight checks before the table leaves the notebook

4 min live

Sessions 1 to 3 asked halfway questions about definitions. This is the ship gate: eight things that are either true of the table in front of you or not, and a table that fails any one of them is a table someone will act on for the wrong reason. Score the table you have, not the one you intend to build.

THE SHIP GATE eight ticks open it, seven do not SHIP the arm lifts on 8 of 8 7 of 8 is a table with a reason to be wrong built in Snapshot date is a literal in the code, with its reason The person id is the person, not the checkout The cut is written down, and why that cut Bought-once share of the top group has been checked Every action has a holdout behind it One dated file per run, none overwritten The taxonomy's empty-room count is known The spec has a named owner The owner is tangerine because it is the check that keeps the other seven true next quarter, when nobody in tonight's room is running the code.
🔍 Click to zoom - seven of these are about the table; the eighth is about who will still be looking at it in six months
The snapshot date is a literal in the code, with the reason it was chosen in a comment beside it, and nothing in the pipeline computes "today" from the data or the clock. Session 1. On Olist the last timestamp is 17 October 2018 and the honest snapshot is 1 September, because the 20 orders dated later are all undelivered. A computed maximum would have made every customer 47 days staler than the data supports, and would move again on the next export.
The person id column is the person, and I have checked its cardinality against the order count rather than trusting the column name. Session 1. Olist carries customer_id, one per order, 99,441 of them, and customer_unique_id, 96,096. Group by the first and frequency is 1 for everyone by construction; group by the second and 2,801 repeat buyers appear.
The cut is written down as a literal rule, with the reason for choosing it, and if the knife moves each run the run prints the knife's value. Sessions 2 and 3. Mean split, median split, thirds, quintiles or a merchant threshold each produce a different set of "best" customers from the same people. The choice is legitimate; the silence about it is not.
I have printed the share of the top group who bought exactly once, and I can say the number out loud before anyone calls them Champions. Session 3. Replicating two vendor defaults on the Olist population put 13.9 and 16.1 percent of customers in the top group, and 91.0 and 92.1 percent of them had bought exactly once. The mean-split top group is 1.28 percent and none of them bought once.
Every action in the action table has a holdout share written next to it, and the holdout is drawn inside the group, not from the whole base. Session 5. Without a held-out slice of the same group there is no way to tell the message's effect from the group's own reorder rate, and the win-back audience is the group where the two are most easily confused.
Every run writes a new file with its snapshot date in the name, the previous file is never overwritten, and the two most recent can be joined on the person id. Session 6. The migration crosstab in Part 1 only exists if both photographs still exist. A file called rfm.csv is a table with no history and therefore no drift to read.
I know how many of the taxonomy's rooms are empty or nearly empty on my base, and the action table does not assign budget to a room with nobody in it. Sessions 1 and 3. Replicating Putler's eleven segments on the Olist population filled 7 of 11, because averaging frequency and monetary quintiles never reaches the corner values when 97 percent of the frequency column is ties. With the mean split, General potential is 0.52 percent and General retain 0.42 percent of customers.
The ten-field spec names one owner, that person has agreed to it, and the next snapshot date is in their calendar. Session 6. Seven of these checks describe the table tonight. The eighth is the only one that keeps them true after tonight, and it is the one most often left blank because it is not a technical question.
Real world

The empty rooms, counted. Row seven exists because of a specific replication. Putler's eleven-segment map, run on the 93,358 Olist customers with rank quintiles, produced 7 non-empty segments: New, Promising, Can't lose and Lost held nobody, because averaging the frequency and monetary quintiles never reaches the corner values when 97 percent of frequency is a single tied value. An action table copied from the vendor's page would have assigned a message, a budget line and a KPI to four rooms with no one in them. The check takes one line, value_counts() on the group column, and it belongs in the gate because the taxonomy's poster never shows the counts.

Build-along

Two snapshots and the one-page spec

★ 22 min · everyone builds

Seven steps in pandas. The September table is the one from session 1. The June table is session 1's homework: the same six steps with SNAPSHOT = datetime(2018, 6, 1), saved as rfm_2018-06-01.csv. Only the September figures below are quoted as measured; wherever a line depends on your June table, the comment says so, and your numbers will differ from the person next to you if any of the ten fields differs.

THE PIPELINE, NOW A LOOP session 1's six steps, run again every cadence, each run leaving a dated file behind 1 · load orders delivered only 2 · freeze the clock a literal, with its rule 3 · R, F, M one row per person 4 · the knife as written in field 6 5 · groups, actions holdout per group 6 · dated save rfm_DATE.csv every cadence: same code, new date, compare with the last file before acting next run in the calendar rfm_2018-06-01.csv rfm_2018-09-01.csv the next snapshot, not yet taken The loop is the model. A table that ran once is a slide; a table that runs every cadence, with its files kept, is a system.
🔍 Click to zoom - box 2 is tangerine because it is the one box whose contents must change by hand every time the loop comes round

Load the two dated tables. The September file is session 1's output. The June file is your homework from session 1, the same pipeline with the snapshot moved back three months. If you do not have it yet, build it now; it takes the six steps and one changed literal.

★ Step 1 · two photographsimport pandas as pd import json, re from datetime import datetime # session 1 homework: rerun the session 1 pipeline with # SNAPSHOT = datetime(2018, 6, 1) and save as rfm_2018-06-01.csv june = pd.read_csv("rfm_2018-06-01.csv") sept = pd.read_csv("rfm_2018-09-01.csv") print(len(june), len(sept)) # your June count, then 93358

Compare the knives. With a mean split, the line between "high spend" and "low spend" is the mean of M in each table. Print both. If they differ, some of the group movement you are about to see is the line moving, not the customers.

★ Step 2 · where the knife sat, each timeprint("knife on M, June:", june["M"].mean().round(2)) # your June table print("knife on M, Sept:", sept["M"].mean().round(2)) # 165.20 print("median M, Sept:", sept["M"].median().round(2)) # 107.78 # the knife on F and R moved too; print them the same way print(june["F"].mean().round(3), sept["F"].mean().round(3)) # ..., 1.033 print(june["R"].mean().round(1), sept["R"].mean().round(1)) # ..., 239.5

Group sizes side by side. Shares, not counts, because the September base is larger. Read the Key groups first; on a marketplace that acquires fast, they shrink as a share even when nobody leaves them.

★ Step 3 · shares, side by sidesizes = pd.concat( [june["group"].value_counts(normalize=True).mul(100).round(2), sept["group"].value_counts(normalize=True).mul(100).round(2)], axis=1, keys=["june_pct", "sept_pct"]).fillna(0) sizes["shift"] = (sizes["sept_pct"] - sizes["june_pct"]).round(2) print(sizes.sort_values("sept_pct", ascending=False)) # sept_pct: General develop 37.88 · Low value 31.91 · Key develop 15.10 # Key potential 12.11 · High value 1.28 · Key win-back 0.79 ... # june_pct and shift: your June table decides these

Join on the person and cross-tabulate. One line. Customers who did not exist in June appear as "new". How to read the off-diagonal cells, and the persona version of the same table, is taught in the retention course's analyst session 4; here the one cell to look at is the June Key win-back row.

★ Step 4 · the migration crosstabboth = sept[["customer_unique_id", "group"]].merge( june[["customer_unique_id", "group"]], on="customer_unique_id", how="left", suffixes=("_sept", "_june")) both["group_june"] = both["group_june"].fillna("new since June") moves = pd.crosstab(both["group_june"], both["group_sept"]) print(moves) print(moves.loc["Key win-back"]) # the row that costs money if nobody reads it

The ten-field spec as a dictionary. Literal values only. Field 6 carries the policy from Part 1 as well as the cut. Field 10 is a name, not a team.

★ Step 5 · rfm_spec.jsonSNAPSHOT = datetime(2018, 9, 1) spec = { "person_id": "customer_unique_id", "orders_counted": "order_status == 'delivered'", "spend": {"column": "payment_value", "aggregate": "total per customer"}, "window": "all time", "snapshot": {"date": SNAPSHOT.date().isoformat(), "rule": "first day after the last complete month"}, "cut": {"rule": "mean split on R, F, M", "knife_policy": "recomputed each snapshot, value printed", "why": "skewed M; median considered, see session 2 notes"}, "taxonomy": {"111": "High value", "101": "Key develop", "011": "Key win-back", "001": "Key potential", "110": "General potential", "100": "General develop", "010": "General retain", "000": "Low value"}, "holdout_share": 0.10, "cadence": "monthly, first business day", "owner": "a named person, not a team", } with open("rfm_spec.json", "w") as f: json.dump(spec, f, indent=2)

A check that fails loudly. Two assertions the gate cares about most: the snapshot in the script is a literal, not a computed maximum, and the person id named in the spec is actually a column in the table. Run it before every save.

★ Step 6 · check_specdef check_spec(spec, table, script_path): src = open(script_path).read() literal = re.search(r"SNAPSHOT\s*=\s*datetime\(\s*\d{4}\s*,\s*\d{1,2}\s*,\s*\d{1,2}\s*\)", src) assert literal, "snapshot is not a literal in the code" assert ".max()" not in src.split("SNAPSHOT")[1].split("\n")[0], \ "snapshot is computed from the data" assert spec["person_id"] in table.columns, \ f"person id {spec['person_id']} is not a column" assert table[spec["person_id"]].is_unique, "one row per person, or it is not RFM" assert 0 < spec["holdout_share"] < 1, "holdout share must be a fraction" assert spec["owner"] and "team" not in spec["owner"].lower(), "owner must be a person" return True check_spec(spec, sept, "rfm_build.py")

The dated save. The table and the crosstab both carry the snapshot in their names. Nothing is overwritten; the stack at the bottom of the figure grows by one every cadence.

★ Step 7 · one more photograph on the pilestamp = SNAPSHOT.strftime("%Y-%m-%d") sept.to_csv(f"rfm_{stamp}.csv", index=False) moves.to_csv(f"rfm_moves_2018-06-01_to_{stamp}.csv") print("saved", f"rfm_{stamp}.csv", "knife on M:", sept["M"].mean().round(2)) # 165.20

Put the check in the same file as the build. A check that lives in a separate notebook is a check nobody runs. check_spec goes at the bottom of rfm_build.py, before the save, so the file refuses to write a table whose snapshot has quietly become .max().

Homework

After the last session

45 min
  1. Write the ten-field spec for your own table, as a JSON file next to the code, with literal values in every field. If a field makes you hesitate, that hesitation is the decision the table has been making silently.
  2. Schedule the next snapshot. A date in the calendar, the cadence from field 9, and the reminder addressed to the owner in field 10. Then run the check from step 6 against the current script and fix whatever it refuses.
  3. Decide the handoff question honestly. Look at your frequency column first. If most of your base bought once, write "RFM is enough, revisit when the question changes" in the spec and mean it. If you have real repeat behaviour and the question is money next year, the retention course's analyst session 6 is the next build.
  4. Send the spec to one colleague and ask them to rebuild the table from it alone. No notebook, no call, just the page. If their group counts match yours, the spec is finished. If they do not, the difference is a missing field, and finding it is the whole point.
Check yourself

Three questions

Clear all three and session 6 gets stamped in your passport.

1 · Between the June and September snapshots, the share of customers in Key win-back changed. Why can that number not be read as "customers moved" without more information?

Three things drift between snapshots: the knife, the group sizes and the individuals. The knife is the one people forget, and on Olist it is especially mobile because the mean spend of R$ 165.20 sits well above the median of R$ 107.78, so a few large baskets move the line for everyone. Neither policy is wrong; not knowing which one you run is.

2 · A shop with one channel and a monthly send asks "which customers are about to leave?" and, separately, "who do I message this month?" Which model answers which?

RFM answers "who, right now, by three coordinates". Money next year is lifetime value, leaving is a churn model, undrawn groups is clustering, and each takes the RFM table as an input. For most shops with a monthly send the honest exit from the decision tree is the tangerine box, and on a 97 percent one-time-buyer base a lifetime-value model has 2,801 repeat buyers' worth of rhythm to learn from.

3 · Two analysts rebuild the RFM table from the same spec and get different group counts. The spec has no window field. Which of them made the mistake?

A missing field is a decision the table makes silently, and two people will make it two ways. The fix is never a meeting; it is a tenth line on the page. Once the spec is a file under version control, the window becomes a diff someone reviews before the run, not a surprise someone explains after it.

Sources covered

What this session teaches, and where it came from

✓The frozen snapshot and one dated file per runStandard practice. The same frozen-history discipline the retention course applies to cohort tables, at analyst session 4; the Olist ragged edge, 20 undelivered orders after August 2018, measured in this build
◐The migration crosstab between two snapshotsOwned by the retention course's analyst session 4; described in one paragraph here and linked, not rebuilt
◐BG/NBD and Gamma-Gamma as the lifetime-value handoffNamed with their inputs, recency, frequency and tenure per customer at the snapshot, and linked to the retention course's analyst session 6; not taught here
✓The Olist mean versus median spendR$ 165.20 against R$ 107.78, and 29.3 percent above the mean, recomputed on 93,358 customers from the public Kaggle dataset in this build; the 7 non-empty of 11 Putler segments replicated on the same population
✓The ten-field RFM specPractice, with no external source; the ten fields are the six decisions of session 1 plus the cut, the taxonomy and actions, the holdout and the cadence that sessions 2 to 5 added

Every fact on this page, and its verification tier, is recorded in the course's source map.

Session 6 cheat sheet · pin this

A photograph with a date on itRecompute on a declared cadence; save every run as rfm_YYYY-MM-DD.csv; never overwrite.
Three things driftThe knife, the group sizes, the individuals. The knife is the one people forget.
Freeze or move the knifeFrozen keeps groups comparable; moving keeps them honest to the base. Write down which, print its value.
Ten fields, one page, no adjectivesPerson id, orders counted, spend, window, snapshot + rule, cut + why, taxonomy + actions, holdout, cadence, owner.
Three handoffs, one usual exitMoney next year: CLV. Leaving: churn model. Undrawn groups: clustering. Monthly send, one channel: RFM is enough.
The gate is 8 of 8Literal snapshot, real person id, written cut, bought-once share checked, holdout per action, dated files, empty rooms counted, named owner.