CustomerBase, the shared contract#
Every model in clvkit reads the same object. BGNBD, MBGNBD,
GammaGamma and CLV don’t each parse your transaction log their own way.
They take a CustomerBase, the one place a raw log becomes an RFM summary, and
fit on that.
from clvkit import CustomerBase, CLV
cb = CustomerBase.from_transactions(log, time_unit="W", collapse="D")
CLV().fit(cb).predict(horizon=52, discount_rate=0.001)
Three columns go in, defaulting to customer_id, date and amount, one
row per purchase. Everything else in the frame is ignored. Building the summary
once and handing the same object to every model is the point. The alternative,
each model re-deriving recency and frequency from the raw log, is where two
models in the same script quietly disagree about what a customer’s recency is.
The philosophy: refuse rather than guess#
The contract has opinions, and each states its price out loud.
Naming the grain is consent to it. time_unit is the clock the parameters
are reported in; collapse is the grain at which same-period purchases become
one event. Leave collapse off and it inherits time_unit, which is the most
common way to fit a defensible-looking model to the wrong data. time_unit="W"
alone doesn’t re-scale the ruler, it deletes every second purchase inside a week,
most of them from your heaviest buyers. So the summary makes you name the grain
instead of inheriting it in silence.
It won’t invent a ruler it doesn’t have. Report weeks off daily events and it converts cleanly, seven days to a week. Report months and it raises, because a month is 28 to 31 days and there’s no exact day-to-month conversion. It refuses rather than pick an average and call it precision.
Negative amounts are your call. Returns and refunds arrive as negative rows.
on_negative names the choice: "net", the default, lets them cancel spend,
"drop" discards them, and "raise" stops so you can look before deciding.
The cost is a step you can’t skip. You build the CustomerBase first, and you
answer its questions about grain and signs before any model reads your data.
That’s the trade, a little ceremony up front against two models disagreeing in
silence later.
See Core for the full signature.