cleverly.datasets.make_sequential_offers#

cleverly.datasets.make_sequential_offers(n: int, *, seed: int | Generator | None = None, backend: Literal['pandas', 'polars'] | str | None = None, policy: str | None = None) → tuple[Any, dict[str, float]][source]#

Draw a weekly-offer campaign as a long panel, with the exact truth of its plans.

Customers receive an offer in each of three weeks: "none", "discount", or "shipping". The outcome full_price is a full-price order in week 4. The frame has one row per customer and week while the customer is observed.

Parameters:
nint

Number of customers.

seedint, Generator, or None

Seed or NumPy random generator.

backend{“pandas”, “polars”, “pyarrow”} or None, default=None

Dataframe backend. None uses pandas when installed, then the first available backend.

policystr or None, default=None

None draws the logged campaign. A key of OFFER_PLANS draws a synthetic deployment check: a fresh cohort in which the CRM sends that plan’s offers and every customer stays tracked.

Returns:
paneldataframe

A long panel with columns customer, week, tenure, engaged, offer, consent, p_none, p_discount, p_shipping, and full_price.

truthdict of str to float

The result of offers_truth().

See also

cleverly.datasets.offers_truth

The exact truth this function returns.

cleverly.datasets.offers_protocol

The protocol of the question the panel answers.

cleverly.LongPanel

The design that reads this panel.

Notes

The law, for weeks \(t = 1, 2, 3\), with \(L_t\) the engagement of week \(t\) and \(A_t\) the offer:

variable

law

tenure

\(N(0, 1)\)

engaged \(L_1\)

Bernoulli, logit \(0.2 + 0.5\,\text{tenure}\)

offer \(A_t\)

logged: engaged (none 0.40, discount 0.25, shipping 0.35); not engaged (none 0.30, discount 0.45, shipping 0.25)

engaged \(L_{t+1}\)

Bernoulli, logit \(-0.4 + 1.0 L_t + 2.2\,[A_t = \text{discount}] + 0.5\,[A_t = \text{shipping}] + 0.3\,\text{tenure}\)

consent

Bernoulli, logit \(2.6 + 0.8 L_t + 0.3\,\text{tenure}\)

full_price

Bernoulli, logit \(-0.9 + 2.0 L_4 + 0.4\,\text{tenure} - 0.5 D + 0.5 L_4\,\text{tenure}\)

\(D\) counts the discounts sent to engaged customers. A discount re-engages a customer who is not engaged, and it wears on a customer who already is. So the ordering of the plans is not monotone in the number of discounts. Each code expires at the end of its week, so no code is redeemable in week 4. Account closure is outside the law.

Engagement \(L_4\) is the week-4 engagement. It enters the outcome and no column.

The panel carries the logged probabilities in p_none, p_discount, and p_shipping, so a design can declare them as the known offer mechanism. A policy draw sets the probability of the deployed offer to one.

Examples

>>> from cleverly.datasets import make_sequential_offers
>>> panel, truth = make_sequential_offers(200, seed=0)
>>> list(panel.columns)[:5]
['customer', 'week', 'tenure', 'engaged', 'offer']
>>> int(panel["week"].max())
3
>>> sorted(panel["offer"].unique())
['discount', 'none', 'shipping']
>>> round(truth["ate_regimen[win_back vs none]"], 3)
0.072