Skip to content

chc.geo_world

Stability tier: experimental.

A geo media-mix world: each geo's KPI made by its own media, its effects drawn from a stated hierarchy, so a model fitted to it can be scored against the truth it was drawn from.

geos geos over weeks weeks. A geo's KPI a week is its base, moved by a season shared by every geo, plus each channel's lift, plus what its neighbours' media lift in it, plus noise:

y_{g,t} = P_g b_g (1 + A sin(2 pi t / 52)) + sum_c m_{g,c,t} + o_{g,t} + e_{g,t}
  • Per head. A channel's lift is the geo's population P_g times a lift per head, m_{g,c,t} = P_g beta_{g,c} D_{c,t} h_c(a_{g,c,t} / P_g): a the geo's spend on the channel through its normalised geometric adstock, h_c the channel's saturation curve with its scale in spend per head a week, and beta_{g,c} the KPI per head a week at the curve's ceiling. A geo twice the size spending twice as much lifts twice as much. As a chc.response.Channel the lift is the curve with its scale times P_g and the coefficient P_g beta_{g,c} D_{c,t}, which GeoMediaWorld.cells returns for chc.allocation.allocate_geos to plan on.
  • The hierarchy. log beta_{g,c} = log beta_c + tau_c z_{g,c}, the z standard normal and independent across geos and channels: beta_c is the channel's national median and tau_c the spread of the geos about it. A geo's base per head b_g and its population are log-normal about the mix's in the same way.
  • National media is bought nationally and allotted to the geos by population, so every geo sees the same spend per head; its lift per head still differs by geo through beta_{g,c}.
  • Spillover, an option: a geo's lift on the channels it buys itself also lifts each of its two neighbours on a ring of the geos, in their order, by spillover / 2 of what it lifts at home. National media has no neighbour to spill to.
  • Drift, an option: each channel's lift moves with the weeks by a national factor D_{c,t} = exp(d_{c,t}), d a random walk from 0 with steps of standard deviation drift a week.
  • The log's spend puts the confounding of chc.mmm in: a geo's spend per head on a channel is the channel's mean times an intensity of the geo's, log-normal with log-SD spend_spread, times 1 + policy sin(2 pi t / 52), a planner raising spend in the weeks the season raises the KPI, times a week's log-normal jitter of mean 1 and log-SD spend_noise.
  • Noise: each head's KPI a week independent with standard deviation noise, so a geo's is noise sqrt(P_g).

Every variate is drawn whatever the parameters, in one order, so two mixes that differ only in a parameter read the same variates at the same seed: common random numbers for a paired comparison.

HONEST SCOPE:

  • The hierarchy is log-normal and independent across channels: no correlation between a geo's effects, no trend, one annual season that every geo shares.
  • The neighbours are a ring of the geos in their order, not a geography.
  • National media's spend per head is the same in every geo, so the geos hold no contrast in it: a model reads its effect from the weeks alone, as a model of the whole country does, and its spread over the geos from how each geo follows the same series. Only the channels a geo buys itself vary across the geos.
  • The curves are JAX's. In double precision (jax_enable_x64) the KPI is exact to rounding; in single, JAX's default, each lift is good to single precision.

MediaChannel dataclass

One channel of a GeoMediaMix, its parameters national.

Attributes:

Name Type Description
name str

the channel's name.

curve Saturation

its saturation per head, the scale in spend per head a week.

effect float

the KPI per head a week at the curve's ceiling, the median over the geos.

spread float

the log-SD of a geo's effect about the median.

retention float

the share of its adstock a week keeps; the kernel is normalised.

spend float

the log's mean spend per head a week.

national bool

bought nationally and allotted to the geos by population.

Raises:

Type Description
TypeError

a curve that is not a chc.response.Saturation.

ValueError

an effect or a spend not above 0, a negative spread, or a retention outside [0, 1).

GeoMediaMix dataclass

The parameters a world is drawn from; draw draws one.

Attributes:

Name Type Description
channels tuple[MediaChannel, ...]

the mix's channels.

geos int

how many geos.

weeks int

how many weeks of history.

population float

a geo's median population.

population_spread float

the log-SD of the populations about it.

base float

the median geo's KPI per head a week before media and season.

base_spread float

the log-SD of the geos' bases per head about it.

season float

the annual season's amplitude, a share of the base, below 1.

policy float

how far the log's spend rises with the season, between -1 and 1: the confounding.

spend_spread float

the log-SD of a geo's spend per head on a channel about the channel's mean.

spend_noise float

the log-SD of a week's spend about its plan.

noise float

the standard deviation of one head's KPI a week.

spillover float

what a geo's media lifts in its two neighbours together, a share of its lift at home; above 0 it needs three geos for the ring.

drift float

the standard deviation a week of each channel's national random walk in the log of its lift.

length int

each adstock kernel's length in weeks.

Raises:

Type Description
ValueError

on any parameter outside the ranges above, no channels, or two with one name.

draw(seed)

One world: its geos' populations and effects, the log's spend, and the KPI it made.

GeoMediaWorld dataclass

One draw of a GeoMediaMix: its geos, the media they ran and the KPI it made.

Arrays run geo by week by channel, the channels in the mix's order.

Attributes:

Name Type Description
mix GeoMediaMix

what the world was drawn from.

population ndarray

(geos,) each geo's population.

effect ndarray

(geos, channels) each geo's KPI per head a week at each curve's ceiling, before drift.

drift ndarray

(weeks, channels) each channel's national factor on its lift, 1 in week 0.

spend ndarray

(geos, weeks, channels) the log's spend, national channels allotted by population.

base ndarray

(geos, weeks) the KPI before media, its season in.

media ndarray

(geos, weeks, channels) each channel's lift at home.

spilled ndarray

(geos, weeks) what the neighbours' media lift in each geo.

noise ndarray

(geos, weeks).

kpi property

(geos, weeks): the base, every channel's lift, what spills in, and the noise.

shares property

(geos,) each geo's share of the population: what national media is allotted by.

cells(week=-1)

Each geo's channels as chc.response.Channel, their lift at week's drift: a row a geo, as chc.allocation.allocate_geos takes them. Spillover is not in them.

lift(spend)

(geos, weeks): what spend, geo by week by channel, lifts in each geo, at home and from its neighbours, with nothing spent before the first week.