SpendProbe.mixes_channels#
- SpendProbe.mixes_channels(*, non_date_dims)[source]#
Measure whether one channel’s spend moves another channel’s column.
The separable readout takes column m of a single all-channels counterfactual to be channel m’s unilateral counterfactual. That holds only while \(v_{t,c}\) is a function of channel c’s spend alone, and nothing in the MMM enforces it:
forward_passhands the saturation the whole(date, channel)tensor, so a transform with a shared denominator – \(x_c / (1 + \sum_{c'} x_{c'})\), channels competing for one pool of attention – is expressible and makes that readout wrong for every channel at once.Neither of the other guards can see it.
measure()collapses every non-date dimension before comparing, so a move that stays within a date is invisible to it, andassert_increment_is_complete()sums over channels, which is exactly the operation the cross-column movement is conserved under. So it is measured here, and separately: one probe per spending channel, perturbing that channel alone at a date it spends, checking that no other channel’s column moves. Per channel rather than once, because mixing need not be symmetric – a single probe on the channel nothing leaks into would come back clean.The cost is one call to the already-compiled evaluator per spending channel, so callers should ask only when a per-channel column is going to be read.
- Parameters:
- non_date_dims
mapping Per node, the dimensions its evaluation carries after
sampleanddate. Onlychannel_contribution’s entry is read, to find which axis the channels lie along: it is not always the last one, and an assumption there would silently compare the wrong slices.
- non_date_dims
- Returns:
- bool
Whether the channels have to be perturbed one at a time.
Truealso when some spending channel could not be probed: correctness beats speed, and per-channel scenarios are right either way while assuming separability is silently wrong.