Skip to content

Add FrequencyReachAdditiveEffect#1968

Draft
PabloRoque wants to merge 8 commits intopymc-labs:mainfrom
PabloRoque:freq-reach
Draft

Add FrequencyReachAdditiveEffect#1968
PabloRoque wants to merge 8 commits intopymc-labs:mainfrom
PabloRoque:freq-reach

Conversation

@PabloRoque
Copy link
Contributor

@PabloRoque PabloRoque commented Oct 1, 2025

Description

Add Frequency/Reach support via the MuEffect interface.
Similar, but slightly different (see below) to the implementation described here

Notes:

1) The formulation is not strictly equivalent. Notice that our frequency_sat: Tensorvariable already applies the multiplicative Beta coefficient. If we want a fully equivalent implementation we need to call , but then we cannot rely on .apply to do dim_handling.

  1. Introduced HillShapeSaturation to make the formulation equivalent

  2. This is a very drafty feature. No support for optimization whatsoever.

Related Issue

  • Closes #
  • Related to #

Checklist


📚 Documentation preview 📚: https://pymc-marketing--1968.org.readthedocs.build/en/1968/

@PabloRoque PabloRoque requested a review from cetagostini October 1, 2025 10:10
@github-actions github-actions bot added the MMM label Oct 1, 2025
@codecov
Copy link

codecov bot commented Oct 1, 2025

Codecov Report

❌ Patch coverage is 21.73913% with 108 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.49%. Comparing base (eb4daf4) to head (9b2b909).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ting/mmm/additive_effect/frequency_reach_effect.py 17.69% 107 Missing ⚠️
pymc_marketing/mmm/components/saturation.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1968      +/-   ##
==========================================
- Coverage   92.51%   91.49%   -1.03%     
==========================================
  Files          68       70       +2     
  Lines        9398     9536     +138     
==========================================
+ Hits         8695     8725      +30     
- Misses        703      811     +108     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we change additive_effect.py into additive_effects/ submodule and has this as a file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All can added to __init__ so that the import is kept

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to move things around, but for me the main question of this PR is:

  • Should we somehow modify SaturationTransformation to offer strong separation between saturation asymptote and lambda, c, slope, etc?

@juanitorduz
Copy link
Collaborator

Amazing!

@juanitorduz juanitorduz added this to the 0.17.0 milestone Oct 2, 2025
@cetagostini cetagostini removed this from the 0.17.0 milestone Oct 15, 2025
Copy link
Contributor

@TeemuSailynoja TeemuSailynoja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of adstock and saturation is reversed in the documentation.
Should we reference the source paper instead of Meridian documentation?

Comment on lines +100 to +103
saturation : SaturationTransformation
Saturation transformation applied after adstock.
adstock : AdstockTransformation
Adstock transformation applied first to the effective exposure signal.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordering here is reversed in relation to the code. Code is correct – first saturation, then adstock.

Comment on lines +68 to +70
* If channels present in the DataFrame are not a subset of the MMM channel
coordinate, a ValueError is raised.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required? I would assume the opposite. Isn't reach + frequency an alternative to using impressions or spend, and having both in the model would effectively double count the contribution of the involved channels?
The estimated contributions are also separate from the channel_contribution deterministic in the parent MMM, so this could lead to confusion.

Comment on lines +222 to +227
beta = pm.Normal(
f"{self.prefix}_beta",
mu=0.0,
sigma=1.0,
dims=(self.channel_coord_name,),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps expose to user as an optional argument of the Prior class, and have this as a default value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants