Skip to content

Commit

Permalink
Add new customer_choice module (#803)
Browse files Browse the repository at this point in the history
* Update version.txt (#640)

* [pre-commit.ci] pre-commit autoupdate (#642)

* Fix build badge (#645)

* Add downloads stats to README

* Pareto/NBD Example Notebook (#646)

* notebook opening and imports

* model definition markdown

* Data Load Notebook section

* WIP model fitting section

* added notebook to docs directory

* notebook edits and graph code

* ppc section and nb cleanup

* demz sampling and WIP plotting

* WIP predictive plots

* WIP heatmap plots

* predictive plots

* WIP covariates and nbqa-ruff edits

* covariate section

* plot additions

* fig sizes

* remove model file

* add spaces, increase indentation, and fix number order to Pareto notebook (#651)

* add spaces, increase indentation, and fix number order

* explicit with 6

* Add link to new Pareto notebook (#649)

* Plot Waterfall Components Decomposition (#631)

* Creating plot waterfall

Co-Authored-By: Carlos Trujillo <[email protected]>

* requested changes

* pre-commit

---------

Co-authored-by: Carlos Trujillo <[email protected]>

* Update resources.md (#652)

Databricks should have a lower-case b.

* fix ylabel (#654)

* [pre-commit.ci] pre-commit autoupdate (#655)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.2](astral-sh/ruff-pre-commit@v0.4.1...v0.4.2)
- [github.com/pre-commit/mirrors-mypy: v1.9.0 → v1.10.0](pre-commit/mirrors-mypy@v1.9.0...v1.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Lift test fixes (#656)

* support for negative values and dates (if used)

* fix terrible spelling

* test dates in coords

* cover numpy objects

* consolidate the tests

* add to docstrings tanh saturation (#657)

Co-authored-by: Juan Orduz <[email protected]>

* Add quickstart to readme (#653)

* add quickstartt to readme

* add pymc labs logo

* typos

* add community links

* fixes

* Add contributors to README (#659)

* Add contributors to README

* small code style improvements

* Time varying intercept (#628)

* Add time-varying prior functionality to DelayedSaturatedMMM

* resolve wd's comments

* resolve failing pre-commits

* add tvp_kwargs to model_config

* fix typo

* replace softplus

* resolve minor review comments

* Add option to supply `ax` to `plot_posterior_predictive`

* bugfix: time_index was not set correctly for OOS

If provided `X` to `_data_setter` was *not* the immediate sequence following the training set, the time_index would we wrong. With this fix, the `date["time_index"]` which gets set upon providing a new `X`, is inferred from the `self.date_column` column of provided `X` (by comparing it to same date column in the training data `self.X`).

* Clean up example notebook

* Make utility function `transform_1d_array`

* 'tvp_kwargs' -> 'intercept_tvp_kwargs'

* move `infer_time_index` into utils

* add tests for new utils

* small fixes (found in tests)

* add tests to cover all added cases

* fix ruff check

* update typehints

* resolve review comments

* refactor model logic for tv intercept

* address review comment for util test

* .

* fix documentation link

* change variable name

* fix hsgp_dims

* update time_varying_prior to be centered on 1

* review fixes

* fix broken test

* add final tests

* fix coverage issues

* Update tests/mmm/test_tvp.py

Co-authored-by: Will Dean <[email protected]>

* Update pymc_marketing/mmm/tvp.py

Co-authored-by: Will Dean <[email protected]>

* Update tests/mmm/test_tvp.py

Co-authored-by: Will Dean <[email protected]>

* Update tests/mmm/test_tvp.py

Co-authored-by: Will Dean <[email protected]>

* significant improvements to notebook

* fix heading

* update notebook to make it EVEN better

* update legend, add watermark

* fix intro

* fix broken test

* copy sweep with grammarly

---------

Co-authored-by: Will Dean <[email protected]>

* Update README.md (#660)

* Add tv intrecept to readme (#661)

* add tv intrecept to readme

* add to comparison table

* fix title level (#663)

* Remove unnecessary NonImplemented errors from abstract methods (#662)

* ignore non-implemented

* remove not implemented error from abstract classes

* simplify docstrings

* Pass conv mode to adstock functions (#665)

* [Try] Fix compressed images in docs. (#667)

* Update pyproject.toml (#671)

* add license (#673)

* use grep and sed in the env line (#675)

* add sample_kwargs (#676)

* MMM NB Improvements (waterfall & error plots) (#664)

* Update version.txt (#677)

* [pre-commit.ci] pre-commit autoupdate (#683)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.3 → v0.4.4](astral-sh/ruff-pre-commit@v0.4.3...v0.4.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* v0 Streamlit MMM Explainer App (#614)

* feat(streamlit_explainer): Pushing files for Streamlit explainer app, to illustrate saturation, adstock and prior concepts in an intuitive, visual way to stakeholders and new MMMers

* chore(readme): Adding a readme for the app

* fix(env): Updating dependencies to include those needed for the Streamlit app

* Drop python 3.9 support (#615)

* drop python 3.9

* try python 3.12

* undo try python 3.12

* add lift tests check

* Add more content to the Gamma-Gamma Notebook  (#573)

* improve nb

* rm warnings and add link to lifetimes quickstart

* address comments

* feedback part 3

* remove warnings manually

* Add more content to the BG/NBD Notebook (#571)

* add more info to the notebook

* hide plots code

* fix plot y labels

* fix plot outputs and remove model build

* improve final note probability plots

* address comments

* use quickstart dataset

* feedback part 3

* remowe warnings manually

* feedback part 4

* Improve MMM Docs (#612)

* improve mmm docs init

* add more code examples to docstrings

* minor improvemeents

* typo

* better phrasing

* add thomas suggestion

* Fix `clv` plotting bugs and edits to Quickstart (#601)

* move fixtures to conftest

* docstrings and moved set_model_fit to conftest

* fixed pandas quickstart warnings

* revert to MockModel and add ParetoNBD support

* quickstart edit for issue 609

* notebook edit

* [pre-commit.ci] pre-commit autoupdate (#616)

* improve coords matching (#623)

* python 3.12 attempt (#618)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactor(saturation): Using pymc-marketing saturation functions rather than coding my own: Removing tanh, logistic and michaelis menten

* refactor(saturation): Remove Hill and Root saturations, as they aren't supported by pymc-marketing currently

* refactor(geometric_adstock): Removing custom adstock and using pymc-marketing adstock function to demo decay. Also updating latex to align with pymc-marketing, where decay factor is represented by alpha rather than beta

* refactor(delayed_adstock): Using pymc-marketing delayed geometric function rather than custom one

* fix(requirements): Adding pymc-marketing to Streamlit requirements for deployment

* Added Dev Container Folder

* refactor(weibull_cdf): Using pymc-marketing function for Weibull CDF

* fix(weibull_cdf): Fixing incorrect dataframe var name for CDF plotting df

* refactor(weibull_pdf): Using pymc-marketing function for WeibullPDF

* refactor(custom_functions): Removing adstock_saturation_functions.py file now that it is no longer required

* chore: Removing devcontainer created by Streamlit

* fix(requirements): Adding preliz to requirements

* refactor(prior_viz): Reworking the prior visualisation to use Preliz instead of custom function, as well as remove the tab-design. Prior distributions can now be specified programmatically.

* refactor(prior_functions.py): Deleting the draw_samples function and replacing it with a programmatic PreliZ function, such that the distribution object is returned when the user passes in the name of a distribution

* fix(requirements): Delete obsolete pymc requirement, which should fix deployment dependency conflicts

* chore(readme): Updating with guidelines on how to add additional distributions or transformation functions to the app

* refactor(plot_config): Moving height and width specifications into constants at top of Adstock and Saturation files, so the plot sizes are set programmatically

---------

Co-authored-by: Juan Orduz <[email protected]>
Co-authored-by: Colt Allen <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Carlos Trujillo <[email protected]>

* Correct BetaGeo docstring example (#693)

* fix example in docstring

* change docstring

* Add `BetaGeoBetaBinom` Distribution Block (#431)

* init commit

* removed scan

* Fix logp

* Remove print statement

* Add test for logp notimplemented errors

* docstrings

* dev notebook added

* updated to vectorize_graph

* import order

* update oldest pymc_version

* Update ci.yml pymc version

* Update pyproject.toml pymc version

* WIP sample prior testing

* sample prior compared against lifetimes

* increase rtol

* remove commented code, add logp reference

* fix latex docstring

* notebook testing and misc edits

* revert latex in docstring

* add ruff ignore comment

---------

Co-authored-by: Ricardo Vieira <[email protected]>

* [pre-commit.ci] pre-commit autoupdate (#705)

* Fix related to column renaming after aggregating test frequency (#698)

Co-authored-by: Colt Allen <[email protected]>

* RFM Segmentation (#680)

* init rfm_segments func

* TODOs

* docstrings and for loop

* docstrings and for loop

* WIP dev notebook debugging

* checkpoint commit for remote pull

* code testing in dev notebook

* unit tests added

* dev notebook cleanup

* clean up type hints

* comments and code cleanup

* docstrings

* move formatting to rfm_summary and quickstart edits

* fix rfm_train_test_split bug

* added test for rfm_quartile_labels

* added rfm score warning

* create bgbb_donations.csv (#710)

* closes #678 (#716)

* use URL for README image (#715)

* use URL for image

* additional links that are relative

* remove forward slashes in URL

* closes #264 (#714)

* [pre-commit.ci] pre-commit autoupdate (#719)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](astral-sh/ruff-pre-commit@v0.4.5...v0.4.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Update `BetaGeoModel` API (#709)

* _extract_predictive_variables util

* deprecation warnings

* expected_purchases_new_customer

* TestBetaGeoModel.setup_class

* test cleanup and test_expected_purchases_new_customer

* expected_probability_alive

* expected_purchases

* TODOs and docstrings

* update runslow tests rtol for new test dataset

* prob_alive_matrix plot fix and notebook testing

* alive loop in bgnbd nb

* quickstart nb fix

* docstring and TODO revisions

* docstring syntax

* docstring edits

* more docstring fixes

* docstrings indent

* docstring indent, clear codecov bug

---------

Co-authored-by: Juan Orduz <[email protected]>

* User-defined media transformations and custom ordering (#632)

* Allowing Custom Saturation & Lagging functions

Co-Authored-By: Carlos Trujillo <[email protected]>

* Small adding

Co-Authored-By: Carlos Trujillo <[email protected]>

* test commit to carlos branch

* hook up saturation class to lift method

* pull out last saturation function into method

* migrate the logic for tests

* reduce what is needed for a new class

* remove the noqa

* model docstring for inheritance

* rewrite of the components

* rewrite of the components

* change name to logistic

* map names for model_config

* switch out order

* Changes

* solving issues

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* solving errors

* New class name

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update pymc_marketing/mmm/transformers.py

Co-authored-by: Will Dean <[email protected]>

* pre-commit change

* Update pymc_marketing/mmm/models/components/base.py

Co-authored-by: Will Dean <[email protected]>

* Update pymc_marketing/mmm/models/components/saturation.py

Co-authored-by: Will Dean <[email protected]>

* Update pymc_marketing/mmm/models/components/lagging.py

Co-authored-by: Will Dean <[email protected]>

* Requested changes

* pre-commit changes

* Requested changes

* more changes

* use reserved parameter name

* Returning name and adding deprecation warning

* Re-structuring the budget optimizer

* Update pymc_marketing/mmm/components/adstock.py

Co-authored-by: Will Dean <[email protected]>

* Will recommendations

* Temporal comment

* test more assumed behavior

* more budget optimizer changes

* docstrings for classes

* small changes about l_max on adstock

* small addition

* Update pymc_marketing/mmm/delayed_saturated_mmm.py

Co-authored-by: Will Dean <[email protected]>

* temporal notebook copy

* update

* docstring

* adding missing

* Update pymc_marketing/mmm/components/adstock.py

Co-authored-by: Will Dean <[email protected]>

* Update pymc_marketing/mmm/components/adstock.py

Co-authored-by: Will Dean <[email protected]>

* Making mypy happy part 1

* Small example

* finally?

* small change

* docstrings

* removing mokeytype

* more docstrings

* Update pymc_marketing/mmm/transformers.py

Co-authored-by: Will Dean <[email protected]>

* tests for out of box saturation and adstocks

* test the passthrough

* Update pymc_marketing/mmm/components/adstock.py

Co-authored-by: Will Dean <[email protected]>

* Changes on hints

* modifications

Must be plt, ax

* notebooks addition

* Update notebook

* Saving model

* mock the fit and move tests around

* fix get_distribution tests

* Correct error

* Deprecation warning and new MMM class

Co-Authored-By: Will Dean <[email protected]>

* Small changes

* updates

* add a lookup for model saving

* fix tests based on recent changes

* Deleting decimals

* test suite

* Update tests/mmm/test_budget_optimizer.py

Juan's test suggestion

* Update tests/mmm/test_budget_optimizer.py

abs and not rel

* running notebooks

* adding hint

* initial docstring check

* update docstrings 1

* Modifying notebook

* use explicit adstock and saturation in MMM

* remove reference to DelayedSaturatedMMM

* request changes

* missing total

* modify docstring

* modify eval

* Small changes

* change

* changes

* import saturation in docstring

* pre-commit

---------

Co-authored-by: Carlos Trujillo <[email protected]>
Co-authored-by: Will Dean <[email protected]>
Co-authored-by: Will Dean <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* CLV Plotting API (#728)

* plot_probability_alive_matrix

* docstrings

* plot_frequency_recency_matrix

* delete dead code

* docstring quick fix

* [pre-commit.ci] pre-commit autoupdate (#730)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](astral-sh/ruff-pre-commit@v0.4.7...v0.4.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Fix some ParetoNBDModel docstring typos (#731)

* Removed the comma after Schmittlein

* Added missing reference

* Removed extra colon

* pass kwargs to minimizer (#737)

* Minor improvements [MMM] (#735)

* Set upper bound pymc 5.16 (#725)

* changes init

* try fix regex

* Media transformation sampling & plotting methods (#734)

* add plotting methods

* add tests for new methods

* saturation support for additional variable dims

* consolidate the logic of sampling

* change warning

* workflow from a fitted model

* change order of tests

* suggestion to use names

* because of new data

---------

Co-authored-by: Juan Orduz <[email protected]>

* improve tests mmm utils (#738)

* improve tests

* empty commit

* remove reduntant function

* `model.fit` doesn't remove prior samples (#741)

* type hint only

* more informative errors

* check for attr

* remove type ignore

* check for attr

* check for attr

* reduce indentation

* new error names

* Hierarchical Model Configuration (#743)

* some base logic and tests

* lookup function once

* add error handling

* implement for mmm and media transformations

* add examples

* add to documentation

* add to docstring

* tests for likelihood

* use deepcopy since keys are added

* set default dims and warn

* fix output_var

* migrate failing tests to model_config

* remove the moved test

* use deepcopy since keys are added

* add to docstrings from feedback

* fix handlers at initialize

* [pre-commit.ci] pre-commit autoupdate (#756)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](astral-sh/ruff-pre-commit@v0.4.8...v0.4.9)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* MMM Component Notebook (#748)

* initial notebook

* add to example

* push up some feedback

* change the number of channels

* updates

* final message

* add more feedback

* remove since wasnt working the way I wanted

* start addressing sphinx warnings and rendering issues (#750)

* start addressing sphinx warnings and rendering issues

* forgot formatting comment

* Add numpydoc as dependency

* fix dataset type

---------

Co-authored-by: Juan Orduz <[email protected]>

* Allowing Hierarchical Non Centered Parametrization (#747)

* Allowing non center parametrization

* update notebook

* Adding example in docstring

* change

* Push code changes.

* A painful and ugly change!

The things one does for democracy!

* Missing parts!

* adding missing test 2D

* Missing raise

---------

Co-authored-by: Will Dean <[email protected]>

* fix np typing (#763)

* fix typing

* empty

* empty

* empty

* dummy

* undo dummy

* empty

* add it back (#764)

* remove noqa from plots (#761)

* remove noqa

* fix escape

* empty

* empty

* Creating Time Base component for Media Contribution (#752)

* Run Ruff Notebooks (#773)

* [pre-commit.ci] pre-commit autoupdate (#779)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](astral-sh/ruff-pre-commit@v0.4.9...v0.4.10)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* `GammaGammaModel` API Improvements (#758)

* utils.customer_lifetime_value

* expected_customer_lifetime_value

* WIP clv.models.gamma_gamma.py

* gamma_gamma API

* fixed circular import

* gamma_gamma tests

* delete tests/datasets/test_summary.csv

* clv test_utils.py

* remove expected_purchases(future_t=0)

* remove monetary_value arg

* WIP docstrings

* notebooks

* docstrings

* Revert "notebooks"

This reverts commit a3154d9.

* gamma-gamma notebook

* docstrings

* Deepcopy of posterior to allow second `fit` call (#790)

* Add prior predictive example notebook (#787)

* CLV Modeling Domains and Docstrings (#785)

* quickstart

* pareto_nbd fit warning

* pareto_nbd docstrings

* clv.utils docstrings

* gamma_gamma docstrings

* beta_geo docstrings

* CLV sections in README and index

* fix nb (#793)

* Run example notebooks CI (#791)

* run notebooks init

* change kernel name

* change kernel name

* change kernel name

* rm docs

* add nb

* fix nb

* fig dpi

* update model object

* no output

* test mmm notebooks

* fix path

* graphviz

* sudo

* paralelize and Path

* reqs

* undo

* comments

* try budget allocation

* add quickstart

* ignore budget allocation

* add make command

* use make

* docs: Update model_builder.py to resolve warning in documentation build (#797)

* Time Varying Media Contribution Notebook (#778)

* Time Varaying notebook

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* requested changes

* quick change

* Adding to index md

* remove change mmm_budget_allocation_example.ipynb

* quick correction

* Small changes

* Small title change

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Will Dean <[email protected]>

* prepare release (#799)

* `Prior` class to represent distributions (#759)

* reimplement hierarchical parameters with wrapper class

* write tests for prior class

* explicit with dependencies

* missing test dependency

* raise KeyError early and test dims

* add prior module to docs

* support for media transformations

* add some documentation

* migrate the delayed_saturated model to Prior class

* migrate the delayed_saturated model to Prior class

* add an example image

* add the example image

* push up some more documentation

* support for student-t

* add configuration notebook

* catch all the errrors

* add more to notebook

* use a sigma which has transform

* add parsing error catches

* add watermark

* test the parsing warnings and error handling

* have a default to dict

* default handles Prior class

* passing plotting tests

* support for lift tests

* fix two failing tests

* update notebook

* change order of pre-commit back

* migrate to Prior and parse_model_config

* migrate the clv tests

* test for warning

* run pre-commit on all files

* switch to Prior in components

* test for the components

* remove the previous dictionary function

* migrate to Prior class in mmm

* remove the unused key,value

* migrate to Prior class in clv tests

* back to dict for warning test

* isort and work out example indentation

* change the example name

* use juans suggestions

* change graph to to_graph

* rerun the time varying media

* rerun the clv notebooks I could get to work

* [pre-commit.ci] pre-commit autoupdate (#801)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](astral-sh/ruff-pre-commit@v0.4.10...v0.5.0)
- [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](pre-commit/mirrors-mypy@v1.10.0...v1.10.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* initial stab at some docs

* register new module in a few places

* initial stab at how-to example notebook + code + API docs

* Pull out seasonality as `YearlyFourier` and `MonthlyFourier` (#802)

* Separate Weibull adstock into CDF & PDF (#810)

* separate out weibull

* add to test suite

* Add typing and package classifiers (#811)

* add pytyped

* add the py.typed file

* add imports to mmm module (#812)

Co-authored-by: Juan Orduz <[email protected]>

* Friday progress on notebook example

* Save & load support for time varying parameters (#815)

* add missing init for save and load

* get rid of warnings from JSON parsing

* new error message without line break

* migrate to Data and non-mutable coords (#816)

Co-authored-by: Juan Orduz <[email protected]>

* improve introduction page

* updates to model priors, plotting market shares, and more

* Add in model maths for MV-ITS, saturated market assumption

* [pre-commit.ci] pre-commit autoupdate (#817)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](astral-sh/ruff-pre-commit@v0.5.0...v0.5.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* fix up / improve the model maths and description

* PoC: Use Pydantic as data validator (#809)

* prior with pydantic

* dependencies

* validate adstock

* make mypy happy

* add validation sample curve

* make the prior type tighter

* add test type

* add validation init mmm

* mmm

* start with Fourier

* fix type

* fix test and imprtove docstrings

* docstrings

* types

* self type

* init validator

* types model builder

* improve docstrings

* more input validations mmm init

* validation budget optimizer

* fix dummy example types

* hsgp kwargs class

* add kwargs

* undo type hint in dict

* fix fourier names

* better docs

* fix tests

* add type hint

* undo

* fix type error

* feedback2

* restrict signature

* serialize fourier

* docs and tests

* fix docs

* work on parsing

* add hsgp to parsing config

* add tests

* uncomment

* undo changes

* undo model config parser

* handle hsgp_kwargs

* add hsgp flag

* docs

* undo type hint

* improve hints

* add more sections to docs

* Update pymc_marketing/mmm/tvp.py

Co-authored-by: Will Dean <[email protected]>

* feedback 4

* fix test

---------

Co-authored-by: Will Dean <[email protected]>

* fixes + describe covariance matrix + add section on unsaturated market model

* update title of intro docs page

* add admonition box

* initial stab at modeling products individually

* fix scenario 3 sampling & commentary + allow user to supply sampler_kwargs

* Date Validation and MMM Model Hamonization (Pydantic) (#824)

* validate base mmm init class

* validate dateformat

* add comment about date

* remove ()

* close to done on docs + code?

* add tests

* enlightenment in terms of unsaturated markets

* extract data generation functions into module code

* remove old generate_data

* constrained -> saturated. parameterize a test

* turn generate_saturated_data into a test fixture

* split plot_causal_impact into plot_causal_impact_sales and plot_causal_impact_market_share

* execute notebook

* revert an accidental change from a global find & replace

* Remove warnings during tests (#823)

* address save and load tests

* catch warning on load

* remove warnings in budget optimizer

* remove plotting warnings

* remove validating warnings

* consolidate the loading function

* remove warnings in tests

* incorporate the docstring feedback

* only one deprecation warnings on DelayedSaturatedMMM

* dont have deprecation on test

* Future-proof `prior_linearized` method call (#806)

* modified: pymc_marketing/mmm/tvp.py

* Update tvp.py

Changed Xs to X in prior_linearized( as per the change in pymc)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: sangeedutta <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Will Dean <[email protected]>

* Create inverse_scaled_logistic_saturation and the corresponding class (#827)

* fix (#842)

* use labs theme as external (#830)

* use labs theme as external

* Update docs/source/index.md

Co-authored-by: Thomas Wiecki <[email protected]>

* install theme from pypi

---------

Co-authored-by: Thomas Wiecki <[email protected]>
Co-authored-by: Juan Orduz <[email protected]>

* specify 0.9.0 as deprecation version (#849)

* add intercept and target variable to example (#850)

Co-authored-by: Juan Orduz <[email protected]>

* Various MMM small documentation fixes (#854)

* make nb ruff astral-sh/ruff-vscode#546

* fix docs

* fix quickstart

* add types

* Update UML diagrams (#856)

* update uml diagrams

* add uml command to Makefile

* Fixing ruff commands in Makefile #825 (#859)

* updated the ruff lint command, added ruff code formating command in Makefile, updated contributing.md

* fixing the issue with the new line at the end of makefile

* updated the PHONY list in makefile

---------

Co-authored-by: Murad Khalilov <[email protected]>

* [pre-commit.ci] pre-commit autoupdate (#855)

updates:
- [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](pre-commit/mirrors-mypy@v1.10.1...v1.11.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Juan Orduz <[email protected]>

* Fix model_builder docstrings (#861)

* Fix model_builder docstrings

* Fix whitespace error

---------

Co-authored-by: Juan Orduz <[email protected]>

* add GH discussions link to README (#866)

* add GH discussions link to README

* add link to MMM hub slack

* Update README.md

---------

Co-authored-by: Will Dean <[email protected]>

* Skip coords with scalar value (#868)

* Fix Visual for hill_saturation function (Issue #851 ) (#857)

* Fix plotting by evaluating tensors.

* Add space after sphinx directive.

* Remove indentation from blank line.

* Add shared y axis for subplots.

---------

Co-authored-by: Patrick Robotham <[email protected]>
Co-authored-by: Will Dean <[email protected]>

* Allow plot MMM components in the original scale (#870)

* add original scale implementation

* add plot nb

* change location

* undo

* make mypy happy

* test plot

* add test

* update plot readme

* fix test

* improve variable description

* Inference changed to dataset (#873)

* infernece changed to dataset

* inference changed dataset for plot_allocated_contribution_by_channel

---------

Co-authored-by: Will Dean <[email protected]>

* Add root saturation function (issue #702) (#858)

* feat: adding root_saturation to transformers.py

* feat: adding RootSaturation class to saturation.py

* chore: adding missing RootSaturation to SATURATION_TRANSFORMATIONS

* feat: adding root_saturation to transformers.py

* feat: adding RootSaturation class to saturation.py

* chore: adding missing RootSaturation to SATURATION_TRANSFORMATIONS

* chore: linting edits

* chore: adding coefficient to function

* chore: linting corrections

* chore: removed empty References section of docstring

* chore: produce visual examples of root saturation

* chore: adding root to test_saturation.py

* chore: adding RootSaturation to init file

---------

Co-authored-by: ruari.walker <[email protected]>
Co-authored-by: Will Dean <[email protected]>

* Check for missing attrs after `sample_prior_predictive` and `fit` (#867)

* separate the attr creation from attachment and perform check

* remove data for CLV

* fix model_builder tests

* fix clv tests

* more specific model builder checks

* rework with no args and kwargs

* rework common load method

* Update pymc_marketing/model_builder.py

* `json.loads` with python types bug (#881)

* loads doesnt support boolean

* defaults for the media transformation

* test for the time_varyign

* [pre-commit.ci] pre-commit autoupdate (#883)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](astral-sh/ruff-pre-commit@v0.5.4...v0.5.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Fix default `ConvMode` in docstring (#864)

* Update docs in transformers.py

docs: Fix the docstring

* Update docs in transformers.py

docs: fix other docstrings

* change the plot default

---------

Co-authored-by: Will Dean <[email protected]>
Co-authored-by: Will Dean <[email protected]>

* Save off media transformations (#882)

* to_dict via lookup_name

* parse to and from dict for attrs

* improve the codecov

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* change test with change in default behavior

* increase the MMM model version

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Update deployment docs (#887)

* update the notebook

* update the runner

* address feedback. add watermark

* Automate UML creation (#886)

* attempt to automate uml

* install graphviz binaries

* use sudo

* Update UML Diagrams

* remove the git configuration

* use the identity of the previous commit

* dont push with no change

* same triggers as others

* restrict to running with repo_changes

---------

Co-authored-by: GitHub Actions <[email protected]>
Co-authored-by: Juan Orduz <[email protected]>

* prepare release (#888)

* Update README.md (#893)

* Add url health job from streamlit app (#902)

* add url health job

* lint

* small improvements model config nb (#906)

* small improvements

* trim

* Add link model deployment to example notebook (#904)

* add link model deployment

* add pymc-marketing to watermark

* Add pymc-marketing version to some MMM notebooks (#907)

* nb part 1

* add mmm example nb

* rm example nb

* Move adstock and saturation method imports to mmm.__all__ (#908)

* Resolves #892: Move adstock and saturation method imports to mmm.__all__

* fix: patch uml GHA

* Update .github/workflows/uml.yml

* Update .github/workflows/uml.yml

* Add permissions: write-all to .github/workflows/uml.yml

---------

Co-authored-by: Will Dean <[email protected]>

* chore(Makefile): Adding a self-documenting command and light command documentation (#910)

Co-authored-by: Juan Orduz <[email protected]>

* Fix uml permissions (#913)

* [pre-commit.ci] pre-commit autoupdate (#914)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](astral-sh/ruff-pre-commit@v0.5.5...v0.5.6)
- [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](pre-commit/mirrors-mypy@v1.11.0...v1.11.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Will Dean <[email protected]>

* Don't run tests for non-code changes (#898)

* test running budget allocator nb (#919)

* make hill pass through the origin (#920)

* Add MMM ROAS Priors Case Study (#916)

* model and data init

* make it work

* add intro

* add nb to index and references

* first complete iteration

* improve formatting

* split paragraph

* pyprojroot

* typos

* add conclusion section

* improve intro

* typos and feedback

* typo (#923)

* [pre-commit.ci] pre-commit autoupdate (#926)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.5.7](astral-sh/ruff-pre-commit@v0.5.6...v0.5.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Original hill function definition (#925)

* MLflow autologging (#921)

* Adding BLAS to the env generation.

* Solving optimizer issues & typos (#933)

* Correcting typo num_days by horizon

* Correcting typo num_days by horizon and scaler

* Running notebooks

* Update UML Diagrams

* Rename horizon by periods

* Adding test requested to check budget outputs

* Running notebooks

* Update UML Diagrams

* Small notebook missing change.

* Correction in tests

* Change on name

* running notebook modifying function

* Update UML Diagrams

* Log number of posterior & tuning samples (#943)

* helper command to view the artifacts from test

* pass tune from kwargs

* test for support of all samplers

* add mlflow as a mock import

* actual import as autolog is missing from docs

* point to GH discussions (#944)

Co-authored-by: Juan Orduz <[email protected]>

* [pre-commit.ci] pre-commit autoupdate (#946)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.7 → v0.6.1](astral-sh/ruff-pre-commit@v0.5.7...v0.6.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* fix typo

* fix admonition block + markdown heading level

* collect results of plot funcs in tests

* add no cover comments

* explicit module level scope for fixture

* consistent use of product_incrementality for folder names

* background_sales -> existing_sales

* innovation_sales -> treatment_sales

* Fallback to defaults in `adstock|saturation_from_dict` (#955)

* Default saturation_from_dict to default_priors

* Default to AdstockTransformation.default priors in adstock_from_dict

* Add PyDocStyle Support (#951)

* change to normal likelihood truncated at zero

* use empirical estimate for HalfNormal sigma for observation noise std

* remove commented out lines

* use just one idata instance - removes idata_counterfactual

* make pre-commit checks pass

* break up code in __init__ into smaller methods

* remove @Property and deal with some consequences of that

* deprecate WeibullAdstock in favor of WeibullCDFAdstock and WeibullPDFAdstock (#957)

* deprecate in favor of WeibullCDFAdstock and WeibullPDFAdstock

* Update UML Diagrams

* Update UML Diagrams

* Raise informative error when including target in `X` (#962)

* add error when target is in X_df

* add test

* rename test

* format

* Ensure `fit` reproducibility (#963)

* use get instead of assign to default to sampler_config if exists

* default to what is given

* write tests based on the issue

* have defaults while not overriding

* Enforce `check_parameters` for `alpha` in `geometric_adstock` (#960)

* Enforce check_parameters in geometric_adstock

* Remove check_parameters on l_max. Revert to original test

* Add test_geometric_adstock_bad_alpha

* use ge, le instead of gt,lt

* Update tests/mmm/test_transformers.py

Co-authored-by: Will Dean <[email protected]>

* Simplify test parameters

* Update tests/mmm/test_transformers.py

Co-authored-by: Will Dean <[email protected]>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Will Dean <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* scaling should be done before hdi computation (#970)

* Fix UML permissions from forks / Run only on merge to main (#967)

* change repo and python file change to kick off

* comment to check for failure

* using the repo

* delete to trigger

* the previous trigger

* push up again

* check for the owner being pymc-labs

* remove unused

* only run after merge to main

* `DelayedSaturatedMMM` deprecations and moving files (#965)

* deprecations and moving files

* Update UML Diagrams

* change the imports in notebooks

* push up the code / test changes. need to run

* remove _get_\w*_function tests

* rerun the tvp notebook

* remove stale test

* move away from string initialization

* change the tvp media example

* Register and allow custom transform for `Prior` class (#972)

* allow register and use custom transform

* add to the example block

* Update pypi.yml (#975)

* [pre-commit.ci] pre-commit autoupdate (#977)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](astral-sh/ruff-pre-commit@v0.6.1...v0.6.2)
- [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](pre-commit/mirrors-mypy@v1.11.1...v1.11.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Added deprecation warning for method keyword (#974)

Co-authored-by: Will Dean <[email protected]>
Co-authored-by: Juan Orduz <[email protected]>

* Improve and sync the README and landing page. (#978)

* add table

* sync index

* empty

* improvements

* Time slice cross validation MMM Notebook (#971)

* nb init

* add param stability

* crps init

* add crps to package

* make mypy happy

* clean

* relax req

* update path

* add crps test

* first iteration

* improvements

* improve tests

* improve tests

* improve references

* feedback 1

* add examples

* fix the wrong trigger (#983)

* Media transformation class and different transformations based on subsets of channels (#968)

* Changed pt=pt to pt_lib = None to avoid showing full module path in docs (#992)

* Changed pt=pt to pt_lib = None to avoid showing full module path in docs

* Changed pt to pt_lib inside calculate_lift_measurements_from_curve

* Linear trend (#991)

* push up some linear-trend work I had

* image for the documentation

* start the slopes at t=0

* additional checks at init

* import at mmm module

* update the image

* Move to reference section

* Move coords to single line

* Add hierarchical trend example

* add image for the example

* migrate to pydantic

* update images after seed

* Correct the latex

* fix check from tests

* revert link

* add item for new module

* Use ModelBuilder for incrementality model (#1101)

* use the modelbuilder mixin

* use Prior class for the background_distribution

* modify the tests

* test for mismatch

* add informed prior method

* begin to modify the existing notebooks

* add y to the mix

* changes to saturated notebook

* run with the accept kwargs

* rename product_incrementality module to cc

* fix typo

* add a placeholder quickstart docs page

* rename a folder (previously missed) product_incrementality -> cc

* more instances of product incrementality -> customer choice

* market_saturated -> saturated_market

* add quickstart/cc/index to the getting_started page

* avoid reusing `result` for multiple different scenarios

* notation change: rename "background" to "existing"

* add kwarg plot_total_sales to plot functions

* rename module cc -> customer_choice

* remove type hint on plot_total_sales to make tests pass

* get docs to build / pass tests

* finish the customer_choice getting started page

* fix typo

* remove manual tight_layout commands

* fix typo

* grammatical fix

* fix typo

* surpress a plot output

* fix typo

* update imports + split data generation code into different .py file

* fix imports in tests

* add docstrings to synthetic data module

* switch rng parameter to random_seed

* import from the customer_choice over submodule

* add docstrings to the model and plot function and mypy

* add file autolabeling

* allow for ax argument

* fix the test seed name

* change import

* increase tests / find some holes

* use random_seed instead of rng

* fix conflicts

* lint

---------

Co-authored-by: Juan Orduz <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Colt Allen <[email protected]>
Co-authored-by: Will Dean <[email protected]>
Co-authored-by: Carlos Trujillo <[email protected]>
Co-authored-by: Carlos Trujillo <[email protected]>
Co-authored-by: Corey Abshire <[email protected]>
Co-authored-by: Ulf Aslak <[email protected]>
Co-authored-by: nialloulton <[email protected]>
Co-authored-by: Louis Magowan <[email protected]>
Co-authored-by: Ricardo Vieira <[email protected]>
Co-authored-by: Ivan Ugrin <[email protected]>
Co-authored-by: Will Dean <[email protected]>
Co-authored-by: Mews <[email protected]>
Co-authored-by: Oriol Abril-Pla <[email protected]>
Co-authored-by: c0d33ngr <[email protected]>
Co-authored-by: Shuvayan Das <[email protected]>
Co-authored-by: sangeedutta <[email protected]>
Co-authored-by: Arthur Mello <[email protected]>
Co-authored-by: Thomas Wiecki <[email protected]>
Co-authored-by: Murad Khalil <[email protected]>
Co-authored-by: Murad Khalilov <[email protected]>
Co-authored-by: Giannis_apost <[email protected]>
Co-authored-by: Christian Luhmann <[email protected]>
Co-authored-by: Patrick Robotham <[email protected]>
Co-authored-by: Patrick Robotham <[email protected]>
Co-authored-by: Ishaan Jolly <[email protected]>
Co-authored-by: Ruari Walker <[email protected]>
Co-authored-by: ruari.walker <[email protected]>
Co-authored-by: Maxim Kochurov <[email protected]>
Co-authored-by: GitHub Actions <[email protected]>
Co-authored-by: Pablo de Roque <[email protected]>
Co-authored-by: Dan Dean <[email protected]>
Co-authored-by: Pablo de Roque <[email protected]>
Co-authored-by: radiokosmos <[email protected]>
Co-authored-by: Juan Orduz <[email protected]>
  • Loading branch information
1 parent 327ac97 commit 5f74b4d
Show file tree
Hide file tree
Showing 29 changed files with 4,416 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ CLV:
- docs/source/notebooks/clv/*
- pymc_marketing/clv/**

"customer choice":
- changed-files:
- any-glob-to-any-file:
- docs/source/notebooks/customer_choice/*
- pymc_marketing/customer_choice/**

"Prior class":
- changed-files:
- any-glob-to-any-file:
Expand Down
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<!--- Please list the modules that are affected by this PR by typing an `x` in the boxes below: -->
- [ ] MMM
- [ ] CLV
- [ ] Customer Choice
<!--- Additionally, if you are a maintainer or reviewer, please make sure that the appropriate labels are added to this PR -->

## Type of change
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ Classes
![](docs/source/uml/classes_clv.png)
## Overview of the customer choice codebase
Packages
![](docs/source/uml/packages_customer_choice.png)
Classes
![](docs/source/uml/classes_customer_choice.png)
---
This guide takes some inspiration from the [Bambi guide to contributing](https://github.com/bambinos/bambi/blob/main/CONTRIBUTING.md)
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ uml: ## Install documentation dependencies and generate UML diagrams
pip install .[docs]
pyreverse pymc_marketing/mmm -d docs/source/uml -f 'ALL' -o png -p mmm
pyreverse pymc_marketing/clv -d docs/source/uml -f 'ALL' -o png -p clv
pyreverse pymc_marketing/customer_choice -d docs/source/uml -f 'ALL' -o png -p customer_choice

mlflow_server: ## Start MLflow server on port 5000
mlflow server --backend-store-uri sqlite:///mlruns.db --default-artifact-root ./mlruns
Expand Down
1 change: 1 addition & 0 deletions docs/source/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
model_config
model_builder
prior
customer_choice
metrics
mlflow
deserialize
Expand Down
1 change: 1 addition & 0 deletions docs/source/getting_started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ installation/index

quickstart/clv/index
quickstart/mmm/index
quickstart/customer_choice/index
:::
41 changes: 41 additions & 0 deletions docs/source/getting_started/quickstart/customer_choice/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Customer Choice Quickstart

```python
from pymc_marketing.customer_choice import (
MVITS,
generate_saturated_data,
plot_product,
)

# Generate simulated data
scenario = {
"total_sales_mu": 1000,
"total_sales_sigma": 5,
"treatment_time": 40,
"n_observations": 100,
"market_shares_before": [[0.7, 0.3, 0]],
"market_shares_after": [[0.65, 0.25, 0.1]],
"market_share_labels": ["competitor", "own", "new"],
"random_seed": rng,
}

data = generate_saturated_data(**scenario)

# Build a multivariate interrupted time series model
model = MVITS(
existing_sales=["competitor", "own"],
saturated_market=True,
)

model.inform_default_prior(
data=data.loc[: scenario1["treatment_time"], ["competitor", "own"]]
)

# Parameter estimation
model.sample(data[["competitor", "own"]], data["new"])

# Visualize the results
model.plot_fit();
```

See the {ref}`howto` section for more information about using the customer choice module.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/guide/customer_choice/inc_simple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions docs/source/guide/customer_choice/incrementality_intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Introduction to causal product incrementality analysis

Let's say your company sells many products into a busy market with many competitors. Over time, both you and your competitors have been releasing new products and discontinuing old ones. Your goal, depending on your company’s strategy, might be to increase market share, sales volume, or profit margins.

One way this is typically done is by releasing new products that you believe will appeal to customers more than your competitors’ products. But how do you know if your new products are successful? If your new product sells, those sales could be coming from a variety of sources:
* **Market growth**: These are sales that come from new customers entering the market. This would be a desirable outcome.
* **Incremental sales**: These are sales that come from customers who would have bought a competitor’s product if you hadn’t released your new product. This is also a good outcome.
* **Cannibalistic sales**: These sales have come from customers who would have bought one of your existing products if you hadn’t released your new product. So while the new product's sales are non-zero, they are cannibalizing sales from your existing products.

So we can imagine a busy marketplace of products, where each product has a certain number of sales. We also have many new products being released and old products being discontinued.

In an effort to understand where sales are coming from we may have purchased retail sales data which gives us time series data on the sales of all products in the market. But how can we use this data to understand where our sales are coming from?

If we can do this, then we could gain valuable insights that could inform our company’s product portfolio decisions. For example, we could:
* Understand which products are driving market growth
* Understand which products are incremental, taking sales from competitors
* Understand which products are cannibalizing sales from other products

We might be tempted to think of cannibalistic sales as 'bad'. But this need not be the case, it depends entirely on your product strategy. For example, it could be that you want to constantly improve your product line up, releasing products with better attributes which are superior to competitors. In this case, you would expect sales of older products to decrease as customers switch to the new products. This can be a desirable outcome.

## Causal inference in product incrementality

Let's develop the basic logic behind a causal understanding of product incrementality. We will use a simple example to illustrate the key concepts.

In the plot below we have a period of time before we release a new product. We can see that sales of products A, B, and C are stable (yes this is a simplification!). But half way through our time period we release a new product and we can see that it sells reasonable volumes. The question is, where are these sales coming from?
![](inc_simple.png)

In this simplified example we could look at how the sales of existing products change, and infer where the sales of the new products came from. In this case we can see that the sales of product A and C have decreased (right at the time of the new product introduction) and so we could reasonably claim that the sales of the new product are cannibalizing sales from products A and C.

To be more precise, we aren't really able to make this causal inference based on the sales data _before_ new product introduction. Instead, we do it by comparing the actual product sales after the new product introduction to a counterfactual scenario where the new product was not introduced. This is the essence of causal inference in product incrementality. The question is how do we calculate the counterfactual sales?

![](inc_counterfactual.png)

In the trivially simple example above where sales are highly stable over time, we can do this with simple visual extrapolation. But in the real world, sales data will be noisy, exhibit seasonality, perhaps with some underlying trend, changes in prices, and marketing campaigns by you and your competitors and so on.

In fact, in many markets it is likely even more complex. We might not be able to neatly isolate time periods around new product introductions. We might have multiple new products being introduced at the same (or nearly the same) time.

![](inc_complex.png)

All of this added complexity will make it non-trivial to infer the causal impact of new products on existing products. The complexity of the product environment will need to be matched by a causal modeling strategy that can handle this complexity.
138 changes: 138 additions & 0 deletions docs/source/guide/customer_choice/mv_its_intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Multivariate interrupted time series models

One modeling approach we could use for causal analysis of product incrementality is the multivariate interrupted time series (MV-ITS) model. This model is a generalization of the interrupted time series model (ITS), which is a common approach in causal inference. The MV-ITS model allows us to estimate the causal impact of an intervention (e.g., a new product introduction) on multiple outcomes (e.g., sales of multiple products) simultaneously.

One of the differences between this model and the standard ITS modeling approach is that rather than having a binary off/on intervention, the time series of sales of the new product can be thought of as a graded intervention.

## Building our intuition

Below we outline the simplest possible version of the model to build up our intuition before we move on to more complex versions.

![](mv_its_schematic.jpg)

In this example we aggregate sales data into your companies total sales and your competitors total sales. We then have a time series of sales data for your company and your competitors. We can see that when we release a new product, our sales are not really affected, but our competitors' total sales decrease. So an intuitively right answer here is that our new product has a high level of incrementality.

```{admonition} The MV-ITS approach
:class: note
The MV-ITS approach models works as follows:
* It models the time series of sales data for your company and your competitors.
* These sales are modeled as normally distributed around an expected value with some degree of observation noise.
* The model expectation could be built up of multiple components, such as a trend, seasonality, and the impact of marketing campaigns. However for this simple example we have an intercept only model.
* Importantly, the expectation described above is decreased by some fraction of the new product sales. This fraction (across multiple sales outcomes) determines where the sales of the new product are coming from. From this, we can work out the level of incrementality of the new product.
```

## The simplest saturated market model

Let's start with the simplest case:
* The model description below assumes a saturated market. That is, new product sales have to be taken from existing sales and there is no growth of the overal size of the market.
* We operate on aggregated sales data in that we have total sales for all your products and all your competitors products. We also have all the sales data for the new product that you released.

We'll start by defining the model likelihood terms:

$$
\begin{aligned}
\vec{sales}_{your} \sim & \mathrm{Normal}(\gamma_{your} - c \cdot \vec{sales}_{new}, \sigma_{your})\\
\vec{sales}_{comp} \sim & \mathrm{Normal}(\gamma_{comp} - (1-c) \cdot \vec{sales}_{new}, \sigma_{comp})
\end{aligned}
$$

where $\vec{sales}_{your}$, $\vec{sales}_{comp}$, and $\vec{sales}_{new}$ are the observed time series of sales of all your products, your competitors products, and your new product, respectively.

The parameter $c \in [0, 1]$ is the proportion of new product sales that are cannibalistic, that is, have been taken from your existing products. Then $1-c$ is the proportion of new product sales that are incremental, that is, have been taken from your competitors products. We can place a Beta prior on $c$ to reflect our prior beliefs about the level of incrementality of the new product for example

The parameters $\sigma_{your}$ and $\sigma_{comp}$ are the standard deviations of the observation noise for your sales and your competitors sales, respectively. We can also place priors on these parameters to reflect our prior beliefs about the level of noise in the data.

This leaves the terms $\gamma_{your}$ and $\gamma_{comp}$, which are terms that model the sales of your products and your competitors products in the absence of the new product (which from a causal perspective we could also call the 'intervention'). One way to think about this would be to construct a model for sales prior to the new product release. This could be a simple model with an intercept term, or a more complex model with trend and seasonality terms. Right now we will just use an intercept term to keep things simple in this first model. In this case $\gamma_{your}$ and $\gamma_{comp}$ are simply parameters, intercept terms for your sales and your competitors sales, respectively. Again, we can place priors on these parameters to reflect our prior beliefs about the level of sales in the absence of the new product, but the exact form is not crucial at this point.

## Modelling products
We could relax one of the simplifications - rather than model aggregated sales for your or your competitors products, we could model sales for each product individually. This would allow us to see which products are most affected by the new product release. We could write the new likelihood terms as:

$$
\begin{aligned}
\vec{sales}_1 \sim & \mathrm{Normal}(\gamma_1 - \beta_1 \cdot \vec{sales}_{new}, \sigma_1)\\
\vec{sales}_2 \sim & \mathrm{Normal}(\gamma_2 - \beta_2 \cdot \vec{sales}_{new}, \sigma_2)\\
\vdots \\
\vec{sales}_P \sim & \mathrm{Normal}(\gamma_P - \beta_P \cdot \vec{sales}_{new}, \sigma_P)
\end{aligned}
$$

So now we have products $p=1, \ldots, P$ and we could model the sales of each product individually. We now have a new parameter $\beta_i$ for each product, which is the proportion of new product sales that are cannibalistic for that product. This allows us to see which products are most affected by the new product release. Because we have the assumption that the market is saturated, we can see that the sum of the $\beta_i$'s should equal 1. So it might be natural to place a [Dirichlet](https://en.wikipedia.org/wiki/Dirichlet_distribution) prior on the $\beta_i$'s:

$$
\beta_1, \beta_2, \ldots, \beta_P \sim \mathrm{Dirichlet}(\alpha_1, \ldots, \alpha_P)
$$

where all the $\alpha$ hyperparameters could be 1, for example.

As long as we have a list of which products are your products and which are your competitors products, we can simply sum the approriate $\beta_i$'s to get the cannibalistic and incremental sales for your products and your competitors products.

```{admonition} The multivariate normal form
:class: note
Note that you could re-write this model equivalently as:
$$
\begin{bmatrix}
\vec{sales}_1 \\
\vec{sales}_2 \\
\vdots \\
\vec{sales}_P
\end{bmatrix}
\sim \mathrm{MultivariateNormal}\left(
\begin{bmatrix}
\gamma_{1} - \beta_1 \cdot \vec{sales}_{new} \\
\gamma_{2} - \beta_2 \cdot \vec{sales}_{new} \\
\vdots \\
\gamma_{P} - \beta_P \cdot \vec{sales}_{new}
\end{bmatrix},
\Sigma
\right)
$$
We can embody the assumption that all the product sales are independent of each other by setting the off-diagonal elements of the covariance matrix $\Sigma$ to zero. The diagonal elements of the covariance matrix are the variances of the observation noise for each product $[ \sigma_1, \sigma_2 \ldots, \sigma_P ]$.
So the covariance matrix would look like:
$$
\Sigma =
\begin{pmatrix}
\sigma_{1}^2 & 0 & \cdots & 0 \\
0 & \sigma_{2}^2 & \cdots & 0 \\
\vdots & \vdots & \ddots & \vdots \\
0 & 0 & \cdots & \sigma_{P}^2
\end{pmatrix}
$$
```

## Relaxing independence assumptions
We could relax the assumption that the sales of your products and your competitors products are independent. We could model the sales of your products and your competitors products as a multivariate normal distribution (see box above).

The key difference would be to additionally estimate the off-diagnal elements of the covariance matrix. This would allow us to model the correlation between the sales of all products. This might require us to put more work in to specifying the prior on the covariance matrix, but in some situations the benefits of this approach could be worth it. The covariance matrix would now look like:

$$
\Sigma =
\begin{pmatrix}
\sigma_{11}^2 & \sigma_{12} & \cdots & \sigma_{1P} \\
\sigma_{21} & \sigma_{22}^2 & \cdots & \sigma_{2P} \\
\vdots & \vdots & \ddots & \vdots \\
\sigma_{P1} & \sigma_{P2} & \cdots & \sigma_{PP}^2
\end{pmatrix}
$$

The disadvantage of this model is that we would have a lot of parameters to estimate. For example, if there are $P$ products then we could have to estimate $P$ $\beta_i$'s and $P$ standard deviations and $P(P-1)/2$ covariances. This need not be a problem - we could place hierarchical priors on the $\beta_i$'s and the standard deviations for example, but it could be problematic with the covariances if we have a large number of products.

## Moving to an unsaturated market

Thus far we have considered the market as saturated. That is, it is assumed that the total sales of existing products are reduced by the same amount as the sales of the new product.

We could relax this assumption and consider an unsaturated market. More specifically, we could model the reduction of sales of existing products is less than the sales of the new product. Putting that another way, introduction of a new product leads to a reduction in existing product sales _but_ there are _additional_ sales of the new product which do not come from a reduction in the sales of existing products.

This requires only a minor change to the multivariate normal form of the model.

Specifically, rather than having $P$ products which are the source of new product sales, we could simply add a psuedo-product which represents new sales. The first $P$ products are as normal, all the existing products. The final pseudo product would represent the market growth. We could simply add an additional $\beta$ parameter and specify the prior as:

$$
\beta_1, \beta_2, \ldots, \beta_P, \beta_{P+1} \sim \mathrm{Dirichlet}(\alpha_1, \ldots, \alpha_P, \alpha_{P+1})
$$

The rest of the model remains unchanged.

Previously the sum of the $\beta$ parameters would be 1, which means that all new product sales are taken from existing products. Now the sum of the $\beta$ parameters relating to existing products ($\beta_1, \beta_2, \ldots, \beta_P$) could be less than 1. When $\beta_{P+1}>0$ then it means that the new product has caused the market to grow - some of the new product sales not come from reductions in sales of existing products.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions docs/source/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ mmm/resources

clv/clv_intro
:::

:::{toctree}
:caption: Customer Choice
:maxdepth: 1

customer_choice/incrementality_intro
customer_choice/mv_its_intro
:::
Loading

0 comments on commit 5f74b4d

Please sign in to comment.