Skip to content

Commit

Permalink
v0 Streamlit MMM Explainer App (#614)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
5 people authored and twiecki committed Sep 10, 2024
1 parent e22e690 commit 9784c5f
Show file tree
Hide file tree
Showing 8 changed files with 1,255 additions and 0 deletions.
3 changes: 3 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ dependencies:
- arviz>=0.13.0
- matplotlib>=3.5.1
- numpy>=1.17
- scipy>=1.11
- pandas
- streamlit>=1.25.0
- pip
# NOTE: Keep minimum pymc version in sync with ci.yml `OLDEST_PYMC_VERSION`
- pymc>=5.12.0
Expand All @@ -29,6 +31,7 @@ dependencies:
- sphinx-notfound-page
- sphinx-design
- watermark
- typing
# lint
- mypy
- pandas-stubs
Expand Down
78 changes: 78 additions & 0 deletions streamlit/mmm-explainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# MMM Visualization App with Streamlit

## Overview

This Streamlit application is designed to provide a dynamic and interactive visualization of key Marketing Mix Modeling (MMM) concepts, including adstock, saturation, and the use of Bayesian priors. It aims to help marketers, data scientists, and anyone interested in understanding MMM more deeply. Through this application, users can explore how different parameters affect adstock, saturation, and Bayesian priors.
You may wish to run the app locally too - rather than relying on the [deployment](https://pymc-marketing-app.streamlit.app/).
In this case, you would just need to install the requirements.txt within the streamlit folder and do `streamlit run Visualise_Priors.py`

## Features

- **Adstock Transformation Visualization**: Interactive charts that demonstrate how the adstock effect changes with different decay rates and lengths of advertising impact. Users can input their parameters to see how adstocked values are calculated over time.

- **Saturation Curve Exploration**: Interactive charts that demonstrate saturation curves, which represents the diminishing returns of marketing spend as it increases. Users can adjust parameters and choose from a variety of saturation transformations.

- **Bayesian Priors**: Interactive charts that demonstrate Bayesian prior distributions, designed to showcase the power of Bayesian methods in handling uncertainty and incorporating prior knowledge into MMM.

- **Customizable Parameters**: All sections of the app include options to customize parameters, allowing users to experiment with different scenarios and understand their impacts on MMM.

## Getting Started

### Deployment

-Paste link to deployment here once reviewed by PyMC devs, hosted deployment is easy to implement-


## Contributing & Adding New Functions

We welcome contributions from the community! Whether it's adding new features, improving documentation, or reporting bugs, please feel free to make a pull request or open an issue.
It's a good idea to always develop and test your changes to the app by running it locally, before submitting a PR.

### Adding New Adstock / Saturation Transformers from pymc-marketing

New transformation functions may be added to pymc-marketing which you may want to have visualised in the app.
To do so, you would just need to add them in the import statements at the top of either `Saturation.py` or `Adstock.py`.
e.g.
```
from pymc_marketing.mmm.transformers import (
logistic_saturation,
michaelis_menten,
tanh_saturation,
my_new_saturation_function
)
```

Then you would have to create a new Streamlit tab
```
# Create tabs for plots
tab1, tab2, tab3, tab4 = st.tabs(["Logistic", "Tanh", "Michaelis-Menten", My New Saturation])
```

And then add whatever plotting code you want for your new function!

### Adding Additional Distributions from PreLiz

PreliZ contains many, many distributions - not all of which are currently visualised.
Adding new distributions is quite simple.
You would need to firstly modify the dictionary of distributions and the parameters you want the user to be able to play around with.
```
# Specify the possible distributions and their paramaters you want to visualise
DISTRIBUTIONS_DICT = {
"Beta": ["alpha", "beta"],
"Bernoulli": ["p"],
"Exponential": ["lam"],
"Gamma": ["alpha", "beta"],
"HalfNormal": ["sigma"],
"LogNormal": ["mu", "sigma"],
"Normal": ["mu", "sigma"],
"Poisson": ["mu"],
"StudentT": ["nu", "mu", "sigma"],
"TruncatedNormal": ["mu", "sigma", "lower", "upper"],
"Uniform": ["lower", "upper"],
"Weibull": ["alpha", "beta"],
"MY_NEW_DIST": ["something", "something_else"],
}
```

And then create new Streamlit input buttons for your new parameters (unless they are covered by existing parameters in the `for param in params.keys():` block) by adding another `elif` line.
Watch out - certain distributions may share parameters of the same name, but that have different accepted ranges. For example, the `mu` parameter in Poisson has to be >0, whereas for a Normal it can be whatever you want. You may need an additional `elif` block in these edge cases.
146 changes: 146 additions & 0 deletions streamlit/mmm-explainer/Visualise_Priors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Copyright 2024 The PyMC Labs Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Import custom functions
import prior_functions as pf

import streamlit as st

# Constants
SEED = 42
N_DRAWS = 50_000
# Specify the possible distributions and their paramaters you want to visualise
DISTRIBUTIONS_DICT = {
"Beta": ["alpha", "beta"],
"Bernoulli": ["p"],
"Exponential": ["lam"],
"Gamma": ["alpha", "beta"],
"HalfNormal": ["sigma"],
"LogNormal": ["mu", "sigma"],
"Normal": ["mu", "sigma"],
"Poisson": ["mu"],
"StudentT": ["nu", "mu", "sigma"],
"TruncatedNormal": ["mu", "sigma", "lower", "upper"],
"Uniform": ["lower", "upper"],
"Weibull": ["alpha", "beta"],
}
PLOT_HEIGHT = 500
PLOT_WIDTH = 1000

# -------------------------- TOP OF PAGE INFORMATION -------------------------

# Set browser / tab config
st.set_page_config(
page_title="MMM App - Prior Distributions Transformations",
page_icon="💎",
)

# Give some context for what the page displays
st.title("Bayesian Prior Distribution Demonstrator")

# -------------------------- VISUALISE PRIOR -------------------------

# Select the distribution to visualise
dist_name = st.selectbox(
"Please select the distribution you would like to visualise:",
options=DISTRIBUTIONS_DICT.keys(),
)
st.header(f":blue[{dist_name} Distribution]") # header

# Variables need to be instantiated to avoid error where upper < lower
lower = None
upper = None

# Initialize parameters with None
params = {param: None for param in DISTRIBUTIONS_DICT[dist_name]}

# User inputs for distribution parameters
for param in params.keys():
if param == "lower":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:", key=param, value=0.0
)
elif param == "upper":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:", key=param, value=2.0
)
elif param == "alpha":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=1.0,
min_value=0.01,
)
elif param == "beta":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=1.0,
min_value=0.01,
)
elif param == "sigma":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=1.0,
min_value=0.01,
)
# Poisson mu must be > 0
elif param == "mu" and dist_name == "Poisson":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=1.0,
min_value=0.01,
)
elif param == "mu":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:", key=param, value=0.0
)
elif param == "p":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=0.5,
min_value=0.0,
max_value=1.0,
)
elif param == "lam":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=1.0,
min_value=0.01,
)
elif param == "nu":
params[param] = st.number_input(
f"Please enter the value for {param.title()}:",
key=param,
value=10.0,
min_value=0.01,
)


# Check to ensure lower < upper
if lower and lower >= upper:
st.error("Error: Lower bound must be less than upper bound.")

## Create the selected distribution and sample from it
dist = pf.get_distribution(dist_name, **params)
draws = dist.rvs(N_DRAWS, random_state=SEED)


# Plot distribution
fig_root = pf.plot_prior_distribution(draws, title=f"{dist_name} Distribution Samples")
fig_root.update_layout(height=PLOT_HEIGHT, width=PLOT_WIDTH)
st.plotly_chart(fig_root, use_container_width=True)
3 changes: 3 additions & 0 deletions streamlit/mmm-explainer/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[theme]
base="light"
primaryColor="#d7abf3"
Loading

0 comments on commit 9784c5f

Please sign in to comment.