Skip to content

Commit 9784c5f

Browse files
louismagowanjuanitorduzColtAllenpre-commit-ci[bot]cetagostini
authored andcommitted
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]>
1 parent e22e690 commit 9784c5f

8 files changed

+1255
-0
lines changed

environment.yml

+3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ dependencies:
88
- arviz>=0.13.0
99
- matplotlib>=3.5.1
1010
- numpy>=1.17
11+
- scipy>=1.11
1112
- pandas
13+
- streamlit>=1.25.0
1214
- pip
1315
# NOTE: Keep minimum pymc version in sync with ci.yml `OLDEST_PYMC_VERSION`
1416
- pymc>=5.12.0
@@ -29,6 +31,7 @@ dependencies:
2931
- sphinx-notfound-page
3032
- sphinx-design
3133
- watermark
34+
- typing
3235
# lint
3336
- mypy
3437
- pandas-stubs

streamlit/mmm-explainer/README.md

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# MMM Visualization App with Streamlit
2+
3+
## Overview
4+
5+
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.
6+
You may wish to run the app locally too - rather than relying on the [deployment](https://pymc-marketing-app.streamlit.app/).
7+
In this case, you would just need to install the requirements.txt within the streamlit folder and do `streamlit run Visualise_Priors.py`
8+
9+
## Features
10+
11+
- **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.
12+
13+
- **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.
14+
15+
- **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.
16+
17+
- **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.
18+
19+
## Getting Started
20+
21+
### Deployment
22+
23+
-Paste link to deployment here once reviewed by PyMC devs, hosted deployment is easy to implement-
24+
25+
26+
## Contributing & Adding New Functions
27+
28+
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.
29+
It's a good idea to always develop and test your changes to the app by running it locally, before submitting a PR.
30+
31+
### Adding New Adstock / Saturation Transformers from pymc-marketing
32+
33+
New transformation functions may be added to pymc-marketing which you may want to have visualised in the app.
34+
To do so, you would just need to add them in the import statements at the top of either `Saturation.py` or `Adstock.py`.
35+
e.g.
36+
```
37+
from pymc_marketing.mmm.transformers import (
38+
logistic_saturation,
39+
michaelis_menten,
40+
tanh_saturation,
41+
my_new_saturation_function
42+
)
43+
```
44+
45+
Then you would have to create a new Streamlit tab
46+
```
47+
# Create tabs for plots
48+
tab1, tab2, tab3, tab4 = st.tabs(["Logistic", "Tanh", "Michaelis-Menten", My New Saturation])
49+
```
50+
51+
And then add whatever plotting code you want for your new function!
52+
53+
### Adding Additional Distributions from PreLiz
54+
55+
PreliZ contains many, many distributions - not all of which are currently visualised.
56+
Adding new distributions is quite simple.
57+
You would need to firstly modify the dictionary of distributions and the parameters you want the user to be able to play around with.
58+
```
59+
# Specify the possible distributions and their paramaters you want to visualise
60+
DISTRIBUTIONS_DICT = {
61+
"Beta": ["alpha", "beta"],
62+
"Bernoulli": ["p"],
63+
"Exponential": ["lam"],
64+
"Gamma": ["alpha", "beta"],
65+
"HalfNormal": ["sigma"],
66+
"LogNormal": ["mu", "sigma"],
67+
"Normal": ["mu", "sigma"],
68+
"Poisson": ["mu"],
69+
"StudentT": ["nu", "mu", "sigma"],
70+
"TruncatedNormal": ["mu", "sigma", "lower", "upper"],
71+
"Uniform": ["lower", "upper"],
72+
"Weibull": ["alpha", "beta"],
73+
"MY_NEW_DIST": ["something", "something_else"],
74+
}
75+
```
76+
77+
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.
78+
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright 2024 The PyMC Labs Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# Import custom functions
15+
import prior_functions as pf
16+
17+
import streamlit as st
18+
19+
# Constants
20+
SEED = 42
21+
N_DRAWS = 50_000
22+
# Specify the possible distributions and their paramaters you want to visualise
23+
DISTRIBUTIONS_DICT = {
24+
"Beta": ["alpha", "beta"],
25+
"Bernoulli": ["p"],
26+
"Exponential": ["lam"],
27+
"Gamma": ["alpha", "beta"],
28+
"HalfNormal": ["sigma"],
29+
"LogNormal": ["mu", "sigma"],
30+
"Normal": ["mu", "sigma"],
31+
"Poisson": ["mu"],
32+
"StudentT": ["nu", "mu", "sigma"],
33+
"TruncatedNormal": ["mu", "sigma", "lower", "upper"],
34+
"Uniform": ["lower", "upper"],
35+
"Weibull": ["alpha", "beta"],
36+
}
37+
PLOT_HEIGHT = 500
38+
PLOT_WIDTH = 1000
39+
40+
# -------------------------- TOP OF PAGE INFORMATION -------------------------
41+
42+
# Set browser / tab config
43+
st.set_page_config(
44+
page_title="MMM App - Prior Distributions Transformations",
45+
page_icon="💎",
46+
)
47+
48+
# Give some context for what the page displays
49+
st.title("Bayesian Prior Distribution Demonstrator")
50+
51+
# -------------------------- VISUALISE PRIOR -------------------------
52+
53+
# Select the distribution to visualise
54+
dist_name = st.selectbox(
55+
"Please select the distribution you would like to visualise:",
56+
options=DISTRIBUTIONS_DICT.keys(),
57+
)
58+
st.header(f":blue[{dist_name} Distribution]") # header
59+
60+
# Variables need to be instantiated to avoid error where upper < lower
61+
lower = None
62+
upper = None
63+
64+
# Initialize parameters with None
65+
params = {param: None for param in DISTRIBUTIONS_DICT[dist_name]}
66+
67+
# User inputs for distribution parameters
68+
for param in params.keys():
69+
if param == "lower":
70+
params[param] = st.number_input(
71+
f"Please enter the value for {param.title()}:", key=param, value=0.0
72+
)
73+
elif param == "upper":
74+
params[param] = st.number_input(
75+
f"Please enter the value for {param.title()}:", key=param, value=2.0
76+
)
77+
elif param == "alpha":
78+
params[param] = st.number_input(
79+
f"Please enter the value for {param.title()}:",
80+
key=param,
81+
value=1.0,
82+
min_value=0.01,
83+
)
84+
elif param == "beta":
85+
params[param] = st.number_input(
86+
f"Please enter the value for {param.title()}:",
87+
key=param,
88+
value=1.0,
89+
min_value=0.01,
90+
)
91+
elif param == "sigma":
92+
params[param] = st.number_input(
93+
f"Please enter the value for {param.title()}:",
94+
key=param,
95+
value=1.0,
96+
min_value=0.01,
97+
)
98+
# Poisson mu must be > 0
99+
elif param == "mu" and dist_name == "Poisson":
100+
params[param] = st.number_input(
101+
f"Please enter the value for {param.title()}:",
102+
key=param,
103+
value=1.0,
104+
min_value=0.01,
105+
)
106+
elif param == "mu":
107+
params[param] = st.number_input(
108+
f"Please enter the value for {param.title()}:", key=param, value=0.0
109+
)
110+
elif param == "p":
111+
params[param] = st.number_input(
112+
f"Please enter the value for {param.title()}:",
113+
key=param,
114+
value=0.5,
115+
min_value=0.0,
116+
max_value=1.0,
117+
)
118+
elif param == "lam":
119+
params[param] = st.number_input(
120+
f"Please enter the value for {param.title()}:",
121+
key=param,
122+
value=1.0,
123+
min_value=0.01,
124+
)
125+
elif param == "nu":
126+
params[param] = st.number_input(
127+
f"Please enter the value for {param.title()}:",
128+
key=param,
129+
value=10.0,
130+
min_value=0.01,
131+
)
132+
133+
134+
# Check to ensure lower < upper
135+
if lower and lower >= upper:
136+
st.error("Error: Lower bound must be less than upper bound.")
137+
138+
## Create the selected distribution and sample from it
139+
dist = pf.get_distribution(dist_name, **params)
140+
draws = dist.rvs(N_DRAWS, random_state=SEED)
141+
142+
143+
# Plot distribution
144+
fig_root = pf.plot_prior_distribution(draws, title=f"{dist_name} Distribution Samples")
145+
fig_root.update_layout(height=PLOT_HEIGHT, width=PLOT_WIDTH)
146+
st.plotly_chart(fig_root, use_container_width=True)

streamlit/mmm-explainer/config.toml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[theme]
2+
base="light"
3+
primaryColor="#d7abf3"

0 commit comments

Comments
 (0)