Spaces:
Running
Running
howardroark
commited on
Commit
•
6f4f21f
1
Parent(s):
beb8613
initial commit
Browse files- .gitignore +163 -0
- README.md +1 -12
- app.py +279 -0
- app_old.py +341 -0
- data_utils/data_generation.py +140 -0
- data_utils/data_simulation.py +22 -0
- data_utils/eda_simulation.py +29 -0
- data_utils/exploratory_data_analysis.py +27 -0
- data_utils/feature_importance.py +18 -0
- data_utils/feature_importance_simulation.py +13 -0
- eval_utils/evaluation.py +57 -0
- eval_utils/evaluation_simulation.py +39 -0
- mlops_utils/wandb_utils.py +47 -0
- models_utils/ml_models.py +55 -0
- models_utils/models_simulation.py +22 -0
- notebooks/Demo_Notebook.ipynb +0 -0
- notebooks/Test.ipynb +152 -0
- requirements.txt +7 -0
.gitignore
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
|
29 |
+
# PyInstaller
|
30 |
+
# Usually these files are written by a python script from a template
|
31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
+
*.manifest
|
33 |
+
*.spec
|
34 |
+
|
35 |
+
# Installer logs
|
36 |
+
pip-log.txt
|
37 |
+
pip-delete-this-directory.txt
|
38 |
+
|
39 |
+
# Unit test / coverage reports
|
40 |
+
htmlcov/
|
41 |
+
.tox/
|
42 |
+
.nox/
|
43 |
+
.coverage
|
44 |
+
.coverage.*
|
45 |
+
.cache
|
46 |
+
nosetests.xml
|
47 |
+
coverage.xml
|
48 |
+
*.cover
|
49 |
+
*.py,cover
|
50 |
+
.hypothesis/
|
51 |
+
.pytest_cache/
|
52 |
+
cover/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
.pybuilder/
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
# For a library or package, you might want to ignore these files since the code is
|
87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
88 |
+
# .python-version
|
89 |
+
|
90 |
+
# pipenv
|
91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
+
# install all needed dependencies.
|
95 |
+
#Pipfile.lock
|
96 |
+
|
97 |
+
# poetry
|
98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
+
# commonly ignored for libraries.
|
101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
+
#poetry.lock
|
103 |
+
|
104 |
+
# pdm
|
105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
+
#pdm.lock
|
107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
+
# in version control.
|
109 |
+
# https://pdm.fming.dev/#use-with-ide
|
110 |
+
.pdm.toml
|
111 |
+
|
112 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
113 |
+
__pypackages__/
|
114 |
+
|
115 |
+
# Celery stuff
|
116 |
+
celerybeat-schedule
|
117 |
+
celerybeat.pid
|
118 |
+
|
119 |
+
# SageMath parsed files
|
120 |
+
*.sage.py
|
121 |
+
|
122 |
+
# Environments
|
123 |
+
.env
|
124 |
+
.venv
|
125 |
+
env/
|
126 |
+
venv/
|
127 |
+
ENV/
|
128 |
+
env.bak/
|
129 |
+
venv.bak/
|
130 |
+
|
131 |
+
# Spyder project settings
|
132 |
+
.spyderproject
|
133 |
+
.spyproject
|
134 |
+
|
135 |
+
# Rope project settings
|
136 |
+
.ropeproject
|
137 |
+
|
138 |
+
# mkdocs documentation
|
139 |
+
/site
|
140 |
+
|
141 |
+
# mypy
|
142 |
+
.mypy_cache/
|
143 |
+
.dmypy.json
|
144 |
+
dmypy.json
|
145 |
+
|
146 |
+
# Pyre type checker
|
147 |
+
.pyre/
|
148 |
+
|
149 |
+
# pytype static type analyzer
|
150 |
+
.pytype/
|
151 |
+
|
152 |
+
# Cython debug symbols
|
153 |
+
cython_debug/
|
154 |
+
|
155 |
+
# PyCharm
|
156 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
157 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
158 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
159 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
160 |
+
#.idea/
|
161 |
+
|
162 |
+
data/
|
163 |
+
wandb/
|
README.md
CHANGED
@@ -1,12 +1 @@
|
|
1 |
-
|
2 |
-
title: Uplift Modeling
|
3 |
-
emoji: 😻
|
4 |
-
colorFrom: pink
|
5 |
-
colorTo: green
|
6 |
-
sdk: streamlit
|
7 |
-
sdk_version: 1.32.2
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
-
---
|
11 |
-
|
12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
+
# uplift_modeling
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
ADDED
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from data_utils.data_generation import UpliftSimulation
|
2 |
+
from data_utils.exploratory_data_analysis import ExploratoryAnalysis
|
3 |
+
from data_utils.feature_importance import FeatureImportance
|
4 |
+
from models_utils.ml_models import ModelTraining
|
5 |
+
from eval_utils.evaluation import ModelEvaluator
|
6 |
+
|
7 |
+
import matplotlib.pyplot as plt
|
8 |
+
import numpy as np
|
9 |
+
import pandas as pd
|
10 |
+
import streamlit as st
|
11 |
+
|
12 |
+
X_names = [
|
13 |
+
'AgeIndex', 'IncomeIndex', 'PurchaseFrequencyIndex',
|
14 |
+
'AccountLifetimeIndex', 'AverageTransactionValueIndex', 'PreferredPaymentMethodIndex', 'RegionIndex',
|
15 |
+
'EmailDiscountCTRIndex', 'WebDiscountCTRIndex', 'SocialMediaEngagementIndex',
|
16 |
+
'DirectMailDiscountResponseIndex', 'InAppDiscountEngagementIndex', 'FlashSaleParticipationIndex',
|
17 |
+
'SeasonalPromoInterestIndex', 'LoyaltyProgramEngagementIndex', 'ReferralBonusUsageIndex',
|
18 |
+
'DiscountCodeRedemptionIndex', 'VIPSaleAccessIndex', 'EarlyAccessOptInIndex',
|
19 |
+
'ProductReviewAfterDiscountIndex', 'UpsellConversionIndex', 'CrossSellInterestIndex',
|
20 |
+
'BundlePurchaseIndex', 'SubscriptionUpgradeIndex', 'CustomerFeedbackIndex',
|
21 |
+
'BrowserTypeIndex', 'DeviceCategoryIndex', 'OperatingSystemIndex',
|
22 |
+
'SessionStartTimeIndex', 'LanguagePreferenceIndex', 'NewsletterSubscriptionIndex',
|
23 |
+
'AccountVerificationStatusIndex', 'AdBlockerPresenceIndex'
|
24 |
+
]
|
25 |
+
|
26 |
+
# Title
|
27 |
+
st.title("Uplift Modeling in Retail Demo")
|
28 |
+
|
29 |
+
tabs = st.sidebar.radio("Navigation", ["Data generation", "Exploratory analysis", "Model training", "Economic effects"])
|
30 |
+
|
31 |
+
if tabs == "Data generation":
|
32 |
+
|
33 |
+
st.header("Data Generation")
|
34 |
+
|
35 |
+
# Description
|
36 |
+
st.write("""
|
37 |
+
This app creates a simulated dataset for a special kind of analysis called uplift modeling, which helps understand the effect of different actions (like promotions) on customer behavior. We use some default settings to make things easy:
|
38 |
+
- We're looking at whether customers make a purchase or not.
|
39 |
+
- We compare different types of promotions (like no discount, 5% off, etc.).
|
40 |
+
- The dataset includes 15 different pieces of information (features) about each customer.
|
41 |
+
""")
|
42 |
+
|
43 |
+
# Interactive number of samples selection
|
44 |
+
n = st.number_input('Number of Samples (n)', min_value=1000, value=10000, step=1000,
|
45 |
+
help="Total number of samples to generate in the dataset.")
|
46 |
+
|
47 |
+
# Default values for other variables
|
48 |
+
y_name = 'conversion'
|
49 |
+
treatment_group_keys = ['control', 'discount_05', 'discount_10', 'discount_15']
|
50 |
+
n_classification_features = 15
|
51 |
+
n_classification_informative = 7
|
52 |
+
n_classification_repeated = 0
|
53 |
+
n_uplift_increase_dict = {'discount_05': 4, 'discount_10': 3, 'discount_15': 3}
|
54 |
+
n_uplift_decrease_dict = {'discount_05': 0, 'discount_10': 0, 'discount_15': 0}
|
55 |
+
positive_class_proportion = 0.05
|
56 |
+
random_seed = 8097
|
57 |
+
|
58 |
+
# Button to generate dataset
|
59 |
+
if st.button('Generate Dataset'):
|
60 |
+
uplift_sim = UpliftSimulation(n=n, y_name=y_name, treatment_group_keys=treatment_group_keys,
|
61 |
+
n_classification_features=n_classification_features,
|
62 |
+
n_classification_informative=n_classification_informative,
|
63 |
+
n_classification_repeated=n_classification_repeated,
|
64 |
+
n_uplift_increase_dict=n_uplift_increase_dict,
|
65 |
+
n_uplift_decrease_dict=n_uplift_decrease_dict,
|
66 |
+
positive_class_proportion=positive_class_proportion,
|
67 |
+
random_seed=random_seed)
|
68 |
+
uplift_sim.simulate_dataset()
|
69 |
+
uplift_sim.apply_discounts_and_clean()
|
70 |
+
uplift_sim.postprocess_tables()
|
71 |
+
uplift_sim.add_monetary_effect()
|
72 |
+
st.session_state.uplift_sim = uplift_sim # Store in session state
|
73 |
+
|
74 |
+
st.write("Dataset Generated Successfully!")
|
75 |
+
|
76 |
+
st.subheader("User profiles")
|
77 |
+
st.write('Features that represent a customer such as age, income, purchase frequency, etc')
|
78 |
+
st.dataframe(uplift_sim.dataframes[0].head(3))
|
79 |
+
|
80 |
+
st.subheader("Treatments data")
|
81 |
+
st.write('Information about the different treatments (discounts) that were applied to the customers as discounts in different channels (web, email, mobile), early access, etc')
|
82 |
+
st.dataframe(uplift_sim.dataframes[1].head(3))
|
83 |
+
|
84 |
+
st.subheader("Other data")
|
85 |
+
st.write('Other data that can be used in the analysis')
|
86 |
+
st.dataframe(uplift_sim.dataframes[2].head(3))
|
87 |
+
|
88 |
+
if tabs == "Exploratory analysis":
|
89 |
+
|
90 |
+
st.header("Exploratory Analysis")
|
91 |
+
|
92 |
+
if 'uplift_sim' in st.session_state:
|
93 |
+
|
94 |
+
st.subheader('Summary statistics')
|
95 |
+
uplift_sim = st.session_state.uplift_sim
|
96 |
+
eda = ExploratoryAnalysis(uplift_sim.df)
|
97 |
+
|
98 |
+
st.write('We begin by computing the total sum of conversions, sales (discounted price) and platform benefit. We can see that the total conversions and the total sales grows as the discount value is bigger. However, the platform benefit decreases.')
|
99 |
+
|
100 |
+
sum_conversions, mean_conversions = eda.compute_summaries()
|
101 |
+
st.write(sum_conversions)
|
102 |
+
st.write(mean_conversions)
|
103 |
+
|
104 |
+
st.write('We can also visualize the tradeoff between conversions and platform benefit by plotting the mean benefit per user on the y-axis and the mean conversion rate on the x-axis, for each treatment group.')
|
105 |
+
mean_benefit_vs_conversion = eda.compute_mean_benefit_vs_conversion()
|
106 |
+
|
107 |
+
fig, ax = plt.subplots()
|
108 |
+
mean_benefit_vs_conversion.plot.scatter(x='conversion', y='benefit', c='DarkBlue', s=50, ax=ax)
|
109 |
+
st.pyplot(fig)
|
110 |
+
|
111 |
+
st.write('''
|
112 |
+
We further compute the Average Treatment Effect (ATE) for both the mean conversion rate and the mean benefit per user:
|
113 |
+
- Conversion ATE = Mean Conversion rate in the discounted group minus Mean Conversion rate in the control group
|
114 |
+
- Benefit ATE = Mean Benefit per user in the discounted group minus Mean Benefit per user in the control group
|
115 |
+
This helps illustrate how the discount value affects Conversion ATE and Benefit ATE.
|
116 |
+
''')
|
117 |
+
mean_conversions_ate = eda.compute_ate()
|
118 |
+
|
119 |
+
fig, ax = plt.subplots()
|
120 |
+
mean_conversions_ate.plot.scatter(x='conversion', y='benefit', c='DarkBlue', s=50, ax=ax)
|
121 |
+
st.pyplot(fig)
|
122 |
+
|
123 |
+
st.subheader('Feature importance')
|
124 |
+
|
125 |
+
# Allow users to select a treatment group
|
126 |
+
treatment_group = st.selectbox(
|
127 |
+
'Select a treatment group',
|
128 |
+
options=['discount_05', 'discount_10', 'discount_15'],
|
129 |
+
index=0 # default to 'discount_05'
|
130 |
+
)
|
131 |
+
|
132 |
+
feature_importance = FeatureImportance(uplift_sim.df, X_names, y_name = 'conversion', treatment_group = treatment_group)
|
133 |
+
fi = feature_importance.compute_feature_importance()
|
134 |
+
fig, ax = plt.subplots()
|
135 |
+
di_df_sorted = fi.sort_values(by='score', ascending=False)
|
136 |
+
di_df_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
137 |
+
st.pyplot(fig)
|
138 |
+
|
139 |
+
st.write("""
|
140 |
+
- AccountLifetimeIndex: Longer-standing accounts are key predictors of customer response to promotions \n
|
141 |
+
- CustomerFeedbackIndex: Customer feedback significantly influences the success of marketing strategies \n
|
142 |
+
- UpsellConversionIndex: The success rate of upselling is an important factor \n
|
143 |
+
- PurchaseFrequencyIndex: More frequent purchases indicate higher engagement and response to marketing efforts \n
|
144 |
+
- ReferralBonusUsedIndex and LoyaltyProgramEngagementIndex: Engagement with these programs is highly indicative of responsiveness to promotions
|
145 |
+
""")
|
146 |
+
|
147 |
+
else:
|
148 |
+
st.error("Please generate the dataset first.")
|
149 |
+
|
150 |
+
if tabs == "Model training":
|
151 |
+
|
152 |
+
st.header("Model Training")
|
153 |
+
|
154 |
+
if 'uplift_sim' in st.session_state:
|
155 |
+
|
156 |
+
uplift_sim = st.session_state.uplift_sim
|
157 |
+
|
158 |
+
model_trainer = ModelTraining(uplift_sim.df, 'conversion', X_names)
|
159 |
+
|
160 |
+
model_type = st.radio("Choose the model type", ('Conversion Model', 'Benefit Model'))
|
161 |
+
|
162 |
+
params = {
|
163 |
+
'n_estimators': st.slider('Number of Estimators', 10, 100, 50),
|
164 |
+
'max_depth': st.slider('Max Depth', 1, 10, 4),
|
165 |
+
'colsample_bytree': st.slider('Colsample by Tree', 0.1, 1.0, 0.2),
|
166 |
+
'subsample': st.slider('Subsample', 0.1, 1.0, 0.2),
|
167 |
+
}
|
168 |
+
control_name = 'control' # st.text_input('Control Group Name', 'control')
|
169 |
+
test_size = st.slider('Test Size', 0.1, 0.9, 0.5)
|
170 |
+
random_state = 20143 # st.slider('Random State', 0, 10000, 20143)
|
171 |
+
|
172 |
+
if st.button('Train Model'):
|
173 |
+
|
174 |
+
model_trainer.split_data(test_size=test_size, random_state=random_state)
|
175 |
+
|
176 |
+
if model_type == 'Conversion Model':
|
177 |
+
y_name = 'conversion' # st.selectbox('Select target variable for conversion', options=uplift_sim.target_options)
|
178 |
+
model_trainer.y_name = y_name
|
179 |
+
tau = model_trainer.fit_predict_classifier(params, control_name)
|
180 |
+
elif model_type == 'BATE Model':
|
181 |
+
y_name = 'benefit' # st.selectbox('Select target variable for benefit', options=uplift_sim.benefit_options)
|
182 |
+
model_trainer.y_name = y_name
|
183 |
+
tau = model_trainer.fit_predict_regressor(params, control_name)
|
184 |
+
|
185 |
+
st.session_state.model_trainer = model_trainer
|
186 |
+
|
187 |
+
feature_importances = model_trainer.compute_feature_importance()
|
188 |
+
|
189 |
+
st.subheader('Feature Importances')
|
190 |
+
fig, ax = plt.subplots()
|
191 |
+
|
192 |
+
for k, v in feature_importances.items():
|
193 |
+
st.write(f"Feature importance for {k}")
|
194 |
+
v.plot(kind='barh', ax=ax)
|
195 |
+
ax.set_xlabel("Importance")
|
196 |
+
ax.set_ylabel("Feature")
|
197 |
+
ax.set_title(f"Feature Importance for {model_type}")
|
198 |
+
st.pyplot(fig)
|
199 |
+
|
200 |
+
else:
|
201 |
+
st.error("Please generate and preprocess the dataset first.")
|
202 |
+
|
203 |
+
if tabs == "Economic effects":
|
204 |
+
|
205 |
+
st.header("Economic Effects Analysis")
|
206 |
+
|
207 |
+
if 'uplift_sim' in st.session_state and 'model_trainer' in st.session_state:
|
208 |
+
df_test = st.session_state.model_trainer.df_test
|
209 |
+
model_type = st.radio("Choose the model type for analysis", ('Conversion Model', 'Benefit Model'))
|
210 |
+
|
211 |
+
# Determine which model to use based on user selection
|
212 |
+
if model_type == 'Conversion Model':
|
213 |
+
model = st.session_state.model_trainer.conversion_learner_t
|
214 |
+
elif model_type == 'Benefit Model':
|
215 |
+
model = st.session_state.model_trainer.benefit_learner_t
|
216 |
+
else:
|
217 |
+
st.error("Invalid model type selected.")
|
218 |
+
st.stop()
|
219 |
+
|
220 |
+
if model == None:
|
221 |
+
st.error("Please train the model first.")
|
222 |
+
st.stop()
|
223 |
+
|
224 |
+
evaluator = ModelEvaluator(model,
|
225 |
+
df_test,
|
226 |
+
X_names # df_test.columns.drop(['conversion', 'benefit', 'treatment_group_key'])
|
227 |
+
)
|
228 |
+
discounts = ['discount_05', 'discount_10', 'discount_15']
|
229 |
+
qini_conversions = {}
|
230 |
+
qini_benefits = {}
|
231 |
+
|
232 |
+
for discount in discounts:
|
233 |
+
qini_conv, qini_ben = evaluator.eval_performance(discount)
|
234 |
+
qini_conversions[discount] = qini_conv
|
235 |
+
qini_benefits[discount] = qini_ben
|
236 |
+
|
237 |
+
# Plotting CATE Conversion
|
238 |
+
st.subheader("CATE Conversion vs Targeted Population")
|
239 |
+
fig, ax_conversion = plt.subplots()
|
240 |
+
for discount, color in zip(discounts, ['b', 'g', 'y']):
|
241 |
+
qini_conversions[discount].plot(ax=ax_conversion, x='index', y='S', color=color)
|
242 |
+
qini_conversions[discount].plot(ax=ax_conversion, x='index', y='Random', color='r', ls='--')
|
243 |
+
|
244 |
+
ax_conversion.legend([f'{d} model' for d in discounts] + [f'{d} random' for d in discounts], prop={'size': 10})
|
245 |
+
ax_conversion.set_xlabel('Fraction of Targeted Users')
|
246 |
+
ax_conversion.set_ylabel('CATE Conversion')
|
247 |
+
ax_conversion.set_title('CATE Conversion vs Targeted Population')
|
248 |
+
st.pyplot(fig)
|
249 |
+
|
250 |
+
# Plotting CATE Benefit
|
251 |
+
st.subheader("CATE Benefit vs Targeted Population")
|
252 |
+
fig, ax_benefit = plt.subplots()
|
253 |
+
for discount, color in zip(discounts, ['b', 'g', 'y']):
|
254 |
+
qini_benefits[discount].plot(ax=ax_benefit, x='index', y='S', color=color)
|
255 |
+
qini_benefits[discount].plot(ax=ax_benefit, x='index', y='Random', color='r', ls='--')
|
256 |
+
|
257 |
+
ax_benefit.legend([f'{d} model' for d in discounts] + [f'{d} random' for d in discounts], prop={'size': 10})
|
258 |
+
ax_benefit.set_xlabel('Fraction of Targeted Users')
|
259 |
+
ax_benefit.set_ylabel('CATE Benefit')
|
260 |
+
ax_benefit.set_title('CATE Benefit vs Targeted Population')
|
261 |
+
st.pyplot(fig)
|
262 |
+
|
263 |
+
# Plotting CATE Benefit vs CATE Conversion
|
264 |
+
st.subheader("CATE Benefit vs CATE Conversion")
|
265 |
+
fig, ax_comp = plt.subplots()
|
266 |
+
colors = ['b', 'g', 'y']
|
267 |
+
for i, discount in enumerate(discounts):
|
268 |
+
qini_conc_test = pd.concat([qini_conversions[discount][['S']], qini_benefits[discount][['S']]], axis=1)
|
269 |
+
qini_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
270 |
+
qini_conc_test.plot(ax=ax_comp, x='cate_conversion', y='cate_benefit', color=colors[i], label=f'{discount} model')
|
271 |
+
|
272 |
+
ax_comp.legend(prop={'size': 10})
|
273 |
+
ax_comp.set_xlabel('CATE Conversion')
|
274 |
+
ax_comp.set_ylabel('CATE Benefit')
|
275 |
+
ax_comp.set_title('CATE Benefit vs CATE Conversion')
|
276 |
+
st.pyplot(fig)
|
277 |
+
|
278 |
+
else:
|
279 |
+
st.error("Please ensure the model is trained and the dataset is prepared.")
|
app_old.py
ADDED
@@ -0,0 +1,341 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
import pandas as pd
|
4 |
+
import streamlit as st
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
|
7 |
+
from data_utils.data_simulation import UpliftSimulationReady
|
8 |
+
from data_utils.eda_simulation import EDASimulationReady
|
9 |
+
from data_utils.feature_importance_simulation import FISimulationReady
|
10 |
+
from models_utils.models_simulation import CATESimulationReady
|
11 |
+
from eval_utils.evaluation_simulation import CATEConversionEvaluateSimulationReady, CATEBenefitEvaluateSimulationReady
|
12 |
+
|
13 |
+
from mlops_utils.wandb_utils import upload_dataset_to_wandb, eda_work_with_dataset_to_wandb, training_results_to_wandb
|
14 |
+
|
15 |
+
st.title('Causal Uplift Modeling')
|
16 |
+
tabs = st.sidebar.radio("Navigation", ["Data", "EDA", "Modeling", "Effect"])
|
17 |
+
|
18 |
+
if tabs == "Data":
|
19 |
+
|
20 |
+
# Needed raw data
|
21 |
+
uplift_simulation = UpliftSimulationReady('./data/raw_data_client/')
|
22 |
+
user_profiles = uplift_simulation.load_user_profiles('user_profiles.csv')
|
23 |
+
uplift_data = uplift_simulation.load_uplift_data('uplift_data.csv')
|
24 |
+
irrelevant_data = uplift_simulation.load_irrelevant_data('irrelevant_data.csv')
|
25 |
+
transaction_data = uplift_simulation.load_other_data('transaction_data.csv')
|
26 |
+
|
27 |
+
# Subtitle
|
28 |
+
st.subheader('Loading data')
|
29 |
+
|
30 |
+
st.write('User profiles')
|
31 |
+
st.write(user_profiles.head(5))
|
32 |
+
|
33 |
+
st.write('Uplift data')
|
34 |
+
st.write(uplift_data.head(5))
|
35 |
+
|
36 |
+
st.write('Other data')
|
37 |
+
st.write(irrelevant_data.head(5))
|
38 |
+
|
39 |
+
st.write('Transaction data')
|
40 |
+
st.write(transaction_data.head(5))
|
41 |
+
|
42 |
+
if st.button('Upload data to wandb'):
|
43 |
+
upload_dataset_to_wandb(['./data/raw_data_client'], 'nl_cate_modeling', 'uplift_data')
|
44 |
+
st.write('Data uploaded to wandb')
|
45 |
+
|
46 |
+
# TODO: add to WANDB data processing step in the beginning
|
47 |
+
# TODO: the tree of updates
|
48 |
+
# TODO: choose the version from MLOps here exactly
|
49 |
+
|
50 |
+
if tabs == "EDA":
|
51 |
+
|
52 |
+
eda_simulation = EDASimulationReady('./data/processed_data/')
|
53 |
+
sum_conversions, mean_conversions = eda_simulation.load_conversions('uplift_classification_processed.csv')
|
54 |
+
|
55 |
+
st.subheader('Exploratory Data Analysis')
|
56 |
+
|
57 |
+
st.write('We can begin by computing the total sum of conversions, sales (discounted price) and platform benefit. We can see that the total conversions and the total sales grows as the discount value is bigger. However the platform benefit decreases.')
|
58 |
+
st.write(sum_conversions)
|
59 |
+
|
60 |
+
st.write('We can repeat the analysis but using the mean instead of the sum. This will give us the mean conversion rate, the mean sales per user and the mean platform benefit per user.')
|
61 |
+
st.write(mean_conversions)
|
62 |
+
|
63 |
+
st.write('To illustrate the tradeoff between conversions and platform benefit we can plot the mean benefit per user in the y-axis and the mean conversion rate in the x-axis, per treatment group.')
|
64 |
+
|
65 |
+
df_pivot_mean = mean_conversions[['mean']]
|
66 |
+
df_pivot_mean.columns = df_pivot_mean.columns.droplevel()
|
67 |
+
|
68 |
+
fig, ax = plt.subplots()
|
69 |
+
df_pivot_mean.plot.scatter(x='conversion',
|
70 |
+
y='benefit',
|
71 |
+
c='DarkBlue',
|
72 |
+
s=50,
|
73 |
+
ax=ax)
|
74 |
+
st.pyplot(fig)
|
75 |
+
|
76 |
+
st.write('''
|
77 |
+
We can also compute the Average Treatment Effect (ATE) for both the mean conversion rate and the mean benefit per user:
|
78 |
+
Conversion ATE = Mean Converstion rate in discounted group minus Mean Conversion rate in control group
|
79 |
+
Benefit ATE = Mean Benefit per user in discounted group minus Mean Benefit per user in control group
|
80 |
+
We can see in the plot below that the bigger the discount value the stronger the Conversion ATE (x-axis), but at the same time the more negative the Benefit ATE (y-axis).
|
81 |
+
''')
|
82 |
+
|
83 |
+
df_pivot_mean_ate = df_pivot_mean - df_pivot_mean.loc['control'].values.squeeze()
|
84 |
+
df_pivot_mean_ate.columns = ['benefit_ate', 'conversion_ate', 'discounted_price_ate']
|
85 |
+
|
86 |
+
fig, ax = plt.subplots()
|
87 |
+
df_pivot_mean_ate.plot.scatter(x='conversion_ate',
|
88 |
+
y='benefit_ate',
|
89 |
+
c='DarkBlue',
|
90 |
+
s=50,
|
91 |
+
ax=ax)
|
92 |
+
st.pyplot(fig)
|
93 |
+
|
94 |
+
st.subheader('Feature Importance')
|
95 |
+
|
96 |
+
fi = FISimulationReady('./data/eda_data/')
|
97 |
+
di_df = fi.load_feature_importance('kl_feature_importance.csv')
|
98 |
+
|
99 |
+
st.write('Feature importance')
|
100 |
+
fig, ax = plt.subplots()
|
101 |
+
di_df_sorted = di_df.sort_values(by='score', ascending=False)
|
102 |
+
di_df_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
103 |
+
st.pyplot(fig)
|
104 |
+
|
105 |
+
if st.button('Upload EDA to wandb'):
|
106 |
+
eda_work_with_dataset_to_wandb(
|
107 |
+
dirs = ['./data/eda_data/'],
|
108 |
+
project_name = 'nl_cate_modeling',
|
109 |
+
dataset_name = 'uplift_data:latest',
|
110 |
+
dataset_type = 'raw_dataset',
|
111 |
+
artifact_type = 'eda')
|
112 |
+
st.write('EDA uploaded to wandb')
|
113 |
+
|
114 |
+
# TODO: add report to WANDB
|
115 |
+
# TODO: add artifacts to WANDB
|
116 |
+
|
117 |
+
if tabs == "Modeling":
|
118 |
+
|
119 |
+
st.subheader('Causal ML modeling')
|
120 |
+
|
121 |
+
st.write('We can begin by modeling the Conditional Average Treatment Effect')
|
122 |
+
if st.button('Train & run CATE conversion model'):
|
123 |
+
# fake trainin via 5 seconds spinner
|
124 |
+
with st.spinner('Training model...'):
|
125 |
+
time.sleep(2)
|
126 |
+
|
127 |
+
st.subheader('Feature importance by discount group')
|
128 |
+
|
129 |
+
model = CATESimulationReady('./data/models_data/model.pkl', './data/models_data/y_pred.pkl')
|
130 |
+
y_pred = model.predict()
|
131 |
+
|
132 |
+
fi05 = model.feature_importance('./data/models_data/discount_05_feature_importance.csv')
|
133 |
+
fi10 = model.feature_importance('./data/models_data/discount_10_feature_importance.csv')
|
134 |
+
fi15 = model.feature_importance('./data/models_data/discount_15_feature_importance.csv')
|
135 |
+
|
136 |
+
st.write('5\% discount group')
|
137 |
+
# plot feature importance as bar chart
|
138 |
+
fig, ax = plt.subplots()
|
139 |
+
fi05_sorted = fi05.sort_values(by='score', ascending=False)
|
140 |
+
fi05_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
141 |
+
st.pyplot(fig)
|
142 |
+
|
143 |
+
st.write('10\% discount group')
|
144 |
+
fig, ax = plt.subplots()
|
145 |
+
fi10_sorted = fi10.sort_values(by='score', ascending=False)
|
146 |
+
fi10_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
147 |
+
st.pyplot(fig)
|
148 |
+
|
149 |
+
st.write('15\% discount group')
|
150 |
+
fig, ax = plt.subplots()
|
151 |
+
fi15_sorted = fi15.sort_values(by='score', ascending=False)
|
152 |
+
fi15_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
153 |
+
st.pyplot(fig)
|
154 |
+
if st.button('Upload convesion model to wandb'):
|
155 |
+
training_results_to_wandb(['./data/models_data'],
|
156 |
+
'nl_cate_modeling',
|
157 |
+
'uplift_data:latest',
|
158 |
+
'raw_dataset',
|
159 |
+
'model_artifacts',
|
160 |
+
'causal_model_conversion')
|
161 |
+
st.write('Models uploaded to wandb')
|
162 |
+
|
163 |
+
st.write('Similarly we can now train a T-Learner on the benefit label, and use the model predictions to evaluate the performance on the CATE conversion and CATE benefit.')
|
164 |
+
if st.button('Train & run CATE benefit model'):
|
165 |
+
# fake trainin via 5 seconds spinner
|
166 |
+
with st.spinner('Training model...'):
|
167 |
+
time.sleep(2)
|
168 |
+
|
169 |
+
st.subheader('Feature importance by discount group')
|
170 |
+
|
171 |
+
model = CATESimulationReady('./data/models_data/model.pkl', './data/models_data/y_pred.pkl')
|
172 |
+
y_pred = model.predict()
|
173 |
+
|
174 |
+
fi05 = model.feature_importance('./data/models_data/discount_05_feature_importance_bate.csv')
|
175 |
+
fi10 = model.feature_importance('./data/models_data/discount_10_feature_importance_bate.csv')
|
176 |
+
fi15 = model.feature_importance('./data/models_data/discount_15_feature_importance_bate.csv')
|
177 |
+
|
178 |
+
st.write('5\% discount group')
|
179 |
+
# plot feature importance as bar chart
|
180 |
+
fig, ax = plt.subplots()
|
181 |
+
fi05_sorted = fi05.sort_values(by='score', ascending=False)
|
182 |
+
fi05_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
183 |
+
st.pyplot(fig)
|
184 |
+
|
185 |
+
st.write('10\% discount group')
|
186 |
+
fig, ax = plt.subplots()
|
187 |
+
fi10_sorted = fi10.sort_values(by='score', ascending=False)
|
188 |
+
fi10_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
189 |
+
st.pyplot(fig)
|
190 |
+
|
191 |
+
st.write('15\% discount group')
|
192 |
+
fig, ax = plt.subplots()
|
193 |
+
fi15_sorted = fi15.sort_values(by='score', ascending=False)
|
194 |
+
fi15_sorted[['feature', 'score']].plot.barh(x='feature', y='score', ax=ax)
|
195 |
+
st.pyplot(fig)
|
196 |
+
if st.button('Upload benefit model to wandb'):
|
197 |
+
training_results_to_wandb(['./data/models_data'],
|
198 |
+
'nl_cate_modeling',
|
199 |
+
'uplift_data:latest',
|
200 |
+
'raw_dataset',
|
201 |
+
'model_artifacts',
|
202 |
+
'causal_model_benefit')
|
203 |
+
st.write('Models uploaded to wandb')
|
204 |
+
|
205 |
+
if tabs == "Effect":
|
206 |
+
|
207 |
+
st.subheader('Causal ML evaluation')
|
208 |
+
st.write('We can evaluate our models by looking at the Qini curves. We can use the CATE conversion model to evaluate the performance on both the Conversion and the Benefit as a function of the fraction of users targeted.')
|
209 |
+
|
210 |
+
# two columns
|
211 |
+
col1, col2 = st.columns(2)
|
212 |
+
|
213 |
+
with col1:
|
214 |
+
|
215 |
+
st.write('CATE conversion model')
|
216 |
+
|
217 |
+
eval = CATEConversionEvaluateSimulationReady('./data/effect_data/')
|
218 |
+
qini_05_conversion_test, qini_05_benefit_test = eval.evaluate(5)
|
219 |
+
qini_10_conversion_test, qini_10_benefit_test = eval.evaluate(10)
|
220 |
+
qini_15_conversion_test, qini_15_benefit_test = eval.evaluate(15)
|
221 |
+
|
222 |
+
# Plot CATE conversion vs Targeted Population
|
223 |
+
fig_conversion, ax_conversion = plt.subplots()
|
224 |
+
qini_05_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='b', ls='--', lw=0.5, label = '5% random')
|
225 |
+
qini_10_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='g', ls='--', lw=0.5, label = '10% random')
|
226 |
+
qini_15_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='y', ls='--', lw=0.5, label = '15% random')
|
227 |
+
qini_05_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='b', label = '5% model')
|
228 |
+
qini_10_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='g', label = '10% model')
|
229 |
+
qini_15_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='y', label = '15% model')
|
230 |
+
ax_conversion.legend()
|
231 |
+
ax_conversion.set_xlabel('Fraction of Targeted Users')
|
232 |
+
ax_conversion.set_ylabel('CATE conversion')
|
233 |
+
ax_conversion.set_title('CATE conversion vs Targeted Population')
|
234 |
+
st.pyplot(fig_conversion)
|
235 |
+
|
236 |
+
# Plot CATE benefit vs Targeted Population
|
237 |
+
fig_benefit, ax_benefit = plt.subplots()
|
238 |
+
qini_05_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='b', ls='--', lw=0.5, label = '5% random')
|
239 |
+
qini_10_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='g', ls='--', lw=0.5, label = '10% random')
|
240 |
+
qini_15_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='y', ls='--', lw=0.5, label = '15% random')
|
241 |
+
qini_05_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='b', label = '5% model')
|
242 |
+
qini_10_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='g', label = '10% model')
|
243 |
+
qini_15_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='y', label = '15% model')
|
244 |
+
ax_benefit.legend()
|
245 |
+
ax_benefit.set_xlabel('Fraction of Targeted Users')
|
246 |
+
ax_benefit.set_ylabel('CATE Benefit')
|
247 |
+
ax_benefit.set_title('CATE benefit vs Targeted Population')
|
248 |
+
st.pyplot(fig_benefit)
|
249 |
+
|
250 |
+
qini_05_conc_test = pd.concat([qini_05_conversion_test[['S']], qini_05_benefit_test[['S']]], axis=1)
|
251 |
+
qini_05_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
252 |
+
qini_10_conc_test = pd.concat([qini_10_conversion_test[['S']], qini_10_benefit_test[['S']]], axis=1)
|
253 |
+
qini_10_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
254 |
+
qini_15_conc_test = pd.concat([qini_15_conversion_test[['S']], qini_15_benefit_test[['S']]], axis=1)
|
255 |
+
qini_15_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
256 |
+
|
257 |
+
fig_conversion, ax_conversion = plt.subplots()
|
258 |
+
qini_05_conc_test.plot(ax=ax_conversion, x='cate_conversion',y='cate_benefit',color='b')
|
259 |
+
qini_10_conc_test.plot(ax=ax_conversion, x='cate_conversion',y='cate_benefit',color='g')
|
260 |
+
qini_15_conc_test.plot(ax=ax_conversion, x='cate_conversion',y='cate_benefit',color='y')
|
261 |
+
ax_conversion.legend(['5% model', '10% model','15% model'], prop={'size': 10})
|
262 |
+
ax_conversion.set_xlabel('CATE Conversion')
|
263 |
+
ax_conversion.set_ylabel('CATE Benefit')
|
264 |
+
ax_conversion.set_title('CATE benefit vs CATE conversion')
|
265 |
+
st.pyplot(fig_conversion)
|
266 |
+
|
267 |
+
if st.button('Upload conversion effects to wandb'):
|
268 |
+
training_results_to_wandb(['./data/effect_data'],
|
269 |
+
'nl_cate_modeling',
|
270 |
+
'causal_model_conversion:latest',
|
271 |
+
'model_artifacts',
|
272 |
+
'effects_artifacts',
|
273 |
+
'convesion_model_evaluation',
|
274 |
+
job_type='evaluation')
|
275 |
+
st.write('Evaluation uploaded to wandb')
|
276 |
+
|
277 |
+
with col2:
|
278 |
+
st.write('CATE benefit model')
|
279 |
+
|
280 |
+
eval = CATEBenefitEvaluateSimulationReady('./data/effect_data/')
|
281 |
+
qini_05_conversion_test, qini_05_benefit_test = eval.evaluate(5)
|
282 |
+
qini_10_conversion_test, qini_10_benefit_test = eval.evaluate(10)
|
283 |
+
qini_15_conversion_test, qini_15_benefit_test = eval.evaluate(15)
|
284 |
+
|
285 |
+
# Plot CATE conversion vs Targeted Population
|
286 |
+
fig_conversion, ax_conversion = plt.subplots()
|
287 |
+
qini_05_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='b', ls='--', lw=0.5, label = '5% random')
|
288 |
+
qini_10_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='g', ls='--', lw=0.5, label = '10% random')
|
289 |
+
qini_15_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='y', ls='--', lw=0.5, label = '15% random')
|
290 |
+
qini_05_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='b', label = '5% model')
|
291 |
+
qini_10_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='g', label = '10% model')
|
292 |
+
qini_15_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='y', label = '15% model')
|
293 |
+
ax_conversion.legend()
|
294 |
+
ax_conversion.set_xlabel('Fraction of Targeted Users')
|
295 |
+
ax_conversion.set_ylabel('CATE conversion')
|
296 |
+
ax_conversion.set_title('CATE conversion vs Targeted Population')
|
297 |
+
st.pyplot(fig_conversion)
|
298 |
+
|
299 |
+
# Plot CATE benefit vs Targeted Population
|
300 |
+
fig_benefit, ax_benefit = plt.subplots()
|
301 |
+
qini_05_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='b', ls='--', lw=0.5, label = '5% random')
|
302 |
+
qini_10_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='g', ls='--', lw=0.5, label = '10% random')
|
303 |
+
qini_15_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='y', ls='--', lw=0.5, label = '15% random')
|
304 |
+
qini_05_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='b', label = '5% model')
|
305 |
+
qini_10_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='g', label = '10% model')
|
306 |
+
qini_15_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='y', label = '15% model')
|
307 |
+
ax_benefit.legend()
|
308 |
+
ax_benefit.set_xlabel('Fraction of Targeted Users')
|
309 |
+
ax_benefit.set_ylabel('CATE Benefit')
|
310 |
+
ax_benefit.set_title('CATE benefit vs Targeted Population')
|
311 |
+
st.pyplot(fig_benefit)
|
312 |
+
|
313 |
+
qini_05_conc_test = pd.concat([qini_05_conversion_test[['S']], qini_05_benefit_test[['S']]], axis=1)
|
314 |
+
qini_05_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
315 |
+
qini_10_conc_test = pd.concat([qini_10_conversion_test[['S']], qini_10_benefit_test[['S']]], axis=1)
|
316 |
+
qini_10_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
317 |
+
qini_15_conc_test = pd.concat([qini_15_conversion_test[['S']], qini_15_benefit_test[['S']]], axis=1)
|
318 |
+
qini_15_conc_test.columns = ['cate_conversion', 'cate_benefit']
|
319 |
+
|
320 |
+
fig_conversion, ax_conversion = plt.subplots()
|
321 |
+
qini_05_conc_test.plot(ax=ax_conversion, x='cate_conversion',y='cate_benefit',color='b')
|
322 |
+
qini_10_conc_test.plot(ax=ax_conversion, x='cate_conversion',y='cate_benefit',color='g')
|
323 |
+
qini_15_conc_test.plot(ax=ax_conversion, x='cate_conversion',y='cate_benefit',color='y')
|
324 |
+
ax_conversion.legend(['5% model', '10% model','15% model'], prop={'size': 10})
|
325 |
+
ax_conversion.set_xlabel('CATE Conversion')
|
326 |
+
ax_conversion.set_ylabel('CATE Benefit')
|
327 |
+
ax_conversion.set_title('CATE benefit vs CATE conversion')
|
328 |
+
st.pyplot(fig_conversion)
|
329 |
+
|
330 |
+
if st.button('Upload benefit effects to wandb'):
|
331 |
+
training_results_to_wandb(['./data/effect_data'],
|
332 |
+
'nl_cate_modeling',
|
333 |
+
'causal_model_benefit:latest',
|
334 |
+
'model_artifacts',
|
335 |
+
'effects_artifacts',
|
336 |
+
'benefit_model_evaluation',
|
337 |
+
job_type='evaluation')
|
338 |
+
st.write('Evaluation uploaded to wandb')
|
339 |
+
|
340 |
+
st.write('To simplify the comparison, we can plot the CATE Benefit as a function of the CATE conversion.')
|
341 |
+
st.write('In the last plot for example we can see that there is a region where offering 15% discount to a targeted group of users is more efficient than giving 10% to everyone. We can obtain the same impact in overall conversion uplift while reducing our benefit loss considerably.')
|
data_utils/data_generation.py
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from functools import reduce
|
3 |
+
from random import randint
|
4 |
+
|
5 |
+
from causalml.dataset import make_uplift_classification
|
6 |
+
|
7 |
+
class UpliftSimulation:
|
8 |
+
|
9 |
+
def __init__(self, n=50000, y_name='conversion',
|
10 |
+
treatment_group_keys=['control', 'discount_05', 'discount_10', 'discount_15'],
|
11 |
+
n_classification_features=15, n_classification_informative=7,
|
12 |
+
n_classification_repeated=0,
|
13 |
+
n_uplift_increase_dict={'discount_05': 4, 'discount_10': 3, 'discount_15': 3},
|
14 |
+
n_uplift_decrease_dict={'discount_05': 0, 'discount_10': 0, 'discount_15': 0},
|
15 |
+
delta_uplift_increase_dict={'discount_05': 0.0020, 'discount_10': 0.0045, 'discount_15': 0.008},
|
16 |
+
delta_uplift_decrease_dict={'discount_05': 0, 'discount_10': 0, 'discount_15': 0},
|
17 |
+
n_uplift_increase_mix_informative_dict={'discount_05': 3, 'discount_10': 2, 'discount_15': 3},
|
18 |
+
n_uplift_decrease_mix_informative_dict={'discount_05': 0, 'discount_10': 0, 'discount_15': 0},
|
19 |
+
positive_class_proportion=0.05, random_seed=8097):
|
20 |
+
self.n = n
|
21 |
+
self.y_name = y_name
|
22 |
+
self.treatment_group_keys = treatment_group_keys
|
23 |
+
self.n_classification_features = n_classification_features
|
24 |
+
self.n_classification_informative = n_classification_informative
|
25 |
+
self.n_classification_repeated = n_classification_repeated
|
26 |
+
self.n_uplift_increase_dict = n_uplift_increase_dict
|
27 |
+
self.n_uplift_decrease_dict = n_uplift_decrease_dict
|
28 |
+
self.delta_uplift_increase_dict = delta_uplift_increase_dict
|
29 |
+
self.delta_uplift_decrease_dict = delta_uplift_decrease_dict
|
30 |
+
self.n_uplift_increase_mix_informative_dict = n_uplift_increase_mix_informative_dict
|
31 |
+
self.n_uplift_decrease_mix_informative_dict = n_uplift_decrease_mix_informative_dict
|
32 |
+
self.positive_class_proportion = positive_class_proportion
|
33 |
+
self.random_seed = random_seed
|
34 |
+
self.df = None
|
35 |
+
self.X_names = None
|
36 |
+
|
37 |
+
def simulate_dataset(self):
|
38 |
+
self.df, self.X_names = make_uplift_classification(
|
39 |
+
treatment_name=self.treatment_group_keys,
|
40 |
+
y_name=self.y_name,
|
41 |
+
n_samples=self.n,
|
42 |
+
n_classification_features=self.n_classification_features,
|
43 |
+
n_classification_informative=self.n_classification_informative,
|
44 |
+
n_classification_repeated=self.n_classification_repeated,
|
45 |
+
n_uplift_increase_dict=self.n_uplift_increase_dict,
|
46 |
+
n_uplift_decrease_dict=self.n_uplift_decrease_dict,
|
47 |
+
delta_uplift_increase_dict=self.delta_uplift_increase_dict,
|
48 |
+
delta_uplift_decrease_dict=self.delta_uplift_decrease_dict,
|
49 |
+
n_uplift_increase_mix_informative_dict=self.n_uplift_increase_mix_informative_dict,
|
50 |
+
n_uplift_decrease_mix_informative_dict=self.n_uplift_decrease_mix_informative_dict,
|
51 |
+
positive_class_proportion=self.positive_class_proportion,
|
52 |
+
random_seed=self.random_seed,
|
53 |
+
)
|
54 |
+
|
55 |
+
def apply_discounts_and_clean(self):
|
56 |
+
discounts_dict = {'control': 0, 'discount_05': 0.05, 'discount_10': 0.10, 'discount_15': 0.15}
|
57 |
+
self.df['discount'] = self.df['treatment_group_key']
|
58 |
+
self.df = self.df.replace({"discount": discounts_dict})
|
59 |
+
self.df.drop(columns=['treatment_effect'], inplace=True)
|
60 |
+
|
61 |
+
|
62 |
+
def postprocess_tables(self):
|
63 |
+
|
64 |
+
# Add a synthetic UserID for each entry
|
65 |
+
self.df['UserID'] = range(len(self.df))
|
66 |
+
|
67 |
+
# Mapping the columns
|
68 |
+
informative_cols = [col for col in self.df.columns if 'informative' in col]
|
69 |
+
uplift_cols = [col for col in self.df.columns if 'uplift' in col]
|
70 |
+
irrelevant_cols = [col for col in self.df.columns if 'irrelevant' in col]
|
71 |
+
transaction_cols = ['treatment_group_key', 'conversion', 'discount']
|
72 |
+
|
73 |
+
# User Demographics and Profiles Table (Including Informative Features)
|
74 |
+
user_profiles = self.df[['UserID'] + informative_cols].copy()
|
75 |
+
|
76 |
+
# Web Interaction Data Table (This might need adjustment based on actual data)
|
77 |
+
# If any of the 'informative' columns relate to web interaction, include them here.
|
78 |
+
|
79 |
+
# Uplift-Related Data Table
|
80 |
+
uplift_data = self.df[['UserID'] + uplift_cols].copy()
|
81 |
+
|
82 |
+
# Adjusting the Uplift-Related Data table to include the mixed features
|
83 |
+
mixed_uplift_columns = ['x31_increase_mix', 'x22_increase_mix', 'x20_increase_mix',
|
84 |
+
'x33_increase_mix', 'x32_increase_mix', 'x27_increase_mix',
|
85 |
+
'x21_increase_mix', 'x26_increase_mix']
|
86 |
+
|
87 |
+
# Assuming uplift_data already includes the 'UserID' column
|
88 |
+
uplift_data = pd.concat([uplift_data, self.df[mixed_uplift_columns]], axis=1)
|
89 |
+
|
90 |
+
# Irrelevant Data Table
|
91 |
+
irrelevant_data = self.df[['UserID'] + irrelevant_cols].copy()
|
92 |
+
|
93 |
+
# Transaction Data Table
|
94 |
+
transaction_data = self.df[['UserID'] + transaction_cols].copy()
|
95 |
+
|
96 |
+
user_profiles.columns = [
|
97 |
+
'UserID', 'AgeIndex', 'IncomeIndex', 'PurchaseFrequencyIndex',
|
98 |
+
'AccountLifetimeIndex', 'AverageTransactionValueIndex', 'PreferredPaymentMethodIndex', 'RegionIndex'
|
99 |
+
]
|
100 |
+
|
101 |
+
uplift_data.columns = [
|
102 |
+
'UserID', 'EmailDiscountCTRIndex', 'WebDiscountCTRIndex', 'SocialMediaEngagementIndex',
|
103 |
+
'DirectMailDiscountResponseIndex', 'InAppDiscountEngagementIndex', 'FlashSaleParticipationIndex',
|
104 |
+
'SeasonalPromoInterestIndex', 'LoyaltyProgramEngagementIndex', 'ReferralBonusUsageIndex',
|
105 |
+
'DiscountCodeRedemptionIndex', 'VIPSaleAccessIndex', 'EarlyAccessOptInIndex',
|
106 |
+
'ProductReviewAfterDiscountIndex', 'UpsellConversionIndex', 'CrossSellInterestIndex',
|
107 |
+
'BundlePurchaseIndex', 'SubscriptionUpgradeIndex', 'CustomerFeedbackIndex'
|
108 |
+
]
|
109 |
+
|
110 |
+
irrelevant_data.columns = [
|
111 |
+
'UserID', 'BrowserTypeIndex', 'DeviceCategoryIndex', 'OperatingSystemIndex',
|
112 |
+
'SessionStartTimeIndex', 'LanguagePreferenceIndex', 'NewsletterSubscriptionIndex',
|
113 |
+
'AccountVerificationStatusIndex', 'AdBlockerPresenceIndex'
|
114 |
+
]
|
115 |
+
|
116 |
+
# transaction_data.columns = [
|
117 |
+
# 'UserID', 'DiscountCategoryIndex', 'PurchaseIndex', 'DiscountPercentageIndex'
|
118 |
+
# ]
|
119 |
+
transaction_data.columns = ['UserID'] + transaction_cols
|
120 |
+
|
121 |
+
# List of all DataFrames to be merged
|
122 |
+
self.dataframes = [user_profiles, uplift_data, irrelevant_data, transaction_data]
|
123 |
+
|
124 |
+
# Merge all DataFrames on 'UserID' in one line
|
125 |
+
self.df = reduce(lambda left, right: pd.merge(left, right, on='UserID'), self.dataframes)
|
126 |
+
|
127 |
+
|
128 |
+
def add_monetary_effect(self):
|
129 |
+
# Adding a monetary effect column
|
130 |
+
def base_price(df, informative_features):
|
131 |
+
if df.conversion == 0:
|
132 |
+
base_price = 0
|
133 |
+
else:
|
134 |
+
base_price = randint(1, 100)
|
135 |
+
return base_price
|
136 |
+
|
137 |
+
informative_features = [k for k in self.X_names if 'informative' in k]
|
138 |
+
self.df['base_price'] = self.df.apply(lambda x: base_price(x, informative_features), axis=1)
|
139 |
+
self.df['discounted_price'] = self.df['base_price']*(1-self.df['discount'])
|
140 |
+
self.df['benefit'] = self.df['discounted_price']-0.8*self.df['base_price']
|
data_utils/data_simulation.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
|
3 |
+
class UpliftSimulationReady:
|
4 |
+
|
5 |
+
def __init__(self, files_path):
|
6 |
+
self.files_path = files_path
|
7 |
+
|
8 |
+
def load_user_profiles(self, file_name):
|
9 |
+
user_profiles = pd.read_csv(self.files_path + file_name)
|
10 |
+
return user_profiles
|
11 |
+
|
12 |
+
def load_uplift_data(self, file_name):
|
13 |
+
uplift_data = pd.read_csv(self.files_path + file_name)
|
14 |
+
return uplift_data
|
15 |
+
|
16 |
+
def load_irrelevant_data(self, file_name):
|
17 |
+
irrelevant_data = pd.read_csv(self.files_path + file_name)
|
18 |
+
return irrelevant_data
|
19 |
+
|
20 |
+
def load_other_data(self, file_name):
|
21 |
+
transaction_data = pd.read_csv(self.files_path + file_name)
|
22 |
+
return transaction_data
|
data_utils/eda_simulation.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
from data_utils.data_simulation import UpliftSimulationReady
|
4 |
+
|
5 |
+
class EDASimulationReady:
|
6 |
+
|
7 |
+
def __init__(self, files_path):
|
8 |
+
self.files_path = files_path
|
9 |
+
|
10 |
+
def load_conversions(self, file_name):
|
11 |
+
|
12 |
+
uplift_simulation = UpliftSimulationReady(self.files_path)
|
13 |
+
df = uplift_simulation.load_uplift_data(file_name)
|
14 |
+
|
15 |
+
sum_conversions = df.pivot_table(values=['conversion','discounted_price','benefit'],
|
16 |
+
index='treatment_group_key',
|
17 |
+
aggfunc=[np.sum],
|
18 |
+
margins=False)
|
19 |
+
|
20 |
+
mean_conversions = df.pivot_table(values=['conversion','discounted_price','benefit'],
|
21 |
+
index='treatment_group_key',
|
22 |
+
aggfunc=[np.mean],
|
23 |
+
margins=False)
|
24 |
+
|
25 |
+
# save to csv
|
26 |
+
sum_conversions.to_csv(self.files_path + 'sum_conversions.csv')
|
27 |
+
mean_conversions.to_csv(self.files_path + 'mean_conversions.csv')
|
28 |
+
|
29 |
+
return sum_conversions, mean_conversions
|
data_utils/exploratory_data_analysis.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
|
3 |
+
class ExploratoryAnalysis:
|
4 |
+
def __init__(self, df):
|
5 |
+
self.df = df
|
6 |
+
|
7 |
+
def compute_summaries(self):
|
8 |
+
sum_conversions = self.df.pivot_table(values=['conversion', 'discounted_price', 'benefit'],
|
9 |
+
index='treatment_group_key',
|
10 |
+
aggfunc='sum',
|
11 |
+
margins=False)
|
12 |
+
|
13 |
+
mean_conversions = self.df.pivot_table(values=['conversion', 'discounted_price', 'benefit'],
|
14 |
+
index='treatment_group_key',
|
15 |
+
aggfunc='mean',
|
16 |
+
margins=False)
|
17 |
+
return sum_conversions, mean_conversions
|
18 |
+
|
19 |
+
def compute_mean_benefit_vs_conversion(self):
|
20 |
+
_, mean_conversions = self.compute_summaries()
|
21 |
+
return mean_conversions[['conversion', 'benefit']]
|
22 |
+
|
23 |
+
def compute_ate(self):
|
24 |
+
_, mean_conversions = self.compute_summaries()
|
25 |
+
control_mean = mean_conversions.loc['control']
|
26 |
+
mean_conversions_ate = mean_conversions - control_mean
|
27 |
+
return mean_conversions_ate
|
data_utils/feature_importance.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from causalml.feature_selection.filters import FilterSelect
|
2 |
+
|
3 |
+
class FeatureImportance:
|
4 |
+
|
5 |
+
def __init__(self, df, X_names, y_name, treatment_group):
|
6 |
+
self.df = df
|
7 |
+
self.X_names = X_names
|
8 |
+
self.y_name = y_name
|
9 |
+
self.treatment_group = treatment_group
|
10 |
+
|
11 |
+
def compute_feature_importance(self):
|
12 |
+
|
13 |
+
filter_method = FilterSelect()
|
14 |
+
method = 'KL'
|
15 |
+
kl_imp = filter_method.get_importance(self.df, self.X_names, self.y_name, method,
|
16 |
+
treatment_group = self.treatment_group,
|
17 |
+
n_bins=20)
|
18 |
+
return kl_imp
|
data_utils/feature_importance_simulation.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
from data_utils.data_simulation import UpliftSimulationReady
|
4 |
+
|
5 |
+
class FISimulationReady:
|
6 |
+
|
7 |
+
def __init__(self, files_path):
|
8 |
+
self.files_path = files_path
|
9 |
+
|
10 |
+
def load_feature_importance(self, file_name):
|
11 |
+
uplift_simulation = UpliftSimulationReady(self.files_path)
|
12 |
+
df = uplift_simulation.load_uplift_data(file_name)
|
13 |
+
return df
|
eval_utils/evaluation.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
|
3 |
+
from causalml.metrics import *
|
4 |
+
|
5 |
+
class ModelEvaluator:
|
6 |
+
def __init__(self, model, df_eval, X_names):
|
7 |
+
self.model = model
|
8 |
+
self.df_eval = df_eval
|
9 |
+
self.X_names = X_names
|
10 |
+
|
11 |
+
def predict_cate(self, discount):
|
12 |
+
"""
|
13 |
+
Predicts the Conditional Average Treatment Effect (CATE) for a given discount level.
|
14 |
+
"""
|
15 |
+
self.df_eval['cate'] = self.model.predict(
|
16 |
+
X=self.df_eval[self.X_names].values,
|
17 |
+
treatment=self.df_eval['treatment_group_key'].values
|
18 |
+
).tolist()
|
19 |
+
self.df_eval[['cate_discount_05', 'cate_discount_10', 'cate_discount_15']] = pd.DataFrame(
|
20 |
+
self.df_eval.cate.tolist(),
|
21 |
+
index=self.df_eval.index
|
22 |
+
)
|
23 |
+
|
24 |
+
def eval_performance(self, discount):
|
25 |
+
"""
|
26 |
+
Evaluates the model's performance for a specific discount, calculating Qini curves for conversion and benefit.
|
27 |
+
"""
|
28 |
+
# Ensure CATE predictions are available
|
29 |
+
if 'cate' not in self.df_eval.columns:
|
30 |
+
self.predict_cate(discount)
|
31 |
+
|
32 |
+
df_eval_disc = self.df_eval[self.df_eval['treatment_group_key'].isin(['control', discount])]
|
33 |
+
df_eval_disc['treatment_num'] = df_eval_disc.apply(
|
34 |
+
lambda x: 0 if x['treatment_group_key'] == 'control' else 1,
|
35 |
+
axis=1
|
36 |
+
)
|
37 |
+
|
38 |
+
cate_col = 'cate_{}'.format(discount)
|
39 |
+
|
40 |
+
df_eval_qini_conversion = pd.DataFrame(
|
41 |
+
[df_eval_disc[cate_col].ravel(), df_eval_disc.treatment_num.ravel(), df_eval_disc['conversion'].ravel()],
|
42 |
+
index=['S', 'w', 'y']
|
43 |
+
).T
|
44 |
+
|
45 |
+
df_eval_qini_benefit = pd.DataFrame(
|
46 |
+
[df_eval_disc[cate_col].ravel(), df_eval_disc.treatment_num.ravel(), df_eval_disc['benefit'].ravel()],
|
47 |
+
index=['S', 'w', 'y']
|
48 |
+
).T
|
49 |
+
|
50 |
+
# Assuming get_qini function exists and calculates Qini coefficient
|
51 |
+
cd_conversion = (get_qini(df_eval_qini_conversion) * 2).reset_index()
|
52 |
+
cd_conversion = cd_conversion / cd_conversion.shape[0]
|
53 |
+
|
54 |
+
cd_benefit = (get_qini(df_eval_qini_benefit) * 2).reset_index()
|
55 |
+
cd_benefit = cd_benefit / cd_benefit.shape[0]
|
56 |
+
|
57 |
+
return cd_conversion, cd_benefit
|
eval_utils/evaluation_simulation.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
|
3 |
+
class CATEConversionEvaluateSimulationReady:
|
4 |
+
|
5 |
+
def __init__(self,data_path):
|
6 |
+
self.data_path = data_path
|
7 |
+
|
8 |
+
def evaluate(self, discount_group):
|
9 |
+
if discount_group == 5:
|
10 |
+
qini_05_conversion_test = pd.read_csv(self.data_path + 'qini_05_conversion_test.csv').drop(columns='Unnamed: 0')
|
11 |
+
qini_05_benefit_test = pd.read_csv(self.data_path + 'qini_05_benefit_test.csv').drop(columns='Unnamed: 0')
|
12 |
+
return qini_05_conversion_test, qini_05_benefit_test
|
13 |
+
elif discount_group == 10:
|
14 |
+
qini_10_conversion_test = pd.read_csv(self.data_path + 'qini_10_conversion_test.csv').drop(columns='Unnamed: 0')
|
15 |
+
qini_10_benefit_test = pd.read_csv(self.data_path + 'qini_10_benefit_test.csv').drop(columns='Unnamed: 0')
|
16 |
+
return qini_10_conversion_test, qini_10_benefit_test
|
17 |
+
elif discount_group == 15:
|
18 |
+
qini_15_conversion_test = pd.read_csv(self.data_path + 'qini_15_conversion_test.csv').drop(columns='Unnamed: 0')
|
19 |
+
qini_15_benefit_test = pd.read_csv(self.data_path + 'qini_15_benefit_test.csv').drop(columns='Unnamed: 0')
|
20 |
+
return qini_15_conversion_test, qini_15_benefit_test
|
21 |
+
|
22 |
+
class CATEBenefitEvaluateSimulationReady:
|
23 |
+
|
24 |
+
def __init__(self,data_path):
|
25 |
+
self.data_path = data_path
|
26 |
+
|
27 |
+
def evaluate(self, discount_group):
|
28 |
+
if discount_group == 5:
|
29 |
+
qini_05_conversion_test = pd.read_csv(self.data_path + 'qini_05_conversion_test_bate.csv').drop(columns='Unnamed: 0')
|
30 |
+
qini_05_benefit_test = pd.read_csv(self.data_path + 'qini_05_benefit_test_bate.csv').drop(columns='Unnamed: 0')
|
31 |
+
return qini_05_conversion_test, qini_05_benefit_test
|
32 |
+
elif discount_group == 10:
|
33 |
+
qini_10_conversion_test = pd.read_csv(self.data_path + 'qini_10_conversion_test_bate.csv').drop(columns='Unnamed: 0')
|
34 |
+
qini_10_benefit_test = pd.read_csv(self.data_path + 'qini_10_benefit_test_bate.csv').drop(columns='Unnamed: 0')
|
35 |
+
return qini_10_conversion_test, qini_10_benefit_test
|
36 |
+
elif discount_group == 15:
|
37 |
+
qini_15_conversion_test = pd.read_csv(self.data_path + 'qini_15_conversion_test_bate.csv').drop(columns='Unnamed: 0')
|
38 |
+
qini_15_benefit_test = pd.read_csv(self.data_path + 'qini_15_benefit_test_bate.csv').drop(columns='Unnamed: 0')
|
39 |
+
return qini_15_conversion_test, qini_15_benefit_test
|
mlops_utils/wandb_utils.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import wandb
|
2 |
+
import pandas as pd
|
3 |
+
import os
|
4 |
+
|
5 |
+
def upload_dataset_to_wandb(dirs, project_name, dataset_name, dataset_type='raw_dataset'):
|
6 |
+
with wandb.init(project=project_name, job_type='load-data') as run:
|
7 |
+
dataset_artifact = wandb.Artifact(dataset_name, type=dataset_type)
|
8 |
+
for dir in dirs:
|
9 |
+
dataset_artifact.add_dir(dir)
|
10 |
+
run.log_artifact(dataset_artifact)
|
11 |
+
|
12 |
+
def eda_work_with_dataset_to_wandb(dirs, project_name, dataset_name, dataset_type, artifact_type):
|
13 |
+
with wandb.init(project=project_name, job_type='eda') as run:
|
14 |
+
dataset_artifact = run.use_artifact(dataset_name, type=dataset_type)
|
15 |
+
eda_artifact = wandb.Artifact('eda_result', type=artifact_type)
|
16 |
+
for dir in dirs:
|
17 |
+
eda_artifact.add_dir(dir)
|
18 |
+
run.log_artifact(eda_artifact)
|
19 |
+
|
20 |
+
run.log({
|
21 |
+
"eda_result": pd.read_csv(
|
22 |
+
os.path.join(dirs[0], "kl_feature_importance.csv")
|
23 |
+
)
|
24 |
+
}
|
25 |
+
)
|
26 |
+
|
27 |
+
def training_results_to_wandb(dirs, project_name, dataset_name, dataset_type, artifact_type, model_name, job_type='train'):
|
28 |
+
with wandb.init(project=project_name, job_type=job_type) as run:
|
29 |
+
dataset_artifact = run.use_artifact(dataset_name, type=dataset_type)
|
30 |
+
model_artifact = wandb.Artifact(model_name, type=artifact_type)
|
31 |
+
for dir in dirs:
|
32 |
+
model_artifact.add_dir(dir)
|
33 |
+
run.log_artifact(model_artifact)
|
34 |
+
|
35 |
+
if job_type == 'train':
|
36 |
+
run.log({
|
37 |
+
"discount_05_feature_importance": pd.read_csv(
|
38 |
+
os.path.join(dirs[0], "discount_05_feature_importance.csv")
|
39 |
+
),
|
40 |
+
"discount_10_feature_importance": pd.read_csv(
|
41 |
+
os.path.join(dirs[0], "discount_10_feature_importance.csv")
|
42 |
+
),
|
43 |
+
"discount_15_feature_importance": pd.read_csv(
|
44 |
+
os.path.join(dirs[0], "discount_15_feature_importance.csv")
|
45 |
+
),
|
46 |
+
}
|
47 |
+
)
|
models_utils/ml_models.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sklearn.model_selection import train_test_split
|
2 |
+
from xgboost import XGBRegressor, XGBClassifier
|
3 |
+
|
4 |
+
from causalml.inference.tree import UpliftRandomForestClassifier
|
5 |
+
from causalml.inference.meta import BaseXRegressor, BaseRRegressor, BaseSRegressor, BaseTRegressor
|
6 |
+
from causalml.inference.meta import BaseSClassifier, BaseTClassifier, BaseXClassifier, BaseRClassifier
|
7 |
+
|
8 |
+
class ModelTraining:
|
9 |
+
def __init__(self, df, y_name, X_names):
|
10 |
+
self.df = df
|
11 |
+
self.y_name = y_name
|
12 |
+
self.X_names = X_names
|
13 |
+
self.df_train = None
|
14 |
+
self.df_test = None
|
15 |
+
self.learner_t = None
|
16 |
+
self.conversion_learner_t = None
|
17 |
+
self.benefit_learner_t = None
|
18 |
+
|
19 |
+
def split_data(self, test_size, random_state):
|
20 |
+
self.df_train, self.df_test = train_test_split(
|
21 |
+
self.df,
|
22 |
+
test_size=test_size,
|
23 |
+
random_state=random_state
|
24 |
+
)
|
25 |
+
|
26 |
+
def fit_predict_classifier(self, params, control_name):
|
27 |
+
self.learner_t = BaseTClassifier(XGBClassifier(**params), control_name=control_name)
|
28 |
+
self.conversion_learner_t = self.learner_t
|
29 |
+
return self._fit_predict()
|
30 |
+
|
31 |
+
def fit_predict_regressor(self, params, control_name):
|
32 |
+
self.learner_t = BaseTRegressor(XGBRegressor(**params), control_name=control_name)
|
33 |
+
self.benefit_learner_t = self.learner_t
|
34 |
+
return self._fit_predict()
|
35 |
+
|
36 |
+
def _fit_predict(self):
|
37 |
+
self.learner_t_tau = self.learner_t.fit_predict(
|
38 |
+
X=self.df_train[self.X_names].values,
|
39 |
+
treatment=self.df_train['treatment_group_key'].values,
|
40 |
+
y=self.df_train[self.y_name].values
|
41 |
+
)
|
42 |
+
self.learner_t.feature_names = self.X_names
|
43 |
+
return self.learner_t_tau
|
44 |
+
|
45 |
+
def compute_feature_importance(self):
|
46 |
+
if self.learner_t is None:
|
47 |
+
raise ValueError("Model must be fitted before computing feature importances.")
|
48 |
+
|
49 |
+
return self.learner_t.get_importance(
|
50 |
+
X=self.df_train[self.X_names],
|
51 |
+
tau=self.learner_t_tau,
|
52 |
+
features=self.X_names,
|
53 |
+
normalize=True,
|
54 |
+
method='auto'
|
55 |
+
)
|
models_utils/models_simulation.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import pickle
|
3 |
+
|
4 |
+
class CATESimulationReady:
|
5 |
+
|
6 |
+
def __init__(self, model_path, y_pred_path):
|
7 |
+
self.model_path = model_path
|
8 |
+
self.y_pred_path = y_pred_path
|
9 |
+
|
10 |
+
# def get_model(self):
|
11 |
+
# model = pd.read_csv(self.model_path)
|
12 |
+
# return model
|
13 |
+
|
14 |
+
def predict(self):
|
15 |
+
with open(self.y_pred_path, 'rb') as f:
|
16 |
+
y_pred = pickle.load(f)
|
17 |
+
return y_pred
|
18 |
+
|
19 |
+
def feature_importance(self, fi_path):
|
20 |
+
fi = pd.read_csv(fi_path)
|
21 |
+
fi.columns = ['feature', 'score']
|
22 |
+
return fi
|
notebooks/Demo_Notebook.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
notebooks/Test.ipynb
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 11,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [],
|
8 |
+
"source": [
|
9 |
+
"import pandas as pd\n",
|
10 |
+
"import matplotlib.pyplot as plt\n",
|
11 |
+
"\n",
|
12 |
+
"class CATEEvaluateSimulationReady:\n",
|
13 |
+
"\n",
|
14 |
+
" def __init__(self,data_path):\n",
|
15 |
+
" self.data_path = data_path\n",
|
16 |
+
"\n",
|
17 |
+
" def evaluate(self, discount_group):\n",
|
18 |
+
" if discount_group == 5:\n",
|
19 |
+
" qini_05_conversion_test = pd.read_csv(self.data_path + 'qini_05_conversion_test.csv').drop(columns='Unnamed: 0')\n",
|
20 |
+
" qini_05_benefit_test = pd.read_csv(self.data_path + 'qini_05_benefit_test.csv').drop(columns='Unnamed: 0')\n",
|
21 |
+
" return qini_05_conversion_test, qini_05_benefit_test\n",
|
22 |
+
" elif discount_group == 10:\n",
|
23 |
+
" qini_10_conversion_test = pd.read_csv(self.data_path + 'qini_10_conversion_test.csv').drop(columns='Unnamed: 0')\n",
|
24 |
+
" qini_10_benefit_test = pd.read_csv(self.data_path + 'qini_10_benefit_test.csv').drop(columns='Unnamed: 0')\n",
|
25 |
+
" return qini_10_conversion_test, qini_10_benefit_test\n",
|
26 |
+
" elif discount_group == 15:\n",
|
27 |
+
" qini_15_conversion_test = pd.read_csv(self.data_path + 'qini_15_conversion_test.csv').drop(columns='Unnamed: 0')\n",
|
28 |
+
" qini_15_benefit_test = pd.read_csv(self.data_path + 'qini_15_benefit_test.csv').drop(columns='Unnamed: 0')\n",
|
29 |
+
" return qini_15_conversion_test, qini_15_benefit_test"
|
30 |
+
]
|
31 |
+
},
|
32 |
+
{
|
33 |
+
"cell_type": "code",
|
34 |
+
"execution_count": 7,
|
35 |
+
"metadata": {},
|
36 |
+
"outputs": [],
|
37 |
+
"source": [
|
38 |
+
"eval = CATEEvaluateSimulationReady('../data/effect_data/')\n",
|
39 |
+
"qini_05_conversion_test, qini_05_benefit_test = eval.evaluate(5)\n",
|
40 |
+
"qini_10_conversion_test, qini_10_benefit_test = eval.evaluate(10)\n",
|
41 |
+
"qini_15_conversion_test, qini_15_benefit_test = eval.evaluate(15)"
|
42 |
+
]
|
43 |
+
},
|
44 |
+
{
|
45 |
+
"cell_type": "code",
|
46 |
+
"execution_count": 23,
|
47 |
+
"metadata": {},
|
48 |
+
"outputs": [
|
49 |
+
{
|
50 |
+
"data": {
|
51 |
+
"text/plain": [
|
52 |
+
"Text(0.5, 1.0, 'CATE conversion vs Targeted Population')"
|
53 |
+
]
|
54 |
+
},
|
55 |
+
"execution_count": 23,
|
56 |
+
"metadata": {},
|
57 |
+
"output_type": "execute_result"
|
58 |
+
},
|
59 |
+
{
|
60 |
+
"data": {
|
61 |
+
"image/png": "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",
|
62 |
+
"text/plain": [
|
63 |
+
"<Figure size 432x288 with 1 Axes>"
|
64 |
+
]
|
65 |
+
},
|
66 |
+
"metadata": {
|
67 |
+
"needs_background": "light"
|
68 |
+
},
|
69 |
+
"output_type": "display_data"
|
70 |
+
}
|
71 |
+
],
|
72 |
+
"source": [
|
73 |
+
"fig_conversion, ax_conversion = plt.subplots()\n",
|
74 |
+
"\n",
|
75 |
+
"qini_05_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='b', ls='--', lw=0.5, label = '5% random')\n",
|
76 |
+
"qini_10_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='g', ls='--', lw=0.5, label = '10% random')\n",
|
77 |
+
"qini_15_conversion_test.plot(ax=ax_conversion, x='index', y='Random', color='y', ls='--', lw=0.5, label = '15% random')\n",
|
78 |
+
"qini_05_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='b', label = '5% model')\n",
|
79 |
+
"qini_10_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='g', label = '10% model')\n",
|
80 |
+
"qini_15_conversion_test.plot(ax=ax_conversion, x='index', y='S', color='y', label = '15% model')\n",
|
81 |
+
"\n",
|
82 |
+
"ax_conversion.legend()\n",
|
83 |
+
"ax_conversion.set_xlabel('Fraction of Targeted Users')\n",
|
84 |
+
"ax_conversion.set_ylabel('CATE conversion')\n",
|
85 |
+
"ax_conversion.set_title('CATE conversion vs Targeted Population')"
|
86 |
+
]
|
87 |
+
},
|
88 |
+
{
|
89 |
+
"cell_type": "code",
|
90 |
+
"execution_count": 26,
|
91 |
+
"metadata": {},
|
92 |
+
"outputs": [
|
93 |
+
{
|
94 |
+
"data": {
|
95 |
+
"text/plain": [
|
96 |
+
"Text(0.5, 1.0, 'CATE benefit vs Targeted Population')"
|
97 |
+
]
|
98 |
+
},
|
99 |
+
"execution_count": 26,
|
100 |
+
"metadata": {},
|
101 |
+
"output_type": "execute_result"
|
102 |
+
},
|
103 |
+
{
|
104 |
+
"data": {
|
105 |
+
"image/png": "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",
|
106 |
+
"text/plain": [
|
107 |
+
"<Figure size 432x288 with 1 Axes>"
|
108 |
+
]
|
109 |
+
},
|
110 |
+
"metadata": {
|
111 |
+
"needs_background": "light"
|
112 |
+
},
|
113 |
+
"output_type": "display_data"
|
114 |
+
}
|
115 |
+
],
|
116 |
+
"source": [
|
117 |
+
"fig_benefit, ax_benefit = plt.subplots()\n",
|
118 |
+
"qini_05_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='b', ls='--', lw=0.5, label = '5% random')\n",
|
119 |
+
"qini_10_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='g', ls='--', lw=0.5, label = '10% random')\n",
|
120 |
+
"qini_15_benefit_test.plot(ax=ax_benefit, x='index', y='Random', color='y', ls='--', lw=0.5, label = '15% random')\n",
|
121 |
+
"qini_05_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='b', label = '5% model')\n",
|
122 |
+
"qini_10_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='g', label = '10% model')\n",
|
123 |
+
"qini_15_benefit_test.plot(ax=ax_benefit, x='index', y='S', color='y', label = '15% model')\n",
|
124 |
+
"ax_benefit.legend()\n",
|
125 |
+
"ax_benefit.set_xlabel('Fraction of Targeted Users')\n",
|
126 |
+
"ax_benefit.set_ylabel('CATE Benefit')\n",
|
127 |
+
"ax_benefit.set_title('CATE benefit vs Targeted Population')"
|
128 |
+
]
|
129 |
+
}
|
130 |
+
],
|
131 |
+
"metadata": {
|
132 |
+
"kernelspec": {
|
133 |
+
"display_name": "Python 3",
|
134 |
+
"language": "python",
|
135 |
+
"name": "python3"
|
136 |
+
},
|
137 |
+
"language_info": {
|
138 |
+
"codemirror_mode": {
|
139 |
+
"name": "ipython",
|
140 |
+
"version": 3
|
141 |
+
},
|
142 |
+
"file_extension": ".py",
|
143 |
+
"mimetype": "text/x-python",
|
144 |
+
"name": "python",
|
145 |
+
"nbconvert_exporter": "python",
|
146 |
+
"pygments_lexer": "ipython3",
|
147 |
+
"version": "3.9.6"
|
148 |
+
}
|
149 |
+
},
|
150 |
+
"nbformat": 4,
|
151 |
+
"nbformat_minor": 2
|
152 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
causalml==0.15.0
|
2 |
+
matplotlib==3.8.3
|
3 |
+
numpy==1.23.5
|
4 |
+
pandas==2.2.1
|
5 |
+
scikit_learn==1.4.1.post1
|
6 |
+
streamlit==1.32.2
|
7 |
+
xgboost==2.0.3
|