File size: 15,403 Bytes
5fc7138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
import gradio as gr
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import plotly
import plotly.graph_objs as go
from plotly.subplots import make_subplots
from sklearn.preprocessing import StandardScaler
from causalml.inference.meta import BaseTClassifier
from sklearn.ensemble import RandomForestClassifier
from data_generator import generate_synthetic_data
from rct_simulator import run_rct_simulation
from rct_analyzer import analyze_rct_results

# Global variables to store generated data and RCT results
generated_data = None
rct_results = None

def perform_eda(discount_level):
    global rct_results, generated_data
    if rct_results is None or generated_data is None:
        return "Please generate customer data and run RCT simulation first.", None, None, None, None

    transactions_df, variant_assignments_df = rct_results

    # Merge data
    merged_df = pd.merge(generated_data, variant_assignments_df, on='customer_id', how='inner')
    merged_df = pd.merge(merged_df, transactions_df, on=['customer_id', 'variant'], how='left')
    merged_df['purchase'] = merged_df['purchase'].fillna(0)
    merged_df['profit'] = merged_df['profit'].fillna(0)

    # Filter for control and selected discount level
    filtered_df = merged_df[merged_df['variant'].isin(['Control', discount_level])]

    # Analyze newsletter_subscription
    newsletter_results = analyze_feature(filtered_df, 'newsletter_subscription')

    # Analyze preferred_payment_method
    payment_results = analyze_feature(filtered_df, 'preferred_payment_method')

    # Create plots
    newsletter_fig = create_bar_plot(newsletter_results, 'newsletter_subscription', discount_level)
    payment_fig = create_bar_plot(payment_results, 'preferred_payment_method', discount_level)

    return (f"EDA completed for {discount_level}",
            newsletter_results, payment_results, newsletter_fig, payment_fig)

def analyze_feature(df, feature):
    control_df = df[df['variant'] == 'Control']
    treatment_df = df[df['variant'] != 'Control']

    control_stats = control_df.groupby(feature).agg({
        'purchase': 'sum',
        'profit': 'sum'
    }).reset_index()

    treatment_stats = treatment_df.groupby(feature).agg({
        'purchase': 'sum',
        'profit': 'sum'
    }).reset_index()

    results = pd.merge(control_stats, treatment_stats, on=feature, suffixes=('_control', '_treatment'))
    results['incremental_purchases'] = results['purchase_treatment'] - results['purchase_control']
    results['incremental_profit'] = results['profit_treatment'] - results['profit_control']

    return results

def create_bar_plot(data, feature, discount_level):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
    
    data[feature] = data[feature].astype(str)  # Ensure the feature is treated as a string
    
    ax1.bar(data[feature], data['incremental_purchases'])
    ax1.set_title(f'Incremental Purchases by {feature}\n({discount_level})', fontsize=14)
    ax1.set_xlabel(feature)
    ax1.set_ylabel('Incremental Purchases')
    ax1.tick_params(axis='x', rotation=45)

    ax2.bar(data[feature], data['incremental_profit'])
    ax2.set_title(f'Incremental Profit by {feature}\n({discount_level})', fontsize=14)
    ax2.set_xlabel(feature)
    ax2.set_ylabel('Incremental Profit')
    ax2.tick_params(axis='x', rotation=45)

    plt.tight_layout()
    return fig

    
def generate_and_display_data(num_customers):
    global generated_data
    generated_data = generate_synthetic_data(num_customers=num_customers)
    
    df_basic_info = generated_data[['customer_id', 'name', 'email', 'age', 'gender', 'region', 'city',
                        'registration_date', 'phone_number', 'preferred_language',
                        'newsletter_subscription', 'preferred_payment_method']]
    
    df_extra_info = generated_data[['customer_id', 'loyalty_level', 'main_browsing_device', 
                        'product_categories_of_interest', 'average_order_value', 
                        'total_orders', 'last_order_date']]
    
    sample_basic = df_basic_info.sample(n=min(10, len(df_basic_info)))
    sample_extra = df_extra_info.sample(n=min(10, len(df_extra_info)))
    
    return (sample_basic, sample_extra,
            f"Generated {num_customers} records. Displaying samples of 10 rows for each dataset.")

def run_and_display_rct(experiment_duration):
    global generated_data, rct_results
    if generated_data is None:
        return None, None, "Please generate customer data first."
    
    transactions_df, variant_assignments_df = run_rct_simulation(generated_data, experiment_duration)
    rct_results = (transactions_df, variant_assignments_df)  # Store both DataFrames as a tuple
    
    sample_assignments = variant_assignments_df.sample(n=min(10, len(variant_assignments_df)))
    sample_transactions = transactions_df.sample(n=min(10, len(transactions_df)))
    
    return (sample_assignments, sample_transactions, 
            f"Ran RCT simulation for {experiment_duration} days. Displaying samples of 10 rows for each dataset.")

def analyze_and_display_results():
    global rct_results
    if rct_results is None:
        return None, None, None, "Please run the RCT simulation first."
    
    transactions_df, variant_assignments_df = rct_results
    overall_df, variant_df, fig = analyze_rct_results(transactions_df, variant_assignments_df)
    return overall_df, variant_df, fig, "Analysis complete. Displaying results and visualizations."

def build_uplift_model(data, features, treatment, control):
    # Prepare the data
    treatment_data = data[data['variant'] == treatment]
    control_data = data[data['variant'] == control]
    combined_data = pd.concat([treatment_data, control_data])
    
    # Create dummy variables for categorical features
    categorical_features = [f for f in features if data[f].dtype == 'object']
    X = pd.get_dummies(data[features], columns=categorical_features)
    
    # Standardize numerical features
    numerical_features = [f for f in features if data[f].dtype in ['int64', 'float64']]
    scaler = StandardScaler()
    X[numerical_features] = scaler.fit_transform(X[numerical_features])
    
    # Prepare y and treatment for the combined data
    y = combined_data['purchase']
    t = (combined_data['variant'] == treatment).astype(int)
    
    # Create and fit the RandomForestClassifier directly
    rf_model = RandomForestClassifier(n_estimators=50, max_depth=4)
    rf_model.fit(X.loc[combined_data.index], y)
    
    # Get feature importances from the RandomForestClassifier
    feature_importances = rf_model.feature_importances_
    
    # Create a dataframe with feature names and their importances
    feature_importance_df = pd.DataFrame({
        'feature': X.columns,
        'importance': feature_importances
    }).sort_values('importance', ascending=False)
    
    # Create and fit the BaseTClassifier model
    model = BaseTClassifier(RandomForestClassifier(n_estimators=50, max_depth=4))
    model.fit(X=X.loc[combined_data.index].values, treatment=t, y=y)
    
    # Predict for all data
    uplift_scores = model.predict(X.values)
    
    # Handle 2D output if necessary
    if uplift_scores.ndim == 2:
        if uplift_scores.shape[1] == 2:
            uplift_scores = uplift_scores[:, 1] - uplift_scores[:, 0]
        elif uplift_scores.shape[1] == 1:
            uplift_scores = uplift_scores.flatten()
    
    return uplift_scores, feature_importance_df

def build_model_and_display(selected_features, treatment):
    global rct_results, generated_data
    if rct_results is None or generated_data is None:
        return "Please generate customer data and run RCT simulation first.", None, None
    
    transactions_df, variant_assignments_df = rct_results
    
    # Prepare the data
    df_with_variant = pd.merge(generated_data, variant_assignments_df, on='customer_id', how='inner')
    transactions_df['purchase'] = 1
    final_df = pd.merge(df_with_variant, transactions_df, on=['customer_id', 'variant'], how='left')
    columns_to_fill = ['purchase', 'price', 'discounted_price', 'cost', 'profit']
    final_df[columns_to_fill] = final_df[columns_to_fill].fillna(0)
    
    # Build the model
    uplift_scores, feature_importance_df = build_uplift_model(final_df, selected_features, treatment, 'Control')
    
    # Calculate statistics
    stats = pd.DataFrame({
        'Metric': ['Mean', 'Std', 'Min', 'Max'],
        'Value': [
            np.mean(uplift_scores),
            np.std(uplift_scores),
            np.min(uplift_scores),
            np.max(uplift_scores)
        ]
    })
    
    # Create feature importance plot
    fig, ax = plt.subplots(figsize=(10, 6))
    sns.barplot(x='importance', y='feature', data=feature_importance_df.head(10), ax=ax)
    ax.set_title(f'Top 10 Feature Importance for {treatment} vs Control')
    ax.set_xlabel('Importance')
    ax.set_ylabel('Feature')
    plt.tight_layout()
    
    info = f"Uplift model built using {len(selected_features)} features.\n"
    info += f"Treatment: {treatment} vs Control\n"
    info += f"Number of samples: {len(uplift_scores)}"
    
    return info, stats, fig

with gr.Blocks() as demo:
    gr.Markdown("# Causal AI - Synthetic Customer Data Generator and RCT Simulator")
    
    with gr.Tab("Generate Customer Data"):
        gr.Markdown("# Generate Synthetic Customers data")
        gr.Markdown("In this section we generate typical data of customers that are registered to our store.")
        gr.Markdown("First we generate some basic attributes that are defined when the customer first registers, such as Name, City or Preferred Language.")
        gr.Markdown("Then we add some extra information that is usually the result of the customer past behavior, such as Loyalty Level, Past Purchases or Categories of interest.")
        gr.Markdown("## Select the number of customers that you want to Generate")
        num_customers_input = gr.Slider(minimum=10000, maximum=500000, value=50000, step=1000, label="Number of Customer Records")
        generate_btn = gr.Button("Generate Customer Data")
        gr.Markdown("## Basic Customer Info Sample")
        basic_info_output = gr.DataFrame()
        gr.Markdown("## Extra Customer Info Sample")
        extra_info_output = gr.DataFrame()
        generate_info = gr.Textbox(label="Generation Info")
        
        generate_btn.click(fn=generate_and_display_data, 
                           inputs=num_customers_input, 
                           outputs=[basic_info_output, extra_info_output, generate_info])
    
    with gr.Tab("Run RCT Simulation"):
        gr.Markdown("# Run a Randomized Control Experiment for data collection and analysis")
        gr.Markdown("In this section we simulate running an Experiment where we offer customers different levels of discounts in the Electronics department.")
        gr.Markdown("We randomly split the customers in 4 groups: Control, 5% discount, 10% discount and 15% discount")
        gr.Markdown("During the experiment runtime we record all the purchases made by the customers. We can decide how long to run the experiment for, where longer periods lead to less noise and more significance in the results.")
        experiment_duration_input = gr.Slider(minimum=10, maximum=60, value=30, step=1, label="Experiment Duration (days)")
        rct_btn = gr.Button("Run RCT Simulation")
        gr.Markdown("## Customer assigment to experiment group:")
        assignments_output = gr.DataFrame()
        gr.Markdown("## Purchases made during experiment runtime:")
        transactions_output = gr.DataFrame()
        rct_info = gr.Textbox(label="RCT Simulation Info")
        rct_btn.click(fn=run_and_display_rct, 
                      inputs=experiment_duration_input, 
                      outputs=[assignments_output, transactions_output, rct_info])

    with gr.Tab("Analyze RCT Results"):
        gr.Markdown("# Experiment Analysis")
        gr.Markdown("In this section we analyze the experiment results. We measure, per each discount value (5%, 10%, 15%) what is the incremental number of Purchases and the incremental Profit compared to the Control group.")
        analyze_btn = gr.Button("Analyze RCT Results")
        gr.Markdown("## Overall metrics")
        overall_metrics_output = gr.DataFrame()
        gr.Markdown("## Metrics by Variant")
        variant_metrics_output = gr.DataFrame()
        gr.Markdown("## Metrics per Variant visualization")
        gr.Markdown("## To-Do: Add confidence intervals")
        plot_output = gr.Plot()
        analysis_info = gr.Textbox(label="Analysis Info")
        
        analyze_btn.click(fn=analyze_and_display_results,
                          inputs=[],
                          outputs=[overall_metrics_output, variant_metrics_output, plot_output, analysis_info])

    with gr.Tab("Exploratory Data Analysis"):
        gr.Markdown("# Exploratory Data Analysis")
        gr.Markdown("In this section, we explore the impact of discounts on different customer segments.")
        
        discount_dropdown = gr.Dropdown(
            choices=['5% discount', '10% discount', '15% discount'],
            label="Select discount level to analyze",
            value='10% discount'
        )
        
        eda_btn = gr.Button("Perform EDA")
        
        eda_info = gr.Textbox(label="EDA Information")
        
        gr.Markdown("## Newsletter Subscription Analysis")
        newsletter_results = gr.DataFrame(label="Newsletter Subscription Results")
        newsletter_plot = gr.Plot(label="Newsletter Subscription Plot")
        
        gr.Markdown("## Preferred Payment Method Analysis")
        payment_results = gr.DataFrame(label="Preferred Payment Method Results")
        payment_plot = gr.Plot(label="Preferred Payment Method Plot")
        
        eda_btn.click(
            fn=perform_eda,
            inputs=[discount_dropdown],
            outputs=[eda_info, newsletter_results, payment_results, newsletter_plot, payment_plot]
        ) 
        
    with gr.Tab("Build Uplift Model"):
        gr.Markdown("## Build Uplift Model")
        
        # Feature selection
        feature_checklist = gr.CheckboxGroup(
            choices=['age', 'gender', 'region', 'preferred_language', 'newsletter_subscription', 
                     'preferred_payment_method', 'loyalty_level', 'main_browsing_device', 
                     'average_order_value', 'total_orders'],
            label="Select features for the model",
            value=['age', 'gender', 'loyalty_level', 'average_order_value', 'total_orders']
        )
        
        # Dropdown for selecting treatment
        treatment_dropdown = gr.Dropdown(
            choices=['5% discount', '10% discount', '15% discount'],
            label="Select treatment",
            value='10% discount'
        )
        
        build_model_btn = gr.Button("Build Uplift Model")
        
        model_info = gr.Textbox(label="Model Information")
        uplift_stats = gr.Dataframe(label="Uplift Score Statistics")
        feature_importance_plot = gr.Plot(label="Feature Importance")
        
        build_model_btn.click(
            fn=build_model_and_display,
            inputs=[feature_checklist, treatment_dropdown],
            outputs=[model_info, uplift_stats, feature_importance_plot]
        )

demo.launch()