CoolFace
Apppublic

causalscience/Member_Rewards_Effect

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
app.py536 linesDownload Raw Back to root
1# Import required libraries2import pandas as pd3import numpy as np4import matplotlib.pyplot as plt5import seaborn as sns6import os7import tempfile8from PIL import Image9import gradio as gr10import statsmodels.formula.api as smf11import dowhy12from dowhy import CausalModel13 14# -----------------------------------------15# Common Data Loading and Processing16# -----------------------------------------17 18def load_data(file_obj, signup_month=3):19    """20    Load and prepare data for analysis.21    Returns the full dataset and the filtered dataset for the specified signup month.22    """23    try:24        # Read file based on extension25        file_path = file_obj.name26        if file_path.endswith('.csv'):27            df = pd.read_csv(file_path)28        elif file_path.endswith(('.xls', '.xlsx')):29            df = pd.read_excel(file_path)30        else:31            return None, None, "Unsupported file format. Please upload a CSV or Excel file."32 33        # Process for the specific signup month34        i = signup_month35 36        # Keep only users with signup_month of 0 (control) or equal to i (treated)37        df_i = df[df.signup_month.isin([0, i])].copy()38        39        # Validate data40        if len(df_i) == 0:41            return None, None, f"No data found for signup month {i}."42            43        # Check if required columns exist44        required_columns = ['user_id', 'month', 'spend', 'signup_month', 'treatment']45        missing_columns = [col for col in required_columns if col not in df.columns]46        if missing_columns:47            return None, None, f"Missing required columns: {', '.join(missing_columns)}"48 49        # Aggregate spending for each user50        df_i_agg = df_i.groupby(["user_id", "signup_month", "treatment"]).apply(51            lambda x: pd.Series({52                "pre_spends": x.loc[x.month < i, "spend"].mean(),53                "post_spends": x.loc[x.month > i, "spend"].mean()54            })55        ).reset_index()56 57        # Drop rows with missing pre or post spend58        df_i_agg = df_i_agg.dropna()59 60        # Check if we have enough data after dropping NA values61        if len(df_i_agg) == 0:62            return None, None, "No complete data available for analysis. All observations have missing pre or post spending."63 64        # Ensure we have both treatment groups represented65        if df_i_agg['treatment'].nunique() < 2:66            return None, None, "Cannot estimate treatment effect: data contains only one treatment group after filtering."67 68        return df, df_i_agg, None69    70    except Exception as e:71        return None, None, f"Error during data loading: {str(e)}"72 73# Create common data visualization for both methods74def create_common_visualization(df, df_i_agg, signup_month, title="Data Visualization"):75    """76    Create visualizations common to both analysis methods77    """78    plt.figure(figsize=(12, 8))79 80    # Plot 1: Pre vs Post Treatment Spending81    plt.subplot(2, 1, 1)82    83    # Handle both boolean and integer treatment indicators safely84    treatment_col = df_i_agg['treatment']85    if treatment_col.dtype == bool:86        control_mask = ~treatment_col87        treated_mask = treatment_col88    else:  # Assume numeric (0/1)89        control_mask = treatment_col == 090        treated_mask = treatment_col == 191        92    control = df_i_agg.loc[control_mask]93    treated = df_i_agg.loc[treated_mask]94    95    # Check if we have data for both groups96    if not control.empty:97        plt.scatter(control["pre_spends"], control["post_spends"], alpha=0.5, label="No Signup")98    if not treated.empty:99        plt.scatter(treated["pre_spends"], treated["post_spends"], alpha=0.5, label="Signed Up")100        101    plt.xlabel("Pre-Treatment Spend")102    plt.ylabel("Post-Treatment Spend")103    plt.title(f"Pre vs Post Treatment Spending (Signup Month = {signup_month})")104    plt.legend()105 106    # Plot 2: Average Monthly Spending Over Time107    plt.subplot(2, 1, 2)108    109    try:110        monthly_spend = df.groupby(["month", "treatment"])["spend"].mean().reset_index()111        sns.lineplot(data=monthly_spend, x="month", y="spend", hue="treatment")112        plt.axvline(x=signup_month, color='red', linestyle='--', alpha=0.7)113    except Exception as e:114        # If line plot fails, just show a message115        plt.text(0.5, 0.5, f"Could not create monthly spend plot: {str(e)}", 116                ha='center', va='center', transform=plt.gca().transAxes)117    118    plt.title("Average Monthly Spend Over Time")119    plt.xlabel("Month")120    plt.ylabel("Average Spend")121    plt.tight_layout()122 123    # Save to a temporary file124    with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:125        viz_path = temp_file.name126        plt.savefig(viz_path)127        plt.close()128        129    return viz_path130 131# -----------------------------------------132# DoWhy Analysis Implementation133# -----------------------------------------134 135def analyze_dowhy(df, df_i_agg, signup_month, method="backdoor.propensity_score_matching", refutation_method="None"):136    """137    Perform causal analysis using DoWhy138    """139    try:140        i = signup_month141        142        # Define the causal graph143        causal_graph = """digraph {144            treatment[label="Program Signup in month i"];145            pre_spends;146            post_spends;147            Z->treatment;148            pre_spends -> treatment;149            treatment->post_spends;150            signup_month->post_spends;151            signup_month->treatment;152        }"""153 154        # Create model and estimate effect155        model = CausalModel(156            data=df_i_agg,157            graph=causal_graph.replace("\n", " "),158            treatment="treatment",159            outcome="post_spends"160        )161 162        # Identify estimand163        identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)164        165        # Estimate effect166        estimate = model.estimate_effect(167            identified_estimand,168            method_name=method,169            target_units="att"170        )171 172        effect_value = float(estimate.value)173        174        # Save causal model visualization175        try:176            # Some environments may not support graphviz, so we'll handle potential errors177            model.view_model(layout="dot")178            causal_graph_path = "causal_model.png"179        except Exception as graph_error:180            causal_graph_path = None181            print(f"Warning: Could not generate causal graph visualization: {graph_error}")182 183        # Add refutation if requested - format exactly as requested by user184        refutation_result = ""185        if refutation_method != "None":186            try:187                refutation = model.refute_estimate(188                    identified_estimand,189                    estimate,190                    method_name=refutation_method,191                    placebo_type="permute" if refutation_method == "placebo_treatment_refuter" else None,192                    num_simulations=20193                )194                195                # Format refutation results as requested196                refutation_result = f"\nRefutation using {refutation_method}:\n"197                refutation_result += f"Original estimate: {effect_value:.2f}\n"198 199                # Access attributes safely - maintain original format200                if hasattr(refutation, 'new_effect'):201                    refutation_result += f"New effect: {refutation.new_effect:.2f}\n"202                203                # Reproduce the result line exactly as requested204                if hasattr(refutation, 'p_value') and hasattr(refutation, 'is_statistically_significant'):205                    refutation_result += f"Result: {{'p_value': np.float64({refutation.p_value}), 'is_statistically_significant': np.{str(refutation.is_statistically_significant)}_}}\n"206                207                # Add full refutation details208                refutation_result += f"Full refutation details:\n"209                refutation_result += f"Refute: Use a Placebo Treatment\n"210                if hasattr(refutation, 'estimated_effect'):211                    refutation_result += f"Estimated effect:{refutation.estimated_effect}\n"212                elif hasattr(refutation, 'estimated_effect_'):213                    refutation_result += f"Estimated effect:{refutation.estimated_effect_}\n"214                else:215                    refutation_result += f"Estimated effect:{effect_value}\n"216                    217                if hasattr(refutation, 'new_effect'):218                    refutation_result += f"New effect:{refutation.new_effect}\n"219                    220                if hasattr(refutation, 'p_value'):221                    refutation_result += f"p value:{refutation.p_value}"222 223            except Exception as e:224                refutation_result = f"\n\nRefutation attempt failed: {str(e)}"225 226        # Create common data visualization227        viz_path = create_common_visualization(228            df, df_i_agg, signup_month, 229            title="DoWhy Data Visualization"230        )231 232        # Result text - formatted exactly as requested233        result_text = f"The causal effect of the rewards program (ATT) is ${effect_value:.2f}."234        if refutation_result:235            result_text += refutation_result236 237        return result_text, causal_graph_path, viz_path238 239    except Exception as e:240        return f"Error during DoWhy analysis: {str(e)}", None, None241 242# -----------------------------------------243# Statsmodels Analysis Implementation244# -----------------------------------------245 246def analyze_statsmodels(df, df_i_agg, signup_month):247    """248    Perform analysis using Statsmodels OLS249    """250    try:251        # Run a regression controlling for pre-treatment spending252        model = smf.ols("post_spends ~ treatment + pre_spends", data=df_i_agg)253        results = model.fit()254 255        # Extract the treatment coefficient256        try:257            # Try different ways to access the treatment coefficient258            if "treatment" in results.params:259                effect_value = results.params["treatment"]260                effect_pvalue = results.pvalues["treatment"]261            elif "treatment[T.1]" in results.params:262                effect_value = results.params["treatment[T.1]"]263                effect_pvalue = results.pvalues["treatment[T.1]"]264            elif "treatment[T.True]" in results.params:265                effect_value = results.params["treatment[T.True]"]266                effect_pvalue = results.pvalues["treatment[T.True]"]267            else:268                # Find any parameter that contains 'treatment'269                treatment_params = [p for p in results.params.index if 'treatment' in p.lower()]270                if treatment_params:271                    effect_value = results.params[treatment_params[0]]272                    effect_pvalue = results.pvalues[treatment_params[0]]273                else:274                    # As a last resort, assume the second coefficient is treatment275                    if len(results.params) > 1:276                        effect_value = results.params[1]277                        effect_pvalue = results.pvalues[1]278                    else:279                        raise KeyError("Cannot identify treatment parameter")280        except Exception as e:281            return f"Error extracting treatment effect: {str(e)}\nAvailable parameters: {results.params.index.tolist()}", None282 283        # Create the summary text - simplified version without full OLS summary284        result_text = f"The causal effect of the rewards program (ATT) using Statsmodels OLS is ${effect_value:.2f}."285        result_text += f"\np-value: {effect_pvalue:.4f} {'(statistically significant)' if effect_pvalue < 0.05 else '(not statistically significant)'}"286        287        # Add R-squared and sample size information288        result_text += f"\n\nR-squared: {results.rsquared:.4f}"289        result_text += f"\nAdjusted R-squared: {results.rsquared_adj:.4f}"290        result_text += f"\nNumber of observations: {results.nobs}"291        292        # Add coefficient for pre-spends293        pre_spends_param = [p for p in results.params.index if 'pre_spends' in p.lower()]294        if pre_spends_param:295            pre_spends_coef = results.params[pre_spends_param[0]]296            pre_spends_pval = results.pvalues[pre_spends_param[0]]297            result_text += f"\n\nPre-treatment spending coefficient: {pre_spends_coef:.4f} (p-value: {pre_spends_pval:.4f})"298        299        # Create common data visualization300        viz_path = create_common_visualization(301            df, df_i_agg, signup_month,302            title="Statsmodels Data Visualization"303        )304 305        return result_text, viz_path306 307    except Exception as e:308        return f"Error during Statsmodels analysis: {str(e)}", None309 310# -----------------------------------------311# Gradio Interface312# -----------------------------------------313 314def gradio_setup(file_obj, signup_month):315    """316    Setup function for the first tab317    """318    if file_obj is None:319        return "Please upload a file to continue."320    321    try:322        # Load and process data323        df, df_i_agg, error_msg = load_data(file_obj, signup_month)324        if error_msg:325            return error_msg326        327        # Basic data statistics 328        stats_text = f"Full dataset: {df.shape[0]} rows, {df.shape[1]} columns\n"329        stats_text += f"Filtered dataset for signup month {signup_month}: {df_i_agg.shape[0]} users\n"330        331        # Calculate treatment group sizes - handle both numeric and boolean treatments332        treatment_col = df_i_agg['treatment']333        if treatment_col.dtype == bool:334            n_control = sum(~treatment_col)335            n_treated = sum(treatment_col)336            control_mask = ~treatment_col337            treated_mask = treatment_col338        else:  # Assume numeric (0/1)339            n_control = sum(treatment_col == 0)340            n_treated = sum(treatment_col == 1)341            control_mask = treatment_col == 0342            treated_mask = treatment_col == 1343        344        stats_text += f"Control group: {n_control} users\n"345        stats_text += f"Treatment group: {n_treated} users\n"346        347        # Add pre/post spending averages348        avg_pre_control = df_i_agg.loc[control_mask, 'pre_spends'].mean()349        avg_pre_treated = df_i_agg.loc[treated_mask, 'pre_spends'].mean()350        avg_post_control = df_i_agg.loc[control_mask, 'post_spends'].mean()351        avg_post_treated = df_i_agg.loc[treated_mask, 'post_spends'].mean()352        353        stats_text += f"Average pre-treatment spending (control): ${avg_pre_control:.2f}\n"354        stats_text += f"Average pre-treatment spending (treated): ${avg_pre_treated:.2f}\n"355        stats_text += f"Average post-treatment spending (control): ${avg_post_control:.2f}\n"356        stats_text += f"Average post-treatment spending (treated): ${avg_post_treated:.2f}\n"357        358        # Simple difference-in-differences calculation359        diff_control = avg_post_control - avg_pre_control360        diff_treated = avg_post_treated - avg_pre_treated361        naive_effect = diff_treated - diff_control362        363        stats_text += f"Naive difference-in-differences estimate: ${naive_effect:.2f}"364            365        return stats_text366            367    except Exception as e:368        return f"Error during setup: {str(e)}"369 370def gradio_dowhy(file_obj, signup_month, method, refutation):371    """372    Wrapper for DoWhy analysis373    """374    if file_obj is None:375        return "Please upload a file in the Setup tab first.", None, None376    377    # Load and process data378    df, df_i_agg, error_msg = load_data(file_obj, signup_month)379    if error_msg:380        return error_msg, None, None381    382    # Run DoWhy analysis383    result_text, causal_graph, data_viz = analyze_dowhy(384        df, df_i_agg, signup_month, method, refutation385    )386    387    return result_text, causal_graph, data_viz388 389def gradio_statsmodels(file_obj, signup_month):390    """391    Wrapper for Statsmodels analysis392    """393    if file_obj is None:394        return "Please upload a file in the Setup tab first.", None395    396    # Load and process data397    df, df_i_agg, error_msg = load_data(file_obj, signup_month)398    if error_msg:399        return error_msg, None400    401    # Run Statsmodels analysis402    result_text, data_viz = analyze_statsmodels(df, df_i_agg, signup_month)403    404    return result_text, data_viz405 406# Create the Gradio interface407def create_interface():408    with gr.Blocks(title="Membership Rewards Program Analysis", theme='freddyaboulton/dracula_revamped') as demo:409        gr.Markdown("""# Membership Rewards Program Effect Analysis410        411This application analyzes the causal effect of a membership rewards program on customer spending using three approaches:4121. A Naive difference-in-differences estimate.4132. DoWhy - A causal inference library that implements causal graphs and various causal estimation methods4143. Statsmodels - A simpler approach using OLS regression to control for pre-treatment spending415 416Start by configuring parameters in the Setup tab, then explore both analysis methods in their respective tabs.417        """)418        419        # Shared signup month variable420        signup_month = gr.State(value=3)421        422        # Create tabs423        with gr.Tabs():424            # Tab 1: Setup425            with gr.TabItem("1. Setup"):426                gr.Markdown("""## Data Setup and Configuration427                428Upload your data file and select the signup month to analyze. This tab shows basic statistics about your data.429                """)430                431                file_input = gr.File(label="Upload Data (CSV/Excel)")432                signup_month_slider = gr.Slider(minimum=1, maximum=11, value=3, step=1, label="Signup Month")433                setup_button = gr.Button("Load Data and Calculate Statistics")434                setup_results = gr.Textbox(label="Data Statistics", lines=10)435                436                # Update signup_month state when the slider changes437                def update_signup_month(month):438                    return month439                440                signup_month_slider.change(441                    fn=update_signup_month,442                    inputs=[signup_month_slider],443                    outputs=[signup_month]444                )445                446                setup_button.click(447                    fn=gradio_setup,448                    inputs=[file_input, signup_month_slider],449                    outputs=[setup_results]450                )451            452            # Tab 2: DoWhy Analysis453            with gr.TabItem("2. DoWhy Analysis"):454                gr.Markdown("""## Causal Analysis with DoWhy455                456DoWhy implements a formal approach to causal inference based on causal graphs and provides various estimation methods along with refutation techniques.457                """)458                459                method = gr.Dropdown(460                    choices=[461                        "backdoor.propensity_score_matching",462                        "backdoor.linear_regression",463                        "backdoor.propensity_score_stratification",464                        "backdoor.propensity_score_weighting"465                    ],466                    value="backdoor.propensity_score_matching",467                    label="Estimation Method"468                )469                470                refutation = gr.Dropdown(471                    choices=[472                        "None",473                        "placebo_treatment_refuter",474                        "random_common_cause_refuter",475                        "data_subset_refuter"476                    ],477                    value="placebo_treatment_refuter",478                    label="Refutation Method"479                )480                481                dowhy_button = gr.Button("Run DoWhy Analysis")482                dowhy_results = gr.Textbox(label="DoWhy Results", lines=15)483                484                with gr.Row():485                    causal_graph = gr.Image(label="Causal Graph")486                    dowhy_viz = gr.Image(label="Data Visualization")487                488                # Use the stored signup_month from state489                def run_dowhy_with_state_month(file_obj, current_month, method, refutation):490                    # Load and process data491                    df, df_i_agg, error_msg = load_data(file_obj, current_month)492                    if error_msg:493                        return error_msg, None, None494                    495                    # Run DoWhy analysis496                    return analyze_dowhy(df, df_i_agg, current_month, method, refutation)497                498                dowhy_button.click(499                    fn=run_dowhy_with_state_month,500                    inputs=[file_input, signup_month, method, refutation],501                    outputs=[dowhy_results, causal_graph, dowhy_viz]502                )503            504            # Tab 3: Statsmodels Analysis505            with gr.TabItem("3. Statsmodels Analysis"):506                gr.Markdown("""## Analysis with Statsmodels OLS507                508This approach uses a simple OLS regression model to estimate the treatment effect while controlling for pre-treatment spending.509                """)510                511                statsmodels_button = gr.Button("Run Statsmodels Analysis")512                statsmodels_results = gr.Textbox(label="Statsmodels Results", lines=15)513                statsmodels_viz = gr.Image(label="Data Visualization")514                515                # Use the stored signup_month from state516                def run_statsmodels_with_state_month(file_obj, current_month):517                    # Load and process data518                    df, df_i_agg, error_msg = load_data(file_obj, current_month)519                    if error_msg:520                        return error_msg, None521                    522                    # Run Statsmodels analysis523                    return analyze_statsmodels(df, df_i_agg, current_month)524                525                statsmodels_button.click(526                    fn=run_statsmodels_with_state_month,527                    inputs=[file_input, signup_month],528                    outputs=[statsmodels_results, statsmodels_viz]529                )530    531    return demo532 533# Run the application534if __name__ == "__main__":535    demo = create_interface()536    demo.launch()