Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,176 +1,73 @@
|
|
| 1 |
-
# Gradio App
|
| 2 |
-
#Imports
|
| 3 |
import gradio as gr
|
| 4 |
-
import joblib
|
| 5 |
import pandas as pd
|
| 6 |
-
import
|
| 7 |
import shap
|
| 8 |
-
import
|
| 9 |
-
from sklearn.preprocessing import StandardScaler
|
| 10 |
-
# We'll ask user for human-friendly original features and then transform into the encoded features
|
| 11 |
-
# Input fields based on original Telco columns (commonly present)
|
| 12 |
-
def preprocess_user_input(user_inputs):
|
| 13 |
-
"""
|
| 14 |
-
user_inputs: dict with original columns (human-friendly)
|
| 15 |
-
returns: DataFrame encoded to match feature_cols order
|
| 16 |
-
"""
|
| 17 |
-
# Build single-row DataFrame with original categorical values
|
| 18 |
-
row = pd.DataFrame([user_inputs])
|
| 19 |
-
# same preprocessing as training: get_dummies with drop_first
|
| 20 |
-
row_enc = pd.get_dummies(row, drop_first=True)
|
| 21 |
-
# reindex to feature_cols with 0 fill for missing dummies
|
| 22 |
-
row_enc = row_enc.reindex(columns=feature_cols, fill_value=0)
|
| 23 |
-
# scale numeric columns
|
| 24 |
-
if numeric_cols:
|
| 25 |
-
row_enc[numeric_cols] = scaler.transform(row_enc[numeric_cols])
|
| 26 |
-
return row_enc
|
| 27 |
-
|
| 28 |
-
def create_gauge(prob):
|
| 29 |
-
# prob is in [0,1]; create a Plotly gauge
|
| 30 |
-
fig = go.Figure(go.Indicator(
|
| 31 |
-
mode="gauge+number",
|
| 32 |
-
value=prob * 100,
|
| 33 |
-
number={'suffix': "%"},
|
| 34 |
-
title={'text': "Churn Probability"},
|
| 35 |
-
gauge={
|
| 36 |
-
'axis': {'range': [0, 100]},
|
| 37 |
-
'bar': {'color': "red" if prob > 0.6 else ("orange" if prob > 0.3 else "green")},
|
| 38 |
-
'steps': [
|
| 39 |
-
{'range': [0, 30], 'color': "lightgreen"},
|
| 40 |
-
{'range': [30, 60], 'color': "yellow"},
|
| 41 |
-
{'range': [60, 100], 'color': "lightcoral"}
|
| 42 |
-
],
|
| 43 |
-
}
|
| 44 |
-
))
|
| 45 |
-
fig.update_layout(height=300, margin=dict(l=20, r=20, t=40, b=20))
|
| 46 |
-
return fig
|
| 47 |
-
|
| 48 |
-
def top_shap_barchart(shap_vals, feat_names, top_n=5):
|
| 49 |
-
# shap_vals: 1D array of shap values for the single prediction
|
| 50 |
-
abs_vals = np.abs(shap_vals)
|
| 51 |
-
idx = np.argsort(abs_vals)[::-1][:top_n]
|
| 52 |
-
top_feats = [feat_names[i] for i in idx]
|
| 53 |
-
top_vals = shap_vals[idx]
|
| 54 |
-
# Plotly horizontal bar
|
| 55 |
-
fig = go.Figure(go.Bar(
|
| 56 |
-
x=top_vals[::-1],
|
| 57 |
-
y=[f for f in top_feats[::-1]],
|
| 58 |
-
orientation='h'
|
| 59 |
-
))
|
| 60 |
-
fig.update_layout(title=f"Top {top_n} SHAP feature contributions (positive increases churn)", height=300, margin=dict(l=100))
|
| 61 |
-
return fig
|
| 62 |
|
| 63 |
-
#
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport,
|
| 68 |
-
StreamingTV, StreamingMovies, Contract, PaperlessBilling, PaymentMethod,
|
| 69 |
-
MonthlyCharges, TotalCharges,
|
| 70 |
-
example_choice=None
|
| 71 |
-
):
|
| 72 |
-
# If example button used, ignore other inputs and load preset
|
| 73 |
-
if example_choice == "High churn risk example":
|
| 74 |
-
inputs = {
|
| 75 |
-
"gender": "Male", "SeniorCitizen": "No", "Partner":"No", "Dependents":"No",
|
| 76 |
-
"tenure": 4, "PhoneService":"Yes", "MultipleLines":"No", "InternetService":"Fiber optic",
|
| 77 |
-
"OnlineSecurity":"No","OnlineBackup":"No","DeviceProtection":"No","TechSupport":"No",
|
| 78 |
-
"StreamingTV":"Yes","StreamingMovies":"Yes","Contract":"Month-to-month","PaperlessBilling":"Yes",
|
| 79 |
-
"PaymentMethod":"Electronic check","MonthlyCharges":90.0,"TotalCharges":360.0
|
| 80 |
-
}
|
| 81 |
-
elif example_choice == "Low churn risk example":
|
| 82 |
-
inputs = {
|
| 83 |
-
"gender": "Female", "SeniorCitizen": "No", "Partner":"Yes", "Dependents":"Yes",
|
| 84 |
-
"tenure": 48, "PhoneService":"Yes", "MultipleLines":"Yes", "InternetService":"DSL",
|
| 85 |
-
"OnlineSecurity":"Yes","OnlineBackup":"Yes","DeviceProtection":"Yes","TechSupport":"Yes",
|
| 86 |
-
"StreamingTV":"No","StreamingMovies":"No","Contract":"Two year","PaperlessBilling":"No",
|
| 87 |
-
"PaymentMethod":"Mailed check","MonthlyCharges":40.0,"TotalCharges":1920.0
|
| 88 |
-
}
|
| 89 |
-
else:
|
| 90 |
-
inputs = {
|
| 91 |
-
"gender": gender, "SeniorCitizen": SeniorCitizen, "Partner": Partner, "Dependents": Dependents,
|
| 92 |
-
"tenure": tenure, "PhoneService": PhoneService, "MultipleLines": MultipleLines, "InternetService": InternetService,
|
| 93 |
-
"OnlineSecurity": OnlineSecurity, "OnlineBackup": OnlineBackup, "DeviceProtection": DeviceProtection, "TechSupport": TechSupport,
|
| 94 |
-
"StreamingTV": StreamingTV, "StreamingMovies": StreamingMovies, "Contract": Contract, "PaperlessBilling": PaperlessBilling,
|
| 95 |
-
"PaymentMethod": PaymentMethod, "MonthlyCharges": MonthlyCharges, "TotalCharges": TotalCharges
|
| 96 |
-
}
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
-
# Predict
|
| 102 |
-
prob = float(model.predict_proba(row_enc)[0, 1])
|
| 103 |
-
pred_label = "⚠️ High Risk of Churn" if prob > 0.5 else "✅ Low Risk (likely to stay)"
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
top_contribs = []
|
| 115 |
-
for i in abs_idx:
|
| 116 |
-
sign = "+" if shap_arr[i] > 0 else "-"
|
| 117 |
-
top_contribs.append(f"{feature_cols[i]}: {sign}{abs(shap_arr[i]):.3f}")
|
| 118 |
-
top_text = "\n".join(top_contribs)
|
| 119 |
|
| 120 |
-
# Create visuals
|
| 121 |
-
gauge_fig = create_gauge(prob)
|
| 122 |
-
shap_fig = top_shap_barchart(shap_arr, feature_cols, top_n=5)
|
| 123 |
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
-
return insight, gauge_fig, shap_fig
|
| 128 |
|
| 129 |
-
#
|
| 130 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 131 |
-
gr.Markdown("# 📊 Customer Churn Prediction
|
| 132 |
-
|
| 133 |
-
|
| 134 |
with gr.Row():
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
MultipleLines = gr.Dropdown(["No","Yes","No phone service"], value="No", label="Multiple Lines")
|
| 145 |
-
InternetService = gr.Dropdown(["DSL","Fiber optic","No"], value="Fiber optic", label="Internet Service")
|
| 146 |
-
OnlineSecurity = gr.Dropdown(["No","Yes","No internet service"], value="No", label="Online Security")
|
| 147 |
-
OnlineBackup = gr.Dropdown(["No","Yes","No internet service"], value="No", label="Online Backup")
|
| 148 |
-
DeviceProtection = gr.Dropdown(["No","Yes","No internet service"], value="No", label="Device Protection")
|
| 149 |
-
TechSupport = gr.Dropdown(["No","Yes","No internet service"], value="No", label="Tech Support")
|
| 150 |
-
StreamingTV = gr.Dropdown(["No","Yes","No internet service"], value="No", label="Streaming TV")
|
| 151 |
-
StreamingMovies = gr.Dropdown(["No","Yes","No internet service"], value="No", label="Streaming Movies")
|
| 152 |
-
Contract = gr.Dropdown(["Month-to-month","One year","Two year"], value="Month-to-month", label="Contract")
|
| 153 |
-
PaperlessBilling = gr.Dropdown(["No","Yes"], value="Yes", label="Paperless Billing")
|
| 154 |
-
PaymentMethod = gr.Dropdown(["Electronic check","Mailed check","Bank transfer (automatic)","Credit card (automatic)"],
|
| 155 |
-
value="Electronic check", label="Payment Method")
|
| 156 |
-
example_choice = gr.Radio(["None","High churn risk example","Low churn risk example"], value="None", label="Load example?")
|
| 157 |
-
submit_btn = gr.Button("Predict & Explain", variant="primary")
|
| 158 |
-
|
| 159 |
-
with gr.Column(scale=1):
|
| 160 |
-
out_text = gr.Textbox(label="Prediction & Business Insight", lines=7)
|
| 161 |
-
out_gauge = gr.Plot(label="Churn Probability Gauge")
|
| 162 |
-
out_shap = gr.Plot(label="Top SHAP Contributors")
|
| 163 |
-
|
| 164 |
-
# Hook up the button
|
| 165 |
submit_btn.click(
|
| 166 |
-
|
| 167 |
-
inputs=
|
| 168 |
-
|
| 169 |
-
OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport,
|
| 170 |
-
StreamingTV, StreamingMovies, Contract, PaperlessBilling, PaymentMethod,
|
| 171 |
-
MonthlyCharges, TotalCharges, example_choice],
|
| 172 |
-
outputs=[out_text, out_gauge, out_shap]
|
| 173 |
)
|
| 174 |
|
| 175 |
-
# Launch
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
| 2 |
import pandas as pd
|
| 3 |
+
import joblib
|
| 4 |
import shap
|
| 5 |
+
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
+
# Load model, scaler, and feature list
|
| 8 |
+
model = joblib.load("churn_model.pkl")
|
| 9 |
+
scaler = joblib.load("scaler.pkl")
|
| 10 |
+
feature_cols = joblib.load("feature_cols.pkl") # list of feature names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
# If you saved an explainer, load it; otherwise create new one
|
| 13 |
+
try:
|
| 14 |
+
explainer = joblib.load("explainer.pkl")
|
| 15 |
+
except:
|
| 16 |
+
explainer = shap.Explainer(model, feature_names=feature_cols)
|
| 17 |
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
# Preprocessing Function
|
| 20 |
+
def preprocess_user_input(user_inputs):
|
| 21 |
+
"""
|
| 22 |
+
user_inputs: list of values in correct order
|
| 23 |
+
returns: DataFrame ready for prediction
|
| 24 |
+
"""
|
| 25 |
+
row = pd.DataFrame([user_inputs], columns=feature_cols)
|
| 26 |
+
row_scaled = scaler.transform(row)
|
| 27 |
+
return pd.DataFrame(row_scaled, columns=feature_cols)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
# Prediction + SHAP Explanation
|
| 31 |
+
def predict_and_explain(*inputs):
|
| 32 |
+
# Preprocess
|
| 33 |
+
row_scaled = preprocess_user_input(inputs)
|
| 34 |
+
|
| 35 |
+
# Predict probability and class
|
| 36 |
+
pred_prob = model.predict_proba(row_scaled)[0][1]
|
| 37 |
+
pred_class = model.predict(row_scaled)[0]
|
| 38 |
+
|
| 39 |
+
# SHAP values
|
| 40 |
+
shap_values = explainer(row_scaled)
|
| 41 |
+
|
| 42 |
+
# Explanation as bar chart
|
| 43 |
+
shap_fig = shap.plots.bar(shap_values[0], show=False)
|
| 44 |
+
|
| 45 |
+
result_text = f"**Prediction:** {'Churn' if pred_class==1 else 'No Churn'}\n"
|
| 46 |
+
result_text += f"**Probability of Churn:** {pred_prob:.2%}"
|
| 47 |
+
|
| 48 |
+
return result_text, shap_fig
|
| 49 |
|
|
|
|
| 50 |
|
| 51 |
+
# Build Gradio UI
|
| 52 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 53 |
+
gr.Markdown("## 📊 Customer Churn Prediction with SHAP Explanations")
|
| 54 |
+
|
|
|
|
| 55 |
with gr.Row():
|
| 56 |
+
inputs = []
|
| 57 |
+
for col in feature_cols:
|
| 58 |
+
inputs.append(gr.Number(label=col))
|
| 59 |
+
|
| 60 |
+
output_text = gr.Markdown()
|
| 61 |
+
output_plot = gr.Plot()
|
| 62 |
+
|
| 63 |
+
submit_btn = gr.Button("Predict")
|
| 64 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
submit_btn.click(
|
| 66 |
+
predict_and_explain,
|
| 67 |
+
inputs=inputs,
|
| 68 |
+
outputs=[output_text, output_plot]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
)
|
| 70 |
|
| 71 |
+
# Launch
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
demo.launch()
|