| import streamlit as st
|
| import pandas as pd
|
| import numpy as np
|
| import joblib
|
| from sklearn.preprocessing import StandardScaler
|
|
|
| @st.cache_data
|
| def load_data(path='Data/Customer-Churn.csv'):
|
| df=pd.read_csv(path)
|
| return df
|
|
|
| @st.cache_resource
|
| def build_preprocessor(df):
|
| telco=df.copy()
|
| telco['TotalCharges']=pd.to_numeric(telco['TotalCharges'],errors='coerce')
|
| telco.dropna(how='any',inplace=True)
|
|
|
| bins=[0,12,24,36,48,60,72]
|
| labels=['1-12','13-24','25-36','37-48','49-60','61-72']
|
| telco['tenure_bin']=pd.cut(telco['tenure'],labels=labels,bins=bins,include_lowest=True)
|
|
|
| x_template=telco.drop(columns=['customerID','Churn','tenure'])
|
| x_template=pd.get_dummies(x_template,drop_first=True)
|
|
|
| scaler=StandardScaler()
|
| scaler.fit(x_template)
|
|
|
| print(f"[DEBUG] build_preprocessor : template_cols={x_template.shape[1]},template_rows={x_template.shape[0]}")
|
|
|
| return {
|
| 'template_columns':list(x_template.columns),
|
| 'scaler':scaler,
|
| 'tenure_bins':(bins,labels),
|
| 'sample_df':telco
|
| }
|
|
|
| def preprocessor_input(user_input,prep):
|
| df_in=pd.DataFrame([user_input])
|
|
|
| bins,labels=prep['tenure_bins']
|
| df_in['tenure_bin']=pd.cut(df_in['tenure'],bins=bins,labels=labels,include_lowest=True)
|
|
|
| df_in=df_in.drop(columns=['tenure'])
|
|
|
| df_in_enc=pd.get_dummies(df_in,drop_first=True)
|
|
|
| template_cols=prep['template_columns']
|
| df_in_enc=df_in_enc.reindex(columns=template_cols,fill_value=0)
|
|
|
| scaler=prep['scaler']
|
| x_scaled=scaler.transform(df_in_enc)
|
|
|
| nonzero=[(col,int(val)) for col,val in zip(prep['template_columns'] ,df_in_enc.iloc[0]) if val!=0]
|
| print(f"[DEBUG] Preprocess_input : nonzero_dummies={nonzero[:10]}")
|
| print(f"[DEBUG] preprocess_input: x_scaled_shape={x_scaled.shape},first10={x_scaled.flatten()[:10].tolist()}")
|
|
|
| return x_scaled
|
|
|
| @st.cache_resource
|
| def load_model(path='Models/Ada_boost_churn_model.pkl'):
|
| print(f"[DEBUG] Load Model /; Loading model From {path}")
|
| model=joblib.load(path)
|
| print(f"[DEBUG] load_model : Model Type = {type(model)}")
|
| return model
|
|
|
|
|
| def main():
|
| st.set_page_config(
|
| page_title="Telecom Customer Churn Prediction",
|
| page_icon="π±",
|
| layout="wide"
|
| )
|
|
|
| st.title("π± Telecom Customer Churn Prediction")
|
| st.markdown(
|
| "Predict whether a telecom customer is likely to churn based on customer, account, and billing information."
|
| )
|
| st.divider()
|
|
|
| df = load_data()
|
| prep = build_preprocessor(df)
|
| model = load_model()
|
|
|
| sample = prep["sample_df"]
|
|
|
| with st.form("input_form"):
|
|
|
| st.subheader("π Customer Details")
|
|
|
| user_input = {}
|
|
|
| with st.expander("π€ Customer Information", expanded=True):
|
|
|
| col1, col2 = st.columns(2)
|
|
|
| with col1:
|
| tenure = st.slider(
|
| "Tenure (Months)",
|
| min_value=int(sample["tenure"].min()),
|
| max_value=int(sample["tenure"].max()),
|
| value=12,
|
| )
|
| user_input["tenure"] = tenure
|
|
|
| with col2:
|
| if "SeniorCitizen" in sample.columns:
|
| user_input["SeniorCitizen"] = st.selectbox(
|
| "Senior Citizen",
|
| sorted(sample["SeniorCitizen"].unique())
|
| )
|
|
|
| st.divider()
|
|
|
| st.subheader("π Account & Billing Information")
|
|
|
| cols_to_ask = [
|
| c for c in sample.columns
|
| if c not in [
|
| "customerID",
|
| "Churn",
|
| "tenure",
|
| "tenure_bin",
|
| "SeniorCitizen"
|
| ]
|
| ]
|
|
|
| left, right = st.columns(2)
|
|
|
| for i, col in enumerate(cols_to_ask):
|
|
|
| target = left if i % 2 == 0 else right
|
|
|
| with target:
|
|
|
| if pd.api.types.is_numeric_dtype(sample[col]):
|
|
|
| minv = float(sample[col].min())
|
| maxv = float(sample[col].max())
|
| default = float(sample[col].median())
|
|
|
| if pd.api.types.is_integer_dtype(sample[col]):
|
| minv = int(minv)
|
| maxv = int(maxv)
|
| default = int(default)
|
|
|
| user_input[col] = st.number_input(
|
| col.replace("_", " ").title(),
|
| min_value=minv,
|
| max_value=maxv,
|
| value=default,
|
| )
|
|
|
| else:
|
|
|
| opts = sorted(sample[col].dropna().unique())
|
|
|
| user_input[col] = st.selectbox(
|
| col.replace("_", " ").title(),
|
| opts,
|
| )
|
|
|
| submitted = st.form_submit_button(
|
| "π Predict Churn",
|
| type="primary",
|
| use_container_width=True,
|
| )
|
|
|
| if submitted:
|
|
|
| x_in = preprocessor_input(user_input, prep)
|
|
|
| pred_class = model.predict(x_in)[0]
|
| pred_proba = model.predict_proba(x_in)[0][1]
|
|
|
| churn_text = "Yes" if pred_class == 1 else "No"
|
|
|
| st.divider()
|
| st.subheader("π Prediction Result")
|
|
|
| c1, c2 = st.columns(2)
|
|
|
| with c1:
|
| st.metric("Prediction", churn_text)
|
|
|
| with c2:
|
| st.metric("Churn Probability", f"{pred_proba*100:.2f}%")
|
|
|
| st.progress(float(pred_proba))
|
|
|
| if pred_class == 1:
|
| st.error(
|
| f"β οΈ This customer is likely to churn.\n\nProbability: **{pred_proba*100:.2f}%**"
|
| )
|
| else:
|
| st.success(
|
| f"β
This customer is likely to stay.\n\nProbability of churn: **{pred_proba*100:.2f}%**"
|
| )
|
|
|
| with st.expander("π Debug Information"):
|
|
|
| st.write("### Raw Input")
|
| st.write(user_input)
|
|
|
| bins, labels = prep["tenure_bins"]
|
|
|
| df_in = pd.DataFrame([user_input])
|
| df_in["tenure_bin"] = pd.cut(
|
| df_in["tenure"],
|
| bins=bins,
|
| labels=labels,
|
| include_lowest=True,
|
| )
|
|
|
| st.write("### Input After tenure_bin")
|
| st.write(df_in)
|
|
|
| df_in_enc = pd.get_dummies(df_in.drop(columns=["tenure"]), drop_first=True)
|
|
|
| df_reindexed = df_in_enc.reindex(
|
| columns=prep["template_columns"],
|
| fill_value=0,
|
| )
|
|
|
| st.write("### Reindexed Features")
|
| st.write(df_reindexed)
|
|
|
| st.write("### Scaled Features (First 20)")
|
| st.write(x_in.flatten()[:20].tolist())
|
|
|
| st.success("Prediction Completed Successfully β
")
|
|
|
| st.divider()
|
|
|
| st.info(
|
| "This application reproduces the same preprocessing used during model training, "
|
| "including tenure binning, one-hot encoding (drop_first=True), and feature scaling using StandardScaler."
|
| )
|
|
|
|
|
|
|
| if __name__=='__main__':
|
| main() |