generated from Python-2026Spring/assignment-05-final-project-template
80 lines
4.1 KiB
Python
80 lines
4.1 KiB
Python
|
|
# app.py - 电信客户流失预测Streamlit交互页面
|
|||
|
|
import streamlit as st
|
|||
|
|
import joblib
|
|||
|
|
import pandas as pd
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
import seaborn as sns
|
|||
|
|
|
|||
|
|
# 基础设置(中文显示)
|
|||
|
|
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
|
|||
|
|
plt.rcParams['axes.unicode_minus'] = False
|
|||
|
|
|
|||
|
|
# 缓存模型
|
|||
|
|
@st.cache_resource
|
|||
|
|
def load_trained_model():
|
|||
|
|
try:
|
|||
|
|
model = joblib.load('telco_churn_model.pkl')
|
|||
|
|
return model
|
|||
|
|
except FileNotFoundError:
|
|||
|
|
st.error("❌ 未找到模型文件!请先运行 src/model.py 训练模型")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 页面标题
|
|||
|
|
st.set_page_config(page_title="电信客户流失预测系统", page_icon="📱", layout="wide")
|
|||
|
|
st.title("📱 电信客户流失预测系统")
|
|||
|
|
st.divider()
|
|||
|
|
|
|||
|
|
# 1. 客户信息输入
|
|||
|
|
st.subheader("1. 输入客户信息")
|
|||
|
|
with st.form("customer_form"):
|
|||
|
|
col1, col2 = st.columns(2)
|
|||
|
|
with col1:
|
|||
|
|
tenure = st.number_input("在网时长(月)", min_value=0, max_value=72, value=12)
|
|||
|
|
monthly_charges = st.number_input("月消费金额", min_value=18.0, max_value=120.0, value=69.99)
|
|||
|
|
total_charges = st.number_input("总消费金额", min_value=18.0, max_value=8684.0, value=839.88)
|
|||
|
|
with col2:
|
|||
|
|
contract = st.selectbox("合约类型", ["Month-to-month(月付)", "One year(1年)", "Two year(2年)"])
|
|||
|
|
internet_service = st.selectbox("网络服务", ["DSL(宽带)", "Fiber optic(光纤)", "No(无)"])
|
|||
|
|
payment_method = st.selectbox("支付方式", ["Electronic check(电子支票)", "Mailed check(邮寄支票)", "Bank transfer(银行转账)", "Credit card(信用卡)"])
|
|||
|
|
submit_btn = st.form_submit_button("🚀 预测流失风险")
|
|||
|
|
|
|||
|
|
# 2. 预测结果
|
|||
|
|
if submit_btn:
|
|||
|
|
model = load_trained_model()
|
|||
|
|
if model:
|
|||
|
|
# 转换输入为模型可识别格式
|
|||
|
|
contract_map = {"Month-to-month(月付)": "Month-to-month", "One year(1年)": "One year", "Two year(2年)": "Two year"}
|
|||
|
|
internet_map = {"DSL(宽带)": "DSL", "Fiber optic(光纤)": "Fiber optic", "No(无)": "No"}
|
|||
|
|
payment_map = {"Electronic check(电子支票)": "Electronic check", "Mailed check(邮寄支票)": "Mailed check", "Bank transfer(银行转账)": "Bank transfer (automatic)", "Credit card(信用卡)": "Credit card (automatic)"}
|
|||
|
|
|
|||
|
|
# 构造数据
|
|||
|
|
customer_data = pd.DataFrame({
|
|||
|
|
'customerID': ['TEST001'], 'tenure': [tenure], 'MonthlyCharges': [monthly_charges], 'TotalCharges': [total_charges],
|
|||
|
|
'Contract': [contract_map[contract]], 'InternetService': [internet_map[internet_service]], 'PaymentMethod': [payment_map[payment_method]],
|
|||
|
|
'OnlineSecurity': ['No'], 'TechSupport': ['No'], 'PaperlessBilling': ['Yes'], 'gender': ['Female'],
|
|||
|
|
'SeniorCitizen': [0], 'Partner': ['Yes'], 'Dependents': ['No'], 'PhoneService': ['Yes'],
|
|||
|
|
'MultipleLines': ['No'], 'OnlineBackup': ['Yes'], 'DeviceProtection': ['No'], 'StreamingTV': ['Yes'], 'StreamingMovies': ['No']
|
|||
|
|
})
|
|||
|
|
# 添加特征组合
|
|||
|
|
customer_data['tenure_monthly'] = customer_data['tenure'] * customer_data['MonthlyCharges']
|
|||
|
|
customer_data['tenure_ratio'] = customer_data['tenure'] / (customer_data['TotalCharges'] + 1)
|
|||
|
|
customer_data['monthly_total_ratio'] = customer_data['MonthlyCharges'] / (customer_data['TotalCharges'] + 1)
|
|||
|
|
|
|||
|
|
# 预测
|
|||
|
|
churn_pred = model.predict(customer_data)[0]
|
|||
|
|
churn_prob = model.predict_proba(customer_data)[:, 1][0]
|
|||
|
|
|
|||
|
|
# 展示结果
|
|||
|
|
st.divider()
|
|||
|
|
st.subheader("2. 流失风险预测结果")
|
|||
|
|
if churn_pred == 1:
|
|||
|
|
st.error(f"⚠️ 流失风险高(概率:{churn_prob:.2%})")
|
|||
|
|
else:
|
|||
|
|
st.success(f"✅ 流失风险低(概率:{churn_prob:.2%})")
|
|||
|
|
|
|||
|
|
# 3. 分析与建议
|
|||
|
|
st.divider()
|
|||
|
|
st.subheader("3. 流失影响因素")
|
|||
|
|
st.image("特征重要性TOP10.png", caption="影响流失的TOP10因素")
|
|||
|
|
st.subheader("4. 留存建议")
|
|||
|
|
st.markdown("- 短在网时长+高消费:提供长期合约折扣\n- 光纤用户:优化网络稳定性\n- 电子支票支付:自动续费优惠")
|