SaaS 行业数据分析:AI 客户健康度评分与续费率预测模型
SaaS 行业数据分析:AI 客户健康度评分与续费率预测模型
行业场景与项目复盘 · 第4周 · 朱大喜的数据手记
做 SaaS 数据分析这一年,最让我感到"值了"的项目就是客户健康度评分体系。以前客服团队总是来问"这批客户会不会跑",我只能给一个模糊的趋势图。现在直接甩出一个 0-100 的健康度分数 + 续费概率值,决策效率直接拉满。今天就来复盘这套评分与预测模型是怎么搭起来的。
一、业务背景与问题定义
SaaS 行业的核心商业模式是订阅制,客户生命周期价值(LTV)取决于续费率。但传统方式靠客服"凭感觉"判断客户会不会续费,问题很明显:
- 滞后性:客户都跑了一周了,才发现续费率下降
- 主观偏差:不同客服对同一客户判断完全不同
- 覆盖不足:只能关注头部客户,中小企业客户无人问津
我们要解决的核心问题:能否用数据提前量化每个客户的"健康程度",并预测续费概率?
# 业务指标定义 metrics = { "续费率": "当期续费客户数 / 上期到期客户数", "NDR": "(续费收入 + 升级收入 - 降级收入 - 流失收入) / 上期总收入", "客户健康度": "综合评分 0-100,反映客户持续使用意愿", "预警阈值": "健康度 < 40 为高风险,40-60 为中风险,> 60 为健康" }项目的业务目标是:提前 30 天识别高风险客户,将续费率从 78% 提升至 85% 以上。
二、数据准备与特征工程
客户健康度不是拍脑袋的,我们需要从多个维度提取特征。以下是我们最终使用的特征体系:
import pandas as pd import numpy as np # 加载多源数据 usage_df = pd.read_csv("customer_usage_2025.csv") # 产品使用数据 billing_df = pd.read_csv("customer_billing_2025.csv") # 账单与支付数据 support_df = pd.read_csv("customer_support_2025.csv") # 客服工单数据 nps_df = pd.read_csv("customer_nps_2025.csv") # NPS 调研数据 # 合并为统一客户视图 customer_master = usage_df.merge(billing_df, on="customer_id", how="left") customer_master = customer_master.merge(support_df, on="customer_id", how="left") customer_master = customer_master.merge(nps_df, on="customer_id", how="left") # ===== 特征工程 ===== # 1. 使用活跃度特征 customer_master["login_freq_30d"] = customer_master["login_count_30d"] / 30 # 日均登录频次 customer_master["core_feature_usage"] = customer_master["core_action_count"] / customer_master["login_count_30d"] # 核心功能使用率 customer_master["usage_trend"] = customer_master["login_count_30d"] - customer_master["login_count_60d"] # 使用趋势(近30天-近60天) # 2. 商务健康度特征 customer_master["payment_timeliness"] = (customer_master["on_time_payments"] / customer_master["total_payments"]) # 付款及时率 customer_master["arpu_change"] = customer_master["current_arpu"] - customer_master["previous_arpu"] # ARPU 变化 customer_master["contract_remaining"] = customer_master["contract_end_date"] - pd.Timestamp.now() # 合同剩余天数 # 3. 支持互动特征 customer_master["ticket_rate"] = customer_master["ticket_count"] / customer_master[" tenure_months"] # 月均工单率 customer_master["avg_resolution_hours"] = customer_master["avg_resolution_time"] # 平均解决时长 customer_master["escalation_rate"] = customer_master["escalation_count"] / customer_master["ticket_count"] # 升级率 # 4. 情感特征 customer_master["nps_score"] = customer_master["nps_score"].fillna(50) # 缺失NPS默认中性值 # 标记续费结果(标签) customer_master["churn_label"] = (customer_master["renewed"] == 0).astype(int) # 1=流失, 0=续费特征体系用 Mermaid 图更直观:
为什么使用趋势(近 30 天 - 近 60 天)比绝对值更有信号?一个 HR SaaS 客户这个月登录 60 次、上个月 120 次,和另一个登录 40 次、上个月 15 次的客户比,前者的绝对值仍高于后者(60 > 40)。但如果只看绝对值做分类,模型会把前者判为"安全"客户——而实际上他的使用量已经腰斩。趋势特征捕捉的是行为的加速度:它是上升还是下降,下降了多少。在流失预测场景下,"正在变差"的信号强度通常远高于"当前状态有多好",因为 SaaS 流失是一个渐进过程——客户不是某一天突然决定"我不续了",而是连续 4-6 周使用量缓慢下降。趋势比绝对值更能捕捉这个斜坡。
三、AI 模型构建与训练
我们采用双模型架构:健康度评分用加权评分卡模型,续费预测用 XGBoost 分类模型。评分卡适合解释性要求高的业务场景,XGBoost 适合精准预测。
from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import xgboost as xgb from sklearn.metrics import roc_auc_score, classification_report # ===== 模型1:加权评分卡 ===== # 各维度权重由业务方和数据团队共同确定 weight_config = { "usage_weight": 0.35, # 使用活跃度权重最高 "billing_weight": 0.25, # 商务健康度次之 "support_weight": 0.20, # 支持互动 "nps_weight": 0.20 # 情感 } def calculate_health_score(row, weights): """计算客户健康度评分(0-100)""" # 各维度子评分(归一化到0-100) usage_score = min(100, row["login_freq_30d"] * 50 + row["core_feature_usage"] * 50) # 使用趋势加分或扣分 if row["usage_trend"] > 0: usage_score = min(100, usage_score + 10) else: usage_score = max(0, usage_score - 15) billing_score = row["payment_timeliness"] * 80 + min(20, row["arpu_change"] * 10) # 支持维度:工单少=好,解决快=好,升级少=好 support_score = max(0, 100 - row["ticket_rate"] * 30 - row["escalation_rate"] * 40 - row["avg_resolution_hours"] * 2) nps_score = row["nps_score"] # NPS本身就是0-10,映射到0-100 # 加权求和 total = ( usage_score * weights["usage_weight"] + billing_score * weights["billing_weight"] + support_score * weights["support_weight"] + nps_score * weights["nps_weight"] ) return round(total, 1) # 应用评分函数 customer_master["health_score"] = customer_master.apply( lambda row: calculate_health_score(row, weight_config), axis=1 ) # ===== 模型2:XGBoost 续费预测 ===== feature_cols = [ "login_freq_30d", "core_feature_usage", "usage_trend", "payment_timeliness", "arpu_change", "contract_remaining", "ticket_rate", "avg_resolution_hours", "escalation_rate", "nps_score", "health_score" # 评分卡结果也作为特征输入 ] X = customer_master[feature_cols] y = customer_master["churn_label"] # 训练集/测试集划分 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) # 特征标准化 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # XGBoost 模型训练 xgb_model = xgb.XGBClassifier( n_estimators=200, max_depth=5, learning_rate=0.1, subsample=0.8, colsample_bytree=0.8, objective="binary:logistic", eval_metric="auc", random_state=42 ) xgb_model.fit(X_train_scaled, y_train) # 模型评估 y_pred_proba = xgb_model.predict_proba(X_test_scaled)[:, 1] auc_score = roc_auc_score(y_test, y_pred_proba) print(f"AUC: {auc_score:.4f}") # 输出: AUC: 0.8923 # 续费概率 = 1 - 流失概率 customer_master["renewal_probability"] = 1 - xgb_model.predict_proba(scaler.transform(X))[:, 1]模型架构整体流程如下:
四、业务落地与效果评估
为什么评分卡和 XGBoost 不是替代关系而是互补关系?很多人觉得"都有了 XGBoost 还要评分卡干嘛",但这是把"预测"和"解释"混为一谈。XGBoost 给你续费概率 0.23,客户经理问"为什么是 0.23 而不是 0.8?",你能回答"树的第 5 次分裂用了 login_freq_30d ≤ 2.3",客户经理完全听不懂。评分卡告诉你"使用活跃度 35 分 + 商务健康度 20 分 = 55 分",每个维度可追溯、可干预。更关键的是,评分卡的干预方向是明确的:使用分低了就推培训,商务分低了就推客户成功经理。XGBoost 只是告诉你"这个人要跑",评分卡告诉你"他跑的原因可能是什么"。
模型上线后,我们做了三层落地机制:
# ===== 自动化预警机制 ===== import schedule import time def daily_health_check(): """每日健康度检查与预警推送""" # 重新计算评分和概率 current_scores = customer_master[["customer_id", "health_score", "renewal_probability"]].copy() # 风险分级 current_scores["risk_level"] = current_scores.apply( lambda row: "高风险" if row["health_score"] < 40 and row["renewal_probability"] < 0.5 else ("中风险" if row["health_score"] < 60 or row["renewal_probability"] < 0.7 else "健康"), axis=1 ) # 筛选高风险客户推送给客服团队 high_risk = current_scores[current_scores["risk_level"] == "高风险"] mid_risk = current_scores[current_scores["risk_level"] == "中风险"] # 推送消息(实际接入企业微信/钉钉) print(f"今日高风险客户: {len(high_risk)} 家") print(f"今日中风险客户: {len(mid_risk)} 家") # 生成干预建议 for cid in high_risk["customer_id"].head(10): row = customer_master[customer_master["customer_id"] == cid].iloc[0] if row["usage_trend"] < 0: print(f"客户 {cid}: 使用频次下降,建议产品培训介入") elif row["payment_timeliness"] < 0.7: print(f"客户 {cid}: 付款延迟,建议商务沟通") elif row["escalation_rate"] > 0.3: print(f"客户 {cid}: 工单升级率高,建议技术支持重点跟进") # 每天早上 9 点执行 schedule.every().day.at("09:00").do(daily_health_check)上线三个月后的核心效果:
| 指标 | 上线前 | 上线后 | 变化 |
|---|---|---|---|
| 续费率 | 78% | 84.5% | +6.5% |
| 高风险客户识别提前量 | 0天 | 平均21天 | +21天 |
| 客服干预成功率 | 32% | 58% | +26% |
| NDR | 95% | 102% | +7% |
五、总结
🚨 踩坑提醒
NPS 缺失值用均值填充会掩盖满意度分化:
fillna(50)把没填 NPS 的用户都设为中性——但真实的"未填写用户"往往是两类极端:满意到懒得填 vs 不满到不想填。用 50 填充会让模型认为这些客户"一般满意",实际上第 2 类客户的流失率远高于均值。建议分析"未填写"群体的特征,如果他们的流失率显著不同于 50 分对应的群体,应该单独设一个缺失标记列。XGBoost 的特征重要性不等于业务因果:
core_feature_usage的 feature importance 最高(0.25),不代表"提升核心功能使用率就能降低流失"。可能的情况是:本身就很忠诚的客户才会高频使用核心功能,而不是高频使用导致了忠诚。因果关系的验证需要做 A/B 测试——给一批用户推送核心功能引导,看后续续费率是否提升。评分卡权重由数据驱动但 NPS 覆盖率太低时权重会失真:初期用相关性定权,NPS 权重被推到 0.4。但只有 35% 客户有 NPS 数据,剩下 65% 是填充值。一个三分之二靠填充的特征占 40% 的权重,等于评分体系被"猜"的数据主导。建议对低覆盖率的特征设置权重上限(如覆盖率 <50% 的特征最高权重不超过 0.15),或者对缺失样本单独计算评分。
复盘这个项目,三个关键经验值得记住:
评分卡和机器学习不是对立的——评分卡提供可解释的业务评分,XGBoost 提供精准的概率预测,两者结合比单用任何一个效果都好。健康度评分作为特征输入 XGBoost 后,AUC 提升了 0.05。
特征工程比模型选择更重要——我们花了 60% 的时间在特征定义和数据对齐上。特别是"使用趋势"这个特征(近30天 vs 近60天的变化量),单特征 AUC 就有 0.65,比很多复杂模型都强。
落地机制决定 ROI——模型做出来只是第一步,真正的价值在于每日预警 + 干预建议 + 客服闭环。没有这三层,模型就是摆设。
踩过的坑也有:初期评分卡权重纯靠数据相关性定,结果 NPS 权重过高(0.4),但 NPS 调研覆盖率只有 35%,大量缺失值导致评分失真。后来降到 0.20,缺失用中性值填充,反而更稳定。
下一步计划:引入客户行为序列特征(用 LSTM 编码近 90 天的操作序列),尝试捕捉更细微的使用模式变化。到时候再来和大家分享进展!
