{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5b5a213b",
   "metadata": {},
   "source": [
    "# 客户流失预警 — 生存分析实战\n",
    "\n",
    "对应案例: [客户流失预警](/docs/case-studies/finance/churn-prediction-case/)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "edb0e194",
   "metadata": {},
   "source": [
    "## 1. 环境准备"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25d7428d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np, pandas as pd, matplotlib.pyplot as plt, seaborn as sns\n",
    "from lifelines import KaplanMeierFitter, CoxPHFitter\n",
    "plt.rcParams.update({'figure.figsize':(12,5),'font.size':12})\n",
    "np.random.seed(42)\n",
    "print('Ready')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79fa8528",
   "metadata": {},
   "source": [
    "## 2. 模拟数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77df4594",
   "metadata": {},
   "outputs": [],
   "source": [
    "n=2000\n",
    "df=pd.DataFrame({'age':np.random.normal(38,12,n).clip(18,80).astype(int),\n",
    "'monthly_charges':np.random.lognormal(4,0.4,n).clip(10,200).round(2),\n",
    "'tenure':np.random.exponential(20,n).clip(0,72).astype(int),\n",
    "'contract':np.random.choice(['月付','年付','两年付'],n,p=[0.55,0.25,0.2]),\n",
    "'support_calls':np.random.poisson(2,n).clip(0,15)})\n",
    "hazard=np.clip(0.02+0.003*(df['monthly_charges']-50)/10+0.01*(df['contract']=='月付').astype(int)+0.005*df['support_calls'],0.005,0.15)\n",
    "surv=np.random.exponential(1/hazard)\n",
    "df['observed']=np.minimum(surv,df['tenure']).astype(int)\n",
    "df['churned']=(surv<=df['tenure']).astype(int)\n",
    "print(f\"N={len(df)}, 流失率={df['churned'].mean():.1%}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9db0efe6",
   "metadata": {},
   "source": [
    "## 3. Kaplan-Meier 生存曲线"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6ae9208c",
   "metadata": {},
   "outputs": [],
   "source": [
    "kmf=KaplanMeierFitter()\n",
    "kmf.fit(df['observed'],df['churned'],label='All')\n",
    "kmf.plot_survival_function()\n",
    "plt.title('Kaplan-Meier 生存曲线')\n",
    "plt.ylabel('Survival Probability')\n",
    "print(f'Median: {kmf.median_survival_time_:.1f}m')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e927f1b0",
   "metadata": {},
   "source": [
    "## 4. 按合同分组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66f34c39",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig,ax=plt.subplots(figsize=(12,6))\n",
    "for ct in df['contract'].unique():\n",
    "    m=df['contract']==ct\n",
    "    kmf=KaplanMeierFitter()\n",
    "    kmf.fit(df.loc[m,'observed'],df.loc[m,'churned'],label=ct)\n",
    "    kmf.plot_survival_function(ax=ax)\n",
    "ax.set_title('按合同类型分组')\n",
    "ax.legend(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99aea80e",
   "metadata": {},
   "source": [
    "## 5. Cox 比例风险模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfb525d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "cph_df=pd.get_dummies(df,columns=['contract'],drop_first=True)\n",
    "feats=['age','monthly_charges','support_calls']+[c for c in cph_df.columns if c.startswith('contract_')]\n",
    "cph=CoxPHFitter()\n",
    "cph.fit(cph_df[feats+['observed','churned']],duration_col='observed',event_col='churned')\n",
    "cph.print_summary()\n",
    "cph.plot(); plt.axvline(1,color='gray',ls='--'); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7452c104",
   "metadata": {},
   "source": [
    "---\n",
    "*更多分析见 [案例文档](/docs/case-studies/finance/churn-prediction-case/)*"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
