客户流失预测分析

在完成了一些数据科学的课程后,感到有必要将这些技能应用到一些项目中。为此,分析并构建了一个机器学习模型,该模型基于来自伊朗一家电信公司的数据集,每行代表一年期间的一位客户。从Kaggle获取了这个数据集。在这篇文章中,将分享在处理这些数据时的经验。将了解到从数据库检索数据、处理不平衡数据、数据可视化以及构建机器学习模型的过程。

导入客户流失预测所需的库

在开始分析之前,需要导入一些库。以下是所需的库列表。

# 导入所需的库 import pandas as pd import sqlite3 import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.compose import ColumnTransformer from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, ConfusionMatrixDisplay from imblearn.over_sampling import SMOTE

以上代码展示了需要的库,包括数据处理、可视化和机器学习模型构建所需的库。

从数据库检索数据

从Kaggle获取数据后,将这些数据推送到数据库中,以便在进行这个项目时练习一些SQL。可以在GitHub仓库中找到数据库和数据的CSV文件。在这里,使用SQLite数据库以简化操作。在Python中,已经有一个名为sqlite3的库用于读取SQLite数据库。

# 连接数据库并查看表的数量 conn = sqlite3.connect('database.db') cursor = conn.cursor() cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") print(cursor.fetchall())

运行上述代码后,将看到输出结果,显示数据库中只有一个表。这很合理,因为只向数据库推送了一个表。现在让看看customer_churn表的结构。为此,需要运行以下代码。

# 查看表的结构 cursor.execute("PRAGMA table_info(customer_churn);") print(cursor.fetchall())

如果运行此代码,输出将如下所示。上述表的所有列的含义如下所述:

从结果中,可以说:

  • 表中有14列(表的行数)。
  • 所有列都包含INTEGER或REAL数据类型(type列)。
  • 在创建此表时,没有列应用了NOT NULL约束(notnull列)。
  • 所有列的默认值都是null(dflt_value列)。
  • 没有主键可用(pk列)。

仅用一行代码就了解到了这么多信息,这难道不令人惊叹吗?现在,让看看表中的一些值。运行以下代码。

# 查看表中的一些值 pd.read_sql("SELECT * FROM customer_churn LIMIT 10", conn)

由于有很多列,无法显示所有列。但将简要描述所有列及其值,以更好地理解数据。请参见下图。

从上图中,已经猜到了,尽管所有列都是数值型的,但存在一些编码的分类列。编码的分类列包括Complaints、Charge Amount、Age Group、Tariff Plan、Status和Churn。Churn列是这里的目标列。

现在,是时候将这个数据集读取为pandas dataframe了。但不取所有列。

在年龄上有两个列。因此,只取Age Group,因为在将数据输入机器学习模型之前,必须对那一列进行编码,这里已经完成了。忽略了call_failure、subscription_length、charge_amount和tariff_plan,因为它们对于确定客户流失不太有用。

# 读取数据并进行预处理 tel_data = pd.read_sql("SELECT * FROM customer_churn", conn) tel_data = tel_data.drop(['call_failure', 'subscription_length', 'charge_amount', 'tariff_plan'], axis=1)

现在数据已经准备好进行一些严肃的分析了。

客户流失预测的数据

现在,让看看数值列的汇总统计。

# 查看数值列的汇总统计 tel_data[["seconds_of_use", "frequency_of_use", "frequency_of_sms", "customer_value"]].describe()

以上代码将给出以下输出:

从上述统计数据中,可以很容易地注意到所有数值列的最小值都是0。它们可能是新客户,或者这些列中的缺失值以0的形式存在。哪一个是真的🤨?让找出答案。

# 检查0值是否表示新客户 assert tel_data[tel_data['seconds_of_use'] == 0].shape[0] == tel_data[tel_data['frequency_of_use'] == 0].shape[0]

如果运行上述代码,没有得到任何输出。这可能发生,因为这两个列——seconds_of_use和frequency_of_use——在数据中具有相同的重要性。现在必须找出frequency_of_sms列在这两个列为0的地方是否也是0?让看看🔍。

# 检查0值的分布 tel_data[tel_data['seconds_of_use'] == 0]['frequency_of_sms'].hist(bins=50, log=True) plt.show() tel_data['frequency_of_sms'].hist(bins=50, log=True) plt.show()

如果运行上述代码,将看到以下两个图表。可以很容易地注意到上述两个图表之间的差异。左侧图表绘制在数据集的秒数使用和使用频率列包含值为零的部分。另一个图表绘制在整个数据集上。第一个图表中SMS的最大值是25,但在另一个图表中,这个最大值超过500!所以现在得出结论,这些列中的零值不是缺失值,它们表示它们是新客户。否则,在左侧图表中看不到任何条形。

现在,可能注意到在绘制时使用了对数刻度的y轴。为什么呢?因为frequency_of_sms列中的值之间存在很大的差异。如果一列中的值差异很大,在绘制这些值时不使用对数刻度,那么从图表中得出一些结论是非常困难的。所以当遇到这样的问题时,必须使用对数刻度。

现在,这个数据集包含8列。它们都重要吗?让通过找到相关性矩阵来看看。

# 查看相关性矩阵 tel_data.corr()

输出结果如下所示的图像。

从相关性矩阵中,可以很容易地注意到frequency_of_use和seconds_of_use之间有很高的正相关性。所以可以把它们中的任何一个放入数据中。

# 删除相关性高的列 tel_data_selected = tel_data.drop('frequency_of_use', axis=1)

处理不平衡数据。

在这里,想要正确预测流失的客户。让看看数据中每个类别有多少行。

# 查看每个类别的行数 tel_data_selected['Churn'].value_counts()

输出结果如下。嗯,只有15%的数据与流失客户相关,84%的数据与非流失客户相关。这是一个很大的差异。必须对少数类别进行过采样。为此,使用SMOTE(合成少数过采样技术),它使用最近邻的特征创建合成数据。这种技术在imblearn Python库中可用。

还有一件事。不想在假数据上测试机器学习模型!所以只在训练数据上使用这种技术。为此,使用scikit-learn中的train_test_split来分割数据。

# 分割数据并进行过采样 X = tel_data_selected.drop('Churn', axis=1) y = tel_data_selected['Churn'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) smote = SMOTE(random_state=42) X_train, y_train = smote.fit_resample(X_train, y_train)

现在数据已经平衡了。在训练模型之前还有一件事要做——缩放数值数据。seconds_of_use、frequency_of_sms和customer_value列是数值列。其他列是编码的分类列。所以只需要缩放这3列。为了选择特定的列进行缩放,使用scikit-learn中的ColumnTransformer和StandardScaler进行缩放。

# 缩放数值数据 preprocessor = ColumnTransformer( transformers=[ ('num', StandardScaler(), ['seconds_of_use', 'frequency_of_sms', 'customer_value']) ], remainder='passthrough' )

现在是训练模型的时候了。在这里,使用LogisticRegression进行分类。

# 训练模型 model = LogisticRegression() model.fit(preprocessor.fit_transform(X_train), y_train)

在这里没有使模型过于复杂,以便初学者可以轻松理解。为Logistic Regression使用了所有默认的超参数。

现在需要检查这个默认模型是否在预测流失客户方面表现良好。由于只对特定类别感兴趣,所以使用召回率作为评估指标。召回率计算模型正确识别的实际阳性的百分比(真阳性)。在这里,预测流失的客户,这些是正类别。让看看得到了什么。

# 评估模型 y_pred = model.predict(preprocessor.transform(X_test)) print(classification_report(y_test, y_pred))

从上述分类报告中,可以看到模型在预测客户流失方面给出了不错的分数,这很好!

现在,让绘制混淆矩阵。

# 绘制混淆矩阵 ConfusionMatrixDisplay.from_predictions(y_test, y_pred) plt.show()

从99个流失客户样本中,正确检测到90个样本,9个被误分类。这个结果在多次运行中可能会改变。

如果不进行重采样就传递数据会怎样?让看看。

# 不进行重采样的模型评估 model.fit(X_train, y_train) y_pred = model.predict(X_test) print(classification_report(y_test, y_pred))

现在得到了一个糟糕的召回分数。混淆矩阵呢?让看看。

从混淆矩阵中,可以看到这个模型在检测流失客户方面表现不佳。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485