您的位置:首页 > 编程语言 > Python开发

教你用Python发现即将流失的客户(附代码、安装教程、学习资源)

2017-11-29 14:50 525 查看
作为一名数据分析师,你来到这家跨国银行工作已经半年了。

今天上午,老板把你叫到办公室,面色凝重。

你心里直打鼓,以为自己捅了什么篓子。幸好老板的话让你很快打消了顾虑。

他发愁,是因为最近欧洲区的客户流失严重,许多客户都跑到了竞争对手那里接受服务了。老板问你该怎么办?

你脱口而出“做好客户关系管理啊!”

老板看了你一眼,缓慢地说“我们想知道哪些客户最可能在近期流失”。

没错,在有鱼的地方钓鱼,才是上策。

你明白了自己的任务——通过数据锁定即将流失的客户。这个工作,确实是你这个数据分析师分内的事儿。

你很庆幸,这半年做了很多的数据动态采集和整理工作,使得你手头就有一个比较完备的客户数据集。

下面你需要做的,就是如何从数据中“沙里淘金”,找到那些最可能流失的客户。

可是,该怎么做呢?

你拿出欧洲区客户的数据,端详起来。

客户主要分布在法国、德国和西班牙。

你手里掌握的信息,包括他们的年龄、性别、信用、办卡信息等。客户是否已流失的信息在最后一列(Exited)。

怎么用这些数据来判断顾客是否会流失呢?

以你的专业素养,很容易就判断出这是一个分类问题,属于机器学习中的监督式学习。但是,你之前并没有做过实际项目,该如何着手呢?

别发愁,我一步步给你演示如何用Python和深度神经网络(或者叫“深度学习”)来完成这个分类任务,帮你锁定那些即将流失的客户。

环境

工欲善其事,必先利其器。我们先来安装和搭建环境。

首先是安装Python。

请到这个网址下载Anaconda的最新版本。

请选择左侧的Python 3.6版本下载安装。

其次是新建文件夹,起名为demo-customer-churn-ann,并且从这个链接下载数据,放到该文件夹下。

(注:样例数据来自于匿名化处理后的真实数据集,下载自superdatascience官网。)

打开终端(或者命令行工具),进入demo-customer-churn-ann目录,执行以下命令:

jupyter notebook

浏览器中会显示如下界面:

点击界面右上方的New按钮,新建一个Python 3 Notebook,起名为customer-churn-ann。

准备工作结束,下面我们开始清理数据。

清理

首先,读入数据清理最常用的pandas和numpy包。

import numpy as npimport pandas as pd

从customer_churn.csv里读入数据:

df = pd.read_csv('customer_churn.csv')

看看读入效果如何:

df.head()

这里我们使用了head()函数,只显示前5行。

可以看到,数据完整无误读入。但是并非所有的列都对我们预测用户流失有作用。我们一一甄别一下:

RowNumber:行号,这个肯定没用,删除

CustomerID:用户编号,这个是顺序发放的,删除

Surname:用户姓名,对流失没有影响,删除

CreditScore:信用分数,这个很重要,保留

Geography:用户所在国家/地区,这个有影响,保留

Gender:用户性别,可能有影响,保留

Age:年龄,影响很大,年轻人更容易切换银行,保留

Tenure:当了本银行多少年用户,很重要,保留

Balance:存贷款情况,很重要,保留

NumOfProducts:使用产品数量,很重要,保留

HasCrCard:是否有本行信用卡,很重要,保留

IsActiveMember:是否活跃用户,很重要,保留

EstimatedSalary:估计收入,很重要,保留

Exited:是否已流失,这将作为我们的标签数据

上述数据列甄别过程,就叫做“特征工程”(Feature Engineering),这是机器学习里面最常用的数据预处理方法。如果我们的数据量足够大,机器学习模型足够复杂,是可以跳过这一步的。但是由于我们的数据只有10000条,还需要手动筛选特征。

选定了特征之后,我们来生成特征矩阵X,把刚才我们决定保留的特征都写进来。

X = df.loc[:,['CreditScore', 'Geography', 'Gender', 'Age', 'Tenure', 'Balance', 'NumOfProducts', 'HasCrCard', 'IsActiveMember', 'EstimatedSalary']]

看看特征矩阵的前几行:

X.head()

显示结果如下:

特征矩阵构建准确无误,下面我们构建目标数据y,也就是用户是否流失。

y = df.Exited

y.head()

0 11 02 13 04 0Name: Exited, dtype: int64

此时我们需要的数据基本上齐全了。但是我们发现其中有几列数据还不符合我们的要求。

要做机器学习,只能给机器提供数值,而不能是字符串。可是看看我们的特征矩阵:

X.head()

显然其中的Geography和Gender两项数据都不符合要求。它们都是分类数据。我们需要做转换,把它们变成数值。

在Scikit-learn工具包里面,专门提供了方便的工具LabelEncoder,让我们可以方便地将类别信息变成数值。

from sklearn.preprocessing import LabelEncoder, OneHotEncoderlabelencoder1 = LabelEncoder()X.Geography= labelencoder1.fit_transform(X.Geography)labelencoder2 = LabelEncoder()X.Gender = labelencoder2.fit_transform(X.Gender)

我们需要转换两列,所以建立了两个不同的labelencoder。转换的函数叫做fit_transform。

经过转换,此时我们再来看看特征矩阵的样子:

X.head()

显然,Geography和Gender这两列都从原先描述类别的字符串,变成了数字。

这样是不是就完事大吉了呢?显然,Geography和Gender这两列都从原先描述类别的字符串,变成了数字。

不对,Gender还好说,只有两种取值方式,要么是男,要么是女。我们可以把“是男性”定义为1,那么女性就取值为0。两种取值只是描述类别不同,没有歧义。

而Geography就不同了。因为数据集里面可能的国家地区取值有3种,所以就转换成了0(法国)、1(德国)、2(西班牙)。问题是,这三者之间真的有序列(大小)关系吗?

答案自然是否定的。我们其实还是打算用数值描述分类而已。但是取值有数量的序列差异,就会给机器带来歧义。它并不清楚不同的取值只是某个国家的代码,可能会把这种大小关系带入模型计算,从而产生错误的结果。

解决这个问题,我们就需要引入OneHotEncoder。它也是Scikit-learn提供的一个类,可以帮助我们把类别的取值转变为多个变量组合表示。

咱们这个数据集里,可以把3个国家分别用3个数字组合来表示。例如法国从原先的0,变成(1, 0, 0),德国从1变成(0, 1, 0),而西班牙从2变成(0, 0, 1)。

这样,再也不会出现0和1之外的数字来描述类别,从而避免机器产生误会,错把类别数字当成大小来计算了。

特征矩阵里面,我们只需要转换国别这一列。因为它在第1列的位置(从0开始计数),因而categorical_features只填写它的位置信息。

onehotencoder = OneHotEncoder(categorical_features = [1])X = onehotencoder.fit_transform(X).toarray()

这时候,我们的特征矩阵数据框就被转换成了一个数组。注意所有被OneHotEncoder转换的列会排在最前面,然后才是那些保持原样的数据列。

我们只看转换后的第一行:

X[0]

array([ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 6.19000000e+02, 0.00000000e+00, 4.20000000e+01, 2.00000000e+00, 0.00000000e+00, 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 1.01348880e+05])

这样,总算转换完毕了吧?

没有。

因为本例中,OneHotEncoder转换出来的3列数字,实际上是不独立的。给定其中两列的信息,你自己都可以计算出其中的第3列取值。

好比说,某一行的前两列数字是(0, 0),那么第三列肯定是1。因为这是转换规则决定的。3列里只能有1个是1,其余都是0。

如果你做过多元线性回归,应该知道这种情况下,我们是需要去掉其中一列,才能继续分析的。不然会落入“虚拟变量陷阱”(dummy variable trap)。

我们删掉第0列,避免掉进坑里。

X = np.delete(X, [0], 1)

再次打印第一行:

X[0]

array([ 0.00000000e+00, 0.00000000e+00, 6.19000000e+02, 0.00000000e+00, 4.20000000e+01, 2.00000000e+00, 0.00000000e+00, 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 1.01348880e+05])

检查完毕,现在咱们的特征矩阵处理基本完成。

但是监督式学习,最重要的是有标签(label)数据。本例中的标签就是用户是否流失。我们目前的标签数据框,是这个样子的。

y.head()

0 11 02 13 04 0Name: Exited, dtype: int64

它是一个行向量,我们需要把它先转换成为列向量。你可以想象成把它“竖过来”。

y = y[:, np.newaxis]y

array([[1], [0], [1], ..., [1], [1], [0]])

这样在后面训练的时候,他就可以和前面的特征矩阵一一对应来操作计算了。

既然标签代表了类别,我们也把它用OneHotEncoder转换,这样方便我们后面做分类学习。

onehotencoder = OneHotEncoder()y = onehotencoder.fit_transform(y).toarray()

此时的标签变成两列数据,一列代表顾客存留,一列代表顾客流失。

y

array([[ 0., 1.], [ 1., 0.], [ 0., 1.], ..., [ 0., 1.], [ 0., 1.], [ 1., 0.]])

总体的数据已经齐全了。但是我们不能把它们都用来训练。

这就好像老师不应该把考试题目拿来给学生做作业和练习一样。只有考学生没见过的题,才能区分学生是掌握了正确的解题方法,还是死记硬背了作业答案。

我们拿出20%的数据,放在一边,等着用来做测试。其余8000条数据用来训练机器学习模型。

from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

我们看看训练集的长度:

len(X_train)

8000

再看看测试集的长度:

len(X_test)

2000

确认无误。

是不是可以开始机器学习了?

可以,但是下面这一步也很关键。我们需要把数据进行标准化处理。因为原先每一列数字的取值范围都各不相同,因此有的列方差要远远大于其他列。这样对机器来说,也是很困扰的。数据的标准化处理,可以在保持列内数据多样性的同时,尽量减少不同类别之间差异的影响,可以让机器公平对待全部特征。

我们调用Scikit-learn的StandardScaler类来完成这一过程。

from sklearn.preprocessing import StandardScalersc = StandardScaler()X_train = sc.fit_transform(X_train)X_test = sc.transform(X_test)

注意,我们只对特征矩阵做标准化,标签是不能动的。另外训练集和测试集需要按照统一的标准变化。所以你看,训练集上,我们用了fit_transform函数,先拟合后转换;而在测试集上,我们直接用训练集拟合的结果,只做转换。

X_train

array([[-0.5698444 , 1.74309049, 0.16958176, ..., 0.64259497, -1.03227043, 1.10643166], [ 1.75486502, -0.57369368, -2.30455945, ..., 0.64259497, 0.9687384 , -0.74866447], [-0.5698444 , -0.57369368, -1.19119591, ..., 0.64259497, -1.03227043, 1.48533467], ...,
[-0.5698444 , -0.57369368, 0.9015152 , ..., 0.64259497, -1.03227043, 1.41231994], [-0.5698444 , 1.74309049, -0.62420521, ..., 0.64259497, 0.9687384 , 0.84432121], [ 1.75486502, -0.57369368, -0.28401079, ..., 0.64259497, -1.03227043, 0.32472465]])

你会发现,许多列的方差比原先小得多。机器学习起来,会更加方便。

数据清理和转换工作至此完成。

阅读全文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐