Python 中的自定义转换器和管道
数据清理是任何机器学习项目中最重要的部分。事实上,您的数据可能有多种格式,并且分布在不同的系统中,这使得在将数据提供给 ML 模型之前,必须对其进行适当的处理。数据准备是 ML 流程中最繁琐和耗时的步骤之一(一些调查显示,数据科学家将 80%的时间花在数据准备和管理上!).然而,这也是最关键的一步,因为输入数据是模型的主要成分。Python 提供了某些包,这些包提供了不同的工具来简化数据准备过程,其中一个解决方案是使用自定义转换器和管道。在本文中,让我们看看什么是定制转换器,然后深入研究如何在管道中为均值编码和衬衫尺码编码定制转换器。
变形金刚
如果你研究过机器学习问题,你可能知道 Python 中的 transformers 可以用来清理、减少、扩展或生成要素。fit 方法从训练集中学习参数,transform 方法将变换应用于看不见的数据。预定义转换器的示例包括 StandardScaler、LabelEncoder、Normalize 等。在本文中,让我们来探索定制变压器。
为什么使用定制变压器
在不同的 Python 包中有几个预定义的转换器,允许我们轻松地对数据集应用不同的转换。那么,为什么我们需要定制的变压器呢?根据要解决的问题,自定义转换器可以帮助克服预定义转换器带来的一些无能或挑战。例如,让我们考虑标签编码。比方说,您试图用可能的值{East,Central,West}对列“Region”进行标签编码。如果训练数据集不包含区域列值为“中心”的任何数据,那么 LabelEncoder 无法处理测试或验证数据集中出现“中心”
时的转换。
带管道的定制变压器的应用
自定义变压器可用于顺序应用多个变压器的管道中。一个转换器的输出作为输入馈入下一个转换器,您可能会面临不兼容类型的挑战,即第一个转换器可能会输出 NumPy 数组,而后续的转换器可能会将数据帧作为输入。在这种情况下,定制变压器可以挽救局面。在我们进入编码细节之前,让我们快速刷新一下管道、衬衫尺码和编码的概念。
“管道表示一系列级联的数据转换。管道中的每一步都来自前一步,数据从头到尾流经管道。这有助于优化模型构建过程,从而有更多的时间来理解底层数据。定制变压器可用于均值编码和衬衫尺码的流水线。
衬衫尺码,以其最简单的形式,包括创建箱子并给每个箱子分配一个标签,如小号、中号、大号、超大号,因此称为衬衫尺码。通常,当我们考虑数字变量的转换器时,我们默认使用标准标量或对列应用对数转换。这与衬衫尺寸形成对比,衬衫尺寸允许您从数值变量创建分类变量,并且通常遵循其他方法,如均值编码,然后生成更有意义的特征。
类似地,对于转换分类变量,我们通常考虑标签编码或 One Hot。标签编码为数据分配随机值,这可能会使我们的模型误以为某列中的数据具有显式的层次结构,而实际情况可能并非如此。为了避免这种情况,我们倾向于“一次热编码”列。这可能会给我们留下一个包含太多列的结果数据集。例如,如果我们为一个“State”列创建一个热编码,我们可能最终得到 50 列,这不仅处理起来很繁琐,而且可能没有意义。克服上述挑战的一种方法是使用均值编码。均值编码也是分类变量的转换器,其中我们用目标列取一列的均值。如果我们的目标变量是一个二进制变量(分类问题),均值编码可以很好地工作,但它也有自己的缺点,这是另一个讨论的主题。正如大多数数据科学问题一样,没有一个解决所有问题的最佳解决方案。
对管道中的自定义转换器进行编码
我们使用的数据集是 Kaggle 的“家庭信用违约风险”,可以从这里下载。为了简单起见,我们的代码只检查应用程序数据——“application _ { train | test }”。csv”。它侧重于衬衫尺寸的“AMT_INCOME_TOTAL”列和平均值编码的短列“AMT_INCOME_TOTAL”和“CODE_GENDER”。
首先,导入所需的包,并从文件 application_train.csv(进入变量 df_app_train )和 application_test.csv(进入变量 df_app_test )中读取数据。然后让我们定义一个定制的转换器。
定义自定义变压器
下面显示的“ColumnSelector”是一个自定义转换器,它从输入数据框提供的列中创建一个子集数据框。自定义转换器是从 BaseEstimator 和 TransformerMixin 类继承而来的,如下所示。“init”是一个构造函数。
类<类名> (BaseEstimator,transformermixin):
Column Selector — Custom Transformer that extracts required columns
合体在这种情况下什么也做不了。但是一般来说, fit 用于获取和存储参数以执行转换(当我们讨论衬衫尺寸时会用到更多)。变换用于使用拟合函数的参数对输入数据集进行变换。这里,它基于输入列创建了一个子集数据框。
定义衬衫尺寸定制转换器
DFShirtSize (在上面的代码中)允许创建和标记箱子。
Fit 对给定的列应用对数变换,并获取给定值的截集(bin)并将它们存储在字典中,我们在上面的代码中称之为“ settings ”。设置是一个全局变量,允许在转换功能中重用。
在应用配合之前,确保系列数据与验证和测试数据分开。拟合函数应用于训练数据,然后转换函数用于训练、测试和验证数据集。存储参数的能力确保测试或验证数据集中没有数据泄漏。为了处理测试和验证数据集中的新值,添加了-inf 和 inf 作为 cuts(全局变量)的第一个和最后一个元素。
转换从设置字典中读取参数,相应地创建库并标记输入列值。这里,输入列箱根据设置字典中的切割创建并标记。
定义均值编码自定义变压器
均值编码使用要素在数据集中出现的次数占所有次数的比例。当测试或验证数据中出现看不见的标签时,目标变量的平均值会替换看不见的标签,以避免在数据中创建 Nan。
创建管道
管道步骤可以定义为一个单独的列表或直接在管道中。在这里,“列选择器”转换器使我们能够只将所需的子集传递给下一步。让我们创建一个将经历不同转换的列的列表,然后为管道安排步骤的列表。
SSEncoding 是一个变形金刚列表。管道对象是由 Pipeline(SSEncoding)命令创建的。一旦创建了管道对象,就可以调用 fit 和 transform 来执行数据转换。在应用 *fit 后,*所有的转换函数都应用于调用数据集,并获得和存储参数。上面的 fit 命令在 df_app_train 上同时应用 ColumnSelector 和 DFShirtsize。让我们看一个有数据的例子
改造前数据:
转换后的列车数据:
Train Data upon Transformation
转换后的测试数据:
Test Data upon Transformation
我们使用训练数据来拟合和转换训练和测试数据。这有助于避免数据泄露。如下所示,重复进行均值编码。
调用拟合和变换:
Fit
Transform
对测试数据调用转换:
因此,我们将衬衫尺寸应用于数值,以获得分类值。然后对得到的分类值进行均值编码,以准备可以被 ML 算法使用的数据。使用定制的转换器和管道可以在数据准备过程中为我们提供帮助,让我们有更多的时间进行分析和建模——这是我们应该花费大部分时间的地方。
根据来自Dennis Ignatenko和Brian Monteiro的指导,他们都是我数据科学之旅中令人敬畏的导师。
基于 TensorFlow.js 的神经网络客户流失预测
创建深度神经网络模型来预测客户流失
TL;DR 了解深度学习并使用 TensorFlow.js 创建深度神经网络模型来预测客户流失。了解如何预处理字符串分类数据。
第一天!你在一家大型电信公司获得了这份数据科学家的实习工作。一旦你成为一名高级数据科学家,你就不能停止梦想你将得到的兰博基尼和名牌服装。
连你妈妈都打电话来提醒你把你的统计学博士文凭挂在墙上。这就是生活,谁在乎你 35 岁左右,这是你的第一份工作。
你的团队领导走过来,问你喜欢这份工作吗,并说他可能有任务给你!你开始想象从零开始实现复杂的统计模型,做研究,并添加尖端的方法,但…嗯,现实略有不同。他向您发送了一个 CSV 失败的链接,并要求您预测客户流失。他建议你可以尝试应用深度学习来解决这个问题。
你的梦想现在开始了。该做些工作了!
在你的浏览器中运行本教程的完整源代码:
客户流失数据
我们的数据集电信客户流失来自 Kaggle 。
“预测行为留住客户。您可以分析所有相关的客户数据,并制定有针对性的客户维系计划。”【IBM 样本数据集】
数据集包括以下信息:
- 上个月内离开的客户—这一列称为流失
- 每位客户已注册的服务—电话、多条线路、互联网、在线安全、在线备份、设备保护、技术支持以及流媒体电视和电影
- 客户账户信息——他们成为客户的时间、合同、支付方式、无纸化账单、每月费用和总费用
- 客户的人口统计信息—性别、年龄范围,以及他们是否有伴侣和家属
它有 7,044 个示例和 21 个变量:
- 客户 ID :客户 ID
- 性别:顾客是男性还是女性
- 老年人:客户是否为老年人(1,0)
- 合作伙伴:客户是否有合作伙伴(是,否)
- 家属:客户是否有家属(是,否)
- 任期:客户在公司呆的月数
- 电话服务:客户是否有电话服务(是,否)
- 多线:客户是否有多线(是,否,无电话服务)
- 互联网服务:客户的互联网服务提供商(DSL、光纤、No)
- 在线安全:客户是否有在线安全(是,否,无互联网服务)
- 在线备份:客户是否有在线备份(是,否,无互联网服务)
- 设备保护:客户是否有设备保护(是,否,无互联网服务)
- 技术支持:客户是否有技术支持(是,否,无互联网服务)
- 流媒体电视:客户是否有流媒体电视(是,否,无互联网服务)
- 流媒体电影:客户是否有流媒体电影(是,否,无互联网服务)
- 合同:客户的合同期限(逐月、一年、两年)
- 无纸开票:客户是否无纸开票(是,否)
- 付款方式:客户的付款方式(电子支票、邮寄支票、银行转账(自动)、信用卡(自动))
- 月费:每月向客户收取的金额
- 总费用:向客户收取的总费用
- 客户流失:客户是否流失(是或否)
我们将使用 Papa Parse 来加载数据:
注意,我们忽略了最后一行,因为它是空的。
探测
让我们感受一下我们的数据集。有多少顾客呕吐了?
大约 74%的客户仍在使用该公司的服务。我们有一个非常不平衡的数据集。
性别在流失客户中起作用吗?
似乎没有。我们的女性和男性顾客数量差不多。资历如何?
大约 20%的客户是老年人,与非老年人相比,他们更有可能流失。
客户会在公司呆多久?
似乎你呆得越久,就越有可能留在公司。
月费如何影响流失率?
月租费低($30)的客户更有可能被留住。
每位顾客的总费用是多少?
公司收取的总金额越高,就越有可能留住这个客户。
我们的数据集总共有 21 个要素,我们没有全部看完。然而,我们发现了一些有趣的东西。
我们已经了解到,老年人、任期、每月费用和总费用在某种程度上与流失状况相关。我们将在我们的模型中使用它们!
深度学习
深度学习是机器学习的一个子集,使用深度人工神经网络作为主要模型来解决各种任务。
为了获得深度神经网络,取一个具有一个隐藏层的神经网络(浅层神经网络),并添加更多层。这就是深度神经网络的定义——具有不止一个隐藏层的神经网络!
在深度神经网络中,每层神经元都在前一层的特征/输出上进行训练。因此,您可以创建一个不断抽象的特性层次结构,并学习复杂的概念。
这些网络非常善于发现原始数据(图像、文本、视频和音频记录)中的模式,这些数据是我们拥有的最大量的数据。例如,深度学习可以拍摄数百万张图像,并将其分类为你奶奶的照片、有趣的猫和美味的蛋糕。
深度神经网络是在各种重要问题上拥有最先进的分数。例子有图像识别、图像分割、声音识别、推荐系统、自然语言处理等。
所以基本上,深度学习是大型神经网络。为什么是现在?为什么深度学习之前不实用?
- 大多数深度学习的现实应用需要大量的标记数据:开发一辆无人驾驶汽车可能需要数千小时的视频。
- 训练具有大量参数(权重)的模型需要强大的计算能力:GPU 和 TPU 形式的专用硬件提供大规模并行计算,适合深度学习。
- 大公司储存你的数据已经有一段时间了:他们想利用这些数据赚钱。
- 我们学习了(有点)如何初始化神经网络模型中神经元的权重:主要是使用小的随机值
- 我们有更好的正规化技术(例如辍学)
最后但并非最不重要的是,我们有高性能且(有时)易于使用的软件。像 TensorFlow 、 PyTorch 、 MXNet 和 Chainer 这样的库允许从业者开发、分析、测试和部署不同复杂性的模型,并重用其他从业者和研究人员所做的工作。
预测客户流失
让我们使用“全能”的深度学习机器来预测哪些客户会流失。首先,我们需要做一些数据预处理,因为许多特征是分类的。
数据预处理
我们将使用所有数字(除了 customerID )和以下分类特征:
让我们从数据中创建训练和测试数据集:
以下是我们创建张量的方法:
首先,我们使用函数toCategorical()
将分类特征转换成独热码编码向量。我们通过将字符串值转换成数字并使用 tf.oneHot() 来创建向量。
我们从我们的特征(分类的和数字的)中创建一个 2 维张量,然后将其归一化。另一个,一个热编码,张量是由流失列。
最后,我们将数据分成训练和测试数据集,并返回结果。我们如何编码分类变量?
首先,我们提取特征的所有值的向量。接下来,我们获取唯一值,并从中创建一个 string 到 int 的映射。
注意,我们检查丢失的值,并将它们编码为 0。最后,我们一次性编码每个值。
以下是剩余的效用函数:
构建深度神经网络
我们将把模型的构建和训练打包到一个名为trainModel()
的函数中:
让我们使用 TensorFlow 中的顺序模型 API 创建一个深度神经网络:
我们的深层神经网络有两个隐藏层,分别有 32 个和 64 个神经元。每层都有一个 ReLU 激活功能。
是时候编译我们的模型了:
我们将使用亚当优化器训练我们的模型,并使用二元交叉熵测量我们的误差。
培养
最后,我们将训练数据传递给我们模型的 fit 方法,并训练 100 个时期,混洗数据,并使用其中的 10% 进行验证。我们将使用 tfjs-vis 可视化培训进度:
让我们训练我们的模型:
看起来我们的模型在前十个时期都在学习,在那之后就稳定下来了。
估价
让我们根据测试数据评估我们的模型:
Tensor 0.44808024168014526
Tensor 0.7929078340530396
该模型对测试数据的准确率为 79.2%。让我们看看使用混淆矩阵会犯什么样的错误:
似乎我们的模型在预测保留客户方面过于自信了。根据您的需要,您可以尝试调整模型,更好地预测保留的客户。
结论
干得好!您刚刚构建了一个深度神经网络,它可以以大约 80%的准确率预测客户流失。以下是你学到的东西:
- 什么是深度学习
- 浅层神经网络和深层神经网络有什么区别
- 预处理字符串分类数据
- 在 TensorFlow.js 中构建和评估深度神经网络
但是深度学习会不会更厉害呢?强大到能看懂图像?
在浏览器中运行本教程的完整源代码:
参考
原载于https://www.curiousily.com。
建立机器学习模型(特别是深度神经网络),可以轻松地与现有或新的 web 应用程序集成。想想您的 ReactJs、Vue 或 Angular 应用程序通过机器学习模型的强大功能得到了增强:
建立机器学习模型(特别是深度神经网络),您可以轻松地与现有或新的网络集成…
leanpub.com](https://leanpub.com/deep-learning-for-javascript-hackers)
在 IBM Watson Studio、AWS 和 Databricks 上使用 PySpark 进行客户流失预测
使用大数据工具和云计算服务预测数字音乐服务的客户流失
Image taken from www.thedj.co.uk
客户流失指的是客户停止与一家公司做生意的情况。
根据《哈佛商业评论》的文章,获得一个新客户比留住一个现有客户要贵 5 到 25 倍。
事实上,贝恩公司的 Frederick Reichheld 进行的一项研究表明,将客户保持率提高 5%可以增加 25%到 95%的利润。
因此,降低客户流失率应该是公司的首要任务。如果我们能够提前成功预测客户流失,我们可以通过提供折扣和激励来吸引他们留下来。
在本文中,我们将解决一个名为 Sparkify 的虚拟数字音乐服务的客户流失预测问题。我们将在客户在服务上的活动的大型数据集(约 12GB)上训练我们的预测模型,并尝试根据客户过去的行为预测他们将流失的客户。
由于这个数据集太大,无法在单台计算机上运行,我们将使用 Apache Spark 来帮助我们分析这个数据集。
Apache Spark 是世界上最流行的大数据分布式处理框架(使用多台计算机)之一。Spark 比 Hadoop MapReduce 快得多,它有一个用户友好的 API,可以通过许多流行的语言访问:Scala、Java、Python 和 r。
对于这个任务,我们将使用 Spark 的 Python API py Spark。
PySpark 提供了两种操作数据帧的方法:第一种类似于 Python 的 Pandas 库,另一种使用 SQL 查询。
Spark ML 是 Spark 机器学习库的基于数据框架的 API,它为用户提供了流行的机器学习算法,如线性回归、逻辑回归、随机森林、梯度增强树等。
在本地计算机上设置 PySpark 可能会很棘手。在云服务上运行 PySpark 将简化设置过程,但可能会产生一些成本。
理想情况下,您应该在本地机器上用较小的数据集创建模型的原型以节省成本,然后在准备好分析较大的数据集时将代码转移到基于云的服务。最终,您将需要一个云服务来充分利用 Spark 的分布式计算框架。
另一种方法是用 Databricks 建立一个帐户,并使用他们的社区版来构建你的模型原型。Databricks 社区版提供免费的 6GB 微集群以及集群管理器和笔记本环境。最棒的是访问不受时间限制!
如果您对复制代码或尝试数据集感兴趣,我已经包括了三个云服务的设置说明,IBM Studio Watson、Amazon AWS 和 Databricks。
问题的简要描述
Sparkify 有一个免费层和一个高级订阅计划,客户可以随时取消或从高级降级到免费层。
我们将客户流失事件定义为从高级降级到免费等级或取消服务。
理想情况下,我们希望使用过去的数据来预测客户流失事件,以避免任何前瞻性偏见。有两个月的客户活动数据可用。因此,我们将建立一个模型,利用用户第一个月的行为数据来预测用户在第二个月是否会流失。
完整的数据集为 12 GB。或者,您可以尝试数据集的较小实例,我已经在我的 GitHub 页面上包含了数据集的下载链接。
我现在将介绍三种云计算服务的设置说明:IBM Watson Studio、AWS 和 Databricks。如果你愿意,可以跳过它们,继续阅读“数据集”。
设置 IBM Watson Studio
设置和运行 Spark 最简单的方法之一是通过 IBM Watson Studio 平台。它有一个用户友好的界面,并有一个免费的“建兴计划”。
您将获得每月 50 个容量单位的“精简计划”。默认的 Spark Python 3.5 环境每小时消耗 1.5 个容量单位,这样您就有大约 33 个小时来处理一个项目。
要设置 IBM Watson Studio,您需要注册一个 IBM Cloud 帐户,如果您还没有的话。
接下来,登录 IBM Watson Studio 主页页面并登录。
Select log in
登录后,您将被带到此页面。选择“创建项目”。
Select “Create a project”
接下来,悬停在“数据科学”周围,并单击“创建项目”。
Select “Data Science” and click “Create Project”
输入项目名称,然后选择“创建”。
Enter a project name and select “Create”
选择“添加到项目”。
Select “Add to project”
为您的资产类型选择“笔记本电脑”。
Select “Notebook”
给笔记本起个名字,选择“默认 Spark Python 3.5 xs(1 个 vCPU 和 4 GB RAM 的驱动,2 个各 1 个 vCPU 和 4 GB RAM 的执行器)”。接下来,单击“创建笔记本”。
这将创建一个新的笔记本,您可以在那里开始编码。
要插入数据文件,选择右上角的“查找和添加数据”图标。只需将您想要的数据文件拖放到框中。
要创建新的 Spark 会话并读入数据文件,选择“插入代码”并点击“插入 Spark Session 数据帧”。
这将生成一个预写单元。
Uncomment the last two lines to read in the data frame
您可以取消最后两行的注释,以读取数据文件。请随意更改数据框的名称。
I uncommented the last 2 lines and changed the name of the data frame from df_data_1 to df
您现在可以构建您的项目了!
This is what you will see after you run the first cell
如果您当前的群集因任何原因终止,您可以随时将先前存在的笔记本重新连接到新的群集,并继续您的工作。
完成项目后,您应该停止您的环境并删除您的笔记本和数据文件,以避免任何意外费用。
要停止您的环境,请单击项目页面顶部的“环境”选项卡。单击活动环境右侧的三个点,然后选择“停止”。
Select “Stop” to stop your environment
接下来,转到项目页面顶部的“资产”选项卡。点击数据文件右边的三个点,选择“删除”。对你的笔记本也重复这个步骤。
“Remove” your data file. Do the same for your notebook as well
您可以通过选择“管理”,然后选择“计费和使用”来查看您的计费信息。
To check your billing details
在 IBM Watson Studio 平台上使用 Spark 的一个优点是,它预装了常用的库,如 Pandas 和 Matplotlib 等。这与 AWS EMR 服务形成对比,后者没有预安装这些库。
如果您的 IBM 托管笔记本停止运行或您的互联网浏览器崩溃(是的,它确实发生了),这可能意味着您当前在 IBM Watson Studio 上的设置没有足够的内存来处理这项任务。您可能需要选择一个更简单的模型,对数据集进行降维,或者购买付费计划来访问一个更强大的实例。
设置 Amazon AWS
我现在将分享 Amazon Web Services Elastic MapReduce(EMR)的设置说明。
开始吧。你需要注册一个 AWS 账户。注册时,您需要提供一张信用卡,但不会向您收取任何费用。
您需要选择一个支持计划,免费的基本支持计划应该足够了。
接下来,进入亚马逊 EMR 控制台,点击“亚马逊 EMR 入门”。使用您的帐户登录后,您就可以创建集群了。
Click “Get Started with Amazon EMR”
在右上角选择合适的位置(选择离你最近的一个)。在左侧菜单中选择“集群”,然后单击“创建集群”。
Location, on the top right hand corner, is currently Ohio. Change this to the most appropriate location. Next you can create your clusters.
使用以下设置配置您的群集:
- 版本:emr-5.20.0 或更高版本
- 应用程序::Spark 2.4.0 在 Hadoop 2.8.5 YARN 上,带有 Ganglia 3.7.2 和 Zeppelin 0.8.0
- 实例类型:m3.xlarge
- 实例数量:6
- EC2 密钥对:没有 EC2 密钥对也可以使用,如果你愿意的话
如果您想按原样运行代码,建议您使用 m3.xlarge 的 6 个实例。当然,您可以尝试更少的实例数(例如 3 个)。但是,如果您遇到诸如“会话不活动”之类的错误,这可能意味着您当前的集群设置没有足够的内存来执行该任务。您将需要创建一个具有较大实例的新集群。
其余的设置可以保持默认值,您可以通过单击“创建集群”来完成设置。
**
This picture shows the setup with 4 instances. You are recommended to use 6 instances for this project to avoid memory issues.
You will get a similar error message when running your code if your cluster has insufficient memory for the task
接下来,您将进入一个页面,显示集群正在“启动”。几分钟后,状态将变为“正在运行”。最后会改成“等待”。整个过程可能需要 3 到 10 分钟。此时,您可以进入下一步。
The first status will state that the cluster is “Starting”
Next, the status will change to “Running”
Finally, it will change to “Waiting”. At this point, you can move on to the next step
最后,你可以创建你的笔记本。
- 在左侧菜单中选择“笔记本”。
- 为您的笔记本命名
- 选择“选择现有集群”,然后选择您刚刚创建的集群
- 使用“AWS 服务角色”的默认设置—如果您以前没有这样做过,应该是“EMR_Notebooks_DefaultRole”或“创建默认角色”。
- 您可以保持其他设置不变,然后单击右下角的“创建笔记本”
接下来,等待笔记本的状态从“正在启动”或“待定”变为“就绪”。此时,您可以“打开”笔记本。
现在,您可以开始编码了。
下面提供了创建新的 Spark 会话并读取完整数据集的起始代码:
**# Starter code* **from** pyspark.sql **import** SparkSession*# Create spark session* spark = SparkSession \
.builder \
.appName("Sparkify") \
.getOrCreate()*# Read in full sparkify dataset* event_data = "s3n://dsnd-sparkify/sparkify_event_data.json"df = spark.read.json(event_data)df.head()*
完整的数据集位于:s3n://dsnd-sparkify/sparkify_event_data.json
这是运行代码后您将看到的内容。
如果您终止了群集并停止了笔记本,并且想要重新运行笔记本,则可以创建一个新的群集,并将笔记本重新连接到新的群集。
按照之前给出的说明设置新的集群,然后返回左侧菜单中的“笔记本”选项。单击您已创建的现有笔记本,选择“更改集群”并选择新创建的集群。最后选择“更改集群并启动笔记本”。
To re-connect your notebook to a new cluster, click on your existing notebook
Select “Change cluster”
Select the newly created cluster with the “Choose” button located under the “Choose an existing cluster” option. Finally select “Change cluster and start notebook”
为了避免 AWS 上的任何意外费用,请在完成分析后终止集群并删除笔记本。您可以在左侧菜单的“集群”和“笔记本”选项中检查这一点。
如果您在多个位置设置了集群,请确保检查所有这些位置,因为每个位置都有自己的集群列表!
Ensure all your clusters are terminated when you are done with your analysis
您可以在您的帐户名称下的“我的账单面板”选项下查看您的账单详情。
在 Amazon EMR 上运行代码时,您可能会遇到以下错误或异常:
- type error:“NoneType”类型的对象没有 len()
- KeyError: 14933 ( 数字可能不同)
如果您看到这些错误,请不要担心。通常,它们并不意味着您的代码有问题,它们的出现不会影响您代码的执行。如果在执行代码块时出现这些错误,可以忽略这些错误信息。代码仍将被执行。
An example of error #1
An example of error #2
在 AWS EMR 上运行分析花费了我大约 20 美元。这是考虑到我不得不多次重新运行分析的事实,因为我的 AWS 托管笔记本由于内存不足而崩溃(我最初尝试使用 3、4 和 5 个 m3.xlarge 实例)。
因此,如果您从一个足够大的集群开始(对于这个项目,有 6 个 m3.xlarge 实例),您可能能够运行一次代码而不会遇到任何问题。这有助于降低你的成本。
此外,我在本地机器上开发了模型的原型,这有助于降低最终成本。
需要注意的是,AWS EMR 集群没有预装 Scikit-Learn、Pandas、Matplotlib 等库。您应该能够在没有这些库的情况下完成项目,尽管您将无法执行任何数据可视化。
如果您确实想使用这些库,您需要按照这个链接提供的说明来安装它们。这个视频也会有帮助。
这是一个有点复杂的过程,模糊地涉及编写一个 bash 脚本来安装这些库,将脚本上传到 S3 桶,然后用脚本实例化一个集群。诚然,在撰写本文时,我还没有尝试过。如果我开始安装这些库,我可能会在将来更新这篇文章。
设置数据块
为了注册 Databricks 帐户,您需要一个 AWS 帐户。Databricks 的平台依赖 AWS 作为云基础设施。你可以在这里注册一个 Databricks 账户。
Databricks 的注册过程比前面提到的其他两个云服务稍长,它涉及到在您的 AWS 帐户和 Databricks 帐户设置页面之间穿梭。
然而,一旦您完成了设置并激活了您的帐户,在 Databricks 中设置工作环境就相对容易了。
此外,社区版为您提供了对 6 GB 集群的免费无限制访问(无需担心时间限制!),使其成为构建模型原型的完美环境。
同样,对于 IBM Watson Studio,Databricks 的工作环境预装了常用的库,如 Pandas、Matplotlib。
一旦你创建了你的账户,请前往 Databricks 社区版的登录页面。
Login to your account
您将看到一个类似的页面。
Home page of Databricks Community Edition
在左侧菜单中选择“集群”。
Select “Clusters”
选择页面左上角的“+创建集群”。
Select the blue “+ Create Cluster” button
为您的集群命名。您可以不修改 Databricks 运行时版本。选择要使用的 Python 版本。我为我的项目选择了 Python 3。您可以将“可用性区域”字段留空。完成后,选择“创建集群”。
等待状态从“待定”变为“正在运行”。
Cluster status is “Pending”
Cluster status is “Running”
接下来,在左侧菜单中选择“工作区”,然后单击“用户”。选择电子邮件旁边的箭头,点击“创建”,然后点击“笔记本”。
Creating a new notebook
为您的笔记本命名,您可以保持其他设置不变。
Give a name to your notebook and select “Create”
接下来,您需要上传数据集。在左侧菜单中选择“数据”,然后点击“添加数据”。
只需将您想要上传的文件拖放到灰色框中。上传文件时不要离开页面,否则你将不得不重新做整个过程。
Drag and drop your desired data file
上传完成后,选择“在笔记本中创建表格”。
这将打开一个带有预写单元的示例笔记本,该单元具有读取数据的代码。复制该单元格中的代码,并返回到您之前创建的原始笔记本,然后将代码粘贴到那里。
The example notebook. Copy the first cell and paste it in the notebook you created earlier
恭喜你,你可以开始编码了!
如果你想保存文件,点击“文件”,然后“导出”,然后“iPython 笔记本”。
如果要将代码单元格更改为降价单元格,请在单元格的第一行键入“%md”。
如果您已终止群集,您可以随时创建新群集,并将现有笔记本重新连接到新群集。
尽管 Community Edition 是一项免费服务,但终止您的集群并删除您的笔记本以避免任何意外的 AWS 费用仍然是一个好主意。您可以通过单击左侧菜单中的“集群”来检查集群的状态。
请注意,如果您选择使用完整的 Databricks 平台而不是社区版,您将会产生 AWS 费用。
资料组
数据集的架构:
数据集中的一行:
在这个数据集中要注意的一个关键列是“page”列。“页面”栏记录了用户访问的页面。
以下是用户可以访问的页面列表:
- 取消:用户已经访问了取消页面。并不意味着取消已完成。
- 提交降级:用户已提交从高级层到免费层的降级
- 拇指向下:用户给了一个拇指向下。
- 主页:用户访问了主页
- 降级:用户访问降级页面。不代表提交降级。
- 滚动广告:播放广告。
- 注销:用户注销。
- 保存设置:用户对设置做了一些更改并保存。
- 取消确认否:用户取消订阅。
- 关于:用户访问了关于页面。
- 提交注册:用户提交注册请求。
- 设置:用户访问设置页面。
- 登录:用户登录。
- 注册:用户访问注册页面。并不意味着注册已经完成。
- 添加到播放列表:用户添加歌曲到播放列表。
- 添加好友:用户添加了一个好友。
- 下一首歌:用户听了一首歌。
- 竖起大拇指:用户竖起大拇指。
- 帮助:用户访问了帮助页面。
- 升级:用户从免费升级到高级等级。
其中,我们应该注意的页面可能是:“下一首歌”,它跟踪用户播放的歌曲,“提交降级”,它跟踪用户何时提交降级请求,以及“取消确认”,它跟踪用户的取消请求何时被确认。
通过搜索“提交降级”或“取消确认”页面,我们可以知道客户何时流失。
请注意,用户可以访问“降级”和“取消”页面,但不能提交降级或取消请求。
其余的页面相对简单。它们表明用户已经访问了相关页面。
如前所述,有 2 个月的数据可用。
数据预处理
根据您使用的云计算服务,读入的数据会略有不同。每个服务在数据集中的读取方法在上面的设置说明中有所分享。
如果您正在读取本地计算机上的数据,这是小型数据集的代码:
**# create a Spark session*
spark = SparkSession.builder \
.master("local") \
.appName("Sparkify") \
.getOrCreate()df = spark.read.json("mini_sparkify_event_data.json")*
此代码假定数据集与您的代码位于同一文件目录中。根据数据集的存储位置,您可能需要更改数据集的路径。
数据清理
有几列的值为空。值得注意的是,缺少firstName
、gender
、lastName
、location
、registration
和userAgent
的条目属于未登录或未注册的用户。
Number of null values for each feature on the full dataset
由于我们不知道这些条目跟踪的是哪些用户,而且大多数条目都与登录或主页相关联,因此这些条目提供的价值很小。因此,我们可以从我们的分析中丢弃它们。
**# filter out all entries with missing names.*
*# these also removes entries with missing gender, lastName,
# location, registration and userAgent*df = df.filter(df.firstName.isNotNull())*
不是“NextSong”的页面的artist
、length
和sessionId
为空值。所有这些变量只有在播放歌曲时才有效(只有当page
='NextSong ')时它们才不为空,因此我们可以不修改它们。
特征工程
首先,我们将时间戳转换为日期时间。原始时间戳是以毫秒为单位的,所以我们必须在转换它们之前将它们除以 1000。
**# original timestamp in milliseconds, so divide by 1000* adjust_timestamp = udf(**lambda** x : x//1000, IntegerType())
df = df.withColumn("ts_adj", adjust_timestamp('ts')) *# convert adjusted timestamp to datetime*
df = df.withColumn("datetime", from_unixtime(col("ts_adj"))) *# convert registration timestamp to datetime*
df = df.withColumn("reg_adj", adjust_timestamp('registration')) *# convert adjusted registration timestamp to datetime*
df = df.withColumn("reg_datetime", from_unixtime(col("reg_adj"))) *# drop all the timestamp columns. Will not need them*
columns_to_drop = ['registration', 'ts', 'ts_adj', 'reg_adj']
df = df.drop(*columns_to_drop)*
接下来,我们可以从 0 到 N 标记月份,其中 N 表示数据集中可用月份的总数。我们可以估计分析的开始日期为“2018 年 10 月 1 日 00:00:00”。
**# add start date of analysis*
df = df.withColumn('analysis_start_date',\
lit('2018-10-01 00:00:00')) *# number the months starting from the very first month of the # analysis*
df = df.withColumn("month_num",\
floor(months_between(df.datetime,\
df.analysis_start_date)))*
用二进制(0 或 1)变量替换gender
和level
的字符串变量。
**# engineer free or paid binary variable*
*# free: 0, paid: 1*
df = df.replace(["free", "paid"], ["0", "1"], "level")
*# engineer male and female binary binary variable*
*# male: 0, female: 1*
df = df.replace(["M", "F"], ["0", "1"], "gender")*
我们可以将流失事件定义为用户访问“取消确认”或“提交降级”页面。
***def** define_churn(x):
*"""
Defining churn as cancellation of service or downgrading from premium to free tier.
"""*
**if** x == "Cancellation Confirmation":
** return** 1
**elif** x == "Submit Downgrade":
**return** 1
**else**:
**return** 0 churn_event = udf(**lambda** x : define_churn(x), IntegerType())
df = df.withColumn("churn", churn_event("page"))*
为每个用户生成每月统计数据:
**# number of register page visits*
df_register = df.select('userId', 'month_num', 'page') \
.where(df.page=="Register") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numRegister')*# number of cancel page visits*
df_cancel = df.select('userId', 'month_num', 'page') \
.where(df.page=="Cancel") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numCancelVisits')*# number of upgrade page visits*
df_upgrade = df.select('userId', 'month_num', 'page') \
.where(df.page=="Upgrade") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numUpgradeVisits')*# number of downgrade page visits*
df_downgrade = df.select('userId', 'month_num', 'page') \
.where(df.page=="Downgrade") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numDowngradeVisits')*# number of home page visits*
df_home = df.select('userId', 'month_num', 'page') \
.where(df.page=="Home") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numHomeVisits')*# number of about page visits*
df_about = df.select('userId', 'month_num', 'page') \
.where(df.page=="About") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numAboutVisits')*# number of setting page visits*
df_settings = df.select('userId', 'month_num', 'page') \
.where(df.page=="Settings") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numSettingsVisits')*# number of times user save settings changes*
df_saveSettings = df.select('userId', 'month_num', 'page') \
.where(df.page=="Save Settings") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numSaveSettings')*# number of login page visits*
df_login = df.select('userId', 'month_num', 'page') \
.where(df.page=="Login") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numLogins')*# number of logout page visits*
df_logout = df.select('userId', 'month_num', 'page') \
.where(df.page=="Logout") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numLogouts')*# number of songs added to playlist*
df_addPlaylist = df.select('userId', 'month_num', 'page') \
.where(df.page=="Add to Playlist") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numAddPlaylists')*# number of friends added*
df_addFriend = df.select('userId', 'month_num', 'page') \
.where(df.page=="Add Friend") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numFriends')*# number of thumbs up given*
df_thumbsUp = df.select('userId', 'month_num', 'page') \
.where(df.page=="Thumbs Up") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numThumbsUp')*# number of thumbs down given*
df_thumbsDown = df.select('userId', 'month_num', 'page') \
.where(df.page=="Thumbs Down") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numThumbsDown')*# number of advertisements rolled*
df_advert = df.select('userId', 'month_num', 'page') \
.where(df.page=="Roll Advert") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numAdverts')*# number of songs played*
df_songsPlayed = df.select('userId', 'month_num', 'page') \
.where(df.page=="NextSong") \
.groupBy('userId', 'month_num') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'numSongsPlayed')*# total amount of time user listened to songs*
df_totalListen = df.select('userId', 'month_num', 'length') \
.groupBy('userId', 'month_num') \
.agg({'length':'sum'}) \
.withColumnRenamed('sum(length)', 'totalListenTime')
*# number of songs played per session*
df_songsPerSession = df.select('userId', 'month_num', 'page', 'sessionId') \
.where(df.page=="NextSong") \
.groupBy('userId', 'month_num', 'sessionId') \
.agg({'page':'count'}) \
.withColumnRenamed('count(page)', 'SongsPerSession')*# average number of songs played per session*
df_avgSongsPerSession = df_songsPerSession.groupBy('userId', 'month_num') \
.agg(avg(df_songsPerSession.SongsPerSession).alias ('avgSongsPerSession'))
*# number of singers listened per month*
df_singersPlayed = df.select('userId', 'month_num', 'page', 'artist') \
.where(df.page=="NextSong") \
.groupBy('userId', 'month_num') \
.agg(countDistinct(df.artist).alias('numSingersPlayed'))
*# number of singers per session*
df_singersPerSession = df.select('userId', 'month_num', 'page', 'artist', 'sessionId') \
.where(df.page=="NextSong") \
.groupBy('userId', 'month_num', 'sessionId') \
.agg(countDistinct(df.artist).alias('SingersPerSession'))*# average number of singers per session*
df_avgSingersPerSession = df_singersPerSession.groupBy('userId', 'month_num') \
.agg(avg(df_singersPerSession.SingersPerSession).alias ('avgSingersPerSession'))
*# amount of time spent for each session*
df_userSession = df.groupBy("userId", "month_num", "sessionId") \
.agg(((max(unix_timestamp(df.datetime))-min(unix_timestamp (df.datetime)))/60.0).alias('sessionTimeMins'))*# average time per session*
df_avgUserSession = df_userSession.groupBy('userId', 'month_num').agg(avg(df_userSession.sessionTimeMins).alias ('avgSessionMins'))
*# number of sessions per month*
df_numSession = df.select('userId', 'month_num', 'sessionId').dropDuplicates() \
.groupby('userId', 'month_num').agg({'sessionId':'count'}) \
.withColumnRenamed('count(sessionId)', 'numSessions')*# if user had premium level this month
# if user had premium at any point of the month, assumer he/she has # premium for the whole month for simplicity*
df_level = df.select('userId', 'month_num', 'level') \
.groupBy('userId', 'month_num') \
.agg({'level':'max'}) \
.withColumnRenamed('max(level)', 'level')*# find user's gender
# assuming nobody changes gender midway*
df_gender = df.select('userId', 'month_num', 'gender') \
.groupBy('userId', 'month_num') \
.agg({'gender':'max'}) \
.withColumnRenamed('max(gender)', 'gender')
*# start of each month*
df = df.withColumn("start_of_month", expr("add_months (analysis_start_date, month_num)"))
*# days since registration to start of each month*
df = df.withColumn("daysSinceReg", datediff(df.start_of_month, df.reg_datetime))
df_daysReg = df.select('userId', 'month_num', 'daysSinceReg') \
.groupBy('userId', 'month_num') \
.agg(min(df.daysSinceReg).alias('daysSinceReg'))*# did user churn this month*
df_churn = df.select('userId', 'month_num', 'churn') \
.groupBy('userId', 'month_num') \
.agg({'churn':'max'}) \
.withColumnRenamed('max(churn)', 'churn')*
将这些月度统计数据连接成一个新的数据框架:
*all_data = df_register.join(df_cancel, ['userId', 'month_num'], 'outer') \
.join(df_upgrade, ['userId', 'month_num'], 'outer') \
.join(df_downgrade, ['userId', 'month_num'], 'outer') \
.join(df_home, ['userId', 'month_num'], 'outer') \
.join(df_about, ['userId', 'month_num'], 'outer') \
.join(df_settings, ['userId', 'month_num'], 'outer') \
.join(df_saveSettings, ['userId', 'month_num'], 'outer') \
.join(df_login, ['userId', 'month_num'], 'outer') \
.join(df_logout, ['userId', 'month_num'], 'outer') \
.join(df_addPlaylist, ['userId', 'month_num'], 'outer') \
.join(df_addFriend, ['userId', 'month_num'], 'outer') \
.join(df_thumbsUp, ['userId', 'month_num'], 'outer') \
.join(df_thumbsDown, ['userId', 'month_num'], 'outer') \
.join(df_advert, ['userId', 'month_num'], 'outer') \
.join(df_songsPlayed, ['userId', 'month_num'], 'outer') \
.join(df_totalListen, ['userId', 'month_num'], 'outer') \
.join(df_avgSongsPerSession, ['userId', 'month_num'], 'outer') \
.join(df_singersPlayed, ['userId', 'month_num']) \
.join(df_avgSingersPerSession, ['userId', 'month_num'], 'outer') \
.join(df_avgUserSession, ['userId', 'month_num'], 'outer') \
.join(df_numSession, ['userId', 'month_num'], 'outer') \
.join(df_level, ['userId', 'month_num'], 'outer') \
.join(df_gender, ['userId', 'month_num'], 'outer') \
.join(df_daysReg, ['userId', 'month_num'], 'outer') \
.join(df_churn, ['userId', 'month_num'], 'outer')*
接下来,生成 1 个月的滞后特征。这些将被用作模型的输入特征,而不是本月的统计数据,因为我们不希望有任何前瞻性偏差。
*windowlag = (Window.partitionBy('userId').orderBy('month_num'))
*# generate 1 month lag features*
all_data = all_data.withColumn('numRegister_lastMonth', lag(all_data ['numRegister']).over(windowlag))all_data = all_data.withColumn('numCancelVisits_lastMonth', lag (all_data['numCancelVisits']).over(windowlag))all_data = all_data.withColumn('numUpgradeVisits_lastMonth', lag (all_data['numUpgradeVisits']).over(windowlag))all_data = all_data.withColumn('numDowngradeVisits_lastMonth', lag (all_data['numDowngradeVisits']).over(windowlag))all_data = all_data.withColumn('numHomeVisits_lastMonth', lag (all_data['numHomeVisits']).over(windowlag))all_data = all_data.withColumn('numAboutVisits_lastMonth', lag (all_data['numAboutVisits']).over(windowlag))all_data = all_data.withColumn('numSettingsVisits_lastMonth', lag (all_data['numSettingsVisits']).over(windowlag))all_data = all_data.withColumn('numSaveSettings_lastMonth', lag (all_data['numSaveSettings']).over(windowlag))all_data = all_data.withColumn('numLogins_lastMonth', lag(all_data ['numLogins']).over(windowlag))all_data = all_data.withColumn('numLogouts_lastMonth', lag(all_data ['numLogouts']).over(windowlag))all_data = all_data.withColumn('numAddPlaylists_lastMonth', lag (all_data['numAddPlaylists']).over(windowlag))all_data = all_data.withColumn('numFriends_lastMonth', lag(all_data ['numFriends']).over(windowlag))all_data = all_data.withColumn('numThumbsUp_lastMonth', lag(all_data ['numThumbsUp']).over(windowlag))all_data = all_data.withColumn('numThumbsDown_lastMonth', lag (all_data['numThumbsDown']).over(windowlag))all_data = all_data.withColumn('numAdverts_lastMonth', lag(all_data ['numAdverts']).over(windowlag))all_data = all_data.withColumn('numSongsPlayed_lastMonth', lag (all_data['numSongsPlayed']).over(windowlag))all_data = all_data.withColumn('totalListenTime_lastMonth', lag (all_data['totalListenTime']).over(windowlag))all_data = all_data.withColumn('avgSongsPerSession_lastMonth', lag (all_data['avgSongsPerSession']).over(windowlag))all_data = all_data.withColumn('numSingersPlayed_lastMonth', lag (all_data['numSingersPlayed']).over(windowlag))all_data = all_data.withColumn('avgSingersPerSession_lastMonth', lag (all_data['avgSingersPerSession']).over(windowlag))all_data = all_data.withColumn('avgSessionMins_lastMonth', lag (all_data['avgSessionMins']).over(windowlag))all_data = all_data.withColumn('numSessions_lastMonth', lag(all_data ['numSessions']).over(windowlag))all_data = all_data.withColumn('level_lastMonth', lag(all_data ['level']).over(windowlag))*
将用于我们预测模型的生成特征有:
numRegister_lastMonth
:上月用户注册次数numCancelVisits_lastMonth
:上月用户访问取消页面的次数numUpgradeVisits_lastMonth
:上月用户访问升级页面的次数numDowngradeVisits_lastMonth
:上月用户访问降级页面的次数numHomeVisits_lastMonth
:上月用户访问主页的次数numAboutVisits_lastMonth
:上个月用户访问关于页面的次数numSettingsVisits_lastMonth
:上月用户访问设置页面的次数numSaveSettings_lastMonth
:上个月用户保存设置更改的次数numLogins_lastMonth
:上月用户登录次数numLogouts_lastMonth
:上月用户注销次数numAddPlaylists_lastMonth
:上个月用户添加到播放列表的歌曲数量numFriends_lastMonth
:上个月新增好友用户数numThumbsUp_lastMonth
:上月用户点赞数numThumbsDown_lastMonth
:上个月用户给出的否决数numAdverts_lastMonth
:上个月播放给用户的广告数量numSongsPlayed_lastMonth
:用户上个月播放的歌曲数量totalListenTime_lastMonth
:用户上个月的总收听时间avgSongsPerSession_lastMonth
:上个月用户每次会话平均播放的歌曲数numSongsPlayed_lastMonth
:用户上个月播放的歌曲数量avgSingersPerSession_lastMonth
:上个月用户平均每次播放的歌手数量avgSessionMins_lastMonth
:上个月用户每次会话的平均分钟数numSessions_lastMonth
:上月用户会话数daysSinceReg
:每个用户从注册到当月第一天的天数level_lastMonth
:跟踪用户上个月是付费用户还是免费用户。如果用户在上个月的任何时候都是付费用户,为了简单起见,我们将假设他/她整个月都是付费用户
所有缺少的值都被估算为 0,因为缺少的值通常表示没有页面访问、没有播放歌曲等。
探索性数据分析
以下探索性数据分析是从数据集的中型实例中获得的。在数据集的小实例和大实例中都应该观察到类似的趋势。
下面显示的是第一个月的各种用户统计数据的箱线图,根据这些用户在第二个月是否有变动进行分组。
我们感兴趣的是看看在过去的用户行为中是否有任何可辨别的模式可以表明用户是否会在不久的将来流失。
**
一个令人惊讶的观察是,与没有流失的用户相比,在第二个月流失的用户似乎在第一个月更积极地使用数字音乐服务。被搅动的顾客总是有更高的页面访问量,播放更多的歌曲,听更多的歌手,竖起大拇指和竖起大拇指等等。
或许,这表明下个月有可能流失的用户通常会尝试在当月充分利用他们的服务。
此外,我们注意到注册数、访问取消页面的次数和登录次数都丢失了。
当用户注册或登录时,系统不知道用户的 id。因此,没有为客户记录这些活动的信息。
至于关于取消页面的页面访问的缺失统计,我注意到在第二个月使用该服务的用户中没有一个在第一个月访问过取消页面。很可能在第一个月访问取消页面的用户在第二个月都没有使用该服务。
Premium users are far more likely to churn
与免费用户相比,付费用户似乎更容易流失。请注意,在这种情况下,流失可能意味着从高级级别降级到免费级别,或者完全取消他们的帐户。
Male users are slightly less likely to churn
此外,与女性用户相比,男性用户似乎不太可能流失。
During the second month, the number of users who churned vs number of users who did not churn
第二个月流失的用户数量远远低于没有流失的用户数量。因此,我们将需要使用适当的指标来评估我们的模型,以确保我们的结果不具有欺骗性。我们还可以采用 SMOTE 等技术来处理不平衡的数据集,以提高我们的模型预测用户流失的能力。
总之,大多数流失很可能是由于用户从高级层降级到免费层造成的。我假设,对于新的高级用户,有一个较低的介绍性高级定价计划,不需要任何长期承诺。这些新的高级用户中的许多人可能不愿意对音乐服务做出长期承诺,并且希望在他们拥有高级服务的月份中最大化他们对服务的使用。
或者,也可能是付费用户在长时间使用后发现服务不令人满意,因此想要降低他们的订阅。
系统模型化
从探索性数据分析中,我们注意到,过去的客户活动可以提供一个相对较好的迹象,表明他们是否会在不久的将来流失。我们现在将借助 Spark ML 库建立预测模型,以预测这些客户何时可能流失。
Spark ML 库要求输入特征为数值,所以我们必须将所有字符串变量转换为整数或浮点数。
**# convert userId, gender, level, level_lastMonth to numeric*
convert_numeric = ['userId', 'level', 'gender', 'level_lastMonth']
**for** feat **in** convert_numeric:
featName = feat + "_n"
all_data = all_data.withColumn(featName, all_data[feat].cast ("float"))
all_data = all_data.drop(feat)*
您可以通过运行all_data.persist()
来检查特征的数据类型。此外,确保输入要素中没有空值或“NaN”值。
接下来,我们删除第 0 个月的条目,因为我们没有前几个月的统计数据。我们将放弃当月的统计数据,保留上月的滞后功能和流失标签。
**# first month is month 0*
model_data = all_data \
.filter(all_data.month_num>0) \
.select('userId_n', 'month_num',\ 'numUpgradeVisits_lastMonth',\
'numDowngradeVisits_lastMonth',\
'numHomeVisits_lastMonth',\
'numAboutVisits_lastMonth',\
'numSettingsVisits_lastMonth',\
'numSaveSettings_lastMonth',\
'numLogouts_lastMonth',\
'numAddPlaylists_lastMonth',\
'numFriends_lastMonth',\
'numThumbsUp_lastMonth',\
'numThumbsDown_lastMonth',\
'numAdverts_lastMonth',\
'numSongsPlayed_lastMonth',\
'totalListenTime_lastMonth',\
'avgSongsPerSession_lastMonth',\
'numSingersPlayed_lastMonth',\
'avgSingersPerSession_lastMonth',\
'avgSessionMins_lastMonth',\
'numSessions_lastMonth',\
'level_lastMonth_n',\
'gender_n', 'daysSinceReg', 'churn' ).withColumnRenamed('churn', 'label')*
我们将输入要素和标注存储在数据框model_data
中。
概括地说,我们将使用第一个月的活动数据来预测第二个月的客户流失率。因此,我们将在第二个月执行简单的训练-测试分割,以获得训练和测试数据集。
*train,test = model_data.randomSplit([0.8, 0.2], seed=50)*
注意,一个数据点的所有输入特征都必须放在一个名为features
的向量中。建议也缩放输入要素。列churn
应该改名为label
。Spark ML 库将寻找features
和label
向量,所以使用这个命名约定。
*inputColumns = ['userId_n', 'month_num',\ 'numUpgradeVisits_lastMonth',\
'numDowngradeVisits_lastMonth',\ 'numHomeVisits_lastMonth',\
'numAboutVisits_lastMonth',\ 'numSettingsVisits_lastMonth',\
'numSaveSettings_lastMonth',\ 'numLogouts_lastMonth',\
'numAddPlaylists_lastMonth',\
'numFriends_lastMonth',\ 'numThumbsUp_lastMonth',\
'numThumbsDown_lastMonth',\ 'numAdverts_lastMonth',\
'numSongsPlayed_lastMonth',\
'totalListenTime_lastMonth',\
'avgSongsPerSession_lastMonth',\
'numSingersPlayed_lastMonth',\
'avgSingersPerSession_lastMonth',\ 'avgSessionMins_lastMonth',\
'numSessions_lastMonth',\
'level_lastMonth_n', 'gender_n',\
'daysSinceReg']assembler = VectorAssembler(inputCols=inputColumns,\
outputCol="FeaturesVec") scaler = StandardScaler(inputCol="FeaturesVec",\
outputCol="features",\
withMean=True, withStd=True)*
我们将尝试 3 种类型的模型:逻辑回归,线性支持向量分类机和梯度推进树(GBT)。
**# set max_iter to 10 to reduce computation time * *# Logistic Regression*
lr=LogisticRegression(maxIter=10)
pipeline_lr = Pipeline(stages=[assembler, scaler, lr]) *# Support Vector Machine Classifier*
svc = LinearSVC(maxIter=10)
pipeline_svc = Pipeline(stages=[assembler, scaler, svc]) *# Gradient Boosted Trees*
gbt = GBTClassifier(maxIter=10, seed=42)
pipeline_gbt = Pipeline(stages=[assembler, scaler, gbt])*
将报告测试准确度、f1 分数、精确度和召回率。F1 分数将是首选,因为标签是不平衡的。
**
其中:
- TN(真实否定):我们预测不会流失的客户,以及现实中没有流失的客户。
- FP(误报):客户预测会流失,但实际上并没有流失。
- FN(假阴性):我们预测的客户不会流失,但实际上会流失。
- TP(真阳性):客户预测会流失,但在现实中会流失。
精确度告诉我们精确度我们的预测有多精确(在那些预测会流失的客户中,有多少人流失了),而召回表明我们的模型召回*(模型设法找到了多少最初流失的客户)。*
f1 分数可以看作是精确度和召回率的加权平均值。
由于流失的客户数量少于未流失的客户数量,f1-score 将提供更准确的模型表现,而不是准确性。
使用 PySpark 的MulticlassClassificationEvaluator
报告二进制分类任务的 f1 分数时,需要注意的是,内置函数会将标签 0 和 1 视为单独的类,并返回两个类的加权 f1 分数。这将产生一个可能具有欺骗性的过于乐观的分数。因此,我选择定义自己的函数来计算 f1 分数。更多细节可在代码中找到。
结果
我们将使用 5 个折叠进行 k-fold 交叉验证,并使用 PR 曲线下的区域优化模型。
使用 PR 曲线下的面积优于使用 ROC 曲线下的面积,因为数据集中类的不平衡意味着使用 ROC 曲线下的面积会导致过于乐观的情况。
以下结果是通过在完整数据集上训练我们的模型获得的。
逻辑回归
*paramGrid = ParamGridBuilder() \
.addGrid(lr.regParam,[0.0, 0.05, 0.1]) \
.build() cv_lr = CrossValidator(estimator=pipeline_lr,\
estimatorParamMaps=paramGrid,\
evaluator=\
BinaryClassificationEvaluator(metricName=\
"areaUnderPR"),\
numFolds=5, seed=42) cvModel_lr = cv_lr.fit(train)lr_results = cvModel_lr.transform(test)evaluate_model(lr_results)*
精确度:0。58660 . 68686868661
F1-分数:0.3467676767767
***精度:*0.6666666667
召回:0 . 10000 . 486868686865
混淆矩阵:
*TN:2706.0 | FP:124.0
FN:659.0 | TP: 248.0*
线性支持向量分类器
*paramGrid = ParamGridBuilder() \
.addGrid(svc.regParam,[0.0, 0.05, 0.1]) \
.build()cv_svc = CrossValidator(estimator=pipeline_svc,\
estimatorParamMaps=paramGrid,\
evaluator=\
BinaryClassificationEvaluator(metricName=\
"areaUnderPR"),\
numFolds=5, seed=42)cvModel_svc = cv_svc.fit(train)svc_results = cvModel_svc.transform(test)evaluate_model(svc_results)*
***精确度:*0。46860 . 68868886861
F1-分数:0.18500026767767
精度:0。56660 . 68686888661
召回:0.10000000001
混淆矩阵:
*TN:2793.0 | FP:37.0
FN:808.0 | TP: 99.0*
梯度增强树
*paramGrid = ParamGridBuilder() \ .addGrid(gbt.minInstancesPerNode,[5]) \ .addGrid(gbt.maxDepth,[7])\ .addGrid(gbt.subsamplingRate,[0.75])\
.build()cv_gbt = CrossValidator(estimator=pipeline_gbt,\
estimatorParamMaps=paramGrid,\
evaluator=\
BinaryClassificationEvaluator(metricName=\
"areaUnderPR"),\
numFolds=5, seed=42)cvModel_gbt = cv_gbt.fit(train)gbt_results = cvModel_gbt.transform(test)evaluate_model(gbt_results)*
精确度:0。36860 . 68868686861
F1-分数:0.453638636386
精度:0.50000000001
召回:0 . 35867 . 38888883886
混淆矩阵:
*TN:2614.0 | FP:216.0
FN:618.0 | TP: 289.0*
在测试数据集的 3 个模型中,GBT 模型获得了最高的 f1 值和最高的召回率。但是,它的精度也是最低的。尽管如此,GBT 模型在精确度和召回率之间保持了最佳平衡。
另一方面,线性支持向量分类器具有最高的精度,但 f1 值最低,主要归因于其低召回率。
最终,为预测客户流失而选择的模型将取决于您公司的需求。
如果你优先考虑留住客户,并且不介意在折扣和激励措施上多花一点钱来留住客户,你可以选择 GBT 模式,因为它在召回会流失的客户方面是最好的。
此外,如果您希望在客户维系度与折扣支出和维系客户维系度的激励措施之间保持更好的平衡,GBT 模式也将是理想之选,因为它还拥有最高的 f1 得分。
然而,如果您想要将折扣和激励措施的花费最小化以留住客户,线性支持向量分类器将是理想的,因为它的预测是最精确的。
结论
在本文中,我们实现了一个模型来预测客户流失的时间(从高级层降级到免费层或者取消他们的帐户)。凭借这一款车型,我们成功取得了大约 0.4 的 f1 分数,这是一个相当不错的分数。
可以提高 f1 分数的另一种方法是建立单独的模型来预测这两个事件。这两个事件有可能具有不同的信号,因此使用单独的模型会导致更好的结果。
本文附带的代码可以在 这里 找到。
感谢您阅读本文!如果你有任何想法或反馈,请在下面留下评论或给我发电子邮件到 leexinjie@gmail.com。我很想收到你的来信!
客户细分
深入分析
R 中的实用介绍
Source: Image by the author
顾客是任何企业成功的关键因素。传统智慧告诉我们,留住一个现有客户的成本远远低于获得一个新客户的成本。为了使一个企业有一个可持续的增长,保留它的老客户群和扩大新客户群是非常关键的。这就需要了解与业务相关的客户行为。因此,对于寻求市场竞争优势的企业来说,全方位了解客户至关重要。这方面的一种技术是客户细分,它有助于根据客户与产品的互动来识别相似的客户群,然后针对适当的客户有效地采取不同的营销方案。
本文提供了一个循序渐进的实践介绍,重点介绍了在 r 中执行客户细分的方法之一。为了更好地了解客户细分的思想,本文不包括关于在数据集上执行的探索性数据分析和数据争论的详细信息。
**数据来源:**超市 aggr。客户
所使用的数据集是意大利最大的零售分销公司之一 Coop 针对一个意大利城市的零售市场数据。
超市 aggr。用于分析的客户数据集包含从客户和商店信息聚合的数据,并透视到新列。因此,数据集包含 40 个特征和 60,366 个实例,大小约为 14.0 MB。
研究问题(RQ): 根据客户的购买行为,有哪些不同的客户群?
选择的算法: K 均值,主成分分析(PCA)
算法选择理由: K-means 聚类是一种非常简单快速的 algorithm⁴.这是用于客户细分的流行方法,尤其是对于数字数据。K-means 还具有计算优势,可以很好地处理大型数据集。分层的和基于模型的聚类方法需要计算全距离矩阵,该矩阵表现出有限的可扩展性和对大数据集计算的大存储器需求。相比之下,K-means 聚类在运行时效率更高。考虑到这些事实,并考虑到输入数据集很大且主要包含数值数据,K-means 是客户细分的理想选择。
PCA 是一种降维算法,它通过数据分解和转换为主成分(PC)来可视化数据集的本质,最大化数据的线性方差(方差越大表示对 data)⁵.的理解越多与 K-means 相比,PCA 不是一个直接的解决方案,因此从不同的角度来看,它可以帮助检测 K-means 没有发现的客户群。主成分分析在这里用于这个研究问题,作为一个有价值的交叉检查,以 K-均值数的集群确定。
**选择的特征:**RQ 希望根据顾客的购买行为识别顾客群,即顾客喜欢购买的商店。因此,特性 customer_id、amount_purchased_shop_1、amount_purchased_shop_2、amount_purchased_shop_3、amount_purchased_shop_4、amount_purchased_shop_5 非常重要,并被选择用于此 RQ。
分析:
1)估计最佳聚类数
K-means 要求在算法开始之前指定聚类数。确定最佳聚类数对于输出更好的结果至关重要。
**library**(cluster)
**library**(factoextra)
**library**("metricsgraphics")*# Read file contents*
supermarket_data_clean <- read.csv("Input Dataset/Cleaned Dataset/Supermarket_DataCleaned.csv")*# Prepare data frames for clustering*
*# Select only the rows customer_id, amount_purchased_shop_1, 2, 3, 4, 5*
cluster.slice.temp <- supermarket_data_clean[,c(1,29,30,31,32,33)]
*# Remover customer_id from the clustering data frame*
cluster.slice.data <- supermarket_data_clean[,c(29,30,31,32,33)]*# Scale the data and Determine the ideal number of clusters*
cluster.slice.scale <- scale(cluster.slice.data)wssplot <- **function**(data, nc=15, seed=1234){
wss <- (nrow(data)-1)*sum(apply(data,2,var))
**for** (i **in** 2:nc){
set.seed(seed)
wss[i] <- sum(kmeans(data, centers=i)$withinss)}
plot(1:nc, wss, type="b", xlab="Number of Clusters",
ylab="Within groups sum of squares")}wssplot(cluster.slice.scale)
Source: Image by the author
输出图描绘了群集数量从值 1 到 4 的急剧减少,以及从 4 到 5 的轻微减少,这估计了 4-群集或 5-群集解决方案。
2)执行 K 均值聚类,聚类为 4 和 5
*# Perform k-means on cluster values as 4 and 5**# On entire dataset*
set.seed(123) *# fix the random starting clusters*
kclust4 <- kmeans(cluster.slice.data, 4, nstart = 25)set.seed(123) *# fix the random starting clusters*
kclust5 <- kmeans(cluster.slice.data, 5, nstart = 25)
3)执行主成分分析以可视化聚类
pca <- prcomp(t(cluster.slice.data), scale. = T, center = T)
fviz_eig(pca) +
theme_bw() + scale_y_continuous(labels = scales::comma) +
ggtitle(label='Principal Component Analysis')
Source: Image by the author
正如所观察到的,主成分分析 1 和主成分分析 2 共同解释了大部分数据差异,然后从主成分分析 2 下降到主成分分析 3。因此,这推断出用 PCA 1 和 PC 2 的可视化将给出对数据的良好理解,并且在 PCA 2 之后包括更多的 PCA 将仅导致最小的改进。
具有 4 个聚类 K-均值的 PCA
cluster.pc4 <- prcomp(cluster.slice.data, center = FALSE, scale. = FALSE)$x %>% as.data.frame()
cluster.pc4$kmeans.cluster <- factor(kclust4$cluster)p<-ggplot(cluster.pc4,aes(x=PC1,y=PC2,color=kmeans.cluster))
p+geom_point() +
theme_bw() + scale_y_continuous(labels = scales::comma) +
ggtitle(label='PCA with 4 cluster K-means')
Source: Image by the author
具有 5 个聚类 K 均值的 PCA
cluster.pc5 <- prcomp(cluster.slice.data, center = FALSE, scale. = FALSE)$x %>% as.data.frame()
cluster.pc5$kmeans.cluster <- factor(kclust5$cluster)p<-ggplot(cluster.pc5,aes(x=PC1,y=PC2,color=kmeans.cluster))
p+geom_point() +
theme_bw() + scale_y_continuous(labels = scales::comma) +
ggtitle(label='PCA with 5 cluster K-means')
Source: Image by the author
比较以上两个图,确定 5 个聚类的解决方案将是 K-均值聚类的理想估计。
4)可视化数据中不同的可分离聚类
fviz_cluster(kclust5, data = cluster.slice.data, geom = "point",
stand = FALSE, ellipse.type = "norm") +
theme_bw() + scale_y_continuous(labels = scales::comma) +
ggtitle(label='Customer Clusters')
Source: Image by the author
5)聚类分析
确定属于每个集群的不同客户
*## retrieve customer ID's in each cluster*
head(gather(data.frame(cluster.slice.temp[kclust5$cluster == 1,])))*## retrieve customer ID's in each cluster*
head(gather(data.frame(cluster.slice.temp[kclust5$cluster == 2,])))
head(gather(data.frame(cluster.slice.temp[kclust5$cluster == 3,])))
head(gather(data.frame(cluster.slice.temp[kclust5$cluster == 4,])))
head(gather(data.frame(cluster.slice.temp[kclust5$cluster == 5,])))
6)客户细分
*#Customer segmentation through aggeration of results by mean*
cluster.slice.kmeans.aggregate <- aggregate(cluster.slice.data, by = list(kclust5$cluster), mean)cluster<-c(cluster.slice.kmeans.aggregate$Group.1)
shop1<-c(cluster.slice.kmeans.aggregate$amount_purchased_shop_1)
shop2<-c(cluster.slice.kmeans.aggregate$amount_purchased_shop_2)
shop3<-c(cluster.slice.kmeans.aggregate$amount_purchased_shop_3)
shop4<-c(cluster.slice.kmeans.aggregate$amount_purchased_shop_4)
shop5<-c(cluster.slice.kmeans.aggregate$amount_purchased_shop_5)*# Plot a Bar graph*
Legends <-c(rep("Customers Shop 1", 5), rep("Customers Shop 2", 5), rep("Customers Shop 3", 5), rep("Customers Shop 4", 5), rep("Customers Shop 5", 5))
values <-c(shop1,shop2,shop3,shop4,shop5)
mydata <-data.frame(cluster, values)p <-ggplot(mydata, aes(cluster, values))
p +geom_bar(stat = "identity", aes(fill = Legends)) +
xlab("Cluster") + ylab("Total") +
ggtitle("Customer Segmentation") +
theme_bw() + scale_y_continuous(labels = scales::comma)
Source: Image by the author
**观察:**根据顾客的购买行为对数据进行聚类,即从他们购物最多的商店中,发现了 5 个可分离的聚类进行分析。聚类分析有助于根据客户 id 识别每个聚类中的客户。这有助于了解在每个集群中构建客户群的不同客户。此外,客户细分有助于识别每个细分(聚类)中不同商店的客户。这进一步有助于划分集群并赋予其意义。因此,通过基于他们的购买行为识别五个客户群,并通过确定属于五个不同商店的特定客户来进一步划分这些客户群,来回答研究问题。
**应用:**集群的检测可以帮助企业为每个集群基础制定具体的策略。聚类还可以用于了解客户的购买行为,方法是跟踪数月的客户,并检测从一个聚类转移到另一个聚类的客户数量。这有助于企业更好地组织战略,以增加不同商店的收入。企业可以进一步利用所获得的客户细分洞察来更好地将他们的营销工作集中在正确的客户上,例如,与特定商店相关的折扣和优惠可以只发送给通常在该特定商店购买的那些客户,而不会打扰其他商店的客户。因此,为正确的交易锁定正确的客户有助于降低营销成本、创造更多收入并提高客户满意度。
客户行为分析作为一个重要领域,利用数据分析在特定于客户的业务数据中发现有意义的行为模式。本文分享的见解来自对数据源进行的端到端市场篮子分析中的一种方法,旨在了解消费者的购买决策以及影响这些决策的因素。
参考
[1]https://bigml . com/user/czuriaga/gallery/dataset/5559 C2 c 6200 d5a 6570000084
[2] Pennacchioli 博士、Coscia m .博士、Rinzivillo s .博士、Pedreschi 博士和 gian notti f .博士,2013 年 10 月。解释购买数据中的产品范围效应。大数据,2013 年 IEEE 国际会议(第 648–656 页)。IEEE。
http://www.michelecoscia.com/?page_id=379
[4]哈迪根、约翰·A 和曼切克·A·王 1979."算法 as 136:一个 K 均值聚类算法."皇家统计学会杂志。【T1 系列 C(应用统计学)】28 (1)。JSTOR:100–108。
[5]丁、克里斯和。2004." K-均值聚类通过主成分分析."在第二十一届机器学习国际会议论文集,29 页。ACM。
用 Python 进行客户细分分析
在这篇文章中,我将探索一个关于购物中心顾客的数据集,尝试看看是否有任何可辨别的细分和模式。客户细分有助于理解在一个商业案例中你的客户中有哪些人口统计学和心理学的亚人群。
了解了这一点,你就能更好地理解如何营销和服务他们。这与 UX 创建用户角色的方法类似,但略有不同:创建你的理想客户、他们的痛点、定义性报价等等,以了解他们的观点。
设置
开始时,您可以写出导入语句并加载数据集,调用head()
查看数据预览。
像往常一样,在这里通过克隆回购随意编码。
Import statements
Head call
接下来,您可以对数据调用describe()
来查看每个变量的描述性统计数据。在这里花点时间理解这些数字的含义是很重要的。例如,如果支出分数(1-100)列(显然是 1 到 100 之间的一个值范围)的最小值或最大值超过 100,您就会知道有问题。
Describe call
通过调用 describe,您可以看到没有要清理的值。年龄看起来很正态分布,年收入没有太多的异常值。支出分数实际上在 1 到 100 分之间。一切看起来都很好。
探索数据
了解分类变量在整个数据集中是如何分割的总是很有帮助的。这可以通过一个简单的计数图来实现,如下所示:
在这个数据集中,女性略多于男性。他们也许会成为你以后客户细分工作中的一个重要因素。
年龄呢?
Distribution of ages
年龄大多在 30 到 40 岁之间。回想一下describe()
的通话结果这是有道理的。平均年龄为 38 岁。老年客户较少,因此这种分布是右偏的,因为它的右尾较长。这可能是因为购物中心的吸引力和倾向于在那里购物的人口类型。
您可以通过叠加两个直方图来添加细节,为每个性别创建一个年龄直方图。
Distribution of age by gender
这个数据集中的男性往往比女性年轻。你可以看到,女性在 30-35 岁之间达到顶峰,这也是她们中大多数人下降的时候。这个数据集中的中年女性也比男性多。65-70 岁年龄段中有相当数量的老年人。
收入怎么样?您可以用下面的代码来看看这个发行版:
Distribution of income
大部分收入介于 60 美元和 85,000 美元之间。性别对此有影响吗?
Distribution of income by gender
这个数据集中的女性比男性挣钱少。他们的支出分数是什么样的,相比之下又如何呢?
男性的平均支出分数为 48.5,女性为 51.5。在这个数据集中,女性挣得更少,但在这个商场花得更多。
您可以进一步增加复杂性,以便更好地理解数据。因为大部分是定量变量和一个干净的二元分类变量,所以做一些散点图是有帮助的。
Age and income, colored by gender
没有明确的相关性。事实上,你可以量化这一点,并且很容易地对所有变量进行量化。就叫sns.heatmap(customers.corr(), annot=True)
。
Correlation heat map of each variable
从上面的图中可以看出,唯一稍微相关的变量是支出分数和年龄。这是一种负相关关系,因此在这个数据集中,客户的年龄越大,他们的消费得分就越低。但是因为是 0.33,所以根本不是强相关。它的信息量仍然很少,并且遵循基本的逻辑。
编码下面的图显示了这一趋势。
Age to spending score by gender
你现在可以看到轻微的负相关。你难道不想知道是男人还是女人有更强的相关性吗?您可以通过之前创建的两个按性别划分的数据框中的热图来验证这一点。
Correlation heat map of female customers
将female_customers
数据框替换为男士数据框,为他们展示了以下情节:
Correlation heat map of male customers
在这种情况下,年龄对女性的支出分数影响更大。没有任何其他东西真的有足够强的相关性来说明任何事情。
现在你可以用一个漂亮的lmplot
放大女性消费分数与年龄的关系。
Scatter of age to spending score for women, with a regression line and bootstrap interval about the line
最后,您可以使用以下代码查看按性别着色的收入支出比得分:
Spending score and income by gender
这里有一些模式。但是零相关。但是你可以把这些看作是客户群:
- 低收入,低支出分数
- 低收入,高支出分数
- 中等收入,中等支出分数
- 高收入,低支出分数
- 高收入,高支出分数
解释和行动
回到这类分析的业务和营销用例,可以测试以下假设。
- 向女性推销更便宜的商品会改变购买频率或数量吗?
- 对年轻女性进行更多的营销会导致更高的销售额吗,因为她们的消费得分往往更高?
- 广告、定价、品牌和其他策略如何影响老年女性(40 岁以上)的消费分数?
要回答这些问题,需要更多的数据。
计划如何收集更多数据以构建具有更多功能的数据集将会很有帮助。功能越多,就越能理解是什么决定了支出分数。一旦更好地理解了这一点,你就能理解什么因素会导致支出分数增加,从而带来更大的利润。
KPI
本着业务用例的精神,我将定义下面的 KPI 作为一个例子来展示你如何知道你的努力是否有回报。
- 在推出更多针对妇女的营销活动后,妇女购买的频率和数量发生了变化。
- 引入针对年轻女性的营销活动后,支出分数的变化。
- 引入针对老年女性的营销活动后,消费得分的变化。
如果你觉得这篇文章很有用或者学到了新的东西,考虑捐赠任何数量的钱来支付给下一个学习者!
感谢阅读和快乐编码!
奢侈的生活
客户细分数据科学
问题公式化、数据建模和聚类分析
在市场营销中,客户细分是将客户按共同特质分组的过程。根据客户类型辨别购买习惯有助于恰当地营销。例如,它揭示了各部分的大小,我们从中赚了多少,等等。这有助于决定如何分配营销预算。
在数据科学中,聚类是根据一些共同特征对对象进行分组的过程。
看到联系了吗?聚类——一种数据科学方法——非常适合客户细分——一种用例。也就是说,正如经常发生的那样,当我们开始挖掘时,还有更多的东西。我们称之为建模。
让我们先来看看我们想要细分的具体特征。我们将区分 B2B 营销和 B2C 营销,因为它们的特点各不相同。
B2B 营销
客户是企业,即公司。一组有用的特征是所谓的公司分类:行业、地点、公司规模(小、中、大等)。
B2C 营销
在这里,顾客是消费者,即人。用于细分的有用特征是所谓的人口统计特征:年龄、性别、地点、种族、收入水平等等。
建模
现在,我们从 B2B 和 B2C 的细节中抽象出来,看看建模本身。虽然我们将使用 B2B 示例进行说明,但是建模更加通用。
单一结构化因素
先考虑单一特质,说行业。我们希望通过这一特点来细分客户。首先,让我们假设行业有少量的干净值。(“干净”是指来自受控的词汇表,例如下拉列表中的词汇表。)
这种分割很容易做到。它只是涉及到分组和聚合。
单一非结构化因素
现在假设行业价值观不干净。它们以自由格式存储在文本字段中。在数据输入时,任何有意义的值都会被输入。
假设我们的数据库中有 100,000 条客户记录,其中大多数都填写了行业字段值。
我们的细分问题立刻变得更加困难。同一个行业在不同的记录中往往会有不同的表达方式。词序可能有所不同;缩略语可能会被使用;连接词可能存在,也可能不存在;可能会用到同义词。
字符串相似性度量 : 解决这个问题的第一个合理尝试是这样的。首先,我们在成对的行业字符串上定义一个相似性度量。我们首先定义一个行业字符串的词集。这只是字符串中的一组不同的标记。熟悉术语单词袋的人可能会认为这是去掉了词频的袋子。接下来,给定两个词集,我们需要一个合理的方法来量化它们的相似性。这里有一个:叫做 Jaccard 相似度。
两个集合的 Jaccard 相似度是它们共有的元素数除以它们的并集中的元素数。
下面是一个例子。
**Jaccard-coefficient({predictive analytics platform},{predictive analytics}) = ⅔**
将记录分组:接下来,我们需要使用这个相似性度量将记录分组。同一聚类中的记录对应该彼此相似;不同簇中的对不匹配。
这说起来容易做起来难。还不清楚我们是否已经完全考虑了我们的问题表述。我会详细说明。“组”这个词似乎意味着一个记录应该恰好属于一个簇。这真的是我们想要的吗?不一定。想象一个行业价值医学信息学。我们可能希望将它放在多个集群中——一些与医疗相关,一些与 IT 相关。
多级行业:行业字段的值可以跨越行业分类的多个级别。例如,一条记录可能将行业字段值设置为一个宽泛的类别,比如说软件。另一个记录可以在更精细的层次上指定行业,比如说数据库软件。所以也许我们要做的是层次聚类,而不是平面聚类。因此,行业为数据库软件的记录可以被分配给一个集群,该集群是行业为软件的集群的后代。
聚类算法:使用哪种算法取决于我们想要哪种类型的聚类——平面的还是层次的?即使在扁平类别中,算法也取决于我们希望聚类是硬的还是软的。(硬表示一个项目恰好属于一个集群。)
平面聚类:适合硬聚类的算法有 k -means 聚类、dbscan、连通分量聚类等。软聚类算法通常是硬聚类算法的软化版本。其中一个是软的,也称为概率的,即聚类的 k 。这是基于一个强大的算法,称为 EM 算法,其中(硬)k 均值聚类是一个特例。
层次聚类:流行的层次聚类算法有凝聚(自下而上)聚类或分裂(自上而下)聚类。在前者中,首先将项目聚集成紧密的簇,然后将簇聚集成较粗糙的簇。重复这个过程,直到我们得到一个聚类树。它的叶子就是物品。树的根是代表整个数据集的单个聚类。
分裂聚类的作用方向相反。它从代表整个数据集的聚类开始;然后,一个集群被一分为二。由此产生的集群成为前者的子集群。因此聚类树是自上而下生长的。
自底向上 vs 自顶向下:有人可能会想,自底向上或者自顶向下,它们是镜像过程,所以为什么要在意使用哪一个呢。选择很重要。合并两个合适的小集群通常比将一个大集群分成两个好的集群更容易。这个事实很大程度上解释了为什么自底向上的聚类比自顶向下的聚类更受欢迎。
另一个重要的考虑是,自底向上和自顶向下的聚类会产生不同的结果。自下而上的建立不同于自上而下的分裂。由于这个原因,当自底向上的结果不够好时,或者我们只想知道在任何特定情况下从自底向上到自顶向下的切换对树的改变有多大时,有时会考虑自顶向下的聚类。
行业名称中的语言结构:回到我们的行业,以非结构化价值为例,我们可能会采取第三种策略。利用行业名称中隐含的语言结构。这可能会更好。下面我们用一个玩具例子来说明这一点。出现的算法可以容易地被增强以适应更现实的场景。
举例:假设只有六个不同的行业价值:软件、数据库软件、分析软件、制造业、汽车制造业、药品制造业。(有些名字是人为的,不要当真。)所寻求的层次结构是
**Software ← Database Software, Software ← Analytics Software
Manufacturing ← Auto Manufacturing, Manufacturing ← Drug Manufacturing**
其中 p ← c 表示父子实例。
利用语言结构的算法:下面是算法。在这个例子中,它将产生我们想要的层次结构。
**Order the set of distinct industry values by the number of words in them. Least number of words first. Denote this ordering as I1, I2, …, Ik.****For each i in 1..k
If Ii does not have a parent, set Ii as a root of the hierarchy.
For each j in (i+1)..k
If Ij’s parent is not set and is_child(Ij,Ii)
parent(Ij) = Ii**
让我们在例子中说明这个算法。首先,我们根据行业名称中的字数对其进行排序。这产生了有序列表
**Software, Manufacturing, Database Software, Analytics Software, Auto Manufacturing, Drug Manufacturing**
接下来,我们将软件作为一个根,扫描到它的右边,找到它的所有子节点。我们发现
**Software ← Database Software, Software ← Analytics Software**
接下来,我们将 Manufacturing 作为这个层次结构的根,并扫描到它的右边,找到它的所有子代,跳过已经有父代的行业名称。我们发现
**Manufacturing ← Auto Manufacturing, Manufacturing ← Drug Manufacturing**
接下来,我们尝试查找每个两个单词的行业名称的子代。我们什么也没找到。所以我们结束了。
多重因素,混合结构
现在考虑结合多种因素对公司进行细分。具体来说,是根据关键的公司分类:行业、地点和公司规模。这些因素中有些可能是结构化的,有些可能是非结构化的。例如,和以前一样,工业是无组织的。公司规模可以是结构化的:用固定数量的值表示,如小、中、大。这使问题变得非常复杂。
多因子相似性度量:首先,让我们定义一个合适的度量,量化一对因子元组的相似程度。这一定义需要仔细考虑,因为这一指标将极大地影响哪些公司被归入哪些细分市场。
序言 1 —因子描述:首先,让我们细化我们的因子描述。我们将像以前一样保持行业的非结构化。我们将使公司规模有序。这只是意味着公司规模将有一套固定的有序值。举个例子,小型,中型,大型。我们以后可以给公司规模添加更多的值,比如非常小和非常大。我们甚至可以通过数据驱动的公司规模宁滨过程来自动确定一组值。结果值可能因数据集而异。只要价值观是有序的,我们就是好的。(顺便说一下,并不是每组值都可以排序。考虑性别=女,男。没有自然的顺序。我们可以强加一些人为的秩序,但这可能不太好。)
对于位置,首先让我们保持简单,只选择 5 位数字的邮政编码。(这确实将我们的公司局限在美国。)
序言 2 —关于指标的一些注释:接下来,我们在这些因素定义下定义一个合适的指标。值得注意的是,如果我们修改我们的因素定义,该指标可能需要大规模的修订。例如,假设我们将位置定义为邮政编码、城市、州、国家的组合。量化两个地点的相似程度现在要复杂得多。
单个因素的相似性度量
我们将采用为每个因素单独定义相似性度量的方法,并在此基础上构建我们的多因素相似性度量。
公共部分— Sigmoid 函数:在我们深入研究各个指标之前,我们想提一下贯穿其设计的一个关键点。在所有这些方法中,我们使用 sigmoid 函数作为阶梯函数的软近似。我们这样做是因为当被比较的值不够相似时,我们希望相似性迅速下降到 0。这使我们能够控制相似性检测和假阳性率。
正因为如此,我们先来定义一下 sigmoid 函数。
**sigmoid_{a,b}(x) = 1/(1+exp(-a*(x-b)))**
a
控制 sigmoid 的陡度,即它逼近阶跃函数的程度。b
控制软阈值。就像一个阶跃函数,sigmoid 识别b
两侧的值。
公司名称相似度指标:让我们从比较两家公司规模的指标开始。让我们将公司规模值按顺序排列为 1 、 2 、 3 、…、 k 。也就是说, 1 表示最小公司尺寸仓位, k 表示最大公司尺寸仓位。我们的相似性度量是
**S(i,j) = sigmoid_{a,b}(-|i-j|)**
这里a>0
和b>0
让我们控制 s 形的陡度和阈值。我们使用了-|i-j|
而不是|i-j|
作为参数,因为我们寻求一个反 s 形行为。相反,我们可以将这个负号吸收到a
中,但是让它显式更直观。
S(i,j)有一个简单的直觉,即箱 i 和 j 在它们的等级顺序中越接近,即在它们所代表的公司的规模中,相似性值越高。
我们将选择合理的默认值,这样如果你不喜欢自由参数,你可以忘记它们。具体来说,我们将a
设置为 1,这是最常用的 sigmoid 陡度。为了更好地揭示什么值b
有意义,让我们将 sigmoid 重写为sigmoid(b-|i-j|)
。我们将把b
设置为 1.5。这将使我们得到当|i-j|<=1.5
s 形值从下方越过 0.5 时的行为。当然,我们可以随心所欲地微调这种行为。
zip 相似性度量:接下来,让我们研究 zip 值的度量。我们将利用 5 位数的邮政编码是数字的事实,一般来说,数字越近,邮政编码位置就越近。我们使用
**S(z1,z2) =sigmoid_{a,b}(-|z1-z2|)**
这次我们会选择a>0
远小于 1、b
到~ 20。直觉是,我们希望 S(z1,z2)大于 0.5,即使|z1-z2|大约为 20,粗略地说。此外,我们希望交叉点有低陡度。当然,我们可以按照自己认为合适的方式微调这些值。
替换和转置错误:虽然这种度量对邻近性建模得足够好,但它没有考虑替换或转置错误。让我们先来看一个例子。(我们有意省略了插入和删除错误,因为它们改变了位数。)
**Substitution error: 21**0**34, 21**8**34, Transposition error: 2**10**34, 2**01**34**
和上面的例子一样,我们将只考虑单个数字替换错误和相邻数字换位错误。下面是一个简单的度量标准,可以容纳一位数的替换错误:
**S2(z1,z2) = sigmoid_{a,b}(-sum_i NE(z1[i],z2[i]))**
这里x
等于y
时NE(x,y)
等于 0,不等于时为 1。我们将a
设置为 1,将b
设置为 1.5,因为我们寻求的行为(根据 sigmoid 的论证)正是我们寻求的公司规模相似性。
类似地,我们可以定义一个最多容纳一个相邻数字换位的度量。
**T(z1,z2) = sigmoid_{a,b}(-#adjacent-digit-transpositions(z1,z2))**
我们使用与替代计分相同的a
和b
。
现在,我们可以将所有这些指标合并成一个指标。
**Sim(z1,z2) = Max(S(z1,z2),S2(z1,z2),T(z1,z2) )**
行业相似性度量:最后,对于行业,我们将使用其词集上的 Jaccard 系数作为相似性度量,并通过 sigmoid 进一步转换。即
**S_industry(I1,I2) = sigmoid_{a,b}(Jaccard-coefficient(I1,I2))**
如何选择a
和b
的默认值我们就不详细描述了。相反,我们将注意到a
应该是大于 1 的正数,并且b
应该被选择为我们认为处于相似性边缘的 Jaccard 系数的值。请注意,Jaccard-coefficient 返回一个介于 0 和 1 之间的值,其中 0 表示最不相似,1 表示最相似。
多因素指标
我们现在准备在这些指标的基础上构建我们的多因素指标。首先,让我们简单讨论一下这种方法的利弊。这种模块化设计使得添加新的因素或删除现有的因素变得容易。它还使得调整单个因子值的相似性对整体度量的相对影响变得容易。也就是说,它不能自然地模拟因素之间的相互作用。为了对这些进行建模,需要明确地添加特定的交互术语。
我们的多因素指标是
**S(C1,C2) = w_zip*S_zip(C1.zip,C2.zip) + w_industry*S_industry(C1.industry,C2.industry) + w_company_size*S(C1.company_size_bin_rank,C2.company_size_bin_rank)**
因素权重让我们可以控制整体指标中单个因素相似性的相对影响。
专门化:现在我们有了这个指标,我们可以通过简单地将某些因素权重设置为 0 来构建它的各种专门化。因此,我们可以实现所有的单因素版本——基于 zip 的相似性、基于行业的相似性、基于公司规模的相似性——以及所有的双因素版本。有了这些专业化,我们可以更加灵活地构建细分市场。实际上,我们可以根据一个因素、两个因素或所有三个因素进行分组。请注意,“分组依据”实际上意味着“模糊分组依据”。
聚类相似性矩阵:我们想要提出的关键点是,利用我们定义的成对相似性度量的自然方法是计算所有成对公司之间的成对相似性,并在相似性矩阵中捕捉这些相似性。(除非公司的数量太多,以至于计算两两相似度会太慢。我们将假设情况并非如此。)
然后,聚类算法可以直接在这个矩阵上工作。这就分开了顾虑。聚类算法只对相似矩阵进行操作,而不关心它是如何产生的。相似性度量关注量化实际的相似性,而不关心对象将如何在下游聚集。
一个具体的算法——图划分:我们来看一个相似矩阵上的具体聚类算法。我们将把矩阵表示成图形。图中的节点是对象,在我们的例子中是公司。如果对应的对象足够相似,则两个节点由一条边连接。边上的权重捕获实际的相似性值。
该算法的高级思想是将图划分成由稀疏交叉区域连接的稠密子图。密集的子图将表示紧密的聚类。稀疏的交叉区域将确保这些簇被很好地分开。
我们来提炼一下这个描述。我们首先需要一些关键概念。
子图密度:我们将一个子图在 k 个节点上的密度定义为连接这组节点对的边上的权重之和除以 k ( k -1)/2。这假设边上的权重介于 0 和 1 之间。非边可以被视为权重为 0 的边。在这种表示下,子图的密度是其中一条边的平均权重。
节点权重:我们将节点的权重定义为接触该节点的边的权重之和。我们将节点集上的节点的权重定义为接触该节点的边的权重之和,该节点的另一个端点在该集中。例如,节点 b 、 c 和 d 上的节点 a 的权重是边 a - b 、 a - c 和 a - d 的权重之和。
实际算法:接下来,我们描述一个简单的启发式算法,它可以找到一个好的聚类。
**find_one_good_cluster(G)
C = {v}, where v is a highest-weight node in G
Repeat
Find a node v in G-C whose weight on C is
sufficiently high.
If no such v exists
Close C.
remaining_clusters = find_one_good_cluster(G-C)
Return remaining_clusters + {C}
Else
Add v to C
forever**
“足够高”测试使算法偏向于寻找紧密的聚类。(不保证紧密性,因为紧密性检查仅在节点添加到集群时进行。)该算法并不明确倾向于跨簇稀疏。这是隐性的。只要一个集群之外的节点对其中的节点具有足够高的累积权重,该集群就会保持增长。因此,当集群停止增长时,其外部的所有节点相对于其中的节点具有较低的累积权重。这有利于稀疏的跨簇区域。
总结
在这篇文章中,我们看到了聚类作为一种数据科学方法是如何作为一个用例很好地适合市场细分的。我们已经研究了 B2B 和 B2C 细分的因素。我们已经研究了当一个因子有干净的值时出现的问题;当一个因子具有非结构化值的长尾时;当一个因子有一小组序数值时。
我们已经讨论了平面聚类、软聚类和层次聚类。我们已经看到了领域知识如何指导我们使用什么算法,甚至让我们远离标准算法。(我指的是利用行业名称中隐含的语言结构,将行业名称聚集成一个行业层次结构。)
我们已经提出了具体的衡量标准,用于量化我们在 B2B 环境中遇到的某些因素的相似性。然后,我们提出了一个多因素相似性度量,并描述了如何在多因素聚类中使用它(考虑通过多个因素的组合来细分市场)。
基于 K 均值聚类的客户细分
为了更好地了解你的客户
Photo by Roman Arkhipov on Unsplash
被困在付费墙后面?点击这里阅读完整故事与我的朋友链接!
客户细分是将市场细分为具有相似特征的离散客户群。客户细分是识别未满足客户需求的有力手段。利用上述数据,公司可以通过开发独特的吸引人的产品和服务来超越竞争对手。
企业对其客户群进行细分的最常见方式有:
- 人口统计信息,如性别、年龄、家庭和婚姻状况、收入、教育和职业。
- 地理信息,因公司范围不同而不同。对于本地化的企业,这些信息可能与特定的城镇或县有关。对于较大的公司,这可能意味着客户的城市,州,甚至居住国。
- 心理图形,如社会阶层、生活方式、性格特征。
- 行为数据,如支出和消费习惯、产品/服务使用情况、期望收益等。
客户细分的优势
- 确定合适的产品定价。
- 开展定制营销活动。
- 设计一个最优的分配策略。
- 选择要部署的特定产品功能。
- 优先考虑新产品开发工作。
挑战
你欠一个超市购物中心,通过会员卡,你有一些关于你的客户的基本数据,如客户 ID,年龄,性别,年收入和消费分数。你需要了解客户,比如谁是目标客户,这样营销团队才能获得这种感觉,并据此制定策略。
k 均值聚类算法
- 指定集群数量 K 。
- 通过首先混洗数据集,然后为质心随机选择 K 个数据点来初始化质心,而无需替换。
- 继续迭代,直到质心没有变化。也就是说,数据点到聚类的分配没有改变。
K Means Clustering where K=3
数据
该项目是在 Kaggle 上举办的商城客户细分数据竞赛的一部分。
数据集可以从 kaggle 网站下载,该网站可以在这里找到。
环境和工具
- sci kit-学习
- 海生的
- numpy
- 熊猫
- matplotlib
代码在哪里?
事不宜迟,让我们从代码开始吧。github 上的完整项目可以在这里找到。
我从加载所有的库和依赖项开始。数据集中的列是客户 id、性别、年龄、收入和支出分数。
我删除了 id 列,因为它似乎与上下文无关。我还画出了顾客的年龄频率。
接下来,我制作了一个支出分数和年收入的方框图,以更好地形象化分布范围。支出分数的范围明显大于年收入范围。
我制作了一个条形图来检查数据集中男性和女性人口的分布。女性人口明显多于男性。
接下来,我绘制了一个条形图,以检查每个年龄组中客户数量的分布。很明显,26-35 岁年龄组的人数超过了所有其他年龄组。
我继续制作一个柱状图,根据顾客的消费分数来可视化顾客的数量。大多数顾客的消费得分在 41-60 之间。
我还制作了一个柱状图,根据客户的年收入来可视化客户的数量。大多数顾客的年收入在 60000 到 90000 英镑之间。
接下来,我绘制了聚类平方和(WCSS)与聚类数量(K 值)的关系图,以计算出最佳聚类数量值。WCSS 测量观测值到其聚类质心的距离总和,由以下公式给出。
其中易为观察的质心 Xi 。主要目标是最大化聚类数量,在有限的情况下,每个数据点成为其自己的聚类质心。
肘法
计算不同 k 值的类内误差平方和(WSS ),并选择 WSS 最先开始减小的 k。在 WSS-对 k 的曲线图中,这可以看做一个肘部。
这些步骤可以总结为以下步骤:
- 通过在 1 到 10 个聚类之间改变 K 值来计算不同 K 值的 K 均值聚类。
- 对于每个 K,计算总的类内平方和(WCSS)。
- 绘制 WCSS 相对于簇数 k 的曲线
- 图中弯曲(拐点)的位置通常被认为是适当聚类数的指示。
使用肘形法发现最佳 K 值为 5。
最后,我绘制了一个 3D 图,直观地显示了顾客的消费分数和年收入。数据点分为 5 类,用不同的颜色表示,如 3D 图所示。
结果
结论
k 表示聚类是最流行的聚类算法之一,并且通常是从业者在解决聚类任务时应用的第一件事,以了解数据集的结构。K 均值的目标是将数据点分组到不同的非重叠子组中。K 均值聚类的一个主要应用是对客户进行细分,以更好地了解他们,从而增加公司的收入。
参考资料/进一步阅读
背景在当今竞争激烈的世界中,了解客户行为并根据以下因素对客户进行分类至关重要…
towardsdatascience.com](/clustering-algorithms-for-customer-segmentation-af637c6830ac) [## 您需要的最全面的 K-Means 聚类指南
概述 K-Means 聚类是数据科学中一种简单而强大的算法,现实世界中有太多的…
www.analyticsvidhya.com](https://www.analyticsvidhya.com/blog/2019/08/comprehensive-guide-k-means-clustering/) [## 机器学习方法:K 均值聚类算法
2015 年 7 月 21 日撰文:EduPristine k-Means 聚类(又名分割)是最常见的机器学习之一…
www.edupristine.com](https://www.edupristine.com/blog/beyond-k-means)
在你走之前
相应的源代码可以在这里找到。
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/abhinavsagar/Kaggle-Solutions/blob/master/customer_segmentation.ipynb)
联系人
如果你想了解我最新的文章和项目,请关注我的媒体。以下是我的一些联系人详细信息:
快乐阅读,快乐学习,快乐编码。
Python 和 Spark 中的 RFM 客户细分
RFM 细分是根据三个标准(组成 RFM 的首字母缩略词)将客户划分成平等群体的好方法:
- 新近度。客户与公司的最后一次活动或交易已经过去了多长时间?
- 频率。在特定时期内,客户与公司的交易频率如何?
- 货币。在特定时期内,客户在该品牌上花费的金额。
在本文中,我们将看到如何将客户和 Apache Spark 中的代码划分成不同的部分。后者不再需要任何介绍;它已经成为这十年中事实上的大数据平台,我们将利用它的一些基本而深刻的功能。
我们将使用 Python 作为与 Spark 交互的编程语言。Python 正成为数据分析领域的通用语言,因此用这种语言进行 RFM 客户细分是有意义的。
让我们开始吧。
1 设置环境
我们将使用 Jupyter 笔记本应用程序在 Spark 中执行 RFM 细分。为了能够在我们的笔记本中使用 Spark,我们需要导入findspark
包以及pyspark
模块及其sql
模块的组件:
随后,我们创建一个SparkSession
来与 Spark 交互。SparkSession 是 Spark 应用程序的统一入口点。
为了确保 spark 连接正常,我们调用 Spark 变量并检查输出:
现在我们应该都准备好了,可以开始我们的 RFM 分析之旅了。
2 关于数据集
在本文中,我们将使用一个电子商务数据集,该数据集包含 2010 年至 2011 年英国注册的无店铺在线零售商的交易,由 UCI 机器学习资源库免费提供。你可以在这里下载数据。
数据集包含我们分析所需的以下相关列:
- 发票号。每次销售的唯一 id。
- 股票代码。每个产品的唯一 id。
- 数量。购买的单位数量。
- 活体内。销售日期。
- 单价。产品的零售价格。
- 客户 ID 。每个客户的唯一 id。
3 数据预处理
数据预处理是数据科学中最重要也是最繁琐的任务之一。大多数数据科学家只花 20%的时间进行实际数据分析,80%的时间用于查找、清理和重组。
我们首先通过使用 Spark 的read
方法将 CSV 文件加载到 Spark 数据帧中,在这里我们指定文件的格式以及文件是否包含标题:
通过调用 Spark 的printSchema
方法,我们检查 CSV 文件每一列的数据类型:
因为我们没有在列的数据类型上指示 Spark,所以它假设它们都是字符串。但是,有必要更改我们后续分析所需的几个列的数据类型,即:数量、单价、日期。我们还必须添加两个新的计算列:
- 总计,等于单价*数量。需要计算每个客户的货币价值。
- RecencyDays ,这是一个表示 2011 年 12 月 31 日(我们选择的参考日)和发票日期之间天数差异的指标。
Spark 的一个重要特性是能够向现有数据框架添加新的计算列。这是通过它的withColumn
方法完成的。这种方法的一个特例是当我们想要改变当前的列时,这是通过将列名放在方法的第一个参数中来实现的。
4 RFM 表的创建
既然我们已经预处理了数据,RFM 表的创建就非常简单了。对于每个客户,我们需要衡量以下指标:
- 新近。我们将把在上一步中计算的最小最近天数视为最近。这是有意义的,因为最近最少天数将为我们提供自客户上次购买以来已经过去的天数。
- 频率。它被计算为顾客购买的次数。
- 货币。它是每个客户所有购买的总价值。
新的 rfm_table 数据帧包含每个客户的 rfm 指标。
我们还没有准备好执行我们的 RFM 分析。在我们深入研究之前,我们应该首先为每个 RFM 指标计算一些有意义的分数。我们为每位客户分配 1 到 4 的分数,其中 1 表示最低分,4 表示最高分。显然,所有指标都达到 4s 的客户被认为是最佳客户。
因为我们从 1 到 4 分配分数,我们可以根据统计四分位数划分 RFM 值。PySpark 中的以下代码执行必要的操作来计算四分位数,并在 RFM 表中创建三个与 RFM 分数相对应的新列。最后,我们将 RFM 分数串联成一列,以便对客户的类型有一个即时的了解。
现在让我们来看看 RFM 表的最终版本:
5 RFM 分析
我们现在可以很容易地查询我们的 RFM 表中的相关业务问题。想知道谁是最好的客户吗?简单的东西:
我们可以根据 RFM 分数对客户进行细分,然后找出每个细分市场中的客户数量。为了可视化这个度量,我们需要将 Spark 数据帧转换成 Pandas 数据帧。这可以通过使用 Spark 的toPandas
方法轻松完成。最后,我们将通过使用 Plotly 在条形图中可视化每个细分市场的客户数量,这是一个交互式数据可视化库。
令人惊讶的是,最好的客户构成了最大的细分市场:),但具有讽刺意味的是,最差的客户(所有得分为 1 的客户)是第二大细分市场:(。
除了最佳和最差客户,我们还可以定义以下重要的客户群:
- 高消费新客户 —这个群体由 4-1-3 和 4-1-4 的客户组成。这些客户只交易过一次,但最近他们花了很多钱。
- 消费最低的活跃忠诚客户 —这一群体由细分市场 3–4–1 和 4–4–1 中的客户组成(他们最近交易过,经常交易,但消费最少)。
- 流失的最佳客户 —这个细分市场由第 1-3-3、1-4-3、1-3-4 和 1-4-4 组的客户组成(他们交易频繁,花费很多,但已经过了很长时间*)。*
决定瞄准哪些客户群以及如何最好地与他们沟通是营销艺术的切入点。
结论
您已到达这篇文章的结尾。在用 PySpark 编写代码的过程中,我们涉及了很多关于客户细分的细节。请到我的 Github 库访问这篇文章的笔记本代码。
感谢您的阅读。
由于这是我第一篇关于媒体的文章,我将非常感谢任何有用的反馈。
基于应用行为分析(逻辑回归)的客户订阅分析和预测
使用 scikit learn library 的逻辑回归算法对客户是否会注册付费订阅进行分类。
我们将根据网站或应用程序上的客户行为进行客户流失分析。我们会对什么样的客户有可能注册一个网站的付费订阅进行分类。在分析
并对数据集进行分类后,我们将能够对有可能注册
付费订阅计划的客户进行针对性的
营销或推荐。
对于这个问题,我们将使用 scikit-learn 库中的逻辑回归算法。这个问题的数据集已经用已经完成的必要特征工程进行了优化。如果你想知道我是如何清理数据以及特征工程是如何完成的,请继续关注,因为我将很快写另一篇关于这个特定数据集的数据清理过程的帖子。
让我们通过首先导入必要的库和数据集来开始这个过程。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from dateutil import parser
%matplotlib qt5
这里%matplotlib qt5 使可视化在一个新窗口中弹出。如果您希望可视化显示在同一个笔记本或控制台中,那么您可以使用%matplotlib inline
dataset = pd.read_csv(“appdata10.csv”)
让我们来看看数据集:
a look at the datset, some of the columns are not seen here cause i couldn’t fit all considering the size
现在,让我们将数据集分为要素和目标变量。目标列名为“已注册”。
X = dataset.drop(columns = 'enrolled')
y = dataset['enrolled']
现在,让我们将数据集分为训练集和测试集。我们需要这样做,以便我们只能在模型训练中使用训练集数据。在有监督的机器学习中,将数据集分成训练集和测试集是很重要的,因为它可以防止过拟合。
为此,我将使用 sklearn 的 train_test_split()函数。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 4)
你可以使用任何随机状态,它只是确保不同环境下的结果是相同的。
现在让我们删除用户列,因为我们不需要它,它也不是一个必需的特性。但是我们需要它来识别用户是否订阅。为此,我们将把用户列保存为标识符。
train_identifier = X_train['user']
X_train = X_train.drop(columns='user')test_identifier = X_test['user']
X_test = X_test.drop(columns='user')
下一步是进行特征缩放。进行要素缩放是为了归一化数据集中不同要素的值。特征缩放帮助我们更快地实现梯度下降。如果数据更加分散,这意味着如果它具有更高的标准偏差,那么与通过要素缩放来缩放数据的情况相比,计算梯度下降将花费相对更多的时间。所以我们把所有的值都放在一定的范围内。这将平等地对待所有列。在上面的数据集中,我们可以看到年龄、星期几、注册的值不在相似的范围内。
为此,我们可以使用 sklearn 的 StandardScaler 函数。
from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train2 = pd.DataFrame(sc_X.fit_transform(X_train))
X_test2 = pd.DataFrame(sc_X.fit_transform(X_test))
X_train2.columns = X_train.columns.values
X_test2.columns = X_test.columns.values
X_train.index = X_train.index.values
X_test.index = X_test.index.values
X_train = X_train2
X_test = X_test2
现在让我们进入激动人心的部分;训练数据集。为此,我们将使用来自 sklearn 库的 LogisticRegression。
逻辑回归用于分类问题。当因变量为二分变量(二元变量)时,进行回归分析是合适的。像所有回归分析一样,逻辑回归是一种预测分析。逻辑回归用于描述数据,并解释一个因变量与一个或多个名义变量、序数变量、区间变量或比率水平自变量之间的关系。
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(random_state=0, penalty=’l1')
logreg.fit(X_train, y_train)
在这里,我使用惩罚参数来防止一个特征与这个特定数据集中的目标变量高度相关,特别是对于最后一个屏幕变量。
既然模型已经定型,我们就可以在测试集上进行预测了。
y_pred = logreg.predict(X_test)
完成预测后,我们可以进行模型评估,看看我们的模型表现如何。为此,我们将使用五个评估指标,混淆矩阵、准确度分数、f1 分数、精确度分数和召回分数。所有这些评估指标都可以在 scikit learn 中找到。
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, precision_score, recall_score
cm = confusion_matrix(y_test, y_pred)
accuracy = accuracy_score(y_test, y_pred)accuracy
0.7679
所以我们达到了 79%的准确率。这个模型已经完成了那个精度的下降工作。
现在,让我们绘制混淆矩阵的热图来可视化混淆矩阵。
我们可以看到,正确预测的值比混淆矩阵中的假阳性和假阴性多 3 倍左右。我们可以进一步使用交叉验证来评估模型,并确保我们的模型做了正确的工作。
from sklearn.model_selection import cross_val_score
accuracies = cross_val_score(estimator=logreg, X=X_train, y= y_train, cv=10)
print("Logistic accuracy: %0.3f (+/- %0.3f)(accuracies.mean(),accuracies.std()*2))Output: Logistic accuracy: 0.767 (+/- 0.010)
所以我们可以说,我们的模型正在做正确的工作,并且是一个逻辑回归的下降工作。
我们可以使用不同的参数调整方法进一步优化我们的模型。但是现在我不打算优化这个模型。如果你想了解参数/超参数调整,你可以搜索 GridSearch,随机搜索,向后消除。我将把这些留到下一篇文章。
这是我的第一个媒体帖子,所以请随时给我建议和建设性的批评,还有我做这个分类的方法。谢谢你。
改进默认的 Mac 终端以提高生产率和改进界面
简单的外壳定制技术
Photo by Goran Ivos on Unsplash
如果你像我一样,你从事许多项目,每个项目可能位于不同的目录中,并可能涉及多个分支。最近,我对我的终端进行了定制,使其信息量更大,更易于阅读和使用。
在本文中,我将向您介绍我定制终端的步骤。有很多方法你可以去做,但在这里我提出了一个我发现最简单,最适合我的需要。
在本文中,我们将从这里开始:
Old
到这里:
New
安装 iTerm2
iTerm2 扩展了 MacOS 终端,并添加了许多有用的特性。我喜欢的两个特性包括使用 Shell > Split vertical with Current Profile(水平分割也可以)垂直分割窗格的能力,以及配置该终端的众多选项。你也可以在这里找到的其他功能。
- 前往https://www.iterm2.com/downloads.html。
- 下载位于稳定版本下的版本。它将下载 zip 文件。
- 解压 zip 文件,新文件将显示为 iTerm.app 。只要打开这个应用程序,你就进入了 iTerm2。
Two iTerm2 terminals opened side-by-side
把壳牌改成 ZSH
下一步是将我们的后端从bash
改为zsh
。
- 在 iTerm2 中,键入命令
which zsh
。您应该会看到输出为/bin/zsh
。如果没有显示,你需要使用brew install zsh
下载它。然后输入which zsh
,看看你是否能看到一个新的路径/usr/local/bin/zsh
。 - 接下来,键入命令
sudo vi /etc/shells
,然后输入您的密码。 - 文件将会打开。按键盘上的
i
进入插入模式。在其他外壳后添加线/bin/zsh
和/usr/local/bin/zsh
。按 escape 退出插入模式。完成后,通过键入:wq
保存更改,即写入并退出。我们现在可以切换到 zsh。 - 键入命令
chsh -s $(which zsh)
。外壳现在更改为 zsh。 - 关闭 iTerm2,然后再次打开它。
Adding zsh to the list of shells
Changing the shell to zsh
安装我的天啊
哦,我的 zsh 在 Zsh 上面工作。它为我们提供了一个配置文件~/.zshrc
,并允许我们应用主题来使 iTerm2 更具吸引力和可用性。GitHub 回购可在这里获得。
- 运行命令
sh -c “$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
来安装包。 - 重新打开 iTerm2,它将启用 Oh My Zsh。
Installing Oh My Zsh
现在不会有视觉上的差异,但是我们会有~/.zshrc
文件,在那里我们可以对 iTerm2 进行更新。
使用插件
我将使用三个插件,即 git、语法高亮和建议。
- Git 已经包含在插件列表中,所以我们不会下载它。
- 对于另外两个插件,我们必须将它们克隆到正确的目录中。
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlightinggit clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
Installing plugins
一旦完成,使用命令open ~/.zshrc
打开.zshrc
文件。将两个插件名zsh-syntax-highlighting
和zsh-autosuggestions
添加到括号内,在 git 后各占一行。只要保存文件,我们就可以开始了。
Updated plugins to include the new plugins
重新打开 iTerm2,以便新的更改生效。看看下面的图片,你可以看到文本cd
现在是不同的颜色,在写完文本Personal
后,它建议下一个文件夹Important Work
。插件运行得非常好。
Plugins in action
实施电力线设计
我们需要更新~/.zshrc
文件,以显示类似电力线的设计,同时显示我们所在的分支,以及分支中是否有任何更改。
- 使用
open ~/.zshrc
打开.zshrc
文件。 - 然后把 ZSH 主题改成
agnoster
。
agnoster theme
正如我们所看到的,整个路径是可见的,只要我们在 git 存储库中,就会显示当前分支的名称。例如,在上面的例子中,我在master
分支中。如果有尚未提交的更改,分支名称后会出现一个点。
更新颜色
考虑到文本由于颜色的选择而可读性不强,我决定将颜色改为预设的配色方案。
- 当在活动 iTerm2 窗口中时,转到顶角,在 iTerm2 中选择首选项>配置文件>颜色。
- 在窗口右下角的
Color Presets...
下选择Tango Dark
,颜色将会更新。
Select Tango Dark color preset
Updated colors for the shell
添加介绍信息
为了好玩,我们还可以添加一条消息,每次我们都会打开一个新的 iTerm2 窗口或标签。我会显示我的名字,日期和一个随机的财富。
- 安装
fortune
包,它将帮助我们使用brew install fortune
显示随机财富。 - 使用
open ~/.zshrc
打开.zshrc
。 - 转到最后,键入以下命令,每当我打开 iTerm2 窗口或选项卡时,该命令将显示我的姓名和当前日期。
echo "
|| NAME: KARAN BHANOT
|| DATE: $(date)
||
|| $(fortune)
"
Intro message added
结论
最后,我们完成了。我们已经对我们的终端做了许多修改,现在它为我们提供信息,并使它看起来更悦目。
希望你喜欢这篇文章。请分享你的想法、想法和建议。
通过回访定制您的培训循环
在我的上一篇文章中,我们学习了如何从头开始编写 PyTorch 训练循环。我们从一个混乱的循环版本开始,如下所示:
我们把它变成了一个更简洁的版本,看起来像这样:
在这个过程中,我们学会了如何通过为数据集、数据加载器、优化器等等编写类来将相似的东西打包在一起。在本文中,我们将进行更多的打包,并学习如何定制我们的训练循环,以实现深度学习中的复杂技术。
先说我们的 fit 函数。
数据群发
我们的fit()
函数包括我们的模型、损失函数、优化器和两个数据加载器(训练和验证)。
然而,如果我们仔细想想,训练和验证数据加载器并不是两件独立的事情。他们是我们的 数据 。它们必须是一个东西(或者至少是从它派生出来的)。因此,我们将这两个数据加载器合并成一个名为DataBunch.
的类
如果我们可以将数据中的 类的数量 传递给 DataBunch,那也很方便。这是通过传递c
参数来完成的。我们现在可以创建一个 DataBunch 对象,如下所示。
然后,我们可以使用这个对象通过自动识别输入和输出单元的数量来方便地创建模型。
学习者
下一步,我们要做的是结合我们的模型,优化和损失函数。
很多时候,在进行深度学习的实验时,我们想要测试这些东西的一个 组合 。这涉及到对我们代码的各个部分进行大量的修改。但是,将它们组合成一个对象并仅对该对象进行更改是有意义的。这将有助于我们轻松做出明智的决定。
因此,我们将模型、优化器和损失函数组合成一个名为Learner.
的类
我们现在可以使用learn.model
、learn.opt
和learn.loss_func.
来访问它们。我们在训练循环中进行必要的更改。
需要复试
在这一点上,我们有一个非常整洁的训练循环。然而,这些并不是我们想在训练循环中做的唯一事情。我们可能需要添加一些东西,比如
超参数调度
你如何决定学习速度?如果太慢,你的神经网络将会花很长时间去学习(试着用…
sgugger.github.io](https://sgugger.github.io/how-do-you-find-a-good-learning-rate.html)
或 正规化技术
了解如何使用权重衰减来训练更好的模型
becominghuman.ai](https://becominghuman.ai/this-thing-called-weight-decay-a7cd4bcfccab) [## 神经网络中的正则化
用于正则化神经网络的各种方法的概述
becominghuman.ai](https://becominghuman.ai/regularization-in-neural-networks-3b9687e1a68c)
或者混合精确训练
图像分类是深度学习的 Hello World。对我来说,这个项目是使用胸部检测肺炎…
becominghuman.ai](https://becominghuman.ai/mixed-precision-training-using-fastai-435145d3178b)
或者做更复杂的事情比如甘斯。这些只是我们现在知道的事情。更多的调整不断出现。
为了实现这些调整,我们必须为每个调整编写一个单独的训练循环。当我们想使用它们的组合时,一切都会变得一团糟。
这就是 回调 出现的地方。他们帮助我们有效地实现这些调整。让我们看看怎么做。
了解回访
我们首先创建自己的 回调对象 。在这些对象中,我们决定了在训练的某一点上我们想要做什么。比如 fastai 中有一个回调叫做SaveModelCallback()。该回调检查在每个时期结束时我们是否有更好的模型*,如果有,则 保存模型 。这样,我们不仅得到了最后的参数,而且得到了最好的参数。*
**
同样,我们可以为学习速率调度、数据增强的混合或梯度裁剪编写回调。点击查看 fastai 回调的详细列表。
下一步是实现一个CallbackHandler()
。
这个类有两个主要目的。
- 它给我们一个地方来存储我们所有的回调(cbs)。
- 它允许我们很容易地调用所有单独的回调函数。例如,如果我们有 3 个回调函数在一个纪元结束时做了一些事情,那么
cb.on_epoch_end()
将从每个回调对象中调用on_epoch_end()
方法。
最后一步是将这些回访纳入我们的培训循环。我们使用和以前一样的循环,只是稍加修改。在我们的 fit 函数中,我们确保我们遍历了all_batches().
,并且在所有批次中,我们编写了每个批次要遵循的步骤。
这给了我们很大的灵活性来实现我们想要的东西,而不需要修改原始的训练循环。它让我们在训练期间的不同时间点定义不同的行为,如提前停止或打印各种指标。
最后,我们还需要一个Callback class
,我们的回调对象将从其中继承。这个类有所有可能的行为,所以如果我们的对象没有这些行为,我们的代码不会中断。
如果你不喜欢从超类继承,那么你可以使用if hasattr(cb, 'on_epoch_end')
让它更加灵活。
我将用一个理解回调的非常简单的例子来结束这篇文章。我们从一个做一些计算的函数开始,然后休眠一段时间。这个函数代表了深度学习中的一个时代。
我们可以在计算前后向该函数添加行为,如下所示。
然后,我们可以写一个回调函数来处理这些行为。
这就是本文的全部内容。
如果你想了解更多关于深度学习的知识,你可以看看我下面的深度学习系列。
我所有关于深度学习的文章的系统列表
medium.com](https://medium.com/@dipam44/deep-learning-series-30ad108fbe2b)
参考文献:
[1]https://docs.fast.ai/callbacks.html#SaveModelCallback
[2]https://medium . com/@ Edward easling/implementing-callbacks-in-fast-ai-1c 23 de 25 b 6 EB*