营销的数据科学实用指南-全-
营销的数据科学实用指南(全)
原文:
annas-archive.org/md5/40bb0ccf1a2c14d899522b1d83c83f6c译者:飞龙
前言
数据科学和机器学习在营销中的应用正在不断增长,从小型企业到大型组织都在采用这些技术。通过数据科学,你可以更好地理解过去营销策略成功与失败的驱动因素,深入了解客户的行为和他们与产品的互动方式。借助数据科学,你还可以预测客户行为,制定更具针对性和个性化的营销策略,从而实现更低的获客成本、更高的转化率以及更高的净销售额。通过本书,你将能够应用各种数据科学技术,制定数据驱动的营销策略。
本书作为一本实践指南,旨在帮助你完成从简单到复杂的营销任务。你将使用数据科学来了解推动销售和客户参与的因素。你将使用机器学习预测哪些客户更可能与产品互动,并具有最高的预期生命周期价值。你还将使用机器学习了解不同客户群体的数据,并为每个客户推荐最可能购买的产品。读完本书后,你将对各种数据科学和机器学习技术以及它们如何应用于不同营销目标有深刻的了解。
就个人而言,我本会从像这样的书籍中受益匪浅。当我开始从事数据科学和营销工作时,关于不同数据科学和机器学习技术的理论和细节有着丰富的资源,但关于如何将这些技术和方法特别应用于营销的资源却少之又少。学习这些理论与实际将其应用于现实世界中的营销业务案例是完全不同的。在本书中,我希望能分享我在将数据科学和机器学习应用于不同营销目标过程中,通过大量试错所获得的经验和知识。读完本书后,你将能很好地理解不同营销场景下所使用的技术和方法,了解哪里可以找到更多的资源,并且知道读完本书后该学习什么。
本书将使用 Python 和 R 进行数据科学和机器学习练习。如你所知,Python 和 R 是数据科学家、数据分析师和机器学习工程师最常用的两种编程语言,原因在于它们易于使用、拥有丰富的数据科学和机器学习资源,并且拥有庞大的用户社区。在每一章中,我们将指导你如何使用不同的软件包和库,并讲解如何安装它们,所以在开始本书之前,你不必担心该在计算机上安装什么。
本书适合人群
本书面向营销专业人士、数据科学家与分析师、机器学习工程师和软件工程师,他们具备一定的 Python 和 R 工作经验,并且对机器学习和数据科学有一些基础了解。即使你没有深入了解数据科学和机器学习算法背后的理论,也不用担心!本书的重点是机器学习的实际应用,旨在帮助你快速掌握相关知识,并能将其用于下一步的营销策略。如果你曾经学习过数据科学和机器学习,那么这本书将非常适合你。它将引导你如何将数据科学和机器学习的知识与经验应用于营销中的实际案例。如果你是一个对数据科学充满热情并感兴趣的营销专业人士,那么太好了!这本书将是你理想的选择。你将学到数据科学如何帮助你改进营销策略,以及如何使用预测性机器学习模型来精细调整目标营销。本书将引导你一步步地利用数据科学和机器学习来实现你的营销目标。
本书实际上是为所有热衷于将数据科学和机器学习应用于营销的人设计的。如果你有兴趣构建数据驱动的营销策略,解读数据中的客户行为,预测客户如何反应,以及预测客户会如何回应,那么你来对地方了!
本书涵盖的内容
第一章,数据科学与营销,介绍了数据科学如何应用于营销的基础知识。它将简要介绍常用的数据科学和机器学习技术,以及这些技术在制定更好营销策略时的应用方法。还会介绍如何为即将开展的项目配置 Python 和 R 环境。
第二章,关键绩效指标与可视化,讲解了营销中需要跟踪的一些关键绩效指标(KPIs)。本章讨论了如何使用 Python 和 R 来计算这些 KPIs,以及如何构建这些 KPIs 的可视化。
第三章,营销参与度背后的驱动因素,展示了如何使用回归分析来理解客户参与的驱动因素。本章介绍了如何在 Python 和 R 中拟合线性回归模型,并如何从模型中提取截距和系数。通过回归分析获得的洞察,我们将探讨如何利用这些信息潜在地改善营销策略,以提高参与度。
第四章,从参与到转化,讨论了如何使用不同的机器学习模型来理解什么驱动了转化。本章介绍了如何在 Python 和 R 中构建决策树模型,以及如何解释结果并提取转化背后的驱动因素。
第五章,产品分析,带你完成探索性的产品分析。本章引导你通过在 Python 和 R 中使用各种数据聚合和分析方法,深入了解产品的趋势和模式。
第六章,推荐合适的产品,介绍了如何提高产品的可见性,并推荐个别客户最有可能购买的产品。它讨论了如何在 Python 和 R 中使用协同过滤算法来构建推荐模型。然后,介绍了如何将这些推荐应用于营销活动。
第七章,客户行为的探索性分析,进一步深入数据分析。本章讨论了分析客户行为和与产品互动的各种指标。通过使用 Python 和 R,本章扩展了你的知识,涉及到数据可视化和不同的图表技术。
第八章,预测营销参与的可能性,讨论了如何构建机器学习模型来预测客户参与的可能性。本章介绍了如何使用 Python 和 R 训练机器学习算法。然后,讨论了如何评估模型的性能,以及如何将这些模型应用于实现更精准的目标营销。
第九章,客户生命周期价值,介绍了如何获取个别客户的生命周期价值。本章讨论了如何在 Python 和 R 中构建回归模型并评估它们。还会介绍如何利用计算出的客户生命周期价值来制定更好的营销策略。
第十章,数据驱动的客户细分,深入探讨了如何使用数据驱动的方法进行客户细分。本章介绍了聚类算法,使用 Python 和 R 从数据中构建不同的客户群体。
第十一章,保持客户,讨论了如何预测客户流失的可能性,并重点介绍了如何在 Python 和 R 中构建分类模型以及如何评估其性能。本章将讲解如何在 Python 和 R 中使用keras库构建人工神经网络(ANN)模型,这是深度学习的核心。
第十二章,更好的营销策略中的 A/B 测试,介绍了一种基于数据驱动的方法,帮助做出更好的营销策略决策。本章讨论了 A/B 测试的概念以及如何使用 Python 和 R 进行实施和评估。接着讨论了 A/B 测试在实际营销策略中的应用和益处。
第十三章,下一步是什么?,总结了本书中讨论的内容,并讲解了在营销中使用数据科学的实际挑战。本章还介绍了其他数据科学和机器学习的包与库,以及可以应用于未来数据科学项目的其他机器学习算法。
为了最大化地利用本书
为了从本书中获得最大的收获,我强烈建议您彻底完成每一章中的编程练习。每个练习旨在为更高级的练习打下坚实的基础,因此跟随每一个步骤是至关重要的。我还建议您勇于尝试。每一章中讨论的不同技术和方法可以与其他章节的技术结合使用。某一章中的技术并非仅限于该章使用,您可以将一章中学到的技术和方法应用到其他章节中。因此,建议您在完成本书后,从头再阅读一遍例子,并尝试将不同章节中学到的技术混合使用,这将对您有益。
下载示例代码文件
您可以通过在www.packt.com注册您的账户,下载本书的示例代码文件。如果您是在其他地方购买的此书,您可以访问www.packt.com/support并注册,代码文件会直接发送到您的邮箱。
您可以按照以下步骤下载代码文件:
-
登录或注册 www.packt.com。
-
选择“支持”标签。
-
点击“代码下载与勘误”。
-
在搜索框中输入书名,并按照屏幕上的说明操作。
文件下载完成后,请确保使用以下最新版本的解压缩工具进行解压:
-
Windows 版的 WinRAR/7-Zip
-
Mac 版的 Zipeg/iZip/UnRarX
-
Linux 版的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,地址为github.com/PacktPublishing/Hands-On-Data-Science-for-Marketing。如果代码有更新,将会在现有的 GitHub 仓库中进行更新。
我们还提供了其他代码包,来自我们丰富的书籍和视频目录,您可以在github.com/PacktPublishing/查看。快来看看吧!
下载彩色图像
我们还提供了一个 PDF 文件,包含本书中使用的截图/图表的彩色图像。你可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789346343_ColorImages.pdf。
使用的约定
本书中使用了许多文本约定。
CodeInText:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件挂载为系统中的另一个磁盘。”
代码块设置如下:
# total number of conversions
df.conversion.sum()
# total number of clients in the data (= number of rows in the data)
df.shape[0]
当我们希望特别指出代码块中的某部分时,相关行或项目会用粗体显示:
# total number of conversions
df.conversion.sum()
# total number of clients in the data (= number of rows in the data)
df.shape[0]
所有命令行输入或输出如下所示:
$ mkdir css
$ cd css
粗体:表示新术语、重要词汇或你在屏幕上看到的词汇。例如,菜单或对话框中的词汇在文本中会以这样的形式出现。示例如下:“从管理面板中选择‘系统信息’”。
警告或重要提示如下所示。
提示和技巧如下所示。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果你对本书的任何部分有疑问,请在邮件主题中注明书名,并通过customercare@packtpub.com与我们联系。
勘误:尽管我们已经尽力确保内容的准确性,但难免会有错误。如果你在本书中发现错误,我们将非常感激你能向我们报告。请访问www.packt.com/submit-errata,选择你的书籍,点击“勘误提交表单”链接,并填写详细信息。
盗版:如果你在互联网上发现任何我们作品的非法副本,无论以何种形式出现,我们将非常感激你能提供相关位置地址或网站名称。请通过copyright@packt.com联系我们,并提供相关材料的链接。
如果你有兴趣成为作者:如果你在某个领域具有专业知识,并且有兴趣撰写或参与编写一本书,请访问authors.packtpub.com。
评论
请留下评论。在你阅读并使用本书后,为什么不在你购买本书的网站上留下评论呢?潜在读者可以参考并使用你的公正意见来做出购买决定,我们 Packt 也能了解你对我们产品的看法,而我们的作者也可以看到你对其书籍的反馈。谢谢!
想了解更多关于 Packt 的信息,请访问packt.com。
第一部分:介绍与环境设置
本节将向你介绍市场营销中的数据科学,并为即将开展的项目设置 Python 和 R 环境。
本节包含以下章节:
- 第一章,数据科学与市场营销
第一章:数据科学与市场营销
欢迎来到《营销数据科学实战》的第一章!如你所知,数据科学在营销行业中的重要性和应用在过去几年中显著上升。然而,市场营销数据科学仍然是一个相对较新的领域,现有的教育和参考资料远远落后于其发展势头。然而,每年收集到的数据量呈指数级增长,这为从数据中学习并获得洞察力提供了更多机会。
随着数据量的不断增长以及数据科学在市场营销中的应用,我们可以轻松找到数据科学在营销工作中应用的实例。公司开始利用数据科学更好地理解客户行为,并根据客户的活动模式识别不同的客户群体。许多组织还使用机器学习来预测未来的客户行为,例如客户可能购买哪些商品、可能访问哪些网站,以及哪些客户可能流失。数据科学在市场营销中的应用案例几乎无穷无尽,任何规模的公司都可以通过运用数据科学和机器学习来促进其市场营销工作。在本章节的简短介绍之后,我们将学习如何将数据科学和机器学习应用于具体的市场营销任务。
在本章节中,我们将涵盖以下内容:
-
市场营销趋势
-
数据科学在市场营销中的应用
-
设置 Python 环境
-
设置 R 环境
技术要求
你需要安装 Python 和 R 才能运行本书中的大多数代码,安装代码可以在以下链接找到:github.com/PacktPublishing/Hands-On-Data-Science-for-Marketing/tree/master/Chapter01。
市场营销趋势
随着每年数据量的指数级增长以及获取这些宝贵数据集的便利性增加,数据科学和机器学习已成为市场营销中不可或缺的一部分。数据科学在市场营销中的应用从构建有价值的报告和仪表板,到利用复杂的机器学习算法预测客户行为或通过产品和内容与客户互动。近年来,市场营销趋势趋向于更加数据驱动的目标营销。我们将在以下内容中讨论一些我们在营销行业中看到的趋势:
- 数字营销的重要性日益提升:随着人们在线时间的增加,数字营销的重视程度和效果也随着时间不断上升。许多营销活动现在都发生在数字渠道上,如搜索引擎、社交网络、电子邮件和网站。例如,Google Ads 帮助你的品牌通过其搜索引擎、Gmail 或 YouTube 接触到更多潜在客户。你可以轻松自定义目标受众,选择你希望广告展示的对象。Facebook 和 Instagram 是两个知名的社交网络,你可以在这些平台上发布广告,以触及目标客户。在互联网时代,这些营销渠道比传统的营销渠道(如电视广告)更具成本效益。以下是 Google 提供的不同数字营销渠道的示例 (
ads.google.com/start/how-it-works/?subid=us-en-ha-g-aw-c-dr_df_1-b_ex_pl!o2~-1072012490-284305340539-kwd-94527731):

-
市场分析:市场分析是一种监控和分析营销效果的方法。它不仅帮助你了解通过营销获得了多少销售或曝光,还可以帮助你深入了解更多个体层面的模式和趋势。在电子商务企业中,你可以通过市场分析分析和可视化不同类型和细分的客户,以及哪种类型的客户为你的业务带来最多的收入。在媒体行业,通过市场分析,你可以分析哪些内容最能吸引用户,以及关键词搜索的趋势是什么。市场分析还帮助你了解营销活动的成本效益。通过查看投资回报率(ROI),你可以进一步优化未来的营销活动。随着市场分析的应用和使用日益增加,现如今很容易找到各种市场分析软件产品。
-
个性化和目标营销:随着数据科学和机器学习在营销中应用的增加,另一个营销趋势是个体层面的目标营销。各种不同规模的组织利用机器学习算法从用户历史数据中学习,并对其用户基础的更小和更特定的子群体应用不同和专业的营销策略,从而降低每次获取成本,提高投资回报率。在零售业务中,许多公司实施人工智能和机器学习来预测哪些客户更有可能购买以及他们将从他们的店铺购买哪些商品。利用这些预测,他们定制每位客户的营销信息。许多媒体企业也利用人工智能和机器学习来提升个体用户的参与度,以增长其用户基础。由于这些定制和目标营销带来了更高的投资回报率,许多 SaaS 公司,如 Sailthru 和 Oracle,提供了个性化营销的平台。Sailthru 最近发布了一份《零售个性化指数》报告,分析了各种零售公司如何在不同营销渠道中使用个性化营销。在这份报告中,我们可以发现零售公司,如 Sephora、JustFab 和 Walmart,在其网站、电子邮件和其他营销渠道中大量使用个性化营销。您可以在以下链接找到这份报告:
www.sailthru.com/personalization-index/sailthru100/。
在营销方面的总体趋势已经向更加数据驱动和量化方法发展。各大小公司都在越来越多地投资于营销分析和技术。根据 2018 年 2 月的 CMO 调查,过去 5 年中对营销分析的依赖从 30%增加到了 42%。B2C 公司对营销分析的依赖增加了 55%。此外,过去 5 年中使用量化工具来展示营销影响的公司数量增加了 28%。最后,CMO 调查表明,预计在接下来的 3 年内利用人工智能和机器学习的公司比例将增加到 39%。您可以在以下链接找到更多有关这份 2018 年 2 月 CMO 调查报告的详细信息:www.forbes.com/sites/christinemoorman/2018/02/27/marketing-analytics-and-marketing-technology-trends-to-watch/#4ec8a8431b8a。
数据科学在营销中的应用
我们已经讨论了营销趋势,以及趋势如何朝着更多数据驱动和量化营销的方向发展,通常使用数据科学和机器学习。在营销行业应用数据科学和机器学习有多种方法,讨论数据科学和机器学习的典型任务和用法将对我们有益。
在本节中,我们将涵盖机器学习的基础知识、不同类型的学习算法以及典型的数据科学工作流程和流程。
描述性分析与解释性分析与预测性分析的比较
当我们在接下来的章节中进行练习和项目时,我们将主要进行本书中的三种不同类型的分析:描述性、解释性和预测性分析。
-
描述性分析:这种分析旨在更好地理解和描述给定的数据集。分析的目的是通过数量化和统计总结数据所包含的信息。例如,如果你对用户购买历史数据进行描述性分析,你将回答诸如“什么是畅销商品?”、“过去一年的月销量如何?”、“售出商品的平均价格是多少?”在本书中,每当我们介绍新数据集时,我们都将进行描述性分析。特别是在第二章,“关键绩效指标和可视化”,我们将更详细地讨论如何使用描述性分析来分析和计算关键摘要统计数据,并可视化分析结果。
-
解释性分析:当描述性分析的目的是从数据中回答什么和如何时,解释性分析则是利用数据来回答为什么。这种类型的分析通常在你有一个特定问题需要回答时进行。例如,对于电子商务企业,如果你想分析是什么促使用户进行购买,你会进行解释性分析,而不是描述性分析。我们将在第三章中详细讨论这种类型的分析及其示例,“驱动市场参与背后”;和第四章,“从参与到转化”,在这些章节中,我们将使用解释性分析来回答诸如“是什么促使用户更多参与我们的营销活动?”和“是什么让用户从我们的零售店购买商品?”等问题。
-
预测分析: 当你希望预测某个特定的未来事件时,进行此分析。此分析的目的是构建机器学习模型,从历史数据中学习并对未来将要发生的事件进行预测。类似于之前的电子商务和购买历史数据,你可以通过这种分析回答的问题之一可能是,哪位用户最有可能在接下来的七天内进行购买? 通常,为了进行预测分析,你需要首先进行描述性和解释性分析,以更好地理解数据,并生成关于该项目使用哪种类型的学习算法和方法的想法。我们将在第六章 推荐合适的产品、第八章 预测营销参与的可能性、以及第十一章 保持客户中更详细地讨论预测分析及其在营销中的应用。
学习算法的类型
现在,让我们讨论更多关于机器学习和机器学习算法的内容。广义上说,机器学习算法分为三种类型:监督学习、无监督学习和强化学习。我们首先来了解这三种不同类型的机器学习算法是如何相互区分的:
-
监督学习算法: 当预测目标或结果已知时,使用这些算法。例如,如果我们想使用机器学习预测未来几天谁会进行购买,那么我们将使用监督学习算法。在这里,预测目标或结果是此人在给定时间窗口内是否进行了购买。基于历史购买数据,我们需要构建特征来描述每个数据点,如用户的年龄、地址、最后一次购买日期等,然后监督学习算法将从这些数据中学习如何将这些特征映射到预测目标或结果。我们将在第三章 营销参与驱动因素、第四章 从参与到转化、第八章 预测营销参与的可能性,以及最后的第十一章 保持客户中探讨如何在营销中使用这些算法。
-
无监督学习算法:与监督学习算法不同,无监督学习算法在我们没有特定预测目标或结果时使用。这类机器学习算法常用于聚类和推荐系统。例如,你可以使用无监督学习算法将客户群体根据行为划分为不同的子组或细分市场。在这种情况下,我们没有具体的目标或结果想要预测,我们只是将相似的客户分到不同的细分群体中。在第六章《推荐合适的产品》和第十章《数据驱动的客户细分》中,我们将探讨如何在市场营销中使用无监督学习算法。
-
强化学习算法:这些算法在我们希望模型在没有先验知识或经验的情况下不断学习和自我训练时使用。在强化学习的情况下,模型通过大量的试错来学习如何进行预测。一个应用强化学习的市场营销例子是,当你有多种营销策略想要测试并选择效果最好的策略时。此时,你可以运行一个强化学习算法,让它每次随机选择一个策略,当出现积极结果时给予奖励。经过多次的试错迭代后,强化学习模型将学会根据每种营销策略所获得的总奖励来选择最佳策略。
数据科学工作流程
现在我们已经涵盖了机器学习算法的基础知识和不同类型,接下来我们讨论数据科学的工作流程。一个典型的工作流程如下所示:
-
问题定义:通常,任何数据科学和机器学习项目都从问题定义开始。在第一步中,你需要定义你希望通过数据科学解决的问题、项目的范围以及解决该问题的方法。当你思考解决问题的方法时,你需要头脑风暴,思考之前讨论过的分析类型(描述性分析、解释性分析、预测性分析)和学习算法类型(监督学习、无监督学习、强化学习)中哪些适合解决给定的问题。
-
数据收集:一旦你清楚地定义了项目的目标,就可以进入数据收集阶段。这是你收集所有需要的数据以继续进行数据科学项目的阶段。你可能需要从第三方供应商购买数据、从网上抓取和提取数据,或者使用公开的数据。在某些情况下,你还需要从内部系统中收集数据。根据不同的情况,数据收集阶段可能很简单,也可能很繁琐。
-
数据准备:当你从数据收集阶段收集到所有需要的数据后,接下来的步骤就是数据准备。这个步骤的目标是将数据转化并为未来的步骤做准备。如果数据源的格式不同,那么你需要转换并统一数据。如果数据没有特定的结构,那么你需要将数据结构化,通常是以表格格式,以便于你能够轻松进行不同的分析并构建机器学习模型。
-
数据分析:完成数据准备步骤后,你就需要开始查看数据。在数据分析阶段,通常会进行描述性分析,计算一些描述性统计数据,并构建可视化图表以更好地理解数据。在这一阶段,你很可能能够发现一些可识别的模式,并从数据中获得一些洞察。你也可能会发现数据中的异常情况,例如缺失值、损坏的数据或重复记录。
-
特征工程:特征工程是数据科学和机器学习中最重要的部分,因为它直接影响预测模型的表现。特征工程需要专业知识和良好的领域知识,因为它要求你将原始数据转化为更加信息丰富的数据,以便算法从中学习。特征工程的一个好例子是将文本数据转换为数值数据。由于机器学习算法只能从数值数据中学习,因此你需要提出一个想法和策略,将文本数据转化为数值数据。随着本书的进展以及我们构建机器学习模型的过程中,我们将讨论并实验各种特征工程技术。
-
模型构建:一旦完成特征工程步骤,你就可以开始训练和测试机器学习模型了。在这一步,你可以尝试不同的学习算法,找出最适合你使用案例的算法。在这一步,值得注意的一点是验证指标。拥有一个好的模型性能衡量标准很重要,因为机器学习算法会尝试在给定的性能度量上进行优化。随着我们在接下来的章节中开始构建机器学习模型,我们将详细讨论根据我们所处理问题的类型使用哪些指标。
以下图表展示了典型数据科学项目的整体工作流程:

从这个图表中可以看出,数据科学工作通常不是一次性完成的。当你发现模型表现不佳,或者注意到可以改进输入数据的质量时,你可能需要重复数据收集步骤。当你有了更好的思路和策略来从原始数据集构建特征时,可能还需要重新审视特征工程步骤。如果你认为通过调整学习算法的超参数可以改进模型结果,模型构建步骤可能需要多次重复。在接下来的章节中,当我们进行实际项目和练习时,我们将更详细地讨论某些步骤以及我们可以使用的不同技术。
设置 Python 环境
现在我们已经讨论了数据科学的一些基础知识及其在营销中的应用,让我们开始为接下来的章节和项目准备开发环境。如果你打算使用 R 语言进行练习,可以跳过这一部分,直接进入设置 R 环境部分。对于打算使用 Python 语言进行练习的同学,即使你已经熟悉 Python,按照这些步骤安装所有所需的 Python 包并准备好 Python 环境仍然会对你有所帮助。
安装 Anaconda 发行版
在本书的数据科学和机器学习任务中,我们将使用许多不同的 Python 包。举例来说,我们将使用 pandas 包进行数据清理和数据分析。您可以在以下链接找到有关此包的更多信息:pandas.pydata.org/。我们还将使用 scikit-learn 包来构建机器学习模型。有关此包的更多信息,请访问以下页面:scikit-learn.org/stable/。我们还会经常使用 numpy 这个 Python 包。当我们需要对多维数据进行数学和科学操作时,此包将非常有用。您可以在此页面找到有关此包的更多信息:www.numpy.org/。除了我们刚刚提到的这三个包之外,我们还将使用一些其他 Python 库,并在使用它们时逐个进行详细讨论。
由于我们在数据科学和机器学习任务中需要各种 Python 库,有时单独安装它们可能会有些麻烦。幸运的是,Anaconda 发行版可以一次性安装所有所需的包。为了下载 Anaconda 发行版,请访问 www.anaconda.com/download/ 进行安装。当您访问此链接时,网页应如下所示:

在本书中,我们将在 Python 3 中使用 Anaconda 5.2。一旦您下载了 Anaconda 发行版,可以使用安装程序安装所有包。在 macOS 上,安装程序如下所示:

一旦您按照安装程序的步骤完成 Anaconda 发行版的安装,我们现在已经准备好开始运行数据科学和机器学习任务。在接下来的部分,我们将构建一个简单的逻辑回归模型,以了解如何使用我们刚刚安装的关键 Python 库进行未来的练习。
Python 中的简单逻辑回归模型
现在我们已经安装了所有的包,让我们测试一下是否可以使用它们。我们将来会使用 Jupyter Notebook 来进行所有的数据分析、数据可视化和机器学习任务。Jupyter Notebook 是一个开源的 web 应用程序,您可以轻松编写代码、显示图表,并与他人分享笔记本。您可以在此链接找到更多关于 Jupyter Notebook 的信息:jupyter.org/。
由于 Jupyter Notebook 是前一节中刚刚安装的 Anaconda 发行版的一部分,您的计算机上应该已经安装了它。
要启动 Jupyter Notebook,您可以打开终端窗口并输入以下命令:
jupyter notebook
当您输入此命令时,应该会看到类似以下截图的输出:

最后,它应该会在你的浏览器中打开一个网页应用程序。网页界面应该如下所示:

从这张截图可以看到,你可以通过点击右上角的 New 按钮,然后选择 Python 3 来创建一个新的 Jupyter Notebook。这将创建一个新的空白笔记本,选择 Python 3 作为编程语言。新创建的笔记本应该如下所示:

为了更改这个笔记本的名称,你只需点击顶部栏,那里写着Untitled,然后输入一个新的名称。
现在我们已经创建了一个笔记本,接下来让我们使用一些 Python 库来构建一个简单的逻辑回归模型。在第一个单元格中,我们将导入numpy和scikit-learn库。代码如下所示:
import numpy as np
from sklearn.linear_model import LogisticRegression
从这段代码示例中可以看到,我们已经导入了numpy库并将其别名为np。这是numpy库的标准别名。同时,我们只导入了scikit-learn库中linear_model模块下的LogisticRegression模块(sklearn.linear_model)。
为了构建一个模型,我们需要数据。在本章的演示和测试中,我们将创建二维输入数据和二元输出。以下代码展示了我们是如何创建输入和输出数据的:
input_data = np.array([
[0, 0],
[0.25, 0.25],
[0.5, 0.5],
[1, 1],
])
output_data = [
0,
0,
1,
1
]
从这段代码示例中可以看出,我们使用numpy数组数据类型创建了 4 行 2 列的输入数据。输出是二元的,只能取0或1。
使用这些数据,我们可以训练一个逻辑回归模型,代码如下所示:
logit_model = LogisticRegression()
logit_model.fit(input_data, output_data)
从这段代码中可以看到,我们使用LogisticRegression实例化了一个模型对象。然后,我们使用了fit函数,输入和输出数据作为参数,用于训练逻辑回归模型。你可以通过以下代码来获取这个逻辑回归模型的系数和截距:
logit_model.coef_ # output: array([[0.43001235, 0.43001235]])
logit_model.intercept_ # output: array([-0.18498028])
到目前为止,我们的 Jupyter Notebook 如下所示:

为了对新数据进行预测,你可以使用逻辑回归模型对象logit_model的predict函数。此函数将返回每个输入的预测输出类别。代码如下所示:
predicted_output = logit_model.predict(input_data)
到目前为止,我们已经尝试了如何使用numpy和scikit-learn库来构建机器学习模型。接下来,我们将熟悉另一个用于数据可视化的库。在本书的各章节中,我们将大量使用matplotlib库来可视化任何数据分析结果。欲了解更多信息,你可以访问这个页面:matplotlib.org/。
让我们首先看一下以下代码:
import matplotlib.pyplot as plt
plt.scatter(
x=input_data[:,0],
y=input_data[:,1],
color=[('red' if x == 1 else 'blue') for x in output_data]
)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Actual')
plt.grid()
plt.show()
正如你从这个代码片段中看到的,你可以像在代码的第一行那样轻松导入matplotlib库。为了构建一个散点图,我们使用了scatter函数,它接受x和y的值,以及每个点的color。你可以使用xlabel函数来修改* x 轴的标签,使用ylabel函数来修改 y *轴的标签。通过title函数,你可以修改图表的标题。grid函数会在图表中显示网格,而你需要调用show函数来实际显示图表。
Jupyter Notebook 应该如下所示:

需要注意的一点是以下代码:
%matplotlib inline
这是为了在 Web 应用程序中显示图表。如果没有这一行代码,图表将不会显示在 Web 界面中。为了将实际输出与模型的预测进行比较,我们使用预测值构建了另一个散点图。
代码和图表如下所示:

如果将这张图与前一张图进行比较,你会发现模型正确预测了四分之三的输出,并且错误预测了一个点。
你可以从以下链接下载我们在本节中使用的完整 Jupyter Notebook:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.1/python/Setting%20Up%20Python%20Environment.ipynb。
在本书中,我们将频繁使用这三个 Python 库,这些库是我们刚刚实验过的。随着章节的推进,我们将介绍这些 Python 库的更多高级特性和功能,以及如何充分利用它们进行数据科学和机器学习任务。
设置 R 环境
对于那些打算在接下来的练习和项目中使用 R 语言的朋友,我们将在本书中讨论如何为数据科学和机器学习任务准备 R 环境。我们将从安装 R 和 RStudio 开始,然后使用 R 构建一个简单的逻辑回归模型,帮助自己熟悉 R 语言在数据科学中的应用。
安装 R 和 RStudio
除了 Python,R 也是数据科学和机器学习中最常用的语言之一。它非常容易使用,而且有大量用于机器学习的 R 库,吸引了许多数据科学家。为了使用这门语言,你需要从以下链接下载: www.r-project.org/。如果你访问该网页,它的界面会类似于以下截图:

你可以在此网页找到有关 R 的更多信息。为了让你下载,请点击页面中的下载 R 链接。它会要求你选择一个 CRAN 镜像。你可以选择距离你最近的镜像位置来下载 R。下载完成后,按照安装程序中的步骤进行安装。在 macOS 上的安装程序如下图所示:

安装完 R 后,我们还需要为我们的 R 开发环境安装一个工具。在本书中,我们将使用 RStudio,这是一个流行的 R 编程语言 IDE。你可以通过以下链接下载 RStudio:www.rstudio.com/products/rstudio/download/。当你访问这个 RStudio 下载页面时,页面应显示如下截图:

本书中将使用 RStudio Desktop Open Source License 版本,但如果你已经拥有其他版本的许可证,也可以随意使用。下载并安装 RStudio 后,打开 RStudio 时你将看到类似下面的截图:

现在我们已经准备好了 R 环境,接下来让我们构建一个简单的逻辑回归模型,以便更好地熟悉 R。
R 中的一个简单逻辑回归模型
让我们通过在 R 中构建一个简单的逻辑回归模型来测试我们的环境设置。打开 RStudio 并创建一个新的 R 脚本文件。你可以使用以下代码在 R 中创建一个数据框:
# Input Data
data <- data.frame(
"X"=c(0, 0.25, 0.5, 1),
"Y"=c(0, 0.5, 0.5, 1),
"output"=c(0, 0, 1, 1)
)
如代码片段所示,我们构建了一个包含X、Y和output列的数据框。X列的取值为0、0.25、0.5和1,Y列的取值为0、0.5、0.5和1,output是一个二元分类,取值为0或1。data如下所示:

现在我们有了训练逻辑回归模型的数据,接下来让我们看一下以下代码:
# Train logistic regression
logit.fit <- glm(
output ~ X + Y,
data = data,
family = binomial
)
如代码片段所示,我们在 R 中使用glm函数来拟合逻辑回归模型。由于glm函数用于拟合任何线性模型,我们需要定义模型的family变量,以便指定我们要训练的模型类型。为了训练逻辑回归模型,我们在glm函数中使用binomial作为family参数。第一个参数output ~ X + Y定义了该模型的公式,而data参数则用于定义训练模型所使用的数据框。
在 R 中,你可以使用summary函数来获取拟合的逻辑回归模型的详细信息,如下代码所示:
# Show Fitted Results
summary(logit.fit)
这将输出如下截图所示的内容:

如你所见,从这个输出中,我们可以轻松找到模型的系数和截距。我们将在本书中频繁使用这个summary函数,以更好地理解训练好的模型。
有了训练好的模型,我们可以使用以下代码对新数据进行预测:
# Predict Class Probabilities
logit.probs <- predict(
logit.fit,
newdata=data,
type="response"
)
# Predict Classes
logit.pred <- ifelse(logit.probs > 0.5, 1, 0)
logit.pred # output: 0 0 1 1
如你从这个代码片段中看到的,我们使用predict函数通过训练好的模型logit.fit和在参数newdata中定义的新数据进行预测。这个predict函数将输出新数据中每个示例的概率或可能性;为了将这个输出转换成二进制类别,我们可以使用ifelse函数将高于某一阈值(在此例中为0.5)的输出编码为1,其余部分编码为0。
最后,让我们快速看一下如何在 R 中构建图表。我们将在本书中使用 R 包ggplot2进行绘图。因此,了解如何导入这个绘图库并使用它进行数据可视化将对你有帮助。如果这是你第一次使用这个包,你很可能在尝试导入ggplot2包时会看到以下错误信息:

如消息所示,ggplot2包尚未安装在你的计算机上。要安装任何 R 包,你只需运行以下命令:
install.packages('ggplot2')
如果你运行这个命令,你会看到类似以下的输出:

一旦这个库的安装完成,你就可以导入并使用这个库了。我们将使用ggplot2库构建一个简单的散点图,如下所示的代码片段:
# Plotting Library
library(ggplot2)
# Simple Scatterplot
ggplot(data, aes(x=X, y=Y, color=output)) +
geom_point(size=3, shape=19) +
ggtitle('Actual') +
theme(plot.title = element_text(hjust = 0.5))
如你从这个代码片段中看到的,你可以使用ggplot2包中的ggplot函数构建一个基于data的散点图。为了改变散点图中点的形状和大小,你可以使用geom_point函数。你还可以使用ggtitle函数来更改图表的标题。当你运行这段代码时,你将看到以下图表:

我们将对预测结果执行相同的操作。代码如下:
ggplot(data, aes(x=X, y=Y, color=logit.pred)) +
geom_point(size=3, shape=19) +
ggtitle('Predicted') +
theme(plot.title = element_text(hjust = 0.5))
输出如下所示:

在本书中,我们将大量使用这些函数和绘图库ggplot2,因此随着章节和练习的进行,你将越来越习惯于用 R 编程,并且能够熟练使用这些其他库。
你可以从以下链接查看并下载用于本节的完整 R 代码:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.1/R/SettingUpREnvironment.R.
总结
在这一章中,我们讨论了市场营销中的整体趋势,并了解了数据科学和机器学习在营销行业中日益重要的作用。随着数据量的增加,以及我们观察到利用数据科学和机器学习进行营销的好处,各种规模的公司都在投资构建更多以数据为驱动的定量营销策略。
我们还学习了不同类型的分析方法,特别是本书中我们将频繁使用的三种分析方法——描述性分析、解释性分析和预测性分析——以及这些分析的不同使用案例。在这一章中,我们介绍了不同类型的机器学习算法,以及数据科学中的典型工作流程。最后,我们花了一些时间在 Python 和 R 中设置我们的开发环境,并通过构建一个简单的逻辑回归模型来测试我们的环境设置。
在下一章中,我们将介绍一些关键绩效指标(KPIs)以及如何可视化这些关键指标。我们将学习如何在 Python 中计算并构建这些 KPIs 的可视化图表,使用不同的包,如pandas、numpy和matplotlib。对于那些使用 R 语言跟随本书练习的读者,我们还将讨论如何使用 R 来计算并绘制这些 KPIs,利用 R 中的各种统计和数学函数,以及用于可视化的ggplot2包。
第二部分:描述性分析与解释性分析
在本节中,你将学习在营销行业中常用的关键绩效指标(KPI),如何使用 Python 和 R 中的图表库来可视化指标,以及如何使用机器学习算法理解什么因素推动了营销活动的成功与失败。
本节包括以下章节:
-
第二章,关键绩效指标与可视化
-
第三章,营销互动背后的驱动因素
-
第四章,从互动到转化
第二章:关键绩效指标和可视化
当你进行营销活动或其他任何营销工作时,你很可能想要了解每个活动的表现如何,并理解每个营销努力的优缺点。在本章中,我们将讨论一些常用的关键绩效指标(KPI),这些指标帮助你跟踪营销工作表现。更具体地说,我们将涵盖诸如销售收入、客户获取成本(CPA)、数字营销 KPI 和网站流量等指标。我们将学习这些 KPI 如何帮助你朝着营销目标稳步前进。
在讨论了一些常用的 KPI 后,我们将学习如何使用 Python 和/或 R 来计算这些 KPI 并构建它们的可视化。在本章中,我们将使用一个银行营销数据集,展示一个金融组织营销活动的真实案例。对于 Python 项目,我们将学习如何使用pandas和matplotlib库来分析数据并构建可视化图表。对于 R 项目,我们将介绍dplyr和ggplot2库来分析和处理数据,并创建可视化图表。
特别地,本章将涵盖以下主题:
-
用于衡量不同营销活动表现的 KPI
-
使用 Python 计算和可视化关键绩效指标(KPI)
-
使用 R 计算和可视化关键绩效指标(KPI)
用于衡量不同营销活动表现的 KPI
每一项营销努力都需要公司投入资金。当你通过电子邮件进行营销活动时,发送每封邮件都会产生一定的费用。当你在社交网络服务或广播媒体上开展营销活动时,也需要一些资本。由于每项营销活动都涉及一定的成本,因此,检查营销活动的表现并跟踪营销活动的投资回报率(ROI)是至关重要的。本节我们将主要讨论如何跟踪销售收入、CPA 和数字营销 KPI。
销售收入
很明显,每个营销努力的目标都是为公司创造和增长更多的收入。没有公司希望在营销上的花费超过其带来的收入。为了准确报告销售收入,你需要清楚地定义如何将销售归因于每一项营销活动。部分销售可能来源于电子邮件营销活动,而另一些可能来自电视或公共交通上的广告。也有可能一些销售是自然发生的,并没有归因于任何营销活动。
为了正确报告每个营销活动所带来的销售收入,你需要明确定义规则,将销售额归因于每个营销活动。例如,如果你是一家电子商务公司,通过电子邮件和电视广告活动进行促销,你可能希望在电子邮件中使用与电视广告中不同的 URL。这样,你就可以将通过电子邮件营销活动获得的销售与通过电视营销活动获得的销售区分开来。
根据你的需求,你可能还希望报告时间序列的销售收入数据。你可以以电子表格的格式报告,如下所示:

你还可以通过折线图报告时间序列的销售收入数据,如下所示:

我们将在本章末的 Python 和 R 练习中讨论更多关于报告 KPI 时可以使用的不同类型的图表和数据可视化方式。
每客户获取成本(CPA)
另一个衡量营销效果的方法是 CPA(每客户获取成本)。这个 KPI 指标告诉你,通过营销活动获取一个客户的成本是多少。高 CPA 意味着获取新客户的成本较高,而低 CPA 显然意味着获取新客户的成本较低。根据不同的业务类型,即使 CPA 较高,你仍然可以拥有一个非常有利可图的营销活动。例如,如果你销售的是非常奢华且高端的产品,目标客户群体较小,获取这些客户的成本较高,那么你的 CPA 可能较高,但你所获得的每个客户的价值可能更高,从而导致一个有利可图的营销活动。
我们将看一下以下这个假设案例:

如果你查看这个电子表格,欢乐时光活动是最昂贵的营销活动,无论是从总成本还是 CPA 来看。然而,它产生了最多的销售额和每客户销售额;因此,它是最有价值的活动。另一方面,广播广告的 CPA 最低,尽管总成本是第二高的,因为它帮助企业获得了最多的客户。然而,这些客户的总销售额并没有超过该活动的总成本,并给公司带来了净亏损。
即使这是一个假设情境,类似的情况也可能发生在现实生活中。营销活动,比如欢乐时光活动和网络研讨会,比广播广告更能精准地瞄准客户。通过高度精准的营销活动获取的客户质量比通过非精准营销活动获取的客户好得多。
现在我们已经看到如何分解营销活动结果,以更深入地分析成本效益,我们将看看一些常用的数字营销 KPI。
数字营销 KPI
随着营销渠道选择的增加,如社交网络服务、博客和搜索引擎,报告数字营销效果变得越来越重要。之前讨论过的 KPI、销售收入和获取成本,同样适用于数字营销领域。
作为一个例子,基于单一归因逻辑,你可以分析通过不同社交网络服务(如 Facebook、LinkedIn 和 Instagram)生成了多少销售。你还可以分析通过这些营销渠道获得了多少客户,并查看各个数字营销活动的 CPA 和所生成的价值。让我们来讨论更多的数字营销 KPI:
- 点击率(CTR)是另一个数字营销中常常关注的 KPI。CTR 是观看了广告并点击广告的人的百分比。公式如下:

点击率(CTR)是数字营销渠道中的一个重要指标,因为它衡量了你的在线营销在将流量引导到你的网站方面的效果。
-
然后,你可以使用潜在客户比率来衡量有多少网站流量可以转化为潜在客户。通常,只有一部分网站流量适合成为你的客户。这些营销合格潜在客户(MQL)是那些已经准备好接受营销的潜在客户,符合特定商业标准,有可能成为会购买的客户,基于他们的特征。当你开始对这些合格的潜在客户进行营销时,你也应该关注转化率。
-
转化率是将潜在客户转化为活跃客户的百分比。你可以根据你的营销目标来定义什么应当视为转化。如果你的目标是查看有多少潜在客户变成了付费客户,那么你可以按照类似下面的公式计算转化率:

如果你的目标是查看有多少潜在客户在你的网站上注册,那么你可以按照以下公式计算转化率:

到目前为止,我们已经查看了各种 KPI,并讨论了这些 KPI 如何帮助你跟踪营销工作的进展和效果。接下来,我们将看看如何使用 Python 和/或 R 来计算这些 KPI 并进行可视化。如果你计划使用本书中介绍的两种编程语言之一,Python 或 R,你可以跳过并直接进入你想要学习的部分。
使用 Python 计算和可视化 KPI
在本节中,我们将讨论如何使用 Python 计算和可视化我们在前几节中讨论的关键绩效指标(KPI)。我们将主要集中于使用银行营销数据分析转化率。对于那些希望使用 R 进行本次练习的读者,您可以跳到下一节。我们将使用pandas和matplotlib库来操作和分析数据,并构建各种图表,以准确报告营销工作的进展和表现。
在本节的练习中,我们将使用UCI 的银行营销数据集,可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/bank+marketing。您可以访问该链接,并通过点击左上角的Data Folder链接下载数据。对于本次练习,我们下载了bank-additional.zip数据,并将使用其中的bank-additional-full.csv文件。
当您打开这个bank-additional-full.csv文件时,您会注意到使用分号(;)作为分隔符,而不是逗号(,)。为了加载这些数据,您可以使用以下代码将数据读取到pandas的DataFrame中:
import pandas as pd
df = pd.read_csv('../data/bank-additional-full.csv', sep=';')
正如您在这段代码中看到的,我们导入了pandas库,并使用别名pd,我们还使用read_csv函数来加载数据。对于除逗号外的其他分隔符,您可以在read_csv函数中使用sep参数来定义自定义分隔符。
如果您查看数据下载页面中的字段描述(archive.ics.uci.edu/ml/datasets/bank+marketing),您会发现输出变量y(它包含客户是否订阅定期存款的信息)被编码为'yes'或'no'。为了简化我们的转化率计算,我们将把该变量编码为'yes'对应1,'no'对应0。您可以使用以下代码进行编码:
df['conversion'] = df['y'].apply(lambda x: 1 if x == 'yes' else 0)
正如您在这段代码中看到的,我们使用apply函数将变量y中的'yes'编码为1,将'no'编码为0,然后将这些编码后的数据作为新列conversion添加进去。代码和我们在 Jupyter Notebook 中加载的数据如下所示:

现在我们已经成功将数据读取到pandas的DataFrame中,我们将开始查看如何使用各种方法和图表来分析和可视化转化率。
聚合转化率
首先,我们将查看聚合转化率。我们可以通过将订阅定期存款的客户总数除以数据中客户的总数来计算这个指标。由于我们已经将输出变量编码为1(表示已转化)和0(表示未转化),并将其存储在名为conversion的列中,我们可以简单地对这一列求和来获得转化的总数。
以下代码片段展示了我们如何对conversion列进行求和,并获得数据中客户的总数:
# total number of conversions
df.conversion.sum()
# total number of clients in the data (= number of rows in the data)
df.shape[0]
以下是我们在 Jupyter Notebook 中进行转化率计算的代码:

如你所见,在 Jupyter Notebook 中的代码输出结果中,我们从总计41188名银行客户中转换了4640名客户,这表明总体转化率为11.27%。在接下来的部分,我们将分析这些转化率如何根据不同年龄组的变化。
按年龄的转化率
总体转化率告诉我们营销活动的整体表现。然而,它并没有给我们太多的洞察。当我们报告并跟踪营销进展时,通常需要深入分析数据,将客户群体细分为多个部分,并计算每个部分的关键绩效指标(KPI)。我们将首先按age将数据划分为更小的细分,并查看不同年龄组的转化率差异。
我们首先来看以下代码:
conversions_by_age = df.groupby(
by='age'
)['conversion'].sum() / df.groupby(
by='age'
)['conversion'].count() * 100.0
如你所见,在这段代码中,我们使用了groupby函数按年龄计算转化率。
我们首先根据变量名age进行分组,并使用sum函数对conversion列进行求和,以获得每个年龄段的总转化数。然后,我们再次按age进行分组,使用count函数统计每个年龄段记录的数量。
使用这两项计算,我们可以按age计算每个年龄段的转化率,如代码所示。以下是每个age的部分转化率计算结果:

另一种查看不同客户年龄段转化率的方法是通过绘制折线图,如下图所示:

用于可视化不同年龄段转化率的代码如下所示:
ax = conversions_by_age.plot(
grid=True,
figsize=(10, 7),
title='Conversion Rates by Age'
)
ax.set_xlabel('age')
ax.set_ylabel('conversion rate (%)')
plt.show()
如代码所示,我们使用了之前构建的conversions_by_age变量和plot函数来绘制折线图。如你从代码中看到的那样,可以通过名为figsize的参数改变图形的大小,通过名为title的参数改变图表的标题。为了更改X轴和Y轴的标签,可以使用set_xlabel和set_ylabel函数。
在之前的折线图中,有一点值得注意的是,老年组的数据显示似乎有很多噪音。对于70岁或以上的人群,转化率波动较大,如果你查看数据,会发现这主要是因为这个年龄组的客户数量相对较少,和其他年龄组相比差距较大。
为了减少这种不必要的噪音,我们可以将多个年龄组合并。在本练习中,我们将银行客户根据年龄分为六个不同的组——18到30岁,30到40岁,40到50岁,50到60岁,60到70岁,以及70岁及以上。可以使用以下代码将客户分组:
df['age_group'] = df['age'].apply(
lambda x: '[18, 30)' if x < 30 else '[30, 40)' if x < 40 \
else '[40, 50)' if x < 50 else '[50, 60)' if x < 60 \
else '[60, 70)' if x < 70 else '70+'
)
如果你查看这段代码,我们正在对age列应用apply函数,将客户分为六个不同的年龄组,并将这些数据添加到一个名为age_group的新列中。为了计算这些新创建的年龄组的转化率,我们可以使用以下代码:
conversions_by_age_group = df.groupby(
by='age_group'
)['conversion'].sum() / df.groupby(
by='age_group'
)['conversion'].count() * 100.0
与之前的情况类似,我们使用groupby、sum和count函数来计算这六个不同年龄组的转化率。结果数据如下所示:

从这个结果可以看出,各年龄组之间的差异比之前要小得多,尤其是在老年组。我们可以使用条形图来可视化这些数据,如下所示的屏幕截图所示:

构建此条形图的代码如下所示:
ax = conversions_by_age_group.loc[
['[18, 30)', '[30, 40)', '[40, 50)', '[50, 60)', '[60, 70)', '70+']
].plot(
kind='bar',
color='skyblue',
grid=True,
figsize=(10, 7),
title='Conversion Rates by Age Groups'
)
ax.set_xlabel('age')
ax.set_ylabel('conversion rate (%)')
plt.show()
正如你从这段代码中看到的,我们使用了之前用来绘制线形图的相同plot函数。唯一的区别是kind参数,通过它我们可以定义想要绘制的不同类型的图形。在这里,我们将kind参数的值设置为bar,以绘制条形图。
你可以在以下仓库中找到完整的代码:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.2/python/ConversionRate.ipynb。
转化与未转化
另一个我们可以观察的方面是已转化客户与未转化客户之间的人口统计差异。这种分析可以帮助我们识别在营销活动中,已转化组与未转化组之间的区别,帮助我们更好地了解目标客户以及哪些类型的客户更容易响应我们的营销活动。在本练习中,我们将比较已转化组与未转化组之间婚姻状况的分布。
我们将首先计算每个婚姻状况的转化和未转化的数量。以下代码展示了如何使用pandas函数来计算这一点:
pd.pivot_table(df, values='y', index='marital', columns='conversion', aggfunc=len)
从这段代码中可以看出,我们正在使用pandas库中的pivot_table函数。我们根据marital和conversion列进行分组,其中marital将成为新DataFrame的索引,conversion将成为列。通过aggfunc参数,我们可以指定要执行的聚合类型。在这里,我们使用len函数简单地计算每个组的客户数量。生成的数据如下所示:

另一种表示这些数据的方式是使用饼图,如下所示:

以下代码展示了我们如何构建这些饼图:
conversions_by_marital_status_df.plot(
kind='pie',
figsize=(15, 7),
startangle=90,
subplots=True,
autopct=lambda x: '%0.1f%%' % x
)
plt.show()
从这段代码中可以看出,我们使用了与之前相同的plot函数,但使用pie作为我们希望构建的图表类型。你可以使用autopct参数来格式化饼图中每个群体的标签。
与数据输出的表格格式相比,饼图使得理解数据的整体分布变得更加容易。通过饼图,我们可以轻松看到married群体在转化组和非转化组中占据最大的比例,而single群体则排在第二。使用饼图,我们可以轻松地可视化两个群体之间的相似性和差异。
按年龄和婚姻状况的转化
到目前为止,我们已经根据一个标准对数据进行了汇总。然而,有时你可能希望根据多个列来对数据进行分组。在本节中,我们将讨论如何根据多个标准分析和报告转化率。作为一个练习,我们将使用前一节中构建的年龄组和婚姻状况作为两个列进行分组。
让我们首先看看代码:
age_marital_df = df.groupby(['age_group', 'marital'])['conversion'].sum().unstack('marital').fillna(0)
age_marital_df = age_marital_df.divide(
df.groupby(
by='age_group'
)['conversion'].count(),
axis=0
)
从这段代码中可以看出,我们正在根据两个列age_group和marital对数据进行分组,并求出转化的数量。然后,我们将这个数量除以每个群体中的客户总数。生成的数据如下所示:

如您从这些数据中看到的,我们现在可以根据两个标准(年龄组和婚姻状况)看到转化率的分布。例如,单身且年龄在18到30岁之间的客户转化率为 13.25%,而已婚且年龄在60到70岁之间的客户转化率为 30.11%。另一种可视化这些数据的方式是使用如下所示的条形图:

在这张条形图中,我们可以清楚地看到每个年龄和婚姻状态群体的转化率分布。我们用来构建这张条形图的代码如下所示:
ax = age_marital_df.loc[
['[18, 30)', '[30, 40)', '[40, 50)', '[50, 60)', '[60, 70)', '70+']
].plot(
kind='bar',
grid=True,
figsize=(10,7)
)
ax.set_title('Conversion rates by Age & Marital Status')
ax.set_xlabel('age group')
ax.set_ylabel('conversion rate (%)')
plt.show()
与之前的情况类似,我们使用了pandas库的plot函数,并将bar传递给该函数的kind参数。由于DataFrame对象age_marital_df对于每种婚姻状态有四列,并且按年龄组进行索引,因此plot函数为每个年龄组的每种婚姻状态构建了一个包含四个条形的条形图。
如果您想要将每个年龄组的四个条形图叠加起来,您可以使用以下代码绘制堆叠条形图:
ax = age_marital_df.loc[
['[18, 30)', '[30, 40)', '[40, 50)', '[50, 60)', '[60, 70)', '70+']
].plot(
kind='bar',
stacked=True,
grid=True,
figsize=(10,7)
)
ax.set_title('Conversion rates by Age & Marital Status')
ax.set_xlabel('age group')
ax.set_ylabel('conversion rate (%)')
plt.show()
如您在这段代码中看到的,唯一的区别是我们在代码中使用的参数stacked。当此参数设置为True时,它将构建一个堆叠条形图,效果如下:

从这个堆叠条形图中可以看到,不同的婚姻状态在每个年龄组中被叠加在一起。通过这种方式,我们不仅可以轻松看到不同年龄组的转化率的整体趋势,还可以看到每个年龄组中不同婚姻状态的转化客户的比例。
我们在此 Python 练习中使用的完整代码和 Jupyter Notebook 可以在以下仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.2/python/ConversionRate.ipynb。
使用 R 计算和可视化 KPI
在本节中,我们将讨论如何使用 R 计算和可视化我们在前几节中讨论的 KPI。我们将主要专注于使用银行营销数据分析转化率。对于那些希望使用 Python 进行此练习的读者,您可以在前一节中找到 Python 的练习代码。我们将使用 R 中的dplyr和ggplot2库来处理和分析数据,并构建各种图表,以准确报告营销工作中的进展和效果。dplyr库提供了多种用于数据科学和机器学习任务的数据处理功能。
本节练习中,我们将使用UCI的银行营销数据集,可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/bank+marketing。您可以点击此链接,在左上角点击Data Folder链接下载数据。为了进行此练习,我们下载了bank-additional.zip数据,并将在该压缩文件中的bank-additional-full.csv文件中使用数据。
当您打开这个bank-additional-full.csv文件时,您会注意到文件中使用分号(;)作为分隔符,而不是逗号(,)。为了加载此数据,您可以使用以下代码将数据读取到一个DataFrame中:
conversionsDF <- read.csv(
file="~/Documents/data-science-for-marketing/ch.2/data/bank-additional-full.csv",
header=TRUE,
sep=";"
)
从这段代码中可以看出,我们使用read.csv函数来加载数据。对于逗号以外的分隔符,你可以在read.csv函数中通过sep参数定义自定义分隔符。如果你的数据文件包含标题行,你可以将header参数设置为TRUE。另一方面,如果数据文件没有标题行,并且数据从第一行开始,你可以将其设置为FALSE。
如果你查看数据下载页面中的字段描述(archive.ics.uci.edu/ml/datasets/bank+marketing),输出变量y表示客户是否订阅了定期存款,其信息编码为'yes'或'no'。为了简化我们的转化率计算,我们将该变量编码为'yes'对应1,'no'对应0。你可以使用以下代码来进行编码:
# Encode conversions as 0s and 1s
conversionsDF$conversion <- as.integer(conversionsDF$y) - 1
从这段代码片段可以看出,我们使用了as.integer函数将'yes'编码为1,将'no'编码为0,并将这个编码后的数据作为新列conversion添加到数据中。由于as.integer函数默认会将'no'和'yes'分别编码为1和2,所以我们将其值减去1。现在,数据在 RStudio 中的显示情况如下:

现在我们已经成功将数据读取到R的DataFrame中,接下来我们将开始探索如何分析和可视化转化率,使用不同的方法和图表。
总体转化率
首先,我们要查看的是总体转化率。我们可以通过将订阅了定期存款的客户数量除以数据中总客户数量来计算这个指标。由于我们已经将输出变量编码为 1(表示已转化)和 0(表示未转化),并将其存储在名为conversion的列中,我们可以简单地对该列求和,得到转化的总客户数量。以下代码片段展示了我们如何对conversion列求和并得到数据中的总客户数:
# total number of conversions
sum(conversionsDF$conversion)
# total number of clients in the data (= number of records in the data)
nrow(conversionsDF)
从这段代码中可以看出,我们使用了 R 中的sum函数来计算总的转化数量,并使用nrow函数来统计数据集中行的数量。顺便提一句,和nrow类似,你可以使用ncol函数来统计DataFrame的列数。
以下截图展示了我们在 RStudio 中的代码显示情况:

从 RStudio 中的代码输出可以看出,我们在41188个银行客户中,共有4640个客户进行了转化,这表明总体转化率为11.27%。在接下来的部分,我们将分析不同年龄组的转化率差异。我们使用了sprintf函数来格式化包含整数和浮点数的字符串。
按年龄分组的转化率
聚合转化率告诉我们营销活动的整体表现。然而,它并没有给我们太多的洞察。在报告和跟踪营销进展时,我们通常希望更深入地分析数据,将客户基础拆分为多个细分市场,并计算各个细分市场的 KPI。我们将首先按年龄将数据拆分成更小的段,并查看不同年龄组的转化率差异。
我们首先来看以下这段代码:
conversionsByAge <- conversionsDF %>%
group_by(Age=age) %>%
summarise(TotalCount=n(), NumConversions=sum(conversion)) %>%
mutate(ConversionRate=NumConversions/TotalCount*100.0)
这段代码中的管道操作符%>%是你可以按顺序应用不同函数的方式。在这段代码中,我们将conversionDF传递给group_by函数,然后将group_by函数的结果传递给summarise函数,最后传递给mutate函数。
在group_by函数中,我们通过age列对DataFrame进行分组。然后,针对每个年龄组,我们使用函数n()计算每组的记录数,并将其命名为TotalCount。同时,我们使用sum函数对每个年龄组的conversion列求和,并将其命名为NumConversions。
最后,我们使用mutate函数,该函数添加新的变量,同时保留原始的DataFrame,来计算每个年龄组的转化率。如你所见,我们只是将NumConversion除以TotalCount,然后乘以100.0来获得转化率。
结果数据如下所示:

查看不同年龄段客户的转化率的另一种方法是绘制折线图,如下图所示:

用于可视化不同年龄段转化率的代码如下所示:
# line chart
ggplot(data=conversionsByAge, aes(x=Age, y=ConversionRate)) +
geom_line() +
ggtitle('Conversion Rates by Age') +
xlab("Age") +
ylab("Conversion Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
从这段代码中可以看出,我们使用ggplot函数初始化一个ggplot对象,数据为conversionsByAge,其中Age列作为x轴,ConversionRate列作为y轴。
然后,我们使用geom_line函数连接观测值,绘制折线图。你可以使用ggtitle函数更改图表标题。此外,你还可以使用xlab和ylab函数分别重命名 x 轴和 y 轴标签。
在前一张折线图中,显而易见的一点是,较高年龄组的噪声似乎很多。70 岁或以上的转化率变化较大,如果你查看数据,会发现这主要是因为该年龄组的客户数量相对较少,与其他年龄组相比。
为了减少这种不必要的噪音,我们可以将多个年龄段合并。在本练习中,我们根据银行客户的年龄将其分为六个不同的组——18到30岁之间、30到40岁之间、40到50岁之间、50到60岁之间、60到70岁之间、以及70岁及以上。以下代码可以用于将客户分组:
# b. by age groups
conversionsByAgeGroup <- conversionsDF %>%
group_by(AgeGroup=cut(age, breaks=seq(20, 70, by = 10)) ) %>%
summarise(TotalCount=n(), NumConversions=sum(conversion)) %>%
mutate(ConversionRate=NumConversions/TotalCount*100.0)
conversionsByAgeGroup$AgeGroup <- as.character(conversionsByAgeGroup$AgeGroup)
conversionsByAgeGroup$AgeGroup[6] <- "70+"
与之前的情况一样,我们使用了group_by函数根据age列将conversionsDF数据分组。这里的不同之处在于,我们如何使用cut函数为每个年龄组创建年龄范围。
breaks参数定义了cut函数将如何划分DataFrame。参数seq(20, 70, by = 10)表示我们将从20到70以10为增量创建一个序列。一旦数据按这些年龄段分组,其他操作与之前相同。我们使用summarise和mutate函数来计算TotalCount、NumConversions和ConversionRate列。
生成的DataFrame在以下截图中展示:

从中可以看出,每个年龄组的差异远小于之前,尤其是在老年组。我们可以通过条形图来可视化这些数据,如下所示:

构建此条形图的代码如下所示:
# bar chart
ggplot(conversionsByAgeGroup, aes(x=AgeGroup, y=ConversionRate)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('Conversion Rates by Age Groups') +
xlab("Age") +
ylab("Conversion Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
从这段代码可以看出,我们将conversionsByAgeGroup数据传递给了ggplot对象,x轴使用了AgeGroup列,y轴使用了ConversionRate列。我们使用了geom_bar函数来构建条形图。
width参数定义了条形图中每个条形的宽度。与之前的折线图类似,你可以使用ggtitle来重新命名图表标题,使用xlab和ylab函数来重新命名* x 轴和 y *轴的标签。
你可以在以下仓库中找到完整的代码:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.2/R/ConversionRate.R。
转化与非转化
另一个我们可以查看的方面是转化客户与非转化客户之间的群体差异。这种类型的分析可以帮助我们识别在市场营销活动中,转化组与非转化组的不同之处,帮助我们更好地理解目标客户,并了解哪些类型的客户对我们的营销活动反应更好。在本练习中,我们将比较转化组与非转化组之间婚姻状况的分布。
我们将首先计算每个婚姻状况下的转化和非转化数量。以下代码展示了我们如何使用R函数来计算这一点:
conversionsByMaritalStatus <- conversionsDF %>%
group_by(Marital=marital, Conversion=conversion) %>%
summarise(Count=n())
从这段代码可以看出,我们正在使用dplyr包中的管道操作符%>%将DataFrame,conversionsDF传递给group_by函数,然后传递给summarise函数。在group_by函数中,我们按两个列进行分组,marital和conversion。在summarise函数中,我们只是通过使用n函数计算每个组中的记录数量。
结果数据显示在以下截图中:

另一种表示这些数据的方式是使用饼图:

以下代码展示了我们如何在 R 中构建这些饼图:
# pie chart
ggplot(conversionsByMaritalStatus, aes(x="", y=Count, fill=Marital)) +
geom_bar(width=1, stat = "identity", position=position_fill()) +
geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) +
coord_polar("y") +
facet_wrap(~Conversion) +
ggtitle('Marital Status (0: Non Conversions, 1: Conversions)') +
theme(
axis.title.x=element_blank(),
axis.title.y=element_blank(),
plot.title=element_text(hjust=0.5),
legend.position='bottom'
)
在 R 中构建饼图时,我们使用相同的geom_bar函数,就像我们在构建柱状图时一样。这里的区别在于coord_polar("y"),它将柱状图转换为饼图。接着,我们使用facet_wrap函数根据Conversion列创建两列饼图。这会生成两个饼图,一个用于转化组,另一个用于非转化组。
与数据输出的表格格式相比,饼图更容易理解数据的整体分布。通过饼图,我们可以很容易看到married组在转化组和非转化组中都占据了最大的比例,而single组位居第二。通过饼图,我们可以轻松地可视化两个组之间的相似性和差异。
按年龄和婚姻状况分组的转化情况
到目前为止,我们已经根据一个标准对数据进行了汇总。然而,有时你可能希望根据多个列来分组数据。在本节中,我们将讨论如何根据多个标准来分析和报告转化率。作为练习,我们将使用上一节中构建的年龄组和婚姻状况作为两个分组标准。
让我们先看一下代码:
#### 5\. Conversions by Age Groups & Marital Status ####
conversionsByAgeMarital <- conversionsDF %>%
group_by(AgeGroup=cut(age, breaks= seq(20, 70, by = 10)), Marital=marital) %>%
summarise(Count=n(), NumConversions=sum(conversion)) %>%
mutate(TotalCount=sum(Count)) %>%
mutate(ConversionRate=NumConversions/TotalCount)
conversionsByAgeMarital$AgeGroup <- as.character(conversionsByAgeMarital$AgeGroup)
conversionsByAgeMarital$AgeGroup[is.na(conversionsByAgeMarital$AgeGroup)] <- "70+"
类似于我们构建自定义年龄组时,我们在group_by中使用cut函数,将年龄分组为从20到70,每 10 岁一组。然而,这次我们也按marital列进行分组。
然后,我们使用summarise函数计算每个组中的记录数量Count,以及每个组中的转化次数NumConversions。接着,使用mutate函数计算每个年龄组的总记录数,命名为TotalCount,以及每个组的转化率,命名为ConversionRate。
结果数据显示在以下截图中:

从这些数据中可以看出,我们现在可以根据两个标准来查看转化率的分布:年龄组和婚姻状况。例如,单身且年龄在20到30岁之间的客户转化率为 11.10%,而已婚且年龄在40到50岁之间的客户转化率为 5.74%。
另一种可视化这些数据的方法是使用条形图:

在这个条形图中,我们可以轻松地看到每个年龄组和婚姻状况组的转化率分布。我们用来构建这个条形图的代码如下:
# bar chart
ggplot(conversionsByAgeMarital, aes(x=AgeGroup, y=ConversionRate, fill=Marital)) +
geom_bar(width=0.5, stat="identity", position="dodge") +
ylab("Conversion Rate (%)") +
xlab("Age") +
ggtitle("Conversion Rates by Age and Marital Status") +
theme(plot.title=element_text(hjust=0.5))
在这里,我们创建了一个ggplot对象,使用了conversionsByAgeMarital数据。我们使用AgeGroup作为X轴,ConversionRate作为Y轴,同时用Marital列为不同的婚姻状况类型指定不同的颜色。接着,我们通过使用geom_bar函数来构建条形图。通过这个配置,ggplot会构建一个按婚姻状况细分的转化率与年龄组之间的堆积条形图,这一点在之前的条形图中已经展示过了。
如果你想要为每个年龄组堆叠这四个条形图,可以使用以下代码来绘制堆积条形图:
# stacked bar chart
ggplot(conversionsByAgeMarital, aes(x=AgeGroup, y=ConversionRate, fill=Marital)) +
geom_bar(width=0.5, stat="identity", position="stack") +
ylab("Conversion Rate (%)") +
xlab("Age") +
ggtitle("Conversion Rates by Age and Marital Status") +
theme(plot.title=element_text(hjust=0.5))
从这段代码中可以看出,唯一的不同之处在于geom_bar函数中的代码position="stack"。如果你将值"dodge"传递给geom_bar函数的position参数,它将创建一个未堆叠的条形图。而如果你将值"stack"传递给geom_bar函数的position参数,它将创建一个堆积条形图,效果如下:

从这个堆积条形图中可以看到,不同的婚姻状况在每个年龄组中堆叠在一起。这样,我们不仅可以轻松地看到不同年龄组之间转化率的总体趋势,还可以看到每个年龄组中不同婚姻状况的转化客户比例。
我们在这个 R 练习中使用的完整代码可以在以下仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.2/R/ConversionRate.R。
总结
在本章中,我们讨论了如何使用描述性分析来报告和分析营销努力的进展和表现。我们讨论了营销中常用的各种关键绩效指标(KPIs),用以跟踪营销活动的进展。我们了解到,评估每个营销策略产生的销售收入有多么重要。在分析销售收入指标时,我们发现从不同角度进行分析非常关键。你可能不仅想查看总销售收入,还要查看时间序列(按月、季度或年度)的销售收入。你还可能希望查看归因于每个营销活动的销售收入,并分析每个活动为公司带来了多少收入。我们还讨论了 CPA 指标,通过这些指标你可以评估营销策略的成本效益。我们也学习了用于数字营销渠道的各种指标,比如点击率(CTR)、潜在客户转化率和转化率。正如我们在 Python 和 R 练习中所看到和实验的那样,我们可以对这些 KPI 指标进行更深入的多层次分析。
在下一章中,我们将学习如何应用数据科学和机器学习技术进行解释性分析。更具体地说,我们将讨论如何使用回归分析和模型来理解营销参与背后的驱动因素。我们还将在下一章中讲解如何解读回归分析结果。
第三章:驱动营销参与度的因素
当你进行营销活动时,你需要重点关注并分析的一个重要指标是客户对营销活动的参与度。例如,在电子邮件营销中,客户参与度可以通过客户打开或忽略你的营销邮件的数量来衡量。客户参与度还可以通过单个客户访问网站的次数来衡量。成功的营销活动会吸引大量客户参与,而无效的营销活动不仅会导致客户参与度较低,还会对你的业务产生负面影响。客户可能会将你的邮件标记为垃圾邮件或取消订阅你的邮件列表。
为了理解是什么影响了客户的参与度,本章我们将讨论如何使用解释性分析(更具体地说,是回归分析)。我们将简要介绍解释性分析的定义,回归分析是什么,以及如何使用逻辑回归模型进行解释性分析。接着,我们将讨论如何在 Python 中使用statsmodels包来构建和解释回归分析结果。对于 R 程序员,我们将讨论如何使用glm来构建和解释回归分析结果。
本章将涵盖以下主题:
-
使用回归分析进行解释性分析
-
使用 Python 进行回归分析
-
使用 R 进行回归分析
使用回归分析进行解释性分析
在第二章中,关键绩效指标与可视化,我们讨论了描述性分析是什么,以及它如何帮助我们更好地理解数据集。我们通过使用各种可视化技术,并在 Python 和 R 中构建不同类型的图表进行了实验。
在本章中,我们将扩展我们的知识,开始讨论在营销中何时、如何以及为什么使用解释性分析。
解释性分析与回归分析
正如我们在第一章中简要讨论的,数据科学与营销,解释性分析的目的是回答我们为何使用数据,而描述性分析的目的是回答我们用数据做什么,如何使用数据。当你进行不同的营销活动时,你通常会发现某些营销活动的表现比其他活动好得多;你可能会想知道,为什么有些营销活动效果如此之好,而有些却没有。例如,你可能想了解哪些类型和群体的客户比其他人更频繁地打开你的营销邮件。另一个例子是,你可能想分析客户群体的哪些属性与更高的转化率和商品购买率密切相关。
通过解释性分析,你可以分析并理解与所需结果高度相关的关键因素。回归分析和回归模型常用于建模属性与结果之间的关系。简而言之,回归分析通过找到最能近似输出值的属性或特征函数来估算输出变量的值。回归分析中一种常用的形式是线性回归。顾名思义,在线性回归中,我们尝试通过特征的线性组合来估计输出变量。如果我们用Y表示输出变量,用X[i]表示每个特征,其中i是第i个特征,那么线性回归公式如下:

从上述公式中可以看到,输出变量Y表示为特征X[i]的线性组合。线性回归模型的目的是找到最能估算输出变量的截距a和系数b[i],并利用给定的特征进行拟合。拟合的线性回归线大致如下所示(图片来源于 towardsdatascience.com/linear-regression-using-python-b136c91bf0a2):

该图中的蓝色点表示数据点,红色线是拟合的或训练过的线性回归线。正如图表所示,线性回归试图通过特征的线性组合来估计目标变量。
在本章中,我们将讨论如何使用回归分析,尤其是逻辑回归模型,来理解是什么驱动了更高的客户参与度。
逻辑回归
逻辑回归是一种回归分析方法,通常用于输出变量是二元的情况(表示正面结果为 1,负面结果为 0)。与其他线性回归模型类似,逻辑回归模型通过特征变量的线性组合来估算输出。唯一的区别是模型估算的内容。与其他线性回归模型不同,逻辑回归模型估算的是事件的对数机会,或者换句话说,是正面事件和负面事件概率之间的对数比率。其公式如下:

左边的比率是成功的机会,表示成功的概率与失败的概率之间的比率。对数机会的曲线,也称为logit 曲线,如下所示:

逻辑回归模型的输出只是 logit 的逆,它的值范围从零到一。在本章中,我们将使用回归分析来理解是什么推动了客户参与度,输出变量将是客户是否对营销电话作出回应。因此,逻辑回归在这种情况下非常适用,因为输出是一个二元变量,可以取两个值:响应和未响应。在接下来的章节中,我们将讨论如何在 Python 和 R 中使用和构建逻辑回归模型,然后我们将讲解如何解读回归分析结果,以理解哪些客户属性与更高的营销参与度高度相关。
使用 Python 进行回归分析
在本节中,你将学习如何在 Python 中使用statsmodels包进行回归分析。对于那些希望使用 R 而非 Python 的读者,可以跳到下一节。在本节开始时,我们将通过使用pandas和matplotlib包更仔细地查看数据,然后我们将讨论如何使用statsmodels库构建回归模型并解读结果。
在本练习中,我们将使用 IBM Watson 提供的公开数据集之一,数据集可以在www.ibm.com/communities/analytics/watson-analytics-blog/marketing-customer-value-analysis/找到。你可以点击链接下载 CSV 格式的数据文件。为了将该数据加载到你的 Jupyter Notebook 中,你可以运行以下代码:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.read_csv('../data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv')
与我们在第二章中做的类似,关键绩效指标与可视化,我们首先导入matplotlib和pandas包;使用pandas中的read_csv函数,我们可以将数据读取到pandas DataFrame 中。稍后我们将使用matplotlib进行数据分析和可视化。
加载后的 DataFrame,df,如下所示:

如我们在第二章中讨论的,关键绩效指标与可视化,DataFrame 的shape属性告诉我们 DataFrame 中行和列的数量,而head函数会显示数据集的前五条记录。一旦成功将数据读取到pandas DataFrame 中,数据应呈现如截图所示。
数据分析与可视化
在进行回归分析之前,我们首先要更详细地查看数据,以便更好地理解我们拥有的数据点以及数据中可以看到的模式。如果你查看数据,你会注意到有一列名为Response,它包含客户是否响应营销呼叫的信息。我们将使用这个字段作为客户参与度的衡量标准。为了进行后续的计算,最好将这个字段编码为数值型。让我们看一下以下代码:
df['Engaged'] = df['Response'].apply(lambda x: 0 if x == 'No' else 1)
正如你在这段代码中看到的,我们使用pandas数据框的apply函数,将未响应营销呼叫的客户(No)编码为0,而响应的客户(Yes)编码为1。我们创建了一个名为Engaged的新字段,存储这些编码值。
参与率
我们首先要查看的是总体参与率。这个参与率简单来说就是响应营销呼叫的客户百分比。请看下面的代码:
engagement_rate_df = pd.DataFrame(
df.groupby('Engaged').count()['Response'] / df.shape[0] * 100.0
)
正如你从这段代码中看到的,我们使用pandas数据框的groupby函数按新创建的字段Engaged进行分组。接着,我们使用count函数统计每个Engaged组中的记录数(或客户数)。通过除以数据框中客户的总数并乘以100.0,我们得到参与率。结果如下所示:

为了更容易阅读,我们可以转置数据框(DataFrame),这意味着我们可以翻转数据框中的行和列。你可以通过使用数据框的T属性来转置pandas数据框。其效果如下所示:

如你所见,大约 14%的客户响应了营销呼叫,其余 86%的客户未做响应。
销售渠道
现在,让我们看看是否能在销售渠道和客户参与度之间找到任何明显的模式。我们将分析参与和未参与的客户在不同销售渠道中的分布。首先看一下以下代码:
engagement_by_sales_channel_df = pd.pivot_table(
df, values='Response', index='Sales Channel', columns='Engaged', aggfunc=len
).fillna(0.0)
engagement_by_sales_channel_df.columns = ['Not Engaged', 'Engaged']
正如你在这段代码中看到的,我们正在使用pandas库的pivot_table函数按Sales Channel和Response变量进行分组。运行这段代码后,engagement_by_sales_channel_df将包含如下数据:

正如你在上一节中已经注意到的,未参与营销活动的客户明显更多,因此在原始数据中很难直接看出参与客户与未参与客户在销售渠道分布上的差异。为了让这些差异更加直观地显现出来,我们可以使用以下代码构建饼图:
engagement_by_sales_channel_df.plot(
kind='pie',
figsize=(15, 7),
startangle=90,
subplots=True,
autopct=lambda x: '%0.1f%%' % x
)
plt.show()
一旦你运行这段代码,你将看到以下饼图,展示了已参与和未参与客户在不同销售渠道中的分布情况:

与之前显示每个销售渠道中已参与和未参与客户原始计数的表格相比,这些饼图帮助我们更轻松地可视化并发现分布差异。如你从这些图表中看到的,超过一半的已参与客户来自代理商,而未参与客户则更均匀地分布在所有四个不同的渠道中。正如你从这些图表中看到的,分析和可视化数据能够帮助我们发现数据中的有趣模式,这将在本章后续的回归分析中提供帮助。
总赔付金额
在我们进入回归分析之前,我们要先看一下Total Claim Amount在已参与和未参与组之间分布的差异。我们将通过箱形图来可视化这一点。首先,让我们看一下如何在 Python 中构建箱形图,如下所示:
ax = df[['Engaged', 'Total Claim Amount']].boxplot(
by='Engaged',
showfliers=False,
figsize=(7,5)
)
ax.set_xlabel('Engaged')
ax.set_ylabel('Total Claim Amount')
ax.set_title('Total Claim Amount Distributions by Engagements')
plt.suptitle("")
plt.show()
如你在这段代码中看到的,从pandas DataFrame 中构建箱形图非常简单。你只需调用boxplot函数。箱形图是可视化连续变量分布的一个极好的方式。它们展示了最小值、最大值、第一个四分位数、中位数和第三个四分位数,一目了然。以下箱形图展示了Total Claim Amount在已参与和未参与组之间的分布:

中央矩形从第一个四分位数到第三个四分位数,绿色线条显示中位数。下边和上边分别显示分布的最小值和最大值。需要注意的是,在之前的代码中使用了showfliers=False参数。让我们看看当我们将该参数设置为True时会发生什么,使用以下代码:
ax = df[['Engaged', 'Total Claim Amount']].boxplot(
by='Engaged',
showfliers=True,
figsize=(7,5)
)
ax.set_xlabel('Engaged')
ax.set_ylabel('Total Claim Amount')
ax.set_title('Total Claim Amount Distributions by Engagements')
plt.suptitle("")
plt.show()
使用这段代码并设置showfliers=True标志,生成的箱形图如下所示:

正如你在这些箱形图中所注意到的,它们在上边界线之上绘制了许多点,这些点代表了之前箱形图中的最大值。上边界线上的点显示了基于四分位距(IQR)判定的疑似异常值。IQR 即第一个四分位数和第三个四分位数之间的范围,任何超过第三个四分位数1.5*IQR或低于第一个四分位数1.5*IQR的点都被认为是疑似异常值,并通过这些点显示出来。
回归分析
到目前为止,我们已经分析了数据中字段的类型以及参与组和非参与组之间的模式差异。现在,我们将讨论如何使用 statsmodels 包在 Python 中进行回归分析及其解释。我们将首先用连续变量构建一个逻辑回归模型,并学习如何解释结果。接着,我们将讨论在拟合回归模型时如何处理分类变量,以及这些分类变量对拟合的逻辑回归模型的影响。
连续变量
在线性回归中,包括逻辑回归,当特征变量是连续的时,拟合回归模型非常简单,因为它只需要找到特征变量的线性组合,利用数值特征来估计输出变量。为了用连续变量拟合回归模型,首先让我们看看如何获取 pandas DataFrame 中列的数据类型。请看以下内容:

如你从这张 Jupyter Notebook 截图中看到的,pandas Series 对象的 dtype 属性告诉你它包含的数据类型。从这张截图可以看出,Income 变量是整数,而 Customer Lifetime Value 特征是浮点数。为了快速查看具有数值的变量的分布,你也可以执行以下操作:

如你在这个 Jupyter Notebook 截图中看到的,pandas DataFrame 的 describe 函数显示了所有数值列的分布。例如,你可以看到 Customer Lifetime Value 列中共有 9134 条记录,均值为 8004.94,范围从 1898.01 到 83325.38。
我们将把这些连续变量的名称存储在一个单独的变量中,命名为 continuous_vars。请看以下代码:
continuous_vars = [
'Customer Lifetime Value', 'Income', 'Monthly Premium Auto',
'Months Since Last Claim', 'Months Since Policy Inception',
'Number of Open Complaints', 'Number of Policies',
'Total Claim Amount'
]
现在我们已经知道哪些列是连续变量,让我们开始拟合逻辑回归模型。为此,我们需要先导入 statsmodels 包,如下代码所示:
import statsmodels.formula.api as sm
导入 statsmodels 包后,启动一个逻辑回归模型的代码非常简单,如下所示:
logit = sm.Logit(
df['Engaged'],
df[continuous_vars]
)
如你从这段代码中看到的,我们正在使用 statsmodels 包中的 Logit 函数。我们将 Engaged 列作为输出变量,模型将学习如何估计该输出,而 continuous_vars 列包含所有的连续变量,作为输入变量。定义了输出和输入变量后,一旦创建了逻辑回归对象,我们可以使用以下代码来训练或拟合这个模型:
logit_fit = logit.fit()
如你在这段代码中看到的,我们正在使用逻辑回归对象logit的fit函数来训练逻辑回归模型。运行这段代码后,训练好的模型logit_fit将学习出最佳解决方案,通过使用输入变量来最佳估计输出变量Engaged。若要获得已训练模型的详细描述,可以使用以下代码:
logit_fit.summary()
当你运行这段代码时,summary函数将在 Jupyter Notebook 中显示以下输出:

让我们更仔细地看看这个模型输出。coef表示每个输入变量的系数,z表示z-分数,它是离均值的标准差个数。P>|z|列表示p-值,它表示通过偶然性观察到特征与输出变量之间关系的可能性。因此,P>|z|的值越低,给定特征与输出变量之间的关系越强,且不太可能是偶然的。通常,0.05是一个良好的p-值临界点,任何小于0.05的值都表示给定特征与输出变量之间存在强关系。
从这个模型输出中,我们可以看到,Income、Monthly Premium Auto、Months Since Last Claim、Months Since Policy Inception和Number of Policies变量与输出变量Engaged有显著的关系。例如,Number of Policies变量是显著的,并且与Engaged呈负相关。这表明,客户拥有的保单越多,他们响应营销电话的可能性越小。另一个例子是,Months Since Last Claim变量也是显著的,并且与输出变量Engaged呈负相关。这意味着,自上次理赔以来的时间越长,客户回应营销电话的可能性就越低。
从这些例子中你可以看出,通过查看模型输出中特征的p-值和系数,你可以轻松地解释回归分析结果。这是理解哪些客户特征与我们关心的结果显著相关的好方法。
分类变量
正如你在上一节中看到的连续变量的例子,理解输入和输出变量之间的关系是相当直接的,尤其是通过系数和p-值。然而,当我们引入类别变量时,情况就变得不那么直观了。类别变量通常没有任何自然顺序,或者它们被编码为非数值型的值,但在回归分析中,我们需要输入变量具有数值型的值,这些值能够表示变量的顺序或大小。例如,我们无法轻松地为数据集中的State变量编码出某种顺序或数值。这就是为什么在进行回归分析时,我们需要与连续变量不同地处理类别变量。在 Python 中,当使用pandas包时,有多种方式来处理类别变量。我们首先来看一下如何对类别变量进行因子化,如下面的代码所示:
gender_values, gender_labels = df['Gender'].factorize()
pandas的factorize函数通过枚举值对类别变量进行数值编码。我们先来看一下下面的输出:

从这个输出中可以看出,Gender变量的值被用零和一编码,其中0表示女性(F),1表示男性(M)。这是一种快速地将类别变量编码为数值型值的方法。然而,当我们希望将自然顺序嵌入编码值时,这个函数就不起作用了。例如,我们数据集中的Education变量有五个不同的类别:High School or Below、Bachelor、College、Master和Doctor。我们可能希望在对Education变量内的不同类别进行编码时,能够嵌入顺序关系。
以下代码展示了另一种在使用pandas时对类别变量进行排序编码的方法:
categories = pd.Categorical(
df['Education'],
categories=['High School or Below', 'Bachelor', 'College', 'Master', 'Doctor']
)
如你在这段代码中看到的,我们使用pd.Categorical函数对df['Education']的值进行编码。我们可以通过参数categories来定义我们希望的排序。在我们的例子中,我们为High School or Below、Bachelor、College、Master和Doctor这些类别分别赋予了0、1、2、3和4的值。输出如下所示:
我们现在将这些编码后的变量添加到 pandas DataFrame df中,如下面的代码所示:
df['GenderFactorized'] = gender_values
df['EducationFactorized'] = categories.codes
使用这两个类别变量,Gender和Education的编码后,我们可以使用以下代码来拟合逻辑回归模型:
logit = sm.Logit(
df['Engaged'],
df[[
'GenderFactorized',
'EducationFactorized'
]]
)
logit_fit = logit.fit()
类似于之前如何使用连续变量拟合逻辑回归模型,我们可以使用statsmodels包中的Logit函数,利用编码后的类别变量GenderFactorized和EducationFactorized来拟合逻辑回归模型。使用拟合后的逻辑回归模型对象的summary函数,我们将得到如下输出:

如你所见,从输出结果中以及查看P>|z|列中的p-值,GenderFactorized和EducationFactorized这两个变量似乎与输出变量Engaged之间存在显著关系。如果我们查看这两个变量的系数,可以看到它们与输出变量呈负相关。这表明,与女性客户(在GenderFactorized变量中编码为0)相比,男性客户(在GenderFactorized变量中编码为1)更不可能参与营销电话。同样,客户的教育水平越高,他们参与营销电话的可能性就越低。
我们已经讨论了在pandas中处理分类变量的两种方法,分别是使用factorize和Categorical函数。通过这些技术,我们可以了解不同类别的分类变量与输出变量之间的相关性。
结合连续变量和分类变量
本章我们将进行的最后一个 Python 练习涉及将连续变量和分类变量结合起来进行回归分析。我们可以通过同时使用分类变量和连续变量来拟合逻辑回归模型,代码如下所示:
logit = sm.Logit(
df['Engaged'],
df[['Customer Lifetime Value',
'Income',
'Monthly Premium Auto',
'Months Since Last Claim',
'Months Since Policy Inception',
'Number of Open Complaints',
'Number of Policies',
'Total Claim Amount',
'GenderFactorized',
'EducationFactorized'
]]
)
logit_fit = logit.fit()
与之前的代码唯一不同的是我们选择的特征,用于拟合逻辑回归模型。如你所见,在这段代码中,我们现在使用连续变量以及之前部分章节中创建的两个编码后的分类变量GenderFactorized和EducationFactorized来拟合逻辑回归模型。结果如下所示:

让我们仔细看看这个输出。Income、Monthly Premium Auto、Months Since Last Claim、Months Since Policy Inception、Number of Open Complaints、Number of Policies和GenderFactorized这些变量在0.05的显著性水平下具有显著性,且它们与输出变量Engaged之间的关系都是负相关的。因此,收入越高,客户参与营销电话的可能性就越低。同样,客户拥有的保单越多,他们参与营销电话的可能性就越低。
最后,男性客户比女性客户更不可能参与营销电话,我们可以从GenderFactorized的系数中看到这一点。从这个回归分析的输出结果来看,我们可以轻松看到输入变量与输出变量之间的关系,并且可以理解哪些客户的属性与他们是否参与营销电话之间是正相关还是负相关。
本章中 Python 练习的完整代码可以在 github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.3/python/RegressionAnalysis.ipynb 找到。
使用 R 进行回归分析
在本节中,你将学习如何使用 R 中的 glm 函数进行回归分析。对于那些希望使用 Python 而非 R 进行此练习的读者,Python 的逐步操作说明可以在上一节找到。我们将从更仔细地分析数据开始,使用 dplyr 包,然后讨论如何使用 glm 函数构建回归模型并解读结果。
在本次练习中,我们将使用来自 IBM Watson 的一个公开数据集,可以在 www.ibm.com/communities/analytics/watson-analytics-blog/marketing-customer-value-analysis/ 找到。你可以点击此链接并下载 CSV 格式的数据文件。为了将数据加载到你的 RStudio 中,你可以运行以下代码:
library(dplyr)
library(ggplot2)
# Load data
df <- read.csv(
file="~/Documents/data-science-for-marketing/ch.3/data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv",
header=TRUE,
sep=","
)
类似于我们在 第二章 关键绩效指标与可视化 中所做的那样,我们将在接下来的章节中首先导入 dplyr 和 ggplot2 包,以进行数据分析和绘图。通过在 R 中使用 read.csv 函数,我们可以将数据读取到 DataFrame 中。由于此 CSV 文件的第一行包含标题,且字段由逗号分隔,我们使用 header=TRUE 和 sep="," 标志以确保正确解析数据。
以下截图展示了数据在 DataFrame 中的原始样子:

现在我们已经将数据加载到 DataFrame 中,让我们开始更仔细地查看和分析数据,以便更好地理解数据的结构。
数据分析与可视化
在我们深入回归分析之前,让我们先更详细地查看数据,以便更好地了解我们拥有的数据点以及可以在数据中看到的模式。如果你查看数据,会注意到一个名为 Response 的列,它包含客户是否回应营销电话的信息。我们将使用这个字段作为客户参与度的衡量标准。为了进行未来的计算,最好将这个字段编码为数值类型。让我们来看一下以下代码:
# Encode Response as 0s and 1s
df$Engaged <- as.integer(df$Response) - 1
如您在这段代码中所见,我们使用as.integer函数将未回应营销电话(No)的客户编码为0,将回应营销电话(Yes)的客户编码为1。由于as.integer函数默认将值编码为1和2,我们通过减去1来将回应值编码为零和一。然后,我们创建了一个名为Engaged的新字段,其中包含这些编码后的值。
参与率
我们首先要看的内容是汇总的参与率。这个参与率简单来说就是回应了营销电话的客户所占的百分比。请看以下代码:
engagementRate <- df %>%
group_by(Engaged) %>%
summarise(Count=n()) %>%
mutate(Percentage=Count/nrow(df)*100.0)
如您在这段代码中所见,我们通过使用group_by函数按新创建的Engaged字段进行分组。然后,我们使用n()函数统计每个Engaged组中的记录或客户数量。通过将其除以数据框df中的总客户数,并乘以100.0,我们得到了参与率。结果如下所示:

为了更方便地阅读,我们可以转置数据框,这意味着我们将数据框中的行和列进行交换。您可以使用 R 中的t函数来转置数据框。代码如下所示:
# Transpose
transposed <- t(engagementRate)
colnames(transposed) <- engagementRate$Engaged
transposed <- transposed[-1,]
转置后的数据框如下所示:

如您所见,通过转置数据框,我们可以更容易地查看参与和未参与客户的总数和百分比。从这些数据中,我们可以看到大约 14%的客户回应了营销电话,剩余的 86%的客户未回应。
销售渠道
现在,让我们看看是否能发现销售渠道和参与之间的明显模式。我们将分析参与和未参与的客户在不同销售渠道中的分布。首先请看以下代码:
salesChannel <- df %>%
group_by(Engaged, Channel=Sales.Channel) %>%
summarise(Count=n())
如您在这段代码中所见,我们使用了 R 中的group_by函数对Sales Channel和Engaged变量进行分组。然后,使用n()函数,我们将统计每个组中的客户数量。运行这段代码后,salesChannel数据框将如下所示:

正如您在上一节中所注意到的,未参与营销活动的客户明显更多,因此,单凭原始数字很难比较并查看参与和未参与客户在销售渠道分布上的差异。为了更直观地区分这些差异,我们可以使用以下代码绘制饼图:
# pie chart
ggplot(salesChannel, aes(x="", y=Count, fill=Channel)) +
geom_bar(width=1, stat = "identity", position=position_fill()) +
geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) +
coord_polar("y") +
facet_wrap(~Engaged) +
ggtitle('Sales Channel (0: Not Engaged, 1: Engaged)') +
theme(
axis.title.x=element_blank(),
axis.title.y=element_blank(),
plot.title=element_text(hjust=0.5),
legend.position='bottom'
)
与我们在第二章《关键绩效指标和可视化》中所做的类似,我们使用ggplot来在 R 中构建图表。如果你还记得那一章,我们可以通过使用geom_bar和coord_polar("y")来构建饼图。通过使用facet_wrap(~Engaged),我们可以将饼图分成两部分:一部分是未参与的客户,另一部分是已参与的客户。一旦你运行了这段代码,你会看到如下饼图,展示了已参与和未参与的客户在不同销售渠道中的分布:

与之前展示的每个销售渠道中已参与和未参与客户的原始计数数据表相比,这些饼图可以帮助我们更直观地看到分布差异。如你从这些图表中所看到的,超过一半的已参与客户来自代理商,而未参与的客户则在所有四个销售渠道中分布较为均匀。从这些图表中可以看出,数据分析和可视化帮助我们发现数据中的有趣模式,这将进一步帮助我们在本章后续进行回归分析时。
总赔偿金额
在我们深入回归分析之前,最后要看的内容是Total Claim Amount在已参与和未参与组之间的分布差异。我们将通过箱线图来可视化这一点。首先,我们来看一下如何在 R 中构建箱线图:
ggplot(df, aes(x="", y=Total.Claim.Amount)) +
geom_boxplot() +
facet_wrap(~Engaged) +
ylab("Total Claim Amount") +
xlab("0: Not Engaged, 1: Engaged") +
ggtitle("Engaged vs. Not Engaged: Total Claim Amount") +
theme(plot.title=element_text(hjust=0.5))
正如你在这段代码中所看到的,在 R 中构建箱线图非常简单。你只需调用ggplot函数,并使用geom_boxplot。箱线图是可视化连续变量分布的一个好方法。它显示了最小值、最大值、第一个四分位数、中位数和第三个四分位数,所有这些都能一目了然地呈现。以下箱线图展示了Total Claim Amount在已参与和未参与组之间的分布:

中央矩形从第一个四分位数到第三个四分位数,矩形内的线表示中位数。矩形的上下端点分别表示分布的最小值和最大值。你还会注意到这些箱线图中,线的上方有一些点。
超过上边线的点表示疑似异常值,这些值是基于 IQR(四分位距)来判断的。IQR 是指第一个四分位数和第三个四分位数之间的范围,也就是箱线图中从第一个四分位数到第三个四分位数的矩形高度。数据点如果高于第三个四分位数1.5*IQR或低于第一个四分位数1.5*IQR,就被认为是疑似异常值,并用点表示。
根据你的分析目标,你可能不在乎(或者你可能不想显示)箱线图中的异常值。我们来看一下以下代码,看看如何从箱线图中去除这些异常值:
# without outliers
ggplot(df, aes(x="", y=Total.Claim.Amount)) +
geom_boxplot(outlier.shape = NA) +
scale_y_continuous(limits = quantile(df$Total.Claim.Amount, c(0.1, 0.9))) +
facet_wrap(~Engaged) +
ylab("Total Claim Amount") +
xlab("0: Not Engaged, 1: Engaged") +
ggtitle("Engaged vs. Not Engaged: Total Claim Amount") +
theme(plot.title=element_text(hjust=0.5))
如你在这段代码中所注意到的,这段代码与之前的唯一区别是geom_boxplot函数中的outlier.shape=NA。我们来看一下现在箱线图的样子:

在这些图中,我们不再看到超出上限的点。根据你想要展示和分析的内容,箱线图中是否包含异常值可能会有所不同。
回归分析
到目前为止,我们已经分析了数据中的字段类型以及参与组和非参与组之间的模式差异。接下来,我们将讨论如何使用glm函数进行回归分析并解释结果。我们将首先构建一个包含连续变量的逻辑回归模型,并学习如何解释其结果。然后,我们将讨论在R中拟合回归模型时如何处理分类变量,以及这些分类变量对拟合的逻辑回归模型的影响。
连续变量
在线性回归中,包括逻辑回归,当特征变量是连续变量时,拟合回归模型非常简单,因为它只需要找到特征变量的线性组合来估计输出变量。为了拟合一个包含连续变量的回归模型,我们首先来看一下如何获取R数据框中各列的数据类型。请看以下代码:
# get data types of each column
sapply(df, class)
在R中使用sapply函数,我们可以将class函数应用于数据框的各列,class函数告诉我们每列的数据类型。此代码的输出如下所示:

如前面的截图所示,我们可以轻松看出哪些列包含数值,哪些列不包含数值。例如,State列的类型是"factor",这意味着该变量是一个分类变量。另一方面,Customer.Lifetime.Value列的类型是"numeric",这意味着该变量是一个具有数值的连续变量。除此之外,我们还可以使用R函数summary来获取数据框中每列的汇总统计信息,这样我们不仅可以查看每列的数据类型,还能看到每列的分布概况。代码如下:
# summary statistics per column
summary(df)
当你运行此代码时,输出将如下所示:

在这个输出中,我们可以轻松地看到每列在R数据框中的分布情况。例如,对于State变量,我们可以看到来自Arizona的记录或客户有1703条,来自California的客户有3150条。另一方面,我们可以看到Customer.Lifetime.Value变量的最小值是1898,均值是8005,最大值是83325。
根据前面代码中的信息,我们可以轻松地通过以下代码只选择数值型列:
# get numeric columns
continuousDF <- select_if(df, is.numeric)
colnames(continuousDF)
如你在这段代码中所见,我们使用了select_if函数,传入的参数是数据框df和条件语句is.numeric,用于定义我们希望从数据框中子选的列类型。使用这个函数,数据框df中只有数值型的列会被选中,并作为一个名为continuousDF的单独变量存储。通过colnames函数,我们可以查看新创建的数据框continuousDF中有哪些列。你应该会看到类似以下的输出:

我们现在准备使用连续变量拟合逻辑回归模型。首先让我们看看以下代码:
# Fit regression model with continuous variables
logit.fit <- glm(Engaged ~ ., data = continuousDF, family = binomial)
在 R 中,你可以通过使用glm函数来拟合回归模型,glm代表广义线性模型。R 的glm函数可以用于各种线性模型。默认情况下,family参数的值是gaussian,这会告诉算法拟合一个简单的线性回归模型。另一方面,如我们在此案例中所示,如果将family设置为binomial,则会拟合一个逻辑回归模型。有关family参数可以使用的不同值的详细描述,你可以参考stat.ethz.ch/R-manual/R-devel/library/stats/html/family.html。
我们传递给glm函数的另外两个参数是formula和data。第一个参数formula是定义模型拟合方式的地方。~左侧的变量是输出变量,右侧的变量是输入变量。在我们的案例中,我们告诉模型通过使用其他所有变量作为输入变量来学习如何估计输出变量Engaged。如果你只想使用部分变量作为输入变量,则可以像下面这样编写公式:
Engaged ~ Income + Customer.Lifetime.Value
在这个公式中,我们告诉模型通过仅使用Income和Customer.Lifetime.Value作为特征来学习如何估计输出变量Engaged。最后,glm函数中的第二个参数data定义了用于训练回归模型的数据。
现在我们已经有了一个训练好的逻辑回归模型,让我们来看看以下的代码,它展示了我们如何从这个模型对象中获取详细的回归分析结果:
summary(logit.fit)
R 中的summary函数提供了回归分析结果的详细描述,结果如下所示:

让我们更详细地看看这个输出。Coefficients部分的Estimate列给出了每个特征系数的计算值。例如,Income变量的系数是0.000002042,Number.of.Policies的系数是-0.02443。我们还可以看到估计的Intercept值是-1.787。z value列给出了z-值,它是人口均值的标准差数,而Pr(>|z|)列是p-值,它表示通过偶然性观察到特征与输出变量之间关系的可能性。因此,Pr(>|z|)值越低,给定特征与输出变量之间的关系就越可能是强烈的,而不是偶然的。通常,0.05是p-值的一个良好的分界点,任何小于0.05的值都表示给定特征与输出变量之间存在强关系。
从输出中Coefficients部分下的Signif. codes部分可以看出,***符号位于Coefficients部分的p-值旁边,表示p-值为0时的最强关系;**表示p-值小于0.001;*表示p-值小于0.05,以此类推。如果你再看一下回归分析输出,只有三个变量——Income、Number.of.Policies和Total.Claim.Amount——在0.1的显著性水平下与输出变量Engaged有显著的关系。此外,我们可以看到,Income和Total.Claim.Amount与Engaged呈正相关,意味着收入越高或总索赔金额越高,客户与营销电话互动的可能性就越大。另一方面,Number.of.Policies与Engaged呈负相关,这表明客户拥有的保单数量越多,客户参与营销电话的可能性就越小。
正如你在这些示例中看到的,通过查看模型输出中各特征的p-值和系数,你可以很容易地解释回归分析结果。这是一种很好地理解哪些客户属性与感兴趣的结果有显著和高度相关性的方法。
类别变量
如您在上一节关于连续变量的案例中看到的那样,从系数和p-值中理解输入变量与输出变量之间的关系是非常直接的。然而,当我们引入分类变量时,这就变得不那么直观了。分类变量通常没有自然的顺序,但在进行线性回归时,我们需要输入变量具有数值,这些数值表示变量的顺序或大小。例如,我们不能轻易地给我们的数据集中的State变量编码特定的顺序或数值。这就是为什么在进行回归分析时,我们需要与连续变量不同地处理分类变量。在 R 语言中,factor函数帮助您轻松处理这些分类变量,以便进行回归分析。请看以下代码:
# a. Education
# Fit regression model with Education factor variables
logit.fit <- glm(Engaged ~ factor(Education), data = df, family = binomial)
summary(logit.fit)
如您在这段代码中所见,我们正在拟合一个以Engaged作为输出变量、以因子化的Education作为输入变量的逻辑回归模型。在我们深入了解这意味着什么之前,先让我们看看以下的回归分析结果:

如您在此输出中所见,factor函数创建了四个额外的变量:factor(Education)College、factor(Education)Doctor、factor(Education)High School or Below和factor(Education)Master。如果给定的客户不属于某一类别,这些变量会被编码为0;如果客户属于某一类别,这些变量则会被编码为1。这样,我们就可以理解每个Education类别与output变量Engaged之间的正向或负向关系。例如,因子变量factor(Education)Doctor具有正系数,这表明如果一个客户拥有博士学位,那么该客户更可能参与营销电话。
如果仔细观察,您会发现这个输出中,Education变量的Bachelor类别没有单独的因子变量。这是因为(Intercept)包含了Bachelor类别的信息。如果一个客户拥有学士学位,那么所有其他的因子变量都会被编码为0。因此,所有系数值都会被抵消,只有(Intercept)的值保留。由于估计的(Intercept)值为负数,如果一个客户拥有学士学位,那么该客户参与营销电话的可能性较低。
让我们来看另一个例子:
# b. Education + Gender
# Fit regression model with Education & Gender variables
logit.fit <- glm(Engaged ~ factor(Education) + factor(Gender), data = df, family = binomial)
summary(logit.fit)
如您在这段代码中所见,我们现在正在拟合一个包含Education和Gender变量的回归模型,输出结果如下:

如果仔细查看此输出,您只会看到一个额外的因子变量 factor(Gender)M,它表示男性客户,而数据中明显包含女性客户。这是因为 Education 变量中的 Bachelor 类别和 Gender 变量中的 F(女性)类别被归为该回归模型的 (Intercept)。因此,当所有因子变量的值为 0 时,基本情况是具有 Bachelor 学位的 female 客户。
对于具有 Bachelor 学位的男性客户,因子变量 factor(Gender)M 将具有值 1,因此,输出变量 Engaged 的估计值将是 (Intercept) 加上 factor(Gender)M 的系数值。
如我们到目前为止所讨论的那样,我们可以通过在 R 中使用 factor 函数来处理分类变量。它本质上与为每个分类变量的每个类别创建一个单独的输入变量相同。使用这种技术,我们可以理解不同类别的分类变量如何与输出变量相关联。
结合连续变量和分类变量
本章我们要做的最后一个练习涉及将连续变量和分类变量结合起来进行回归分析。我们首先将前一节中讨论的两个分类变量 Gender 和 Education 进行因子化,并通过以下代码将它们存储在一个数据框中:
continuousDF$Gender <- factor(df$Gender)
continuousDF$Education <- factor(df$Education)
现在,数据框 continuousDF 包含以下列:

现在,我们将使用以下代码来拟合一个包含分类变量和连续变量的逻辑回归模型:
# Fit regression model with Education & Gender variables
logit.fit <- glm(Engaged ~ ., data = continuousDF, family = binomial)
summary(logit.fit)
您应该得到如下输出:

让我们更仔细地查看此输出。Total.Claim.Amount 变量和 EducationDoctor 变量在 0.05 显著性水平下显著,并且它们与输出变量 Engaged 存在正相关关系。因此,理赔总金额越高,客户与营销电话互动的可能性越大。此外,博士学位的客户比其他教育背景的客户更可能参与营销电话。在 0.1 显著性水平下,我们可以看到 Income、Number.of.Policies 和 EducationMaster 现在与输出变量 Engaged 存在显著关系。从这个回归分析输出中,我们可以轻松看到输入变量和输出变量之间的关系,并理解哪些客户属性与客户对营销电话的互动正相关或负相关。
R 练习的完整代码可以在这个仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.3/R/RegressionAnalysis.R。
总结
本章中,我们讨论了如何使用解释性分析来洞察客户行为。我们探讨了回归分析如何用于深入理解客户行为。更具体地,你学习了如何使用逻辑回归来理解哪些客户属性会推动更高的互动率。在 Python 和 R 的练习中,我们运用了在第二章《关键绩效指标与可视化》中讨论的描述性分析方法,并结合回归分析进行解释性分析。我们从分析数据开始,以便更好地理解和识别数据中的明显模式。在分析数据时,你还学习了通过箱线图(box plot)来可视化数据,使用 Python 中的matplotlib和pandas包,以及 R 中的ggplot2库。
在拟合回归模型时,我们讨论了两种不同类型的变量:连续变量和分类变量。你学习了在拟合逻辑回归模型时,处理分类变量所面临的挑战,以及如何处理这些变量。对于 Python,我们介绍了处理分类变量的两种方法:factorize和Categorical函数,均来自pandas包。对于 R,我们讨论了如何在拟合逻辑回归模型时使用factor函数处理分类变量。通过回归分析结果,我们展示了如何通过查看系数和p-值来解释结果和输入输出变量之间的关系。通过查看回归分析输出,我们可以了解哪些客户属性与客户营销互动之间存在显著关系。
在下一章,我们将扩展你对解释性分析的知识。我们将分析客户互动后,哪些因素推动了转化。你还将学习另一种机器学习算法——决策树,以及如何将其应用于解释性分析。
第四章:从参与到转化
在本章中,我们将扩展你对说明性分析的知识,并向你展示如何使用决策树来理解消费者行为的驱动因素。我们将从比较和解释逻辑回归和决策树模型之间的区别开始,然后我们将讨论决策树是如何构建和训练的。接下来,我们将讨论如何使用训练好的决策树模型来提取有关单个消费者属性(或特征)与目标输出变量之间关系的信息。
在编程练习中,我们将使用 UCI 机器学习库中的银行营销数据集来理解转化的驱动因素。我们将从数据分析开始,以便你更好地理解数据集;然后,我们将使用 Python 中的scikit-learn包和 R 中的rpart包构建决策树模型。最后,你将学会如何通过 Python 中的graphviz包和 R 中的rattle包来可视化这些训练过的决策树模型,从而理解它们的解读方式。到本章结束时,你将熟悉决策树,并且更好地理解何时以及如何使用 Python 或 R 来应用它们。
在本章中,我们将讨论以下主题:
-
决策树
-
决策树与 Python 的解释
-
决策树与 R 的解释
决策树
在上一章中,我们讨论了说明性分析和回归分析。我们将继续这个主题,并介绍另一个机器学习算法,利用它可以从数据中提取客户行为的洞察。在本章中,我们将讨论一种机器学习算法——决策树:它是如何从数据中学习的,以及我们如何解读它们的结果。
逻辑回归与决策树的对比
如果你还记得上一章的内容,逻辑回归模型通过找到特征变量的线性组合来学习数据,从而最好地估计事件发生的对数几率。顾名思义,决策树通过生长一棵树来学习数据。我们将在接下来的章节中详细讨论决策树模型是如何生长的以及如何构建树,但逻辑回归和决策树模型之间的主要区别在于:逻辑回归算法在特征集中搜索一个最佳的线性边界,而决策树算法则通过划分数据来找到发生事件可能性较高的数据子群体。通过一个例子来解释会更容易。让我们来看一下以下图示:

这是一个决策树模型的示例。如你在这个图中所见,它通过某些标准来划分数据。在这个例子中,根节点通过 previous < 0.5 这个标准划分成子节点。如果这个条件成立且为真,那么它会遍历到左子节点。如果不成立,它则遍历到右子节点。左子节点随后通过 age < 61 这个标准进一步划分。树会一直生长,直到找到纯净的节点(即每个节点中的所有数据点都属于同一类)或满足某些停止标准,例如树的最大深度。
如你所见,在这个例子中,数据被划分为七个分区。最左边的节点或分区是针对那些 previous 变量值小于 0.5 且 age 变量值小于 61 的数据点。另一方面,最右边的节点位于底部,针对的是那些 previous 变量值大于 0.5 且 housing 变量值不是 yes 的数据点。
这里需要注意的一点是,不同变量之间有很多交互作用。在这个示例树中,没有任何一个叶节点是通过一个条件来划分的。树中的每个分区都是通过多个标准以及不同feature变量之间的交互作用来形成的。这与逻辑回归模型的主要区别在于,逻辑回归模型在数据中没有线性结构时,表现会很差,因为它们试图在特征变量之间找到线性组合。另一方面,决策树模型对于非线性数据集表现更好,因为它们仅仅是尝试在最纯净的水平上划分数据。
构建决策树
在构建决策树时,树需要提出逻辑来将一个节点划分为子节点。通常用于数据划分的两种主要方法是:Gini 不纯度和熵信息增益。简而言之,Gini 不纯度度量一个分区的不纯度,而熵信息增益度量的是根据所测试的标准划分数据后,所获得的信息量。
让我们快速看一下计算 Gini 不纯度度量的公式:

这里,c 代表类标签,P[i] 代表记录被选择时属于类标签 i 的概率。通过从 1 中减去概率的平方和,Gini 不纯度度量达到零,即当树的每个分区或节点中的所有记录都是纯净的,且只有一个目标类时。
计算 熵 的公式如下:

和之前一样,c代表类标签,P[i]代表记录具有类标签i被选择的概率。构建树时,需要计算每个可能分割的熵,并与分割前的熵进行比较。然后,选择熵度量变化最大或信息增益最高的分割来继续构建树。这个过程将重复,直到所有节点都是纯净的,或满足停止标准。
使用 Python 进行决策树构建与解释
在本节中,你将学习如何使用 Python 的scikit-learn包构建决策树模型,并通过 Python 的graphviz包进行可视化解释结果。对于那些希望使用 R 而非 Python 进行此练习的读者,你可以跳过到下一节。我们将从分析银行营销数据集开始,使用pandas和matplotlib包进行深入分析,随后讨论如何构建和解释决策树模型。
对于这个练习,我们将使用 UCI 机器学习库中的一个公开数据集,地址为archive.ics.uci.edu/ml/datasets/bank+marketing。你可以通过链接下载 ZIP 格式的数据。我们将使用bank.zip文件进行本次练习。当你解压缩该文件时,你会看到两个 CSV 文件:bank.csv和bank-full.csv。我们将在此次 Python 练习中使用bank-full.csv文件。
为了将数据加载到 Jupyter Notebook 中,你可以运行以下代码:
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
df = pd.read_csv('../data/bank-full.csv', sep=";")
从这段代码片段可以看出,我们使用%matplotlib inline命令在 Jupyter Notebook 中显示图表。接下来,我们导入了用于数据分析步骤的matplotlib和pandas包。最后,我们可以通过使用pandas包中的read_csv函数轻松读取数据文件。这里需要注意的是read_csv函数中的sep参数。如果仔细查看数据,你会发现bank-full.csv文件中的字段是由分号(;)分隔的,而不是逗号(,)。为了正确地将数据加载到pandas数据框中,我们需要告诉read_csv函数使用分号作为分隔符,而不是逗号。
一旦加载了数据,它应该看起来像下图所示:

数据分析与可视化
在我们开始分析数据之前,我们将首先对输出变量y进行编码,y包含关于客户是否已转化或订阅定期存款的信息,使用数值表示。你可以使用以下代码将输出变量y编码为零和一:
df['conversion'] = df['y'].apply(lambda x: 0 if x == 'no' else 1)
从这段代码片段可以看出,你可以使用apply函数对输出变量进行编码。我们将这些编码后的值存储在一个新列中,命名为conversion。
转化率
首先,让我们看一下总的转化率。转化率是指订阅定期存款的客户所占的百分比。请看以下代码:
conversion_rate_df = pd.DataFrame(
df.groupby('conversion').count()['y'] / df.shape[0] * 100.0
)
从这段代码中你可以看到,我们按conversion列进行分组,conversion列用1表示那些已经订阅定期存款的客户,0表示那些没有订阅的客户。然后,我们计算每个组别中的客户数量,并将其除以数据集中客户的总数。结果如下所示:

为了更容易查看,你可以通过使用pandas DataFrame 的T属性来转置数据框。正如你所看到的,只有大约 11.7%的客户进行了转化或订阅了定期存款。从这些结果来看,我们可以发现转化组和非转化组之间存在较大的不平衡,这种情况在各种营销数据集中是常见的,并且经常出现。
按工作类别的转化率
可能某些工作类别的转化率确实比其他类别更高。让我们看一下不同工作类别之间的转化率。你可以通过以下代码来实现:
conversion_rate_by_job = df.groupby(
by='job'
)['conversion'].sum() / df.groupby(
by='job'
)['conversion'].count() * 100.0
让我们深入了解一下这段代码。我们首先按job列进行分组,job列包含了每个客户所属的工作类别信息。然后,我们对每个工作类别的conversion列进行求和,从中得到每个工作类别的转化总数。最后,我们将这些转化数除以每个工作类别的客户总数,从而得到每个工作类别的转化率。
结果如下所示:

从这些结果中你可以看到,student组的转化率明显高于其他组,而retired组排在其后。然而,从原始输出中进行比较有些困难,我们可以通过使用图表来更好地展示这些数据。我们可以通过以下代码构建一个水平条形图:
ax = conversion_rate_by_job.plot(
kind='barh',
color='skyblue',
grid=True,
figsize=(10, 7),
title='Conversion Rates by Job'
)
ax.set_xlabel('conversion rate (%)')
ax.set_ylabel('Job')
plt.show()
如果你查看这段代码,你会发现我们正在使用pandas DataFrame 的plot函数,并通过将barh作为kind参数的输入,定义了图表的类型为水平条形图。你可以轻松调整图表的颜色、大小和标题,分别使用color、figsize和title参数。你还可以通过set_xlabel和set_ylabel函数轻松更改x轴和y轴的标签。
生成的图表如下所示:

如你所见,使用水平条形图可以更容易地看到不同工作类别之间的转化率差异。我们可以清楚地看到,student和retired组是转化率最高的两个组,而blue-collar和entrepreneur组则是转化率最低的两个组。
按转换情况划分的违约率
另一个值得关注的客户属性是违约率,看看订阅定期存款的客户与未订阅的客户之间的违约率差异。我们将使用pandas库中的pivot_table函数来分析按转换情况划分的违约率。让我们来看一下下面的代码:
default_by_conversion_df = pd.pivot_table(
df,
values='y',
index='default',
columns='conversion',
aggfunc=len
)
如你所见,这段代码中,我们通过y和default列对数据框df进行透视。通过使用len作为聚合函数,我们可以计算每个透视表单元格下的客户数量。结果如下所示:

仅凭这些原始数据,很难比较转换组和非转换组之间的违约率差异。通过饼图来可视化这些数据是一种可行的方式。你可以使用以下代码来生成饼图:
default_by_conversion_df.plot(
kind='pie',
figsize=(15, 7),
startangle=90,
subplots=True,
autopct=lambda x: '%0.1f%%' % x
)
plt.show()
如你所见,这段代码中,我们只是将'pie'作为输入传递给plot函数的kind参数。生成的饼图如下所示:

从这些饼图中可以看出,比较转换组和非转换组的违约率要容易得多。尽管两组的总体违约率都较低,但非转换组的违约率大约是转换组的两倍。
按转换情况划分的银行余额
接下来,我们将尝试查看转换组和非转换组之间银行余额分布是否存在差异。箱型图通常是可视化变量分布的好方法。让我们来看一下下面的代码:
ax = df[['conversion', 'balance']].boxplot(
by='conversion',
showfliers=True,
figsize=(10, 7)
)
ax.set_xlabel('Conversion')
ax.set_ylabel('Average Bank Balance')
ax.set_title('Average Bank Balance Distributions by Conversion')
plt.suptitle("")
plt.show()
你应该已经熟悉这段代码,因为我们已经讨论了如何使用pandas包构建箱型图。通过使用boxplot函数,我们可以轻松构建如下的箱型图:

由于存在许多异常值,很难发现两个分布之间的差异。让我们构建一个不包含异常值的箱型图。你需要做的唯一更改就是在boxplot函数中将showfliers=True,如下所示:
ax = df[['conversion', 'balance']].boxplot(
by='conversion',
showfliers=False,
figsize=(10, 7)
)
ax.set_xlabel('Conversion')
ax.set_ylabel('Average Bank Balance')
ax.set_title('Average Bank Balance Distributions by Conversion')
plt.suptitle("")
plt.show()
使用这段代码,你将看到如下的两个组别银行余额分布箱型图:

从这些箱型图中,我们可以看到,相比非转换组,转换组的银行余额中位数略高。此外,转换客户的银行余额波动似乎比非转换客户更大。
按联系次数划分的转换率
最后,我们将看看转化率如何随联系方式的数量变化。通常在营销中,更多的营销接触可能会导致营销疲劳,即随着您更频繁地联系客户,转化率下降。让我们看看我们的数据中是否存在营销疲劳。请查看以下代码:
conversions_by_num_contacts = df.groupby(
by='campaign'
)['conversion'].sum() / df.groupby(
by='campaign'
)['conversion'].count() * 100.0
在这段代码中,您可以看到我们是通过campaign列(该列包含了此客户在营销活动中进行的联系方式数量的信息)进行分组,并计算每个联系方式数量的转化率。结果数据如下所示:

和之前一样,查看图表比直接查看原始数据更容易。我们可以使用以下代码通过柱状图绘制这些数据:
ax = conversions_by_num_contacts.plot(
kind='bar',
figsize=(10, 7),
title='Conversion Rates by Number of Contacts',
grid=True,
color='skyblue'
)
ax.set_xlabel('Number of Contacts')
ax.set_ylabel('Conversion Rate (%)')
plt.show()
图表如下所示:

在联系方式数量较高的情况下会有一些噪声,因为这些样本的数量较少,但从这张柱状图中您可以很容易看到整体的下降趋势。随着联系方式数量的增加,转化率缓慢下降。这表明,在给定的营销活动中,随着您与客户的联系更频繁,预期的转化率会下降。
编码类别变量
该数据集包含八个类别变量:job、marital、education、default、housing、loan、contact和month。在我们开始构建决策树之前,需要将这些类别变量编码为数值。在本节中,我们将展示如何编码一些类别变量。
编码月份
我们都知道,month变量只能有 12 个唯一值。让我们快速看看数据集中有哪些值。查看以下代码:
df['month'].unique()
pandas函数unique帮助您快速获取给定列中的唯一值。运行此代码后,您将看到以下输出:

如预期的那样,month列中有 12 个唯一值,从一月到十二月。由于month值之间有自然的顺序关系,我们可以用相应的数字对每个值进行编码。以下是将month的字符串值编码为数字的一种方式:
months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
df['month'] = df['month'].apply(
lambda x: months.index(x)+1
)
使用这段代码,month列的唯一值如下所示:

为了查看每个月的记录数,我们可以使用以下代码:
df.groupby('month').count()['conversion']
结果如下所示:

编码作业
接下来,让我们看看如何编码job列中的不同类别。我们首先使用以下代码查看该列中的唯一值:
df['job'].unique()
job列中的唯一值如下所示:

从这个输出中可以看出,该变量没有自然的顺序。一个 job 类别并不先于另一个,因此我们不能像对待 month 变量那样对该变量进行编码。我们将为每个 job 类别创建虚拟变量。如果你还记得上一章的内容,虚拟变量是一个变量,如果给定记录属于该类别,则编码为 1,否则编码为 0。我们可以使用以下代码轻松完成此操作:
jobs_encoded_df = pd.get_dummies(df['job'])
jobs_encoded_df.columns = ['job_%s' % x for x in jobs_encoded_df.columns]
从这段代码中可以看出,pandas 包中的 get_dummies 函数为 job 变量中的每个类别创建一个虚拟变量,并且如果给定记录属于相应的类别,则编码为 1,如果不属于则编码为 0。然后,我们通过在每个列名前加上 job_ 前缀来重命名列。结果如下所示:

从这张截图中可以看出,第一条记录(或客户)属于 management 工作类别,而第二条记录属于 technician 工作类别。现在我们已经为每个工作类别创建了虚拟变量,我们需要将这些数据附加到现有的 DataFrame 中。请看下面的代码:
df = pd.concat([df, jobs_encoded_df], axis=1)
df.head()
使用 pandas 包中的 concat 函数,您可以轻松地将新创建的包含虚拟变量的 DataFrame jobs_encoded_df 添加到原始 DataFrame df 中。axis=1 参数告诉 concat 函数将第二个 DataFrame 作为列连接到第一个 DataFrame,而不是作为行连接。结果的 DataFrame 如下所示:

如您所见,新创建的虚拟变量被添加到原始 DataFrame 中,作为每条记录的新列。
对婚姻状况进行编码
类似于我们如何对类别变量 job 进行编码,我们将为 marital 变量的每个类别创建虚拟变量。与之前一样,我们使用以下代码对 marital 列进行编码:
marital_encoded_df = pd.get_dummies(df['marital'])
marital_encoded_df.columns = ['marital_%s' % x for x in marital_encoded_df.columns]
编码结果如下:

如您所见,为原始变量 marital 创建了三个新变量:marital_divorced、marital_married 和 marital_single,分别表示客户是否离婚、已婚或单身。为了将这些新创建的虚拟变量添加到原始 DataFrame 中,我们可以使用以下代码:
df = pd.concat([df, marital_encoded_df], axis=1)
到此为止,您的原始 DataFrame df 应该包含所有原始列,以及为 job 和 marital 列新创建的虚拟变量。
对住房和贷款变量进行编码
本节我们将要编码的最后两个分类变量是housing和loan。housing变量有两个独特的值,'yes'和'no',它包含关于客户是否有住房贷款的信息。另一个变量loan也有两个独特的值,'yes'和'no',它告诉我们客户是否有个人贷款。我们可以通过以下代码轻松编码这两个变量:
df['housing'] = df['housing'].apply(lambda x: 1 if x == 'yes' else 0)
df['loan'] = df['loan'].apply(lambda x: 1 if x == 'yes' else 0)
如你所见,我们正在使用apply函数将yes编码为1,将no编码为0,用于housing和loan两个变量。对于本节未讨论的其他分类变量,如果你希望深入探索,可以使用我们讨论过的相同技术来对其进行编码。
构建决策树
现在我们已经编码了所有分类变量,终于可以开始构建决策树模型了。我们将使用以下变量作为决策树模型的特征:

为了使用 Python 构建和训练一个决策树模型,我们将使用scikit-learn(sklearn)包中的tree模块。你可以通过以下代码导入所需的模块:
from sklearn import tree
在sklearn包的tree模块下,有一个名为DecisionTreeClassifier的类,我们可以使用它来训练决策树模型。请看以下代码:
dt_model = tree.DecisionTreeClassifier(
max_depth=4
)
除了我们在这里使用的max_depth参数之外,DecisionTreeClassifier类还有许多其他参数。max_depth参数控制决策树的最大深度,在这里我们将其限制为4,意味着从根节点到叶节点的最大距离为4。你还可以使用criterion参数在基尼不纯度和信息增益的熵度量之间进行选择,这用于衡量分裂的质量。还有许多其他方法可以调整你的决策树模型,我们建议你查阅scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html的文档以获取更多信息。
为了训练这个决策树模型,你可以使用以下代码:
dt_model.fit(df[features], df[response_var])
如你从这段代码中看到的,fit函数接受两个参数:predictor或feature变量和response或target变量。在我们的例子中,response_var是 DataFrame df中的conversion列。运行这段代码后,决策树模型将学习如何进行分类。在接下来的部分中,我们将讨论如何解读训练好的决策树模型的结果。
解释决策树
现在我们已经训练了一个决策树模型,接下来需要从模型中提取洞察。在这一部分,我们将使用一个名为graphviz的包。你可以通过在终端中使用以下命令来安装此包:
conda install python-graphviz
一旦正确安装了这个包,你应该能够按如下方式导入该包:
import graphviz
现在,我们已经使用新包graphviz设置好了环境,接下来让我们看看以下代码,了解如何可视化训练好的决策树:
dot_data = tree.export_graphviz(
dt_model,
feature_names=features,
class_names=['0', '1'],
filled=True,
rounded=True,
special_characters=True
)
graph = graphviz.Source(dot_data)
如你所见,我们首先使用sklearn包中的tree模块的export_graphviz函数导出了训练好的决策树模型dt_model。我们可以通过feature_names参数定义用于训练该模型的特征变量。然后,我们可以定义该模型被训练用于分类的类别(转换与非转换)。export_graphviz函数将训练好的决策树模型以 DOT 格式导出,DOT 是一种图形描述语言。然后,你可以将dot_data传递给graphviz的Source类。graph变量现在包含了一个可渲染的图。根节点及其直接子节点如下所示:

左半部分的树(或根节点左子节点的子节点)如下所示:

右半部分的树(或根节点右子节点的子节点)如下所示:

让我们仔细看看这个图。每个节点包含五行信息,描述了该节点的相关信息。第一行告诉我们分裂的标准。例如,根节点是基于previous变量的值进行分裂的。如果previous变量的值小于或等于0.5,则它会进入左子节点。另一方面,如果previous变量的值大于0.5,则它会进入右子节点。
第二行告诉我们分裂的质量度量值。在这里,我们选择了gini不纯度作为标准,因此我们可以在第二行中看到每个节点中不纯度度量值的变化。第三行告诉我们属于该节点的记录总数。例如,根节点中有45,211个样本,根节点的右子节点中有8,257个样本。
每个节点的第四行告诉我们两个不同类别中的记录组成。第一个元素表示非转化组中的记录数,第二个元素表示转化组中的记录数。例如,在根节点中,非转化组有39,922条记录,转化组有5,289条记录。最后,每个节点的第五行告诉我们该节点的预测或分类结果是什么。例如,如果一个样本属于最左侧的叶节点,那么该决策树模型的分类结果将是0,即非转化。另一方面,如果一个样本属于从左数第八个叶节点,那么该决策树模型的分类结果将是1,即转化。
现在我们知道了每个节点中的每一行意味着什么,让我们来讨论如何从这棵树图中提取洞见。为了理解属于每个叶节点的客户,我们需要沿着树走一遍。例如,那些属于从左数第八个叶节点的客户是previous变量值为0,age大于60.5,marital_divorced变量值为1,以及job_self-employed变量值为1的客户。换句话说,之前未曾联系过、年龄大于60.5、已离婚并且是自雇人士的客户属于这个节点,并且他们有较高的转化几率。
让我们来看另一个例子。那些属于从右数第二个叶节点的客户是previous变量值为1、housing变量值为1、age大于60.5、并且balance小于或等于4,660.5的客户。换句话说,之前曾联系过、拥有住房贷款、年龄大于60.5并且银行存款少于4,660.5的客户属于这个节点,并且其中20个中的29个客户已经转化并订阅了定期存款。
正如你从这两个例子中会注意到的,通过可视化训练好的决策树,你可以从中获得关于谁更可能或更不可能转化的有用洞见。你只需要沿着树的节点走一遍,并理解哪些属性与目标类别高度相关。为了这次练习,我们将树的深度限制为4,但是你可以选择将树生长得比我们在本次练习中使用的更大或更小。
本章节的 Python 练习完整代码可以在仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.4/python/From%20Engagement%20to%20Conversions.ipynb。
使用 R 语言的决策树及其解释
在本节中,您将学习如何使用 R 中的rpart包构建决策树模型,并通过 R 的rattle包通过可视化来解释结果。对于那些希望使用 Python 而不是 R 进行练习的读者,您可以参考上一节中的 Python 示例。我们将通过使用dplyr和ggplot2库深入分析银行营销数据集来开始本节,然后我们将讨论如何构建和解释决策树模型。
对于这个练习,我们将使用来自 UCI 机器学习库的一个公开数据集,您可以在archive.ics.uci.edu/ml/datasets/bank+marketing找到。您可以访问该链接并下载 ZIP 格式的数据。我们将使用bank.zip文件进行此练习。当您解压这个文件时,您会看到两个 CSV 文件:bank.csv和bank-full.csv。我们将使用bank-full.csv文件进行此练习。
为了将这些数据加载到您的 RStudio 中,您可以运行以下代码:
df <- read.csv(
file="../data/bank-full.csv",
header=TRUE,
sep=";"
)
正如您从这段代码中看到的,我们可以通过使用 R 中的read.csv函数轻松读取数据文件。需要注意的一点是read.csv函数中的sep参数。如果仔细查看数据,您会发现bank-full.csv文件中的字段是由分号(;)而不是逗号(,)分隔的。为了正确加载数据到 DataFrame 中,我们需要告诉read.csv函数使用分号作为分隔符,而不是逗号。
一旦您加载了这些数据,它应该看起来像以下截图:

数据分析和可视化
在开始分析数据之前,我们将首先对输出变量y进行编码,该变量包含关于客户是否已转换或订阅定期存款的信息,并用数值表示。您可以使用以下代码将输出变量y编码为零和一:
# Encode conversions as 0s and 1s
df$conversion <- as.integer(df$y) - 1
正如您从这段代码中看到的,您可以使用as.integer函数对输出变量进行编码。由于该函数会将y变量中的no值编码为1,而y变量中的yes值编码为2,我们通过减去1来将其编码为0和1,分别存储这些编码值到一个新列中,名为conversion。
转化率
我们将首先关注的是汇总的转化率。转化率简单地是那些订阅了定期存款的客户的百分比,或者那些在conversion列中被编码为1的客户。请查看以下代码:
sprintf("conversion rate: %0.2f%%", sum(df$conversion)/nrow(df)*100.0)
正如您从这段代码中看到的,我们只是将conversion列中的所有值相加,并除以 DataFrame df中的记录或客户的数量。使用sprintf函数,我们将此转化率数字格式化为两位小数。结果如下:

从这个输出中可以看到,只有约11.7%的客户转化或订阅了定期存款。从这些结果中,我们可以看到转化组和非转化组之间存在较大不平衡,这在各种营销数据集中是常见的现象。
按职业分类的转化率
可能确实存在某些职业类别的转化率比其他职业类别更高的情况。让我们看看不同职业类别之间的转化率。你可以通过运行以下代码来实现:
conversionsByJob <- df %>%
group_by(Job=job) %>%
summarise(Count=n(), NumConversions=sum(conversion)) %>%
mutate(ConversionRate=NumConversions/Count*100.0)
让我们更详细地看一下这段代码。我们首先根据job列进行分组,该列包含每个客户所属的职业类别信息。然后,我们使用n()函数计算每个职业类别中的客户总数,并使用sum函数对每个职业类别的conversion列进行求和。最后,我们将转化总数NumConversion除以每个职业类别中的客户总数Count,并将结果乘以100.0,以计算每个职业类别的转化率。
结果如下所示:

从这些结果可以看出,student组的转化频率明显高于其他组,而retired组排在其后。然而,直接比较这些数据和原始输出有些困难,我们可以通过使用图表更好地呈现这些数据。我们可以使用以下代码构建一个水平条形图:
ggplot(conversionsByJob, aes(x=Job, y=ConversionRate)) +
geom_bar(width=0.5, stat="identity") +
coord_flip() +
ggtitle('Conversion Rates by Job') +
xlab("Job") +
ylab("Conversion Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
如果你查看这段代码,我们使用了ggplot和geom_bar函数,利用conversionsByJob数据(我们在之前的代码中构建的)生成条形图,并将Job变量放在X轴,将ConversionRate变量放在Y轴。然后,我们使用coord_flip函数将垂直条形图转换为水平条形图。你可以使用ggtitle、xlab和ylab函数来更改标题、X轴标签和Y轴标签。
结果图表如下所示:

正如你所看到的,使用水平条形图更容易观察到不同职业类别之间的转化率差异。我们可以清晰地看到,student和retired组是转化率最高的两个组,而blue-collar和entrepreneur组则是转化率最低的两个组。
按转化率分类的违约率
另一个值得关注的客户属性是违约率,以及已订阅定期存款和未订阅定期存款的客户之间的差异。让我们看一下以下的 R 代码:
defaultByConversion <- df %>%
group_by(Default=default, Conversion=conversion) %>%
summarise(Count=n())
从这段代码中可以看到,我们使用 group_by 函数将 DataFrame df 按 default 和 conversion 两列进行分组。通过使用 n() 作为聚合函数,我们可以统计每个四个情况中的客户数量。让我们来看一下以下结果:

从这些原始数据来看,比较转化组和非转化组之间的默认率差异有点困难。可视化这个数据的一种方法是通过饼图。你可以使用以下代码来构建饼图:
ggplot(defaultByConversion, aes(x="", y=Count, fill=Default)) +
geom_bar(width=1, stat = "identity", position=position_fill()) +
geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) +
coord_polar("y") +
facet_wrap(~Conversion) +
ggtitle('Default (0: Non Conversions, 1: Conversions)') +
theme(
axis.title.x=element_blank(),
axis.title.y=element_blank(),
plot.title=element_text(hjust=0.5),
legend.position='bottom'
)
如你所见,我们在这里使用了三个函数:ggplot、geom_bar 和 coord_polar("y")。通过使用 coord_polar("y") 函数,我们可以从条形图生成饼图。然后,我们可以使用 facet_wrap 函数将其拆分成两个饼图:一个是转化组,另一个是非转化组。
看看下面的饼图:

从这些饼图中,你可以更容易地比较转化组和非转化组之间的默认率。尽管两个组中以往的默认比例都较低,但非转化组的默认率大约是转化组的两倍。
按转化次数划分的银行余额
接下来,我们将尝试查看转化组和非转化组之间的银行余额分布是否存在差异。箱线图通常是一种很好的可视化变量分布的方式。我们来看一下下面的代码:
ggplot(df, aes(x="", y=balance)) +
geom_boxplot() +
facet_wrap(~conversion) +
ylab("balance") +
xlab("0: Non-Conversion, 1: Conversion") +
ggtitle("Conversion vs. Non-Conversions: Balance") +
theme(plot.title=element_text(hjust=0.5))
你现在应该已经熟悉这段代码了,因为我们在上一章讨论过如何使用 ggplot 和 geom_boxplot 函数构建箱线图。当你运行这段代码时,你将看到以下箱线图:

由于有很多异常值,识别两组分布之间的差异变得相当困难。让我们构建另一个没有异常值的箱线图。你只需要修改上一段代码中的 outlier.shape = NA 参数,如下所示:
ggplot(df, aes(x="", y=balance)) +
geom_boxplot(outlier.shape = NA) +
scale_y_continuous(limits = c(-2000, 5000)) +
facet_wrap(~conversion) +
ylab("balance") +
xlab("0: Non-Conversion, 1: Conversion") +
ggtitle("Conversion vs. Non-Conversions: Balance") +
theme(plot.title=element_text(hjust=0.5))
使用这段代码,你将看到以下关于两组银行余额分布的箱线图:

从这些箱线图中,我们可以看到转化组的银行余额中位数略高于非转化组。此外,转化客户的银行余额似乎比非转化客户的余额波动更大。
按联系人数量划分的转化率
最后,我们将查看转化率如何随着联系人数的变化而变化。通常在营销中,较高的营销联系人数可能导致营销疲劳,即当你更频繁地联系客户时,转化率会下降。让我们看看我们的数据中是否存在营销疲劳现象。请查看以下代码:
conversionsByNumContacts <- df %>%
group_by(Campaign=campaign) %>%
summarise(Count=n(), NumConversions=sum(conversion)) %>%
mutate(ConversionRate=NumConversions/Count*100.0)
从这段代码中可以看到,我们是通过 campaign 列(该列包含了在该营销活动中对该客户进行的联系次数信息)进行分组,并计算每个联系次数对应的转化率。结果数据如下所示:

和之前一样,查看图表比查看原始数据更为直观。我们可以使用以下代码通过柱状图来绘制这些数据:
ggplot(conversionsByNumContacts, aes(x=Campaign, y=ConversionRate)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('Conversion Rates by Number of Contacts') +
xlab("Number of Contacts") +
ylab("Conversion Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
绘图结果如下所示:

在较高联系次数的情况下,由于样本量较小,会有一些噪声,但从这张柱状图中可以明显看到总体的下降趋势。随着联系次数的增加,转化率逐渐下降。这表明,当你在一个营销活动中更频繁地联系客户时,预期的转化率会降低。
对类别变量进行编码
数据集中有八个类别变量:job、marital、education、default、housing、loan、contact 和 month。在构建决策树之前,我们需要将其中一些类别变量编码为数值。我们将在本节中看看如何对这些类别变量进行编码。
对月份进行编码
我们都知道,month 变量最多只能有 12 个唯一值。让我们快速查看一下数据集中的内容。请查看以下代码:
unique(df$month)
unique 函数帮助你快速获取给定列中的唯一值。当你运行这段代码时,你将获得以下输出:

正如我们所预期的,month 列中有 12 个唯一值,从一月到十二月。由于 month 的值有自然顺序,我们可以用对应的数字对每个值进行编码。将 month 的字符串值编码为数字的一种方式如下:
months = lapply(month.abb, function(x) tolower(x))
df$month <- match(df$month, months)
让我们仔细看看这段代码。month.abb 是一个内置的 R 常量,包含了月份名称的三字母缩写,具体如下:

如你所见,每个缩写的month名称的首字母都被大写。然而,我们数据中的month列的月份名称都是小写的。这就是为什么我们使用tolower函数将month.abb常量中的所有值转换为小写。使用lapply函数,我们可以将这个tolower函数应用于month.abb列表。接着,我们使用match函数,它返回匹配字符串在数组中的位置,将 DataFrame 中month列的字符串值转换为对应的数值。
使用这段代码,month列的唯一值如下所示:

为了查看每个月份的记录数量,我们可以使用以下代码:
df %>%
group_by(month) %>%
summarise(Count=n())
结果如下:

对工作、住房和婚姻变量进行编码
接下来,我们将编码三个变量:job、housing和marital。由于这些变量没有自然的顺序,我们不需要担心哪个类别被编码为哪个值。编码没有顺序的类别变量在 R 中的最简单方法是使用factor函数。让我们看一下以下代码:
df$job <- factor(df$job)
df$housing <- factor(df$housing)
df$marital <- factor(df$marital)
如你所见,我们只是将factor函数应用于这三个变量:job、housing和marital,并将编码后的值存储回 DataFrame df中。对于我们在本节中未讨论的类别变量,如果你希望进一步探索,可以使用我们在本节中讨论的相同技术对它们进行编码。
构建决策树
现在我们已经编码了所有类别变量,终于可以开始构建决策树模型了。我们将使用这些变量作为决策树模型的特征:age、balance、campaign、previous、housing、job和marital。为了使用 R 构建并训练决策树模型,我们将使用rpart包。你可以使用以下代码导入所需的库:
library(rpart)
如果你没有安装rpart包,你可以使用以下命令安装它:
install.packages("rpart")
一旦你导入了所需的库,你可以使用以下代码来构建决策树模型:
fit <- rpart(
conversion ~ age + balance + campaign + previous + housing + job + marital,
method="class",
data=df,
control=rpart.control(maxdepth=4, cp=0.0001)
)
如您所见,rpart 模型的第一个参数是 formula,它定义了特征和目标变量。在这里,我们使用上述变量作为特征,conversion 作为目标变量。然后,我们将此决策树模型定义为一个分类模型,并使用 method="class" 输入。最后,您可以使用 control 输入来微调决策树模型。有许多参数可以通过 control 输入进行调整。在此示例中,我们仅通过 maxdepth 参数将树的最大深度限制为 4,并将复杂度参数 cp 设置为足够小,以便树能够进行分裂。还有许多其他方法可以微调您的决策树模型,我们建议您通过运行 help(rpart) 或 help(rpart.control) 命令,详细查看 R 文档以获取更多信息。
解读决策树
现在我们已经训练了一个决策树模型,接下来需要从模型中提取洞察。在本节中,我们将使用一个名为 rattle 的库:
- 您可以通过在 RStudio 中使用以下命令来安装此包:
install.packages("rattle")
- 一旦正确安装了此库,您应该能够按照以下方式导入该库:
library(rattle)
- 一旦您在 R 环境中设置了这个新的库
rattle,只需一行代码就能可视化训练好的决策树。请查看以下代码:
fancyRpartPlot(fit)
- 如您所见,
fancyRpartPlot函数接受一个rpart模型对象。在这里,模型对象fit是我们在前一步构建的决策树模型。一旦您运行这个命令,它将显示以下图表:

让我们更仔细地看一下这个树形图。每个节点包含三行信息,描述了该节点所具有的信息。节点顶部的数字是标签,表示该节点构建的顺序。我们将使用这个标签来引用树图中的每个节点。然后,每个节点的顶行告诉我们该节点的预测或分类结果。例如,如果一个样本属于标签为 4 的节点,则此决策树模型的分类结果为零,表示未转换。另一方面,如果一个样本属于标签为 23 的节点,则此决策树模型的分类结果为一,表示已转换。
每个节点中的第二行告诉我们该节点中每个类的记录百分比。例如,节点22中的52%记录属于类0,即非转化组,剩下的48%属于类1,即转化组。另一方面,节点13中的39%客户属于类0,剩下的61%属于类1。最后,每个节点中的底部行告诉我们属于每个节点的总记录百分比。例如,大约80%的客户属于节点4,而接近0%的客户属于节点13。
现在我们知道每个节点中每一行的含义了,让我们讨论一下如何从这个树状图中提取见解。为了理解属于每个叶节点的客户,我们需要沿着树走下去。例如,属于节点13的客户是那些previous变量值大于0.5,拥有住房贷款并且age大于或等于61的客户。换句话说,那些在此活动之前已被联系过、年龄超过61且拥有住房贷款的客户属于节点13,他们有较高的转化机会。
让我们来看另一个例子。为了从根节点到达节点22,我们需要使previous变量的值为0,age大于或等于61,marital状态不是married或single,并且job属于以下类别之一:admin、blue-collar、entrepreneur、housemaid、retired或unknown。换句话说,那些在此活动之前未被联系过、年龄超过61、已离婚并且在上述类别中有工作的客户属于节点22,他们有大约50%的转化机会。
正如你从这两个例子中注意到的,你可以通过可视化训练过的决策树模型,得出关于哪些客户更有可能或更不可能转化的有用见解。你只需沿着节点走下去,理解与目标类高度相关的属性是什么。对于这个练习,我们将树的深度限制为4,但你可以选择让树的深度比我们在这个练习中使用的树大或小。
本章节 R 语言练习的完整代码可以在该仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.4/R/FromEngagementToConversions.R。
总结
在这一章中,我们介绍了一种新的机器学习算法——决策树,它可以用于营销分析,以更好地理解数据并洞察客户行为。我们讨论了决策树模型与上章介绍的逻辑回归模型的不同之处。你了解到,决策树模型通过基于某些标准对数据点进行划分来学习数据。我们还讨论了在生长决策树时常用的两种衡量标准:基尼不纯度和信息增益熵。通过使用这两种衡量标准中的任何一种,决策树可以一直生长,直到所有节点都纯净,或者直到满足停止标准。
在我们的 Python 和 R 编程练习中,我们使用了 UCI 机器学习库中的银行营销数据集。我们通过深入分析数据来开始编程练习,使用了 Python 中的pandas和matplotlib包,以及 R 中的dplyr和ggplot2库。接着,你学习了如何使用 Python 中的sklearn包和 R 中的rpart库来训练和生长决策树。通过这些训练好的决策树模型,你还学习了如何可视化和解释结果。为了进行可视化,我们使用了 Python 中的graphviz包和 R 中的rattle库。此外,你还看到如何通过遍历训练过的决策树来解释决策树结果,理解哪些客户群体更可能转化或订阅定期存款,这在我们进行客户行为的解释性分析时非常有用。
在接下来的几章中,我们将改变方向,专注于产品分析。在下一章中,我们将讨论可以进行的探索性分析,以理解和识别产品数据中的模式和趋势。基于下一章的产品分析结果,我们将展示如何构建产品推荐模型。
第三部分:产品可见性与营销
在这一节中,您将学习如何从产品购买历史数据中提取洞察,并如何利用机器学习推荐客户最有可能购买的产品。
本节包括以下章节:
-
第五章,产品分析
-
第六章,推荐正确的产品
第五章:产品分析
从本章开始,我们将转变方向,从对客户行为的分析转向讨论如何利用数据科学进行更细粒度的产品层级分析。各家公司,特别是电子商务企业,日益关注和需求利用数据了解客户如何与不同的产品互动和参与。研究也证明,严格的产品分析可以帮助企业提升用户参与度和转化率,最终带来更高的利润。在本章中,我们将讨论什么是产品分析,以及它如何用于不同的应用场景。
一旦我们熟悉了产品分析的概念,我们将使用来自 UCI 机器学习库的在线零售数据集进行编程练习。我们将从分析数据集中可以观察到的整体时间序列趋势开始。然后,我们将研究客户与单个产品的互动和参与如何随时间变化,目标是最终能够构建一个简单的产品推荐逻辑或算法。对于 Python 练习,我们将主要利用pandas和matplotlib库进行数据分析和可视化。对于 R 练习,我们将主要使用dplyr和ggplot2库,并介绍另外两个 R 库,readxl和lubridate。
在本章中,我们将涵盖以下主题:
-
产品分析的重要性
-
使用 Python 进行产品分析
-
使用 R 进行产品分析
产品分析的重要性
产品分析是一种从数据中提取见解的方法,帮助了解客户如何与所提供的产品互动和参与,不同产品的表现如何,以及业务中的一些可观察到的弱点和优势。然而,产品分析不仅仅停留在分析数据上。产品分析的最终目标是构建可操作的见解和报告,进一步帮助优化和改善产品表现,并根据产品分析的发现生成新的营销或产品创意。
产品分析从跟踪事件开始。这些事件可以是客户的网站访问、页面浏览、浏览器历史记录、购买行为或客户对你所提供产品采取的任何其他操作。然后,你可以开始分析和可视化这些事件中的任何可观察到的模式,目标是创建可操作的见解或报告。产品分析的常见目标如下:
-
提高客户和产品的留存率:通过分析客户浏览和购买的商品,您可以识别出客户反复购买的商品以及这些回头客是谁。另一方面,您还可以识别出客户未购买的商品以及可能流失的客户。分析并理解反复购买商品和回头客的共同属性可以帮助您改善留存策略。
-
识别热门和流行的产品:作为零售企业的营销人员,了解热门和流行产品非常重要。这些畅销产品是企业的主要收入来源,并提供新的销售机会,如交叉销售或捆绑销售。通过产品分析,您应该能够轻松识别和追踪这些热门和流行产品,并基于这些畅销产品制定新的策略,挖掘不同的机会。
-
根据关键属性对客户和产品进行细分:通过客户画像和产品数据,您可以使用产品分析根据客户和产品的属性对其进行细分。细分产品数据的一些方式包括基于其盈利能力、销售量、重复订购量以及退款数量。通过这些细分,您可以获得可操作的洞察,确定下一个目标的产品或客户细分。
-
制定更高投资回报率的营销策略:产品分析还可以用来分析您的营销策略的投资回报率(ROI)。通过分析用于推广某些商品的营销费用以及这些商品所产生的收入,您可以了解哪些方法有效,哪些无效。使用产品分析来进行营销投资回报率分析可以帮助您制定更高效的营销策略。
在这里讨论的产品分析基础上,我们将在以下编程练习中探讨如何利用零售业务数据实现这些产品分析目标。我们将讨论如何使用数据分析回头客的模式及其对整体收入的贡献。此外,我们还将介绍如何使用产品分析来分析畅销产品的行为。更具体地说,我们将讨论如何追踪热门商品的趋势,并简要讨论如何利用这些趋势数据为您的营销策略中的产品推荐提供支持。
使用 Python 进行产品分析
在这一部分,我们将讨论如何使用 Python 中的pandas和matplotlib包进行产品分析。对于那些想使用 R 而非 Python 进行练习的读者,你可以跳到下一部分。我们将从分析收入和购买数量的总体时间序列趋势,以及重复购买客户的购买模式开始,接着分析销售产品的趋势。
对于这个练习,我们将使用来自 UCI 机器学习库的一个公开可用数据集,可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/online+retail#。从这个链接中,你可以下载名为Online Retail.xlsx的 Excel 格式数据。一旦下载了数据,你可以通过运行以下命令将其加载到你的 Jupyter Notebook 中:
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
df = pd.read_excel(io='../data/Online Retail.xlsx', sheet_name='Online Retail')
与前面章节中的其他 Python 练习类似,我们使用%matplotlib inline命令来显示 Jupyter Notebook 中的图表。然后,我们可以导入用于进一步产品分析的matplotlib和pandas包。值得注意的是,在这段代码中,我们使用了pandas包中的新函数read_excel。这个函数允许你将任何 Excel 文件加载到pandas的DataFrame中。从这段代码可以看出,我们向read_excel函数传递了两个参数——io,用于指定数据文件的路径,以及sheet_name,用于指定要加载的 Excel 工作表的名称。
一旦你将数据加载到pandas的DataFrame中,它应当如下所示:

在我们继续进行下一步之前,有一个数据清理的步骤需要完成。我们快速查看一下Quantity(数量)列的分布情况。我们将使用以下代码可视化Quantity的分布:
ax = df['Quantity'].plot.box(
showfliers=False,
grid=True,
figsize=(10, 7)
)
ax.set_ylabel('Order Quantity')
ax.set_title('Quantity Distribution')
plt.suptitle("")
plt.show()
从这段代码可以看到,我们使用pandas的DataFrame的plot.box函数,在箱线图中可视化Quantity列的分布。生成的箱线图如下所示:

从这个图表中可以看到,一些订单的数量是负数。这是因为被取消或退款的订单在我们的数据集中的Quantity列中记录了负值。为了说明本练习中的问题,我们将忽略被取消的订单。我们可以使用以下代码在DataFrame中过滤掉所有被取消的订单:
df = df.loc[df['Quantity'] > 0]
现在,我们可以进行进一步的分析,深入探索我们的数据。
时间序列趋势
在查看产品级别的数据之前,作为一名电商行业的营销人员,了解整体收入和订单或购买数量的时间序列趋势会是非常有益的。这将帮助我们了解业务在收入和订单数量上是增长还是收缩。
首先,我们将查看随着时间推移接收的订单数量。请查看以下代码:
monthly_orders_df = df.set_index('InvoiceDate')['InvoiceNo'].resample('M').nunique()
从这段代码中你可以看到,我们使用了在前几章中没有用到的resample和nunique函数。resample函数重新采样并将时间序列数据转换为我们所需的频率。在我们的示例中,我们通过使用'M'作为目标频率,将时间序列数据重新采样为按月划分的时间序列数据,并计算不同的发票号的数量。这样,我们就可以得到每月独特购买或订单的数量。结果的 DataFrame 如下所示:

通常,时间序列数据通过折线图可视化效果更好。让我们查看以下代码,看看如何将这些月度数据在折线图中进行可视化:
ax = pd.DataFrame(monthly_orders_df.values).plot(
grid=True,
figsize=(10,7),
legend=False
)
ax.set_xlabel('date')
ax.set_ylabel('number of orders/invoices')
ax.set_title('Total Number of Orders Over Time')
plt.xticks(
range(len(monthly_orders_df.index)),
[x.strftime('%m.%Y') for x in monthly_orders_df.index],
rotation=45
)
plt.show()
从这段代码中你可以看到,我们正在使用pandas DataFrame的plot函数。通过使用matplotlib包的xticks函数,我们可以自定义x轴刻度的标签。让我们先看一下以下的图表:

正如你从这张图表中看到的那样,x轴的刻度标签按月和年格式化。如果你回顾之前的代码,我们使用x.strftime('%m.%Y')来格式化,其中x是Python的date对象,%m是月份值的占位符,%Y是年份值的占位符。Python的date对象的strftime函数将日期格式化为给定的格式。
从这张图表中可以明显看到,2011 年 12 月的订单数量出现了突然而剧烈的下降。如果你仔细查看数据,会发现这只是因为我们没有完整的 2011 年 12 月的数据。我们可以使用以下代码来验证这一点:
invoice_dates = df.loc[
df['InvoiceDate'] >= '2011-12-01',
'InvoiceDate'
]
print('Min date: %s\nMax date: %s' % (invoice_dates.min(), invoice_dates.max()))
在这段代码中,我们获取了从 2011 年 12 月 1 日开始的所有发票日期的序列。然后,我们打印出最小和最大日期。当你运行这段代码时,你将得到以下输出:

从这个输出中你可以看到,我们只有 2011 年 12 月 1 日到 12 月 9 日的数据。如果我们用这些数据来分析 12 月的销售和收入,那将是误导性的。为了进一步分析,我们将忽略 2011 年 12 月 1 日的数据。你可以使用以下代码来删除这些数据点:
df = df.loc[df['InvoiceDate'] < '2011-12-01']
现在我们已经排除了 2011 年 12 月的不完整数据,可以使用之前的代码重新绘制折线图。去除 2011 年 12 月的数据点后,折线图如下所示:

让我们仔细看一下这个图表。从 2010 年 12 月到 2011 年 8 月,每月订单数量大约在 1500 左右波动,然后从 2011 年 9 月开始显著增加,到 2011 年 11 月几乎翻倍。对此的一个解释可能是从 2011 年 9 月起,业务实际上在显著增长。另一个解释可能是季节性效应。在电子商务行业,接近年末时销售量激增并不罕见。通常,很多电商企业的销售在 10 月到 1 月间会显著上升,如果没有前一年的数据,很难判断这次销售的激增是由于业务增长还是季节性因素。我们建议在分析数据时,将今年的数据与去年进行比较。
让我们快速看一下月度收入数据,通过以下代码:
df['Sales'] = df['Quantity'] * df['UnitPrice']
monthly_revenue_df = df.set_index('InvoiceDate')['Sales'].resample('M').sum()
从这段代码中可以看到,我们首先要做的是计算每个订单的总销售额,简单来说,就是UnitPrice乘以Quantity。计算并创建了这个Sales列后,我们可以使用带'M'标志的resample函数对我们的时间序列数据进行重采样,将其转换为按月的数据。然后,使用sum作为聚合函数,我们就可以获得每月的销售收入数据。得到的数据如下所示:

我们可以使用以下代码将这些数据可视化为折线图:
ax = pd.DataFrame(monthly_revenue_df.values).plot(
grid=True,
figsize=(10,7),
legend=False
)
ax.set_xlabel('date')
ax.set_ylabel('sales')
ax.set_title('Total Revenue Over Time')
ax.set_ylim([0, max(monthly_revenue_df.values)+100000])
plt.xticks(
range(len(monthly_revenue_df.index)),
[x.strftime('%m.%Y') for x in monthly_revenue_df.index],
rotation=45
)
plt.show()
如前所述,我们可以使用pandas的DataFrame绘图函数来构建折线图,并使用matplotlib包的xticks函数来重新命名X 轴上的刻度标签。该折线图如下所示:

我们在这个月度收入图表中看到的模式与之前的月度订单总数随时间变化图表类似。从 2010 年 12 月到 2011 年 8 月,月收入大约在 70 万左右波动,然后从 2011 年 9 月开始显著增加。如前所述,为了验证这次销售和收入的显著增长是由于业务增长还是季节性因素,我们需要进一步回顾销售历史,并将今年的销售与去年的销售进行比较。
这种广泛的时间序列分析可以帮助营销人员更好地了解业务的整体表现,并识别可能在业务中发生的潜在问题。一般来说,最好先进行较为广泛的分析,然后再深入到业务的具体和细节部分,进行进一步的产品分析。
重复购买客户
另一个成功业务的重要因素是它如何保持客户忠诚度以及其重复购买和客户的数量。在本节中,我们将分析每月的重复购买次数,以及每月收入中有多少来自这些重复购买和客户。一个典型的强大而稳定的企业,往往会有源源不断的来自现有客户的销售。让我们看看在本章中我们正在分析的在线零售业务,重复购买和现有客户的销售占比有多少。
我们将分析每月的重复购买次数。这意味着一个客户在给定的月份内下了多个订单。让我们快速看一下我们拥有的数据:

正如你从这张数据快照中可能注意到的,针对一个购买订单(InvoiceNo)有多个记录。然而,我们需要的是每个订单的汇总数据,以便DataFrame中的一条记录代表一个购买订单。我们可以通过以下代码按InvoiceNo对这些原始数据进行汇总:
invoice_customer_df = df.groupby(
by=['InvoiceNo', 'InvoiceDate']
).agg({
'Sales': sum,
'CustomerID': max,
'Country': max,
}).reset_index()
如你所见,在这段代码中,我们将DataFrame(df)按InvoiceNo和InvoiceDate进行分组,并对所有Sales进行求和。这样,我们得到的新DataFrame(invoice_customer_df)中,每个购买订单都有一条记录。结果的DataFrame如下所示:

如你所见,这里DataFrame中的每一条记录现在都包含了我们每个订单所需的所有信息。现在,我们需要按月汇总这些数据,并计算在给定月份内购买超过一次的客户数量。请查看以下代码:
monthly_repeat_customers_df = invoice_customer_df.set_index('InvoiceDate').groupby([
pd.Grouper(freq='M'), 'CustomerID'
]).filter(lambda x: len(x) > 1).resample('M').nunique()['CustomerID']
让我们仔细看看这段代码中的groupby函数。这里,我们按两个条件进行分组——pd.Grouper(freq='M')和CustomerID。第一个groupby条件,pd.Grouper(freq='M'),是按索引InvoiceDate将数据分组到每个月。接着,我们按每个CustomerID对数据进行分组。通过使用filter函数,我们可以按自定义规则筛选数据。这里,筛选规则lambda x: len(x) > 1表示我们想要检索那些在组内有多条记录的数据。换句话说,我们只想检索那些在某个月内下了多个订单的客户。最后,我们通过resample('M')和nunique按月重新采样并汇总,统计每个月内的独立客户数。
结果数据如下所示:

现在让我们将这些数字与每月顾客的总数进行比较。你可以使用以下代码计算每月顾客的总数:
monthly_unique_customers_df = df.set_index('InvoiceDate')['CustomerID'].resample('M').nunique()
结果数据如下所示:

如果你比较这两组数字,大约有 20%到 30%的顾客是回头客。你可以使用以下代码计算每个月回头客的百分比:
monthly_repeat_percentage = monthly_repeat_customers_df/monthly_unique_customers_df*100.0
让我们将所有这些数据绘制到一个图表中:
ax = pd.DataFrame(monthly_repeat_customers_df.values).plot(
figsize=(10,7)
)
pd.DataFrame(monthly_unique_customers_df.values).plot(
ax=ax,
grid=True
)
ax2 = pd.DataFrame(monthly_repeat_percentage.values).plot.bar(
ax=ax,
grid=True,
secondary_y=True,
color='green',
alpha=0.2
)
ax.set_xlabel('date')
ax.set_ylabel('number of customers')
ax.set_title('Number of All vs. Repeat Customers Over Time')
ax2.set_ylabel('percentage (%)')
ax.legend(['Repeat Customers', 'All Customers'])
ax2.legend(['Percentage of Repeat'], loc='upper right')
ax.set_ylim([0, monthly_unique_customers_df.values.max()+100])
ax2.set_ylim([0, 100])
plt.xticks(
range(len(monthly_repeat_customers_df.index)),
[x.strftime('%m.%Y') for x in monthly_repeat_customers_df.index],
rotation=45
)
plt.show()
在这段代码中,你会注意到在plot函数中新增了一个标志secondary_y=True。顾名思义,如果你将secondary_y标志设置为True,它将会在图表的右侧创建一个新的Y 轴。这对于你想要可视化两个不同尺度的数据集非常有用。在我们的案例中,一个数据集的尺度是用户数量,另一个数据集的尺度是百分比。使用这个secondary_y标志,我们可以轻松地在一个图表中可视化不同尺度的数据。
一旦你运行这段代码,你会看到以下图表:

从这张图表中可以看到,回头客和所有顾客的数量从 2011 年 9 月开始显著上升。回头客的百分比似乎保持在大约 20%到 30%之间。这家在线零售业务将从这稳定的回头客流中受益,因为它们将帮助企业产生稳定的销售流。现在让我们分析这些回头客贡献的月收入。
以下代码展示了如何计算回头客的月收入:
monthly_rev_repeat_customers_df = invoice_customer_df.set_index('InvoiceDate').groupby([
pd.Grouper(freq='M'), 'CustomerID'
]).filter(lambda x: len(x) > 1).resample('M').sum()['Sales']
monthly_rev_perc_repeat_customers_df = monthly_rev_repeat_customers_df/monthly_revenue_df * 100.0
这段代码和之前的代码唯一的不同点是resample('M')后面的聚合函数sum。在之前的情况中,当我们计算每月回头客数量时,我们使用了nunique函数。然而,这次我们使用了sum函数来计算每个月回头客的所有销售总和。为了可视化,你可以使用以下代码:
ax = pd.DataFrame(monthly_revenue_df.values).plot(figsize=(12,9))
pd.DataFrame(monthly_rev_repeat_customers_df.values).plot(
ax=ax,
grid=True,
)
ax.set_xlabel('date')
ax.set_ylabel('sales')
ax.set_title('Total Revenue vs. Revenue from Repeat Customers')
ax.legend(['Total Revenue', 'Repeat Customer Revenue'])
ax.set_ylim([0, max(monthly_revenue_df.values)+100000])
ax2 = ax.twinx()
pd.DataFrame(monthly_rev_perc_repeat_customers_df.values).plot(
ax=ax2,
kind='bar',
color='g',
alpha=0.2
)
ax2.set_ylim([0, max(monthly_rev_perc_repeat_customers_df.values)+30])
ax2.set_ylabel('percentage (%)')
ax2.legend(['Repeat Revenue Percentage'])
ax2.set_xticklabels([
x.strftime('%m.%Y') for x in monthly_rev_perc_repeat_customers_df.index
])
plt.show()
需要注意的是,这段代码中的一行:ax2 = ax.twinx()。这实际上和我们之前讨论的secondary_y标志起到相同的作用。twinx函数只是创建了一个共享相同x 轴的双Y 轴,效果与secondary_y标志相同。生成的图表如下所示:

我们看到与之前类似的模式,2011 年 9 月开始收入有了显著增长。这里需要注意的一点是来自重复购买客户的月收入占比。我们看到大约 20-30%的购买客户是重复购买客户。然而,在这个图表中,我们可以看到大约 40-50%的总收入来自重复购买客户。换句话说,大约一半的收入是由这 20-30%的重复购买客户驱动的。这显示了留住现有客户的重要性。
随时间变化的畅销商品
到目前为止,我们分析了整体的时间序列模式,以及客户如何与整个业务进行互动,但我们还没有分析客户如何与单个产品进行互动。在本节中,我们将探索并分析客户如何与销售的单个产品互动。更具体地,我们将查看前五名畅销产品随时间变化的趋势。
对于时间序列趋势商品分析,我们来统计每个产品在每个时期销售的商品数量。请查看以下代码:
date_item_df = df.set_index('InvoiceDate').groupby([
pd.Grouper(freq='M'), 'StockCode'
])['Quantity'].sum()
从这段代码可以看出,我们正在按月对DataFrame df进行分组,按StockCode(每个产品的唯一编码)进行分组,然后对每个月和StockCode的销售数量进行求和。结果的前九条记录如下所示:

有了data_item_df中的这些数据,我们来看看 2011 年 11 月 30 日最畅销的商品。请查看以下代码:
# Rank items by the last month sales
last_month_sorted_df = date_item_df.loc['2011-11-30'].sort_values(
by='Quantity', ascending=False
).reset_index()
从这段代码可以看出,我们可以使用sort_values函数通过提供输入参数by中的列名来按任何我们想要的列对pandas DataFrame进行排序。在这里,我们通过将ascending标志设置为False,按照Quantity列对数据进行降序排序。结果如下所示:

从这个结果可以看出,代码为 23084、84826、22197、22086 和 85099B 的产品是 2011 年 11 月的前五名畅销产品。
现在我们知道了 2011 年 11 月的前五名畅销产品,让我们再次汇总这五个产品的月销售数据。请查看以下代码:
date_item_df = df.loc[
df['StockCode'].isin([23084, 84826, 22197, 22086, '85099B'])
].set_index('InvoiceDate').groupby([
pd.Grouper(freq='M'), 'StockCode'
])['Quantity'].sum()
从这段代码可以看出,我们仍然是按月和StockCode对数据进行分组,并对销售数量进行求和。然而,这里需要注意的一点是isin操作符。isin操作符在loc操作符内检查每条记录是否与数组中的某个元素匹配。在我们的例子中,我们正在检查每条记录的StockCode是否与前五名畅销产品的商品代码匹配。使用这段代码,我们可以仅针对 2011 年 11 月的前五名畅销产品按月和产品汇总数据。结果的前几条记录如下所示:

现在我们有了这五种产品的月度销售数据,我们需要将这些数据转换成表格格式,其中列是各个商品编号,行索引是发票日期,值是销售的商品数量,这样我们才能将数据可视化为时间序列图。以下代码展示了如何将这些数据转换成表格格式:
trending_itmes_df = date_item_df.reset_index().pivot('InvoiceDate','StockCode').fillna(0)
trending_itmes_df = trending_itmes_df.reset_index()
trending_itmes_df = trending_itmes_df.set_index('InvoiceDate')
trending_itmes_df.columns = trending_itmes_df.columns.droplevel(0)
正如你在这段代码中看到的,我们正在使用pivot函数来对这个DataFrame进行pivot,其中索引是InvoiceDate,而列是StockCode列中的各个商品编号。结果如下所示:

通过这些时间序列数据,我们现在可以可视化随时间变化的趋势。您可以使用以下代码来构建一个展示趋势商品的时间序列图:
ax = pd.DataFrame(trending_itmes_df.values).plot(
figsize=(10,7),
grid=True,
)
ax.set_ylabel('number of purchases')
ax.set_xlabel('date')
ax.set_title('Item Trends over Time')
ax.legend(trending_itmes_df.columns, loc='upper left')
plt.xticks(
range(len(trending_itmes_df.index)),
[x.strftime('%m.%Y') for x in trending_itmes_df.index],
rotation=45
)
plt.show()
当你运行这段代码时,你应该看到如下图表:

让我们仔细看看这个时间序列图。这五种产品的销量在 2011 年 11 月激增,尤其是产品编号为 85099B 的产品,其销量在 2011 年 2 月至 2011 年 10 月几乎为零。然后,它在 2011 年 11 月突然激增。或许值得深入探讨一下是什么原因导致了这次激增。这可能是一个季节性影响很大的产品,因此这个产品在 11 月变得非常受欢迎,或者也有可能是由于趋势的真实变化,导致该产品突然比以前更受欢迎。
其余四种最畅销产品 22086、22197、23084 和 84826 的销量似乎是在 2011 年 11 月之前的几个月内逐渐积累起来的。作为市场营销人员,值得深入探讨是什么因素导致这些商品的热度不断上升。你可以看看这些商品是否通常在寒冷季节更受欢迎,或者它们是否在市场上出现了逐渐上升的趋势。
分析产品受欢迎程度的趋势和变化,不仅有助于你了解客户最喜欢和最常购买的商品,还能帮助你调整营销信息。例如,你可以在营销邮件、电话或广告中推荐这些日益受欢迎的商品,从而提高客户的参与度。研究表明,当你的客户对这些商品更加感兴趣,并且更有可能购买时,通过更多地推广这些商品,你可能会获得更高的营销参与度,最终可能会获得更高的转化率。当你针对这些趋势商品进行营销时,使用这些受欢迎和热门商品是一种建立产品推荐引擎的方式,我们将在下一章深入展开并进行实验。
本节中 Python 练习的完整代码可以在以下链接找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.5/python/Product%20Analytics.ipynb。
使用 R 进行产品分析
在本节中,我们将讨论如何使用 R 中的dplyr和ggplot2库进行产品分析。对于那些想使用 Python 而非 R 的读者,可以跳过本节并进入下一节。我们将从分析收入、购买数量以及重复购买客户的购买模式的总体时间序列趋势开始,然后再分析销售产品的趋势。
对于这个练习,我们将使用 UCI 机器学习库中公开提供的一个数据集,数据集可以在以下链接找到:archive.ics.uci.edu/ml/datasets/online+retail#。你可以点击这个链接并下载名为Online Retail.xlsx的 Microsoft Excel 格式数据。一旦你下载了这些数据,可以通过运行以下代码加载它:
# install.packages("readxl")
library(readxl)
#### 1\. Load Data ####
df <- read_excel(
path="~/Documents/research/data-science-marketing/ch.5/data/Online Retail.xlsx",
sheet="Online Retail"
)
如你从这段代码中可能注意到的,与前几章不同的地方是我们在使用readxl库和read_excel函数。由于我们的数据是 Excel 格式的,因此不能使用我们之前使用的read.csv函数。为了加载 Excel 格式的数据集,我们需要使用readxl库,可以通过在 RStudio 中运行install.packages("readxl")命令进行安装。在readxl库中,有一个名为read_excel的函数,帮助你轻松加载 Excel 文件。
当你将这些数据加载到DataFrame中后,它应该如下所示:

在进入下一步之前,我们需要进行一个数据清理步骤。让我们快速查看Quantity列的分布情况。我们将使用以下代码可视化Quantity的分布:
ggplot(df, aes(x="", y=Quantity)) +
geom_boxplot(outlier.shape = NA) +
ylim(c(-15, 25))+
ylab("order quantity") +
xlab("") +
ggtitle("Quantity Distribution") +
theme(plot.title=element_text(hjust=0.5))
如你所见,我们正在使用geom_boxplot可视化Quantity列的分布情况,呈现为箱型图。
生成的箱型图如下所示:

如你从该图中看到的那样,部分订单的数量为负。这是因为取消或退款的订单在数据集中的Quantity列里会以负值记录。为了本次练习的说明,我们将忽略这些取消的订单。我们可以通过以下代码过滤掉所有取消的订单:
# filter out orders with negative quantity (cancel orders)
df <- df[which(df$Quantity > 0),]
现在,我们可以进行进一步的分析,深入探索数据。
时间序列趋势
在我们查看产品层级数据之前,作为一名电子商务企业的营销人员,了解整体收入和订单或购买数量的时间序列趋势会非常有帮助。这有助于我们了解企业在整体收入和订单数量方面是否在增长或缩小。
首先,我们将研究一段时间内的订单数量。请看以下代码:
# install.packages("lubridate")
library(lubridate)
timeSeriesNumInvoices <- df %>%
group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>%
summarise(NumOrders=n_distinct(InvoiceNo))
在这段代码中,我们首先使用group_by函数按月对数据进行分组。为了按月分组,我们使用了lubridate库中的floor_date函数。如果你还没有安装这个库,可以使用install.packages("lubridate")命令进行安装。floor_date函数会将日期向下舍入到指定的单位。在我们的案例中,我们将InvoiceDate列的日期向下舍入到每个月的第一天。然后,对于每个月,我们通过对InvoiceNo列使用n_distinct函数来计算唯一购买订单的数量。最终得到的DataFrame如下所示:

通常,时间序列数据使用折线图来展示效果更好。让我们看一下下面的代码,看看如何将这些按月的数据可视化为折线图:
ggplot(timeSeriesNumInvoices, aes(x=InvoiceDate, y=NumOrders)) +
geom_line() +
ylim(c(0, max(timeSeriesNumInvoices$NumOrders) + 1000)) +
ylab("number of orders") +
xlab("date") +
ggtitle("Number of Orders over Time") +
theme(plot.title=element_text(hjust=0.5))
正如你从这段代码中看到的,我们使用了ggplot函数,它来自ggplot2库,并结合geom_line函数,通过折线图展示数据。首先让我们来看一下下面的图表:

从这张图表中可以看出,有一个显著的、突然而剧烈的订单数量下降发生在 2011 年 12 月。如果仔细查看数据,实际上是因为我们没有 2011 年 12 月完整的数据。我们可以通过使用以下代码来验证这一点:
summary(df[which(df$InvoiceDate >= as.Date("2011-12-01")),"InvoiceDate"])
在这段代码中,我们获得了从 2011 年 12 月 1 日开始的所有发票日期的摘要,结果如下所示:

正如你从输出中看到的,我们只拥有 2011 年 12 月 1 日至 12 月 9 日的数据。如果我们用这些数据来分析 12 月的销售和收入,会导致误导,因为我们无法从现有数据集中获得完整的 12 月情况。为了进一步分析,我们将忽略 2011 年 12 月 1 日的数据。你可以使用以下代码来去除这些数据点:
df <- df[which(df$InvoiceDate < as.Date("2011-12-01")),]
现在我们已经筛选出了 2011 年 12 月的不完整数据,可以使用之前的代码重新绘制折线图。去掉 2011 年 12 月的数据点后,折线图看起来如下所示:

让我们更仔细地看看这个图表。每月订单数量似乎在 2010 年 12 月到 2011 年 8 月之间大约保持在 1,500 左右,然后从 2011 年 9 月开始显著增加,到 2011 年 11 月几乎翻倍。对此的一个解释可能是,业务自 2011 年 9 月开始确实显著增长。另一个解释可能是季节性因素。在电子商务业务中,接近年末时销售出现激增并不罕见。通常,很多电子商务企业的销售额在 10 月到 1 月之间会大幅上升,而没有前一年的数据,很难判断这次销售激增是由于业务增长还是季节性因素。当你分析数据时,我们建议将今年的数据与去年的数据进行对比。
类似于每月订单数量,我们快速看一下每月收入数据。请看下面的代码:
df$Sales <- df$Quantity * df$UnitPrice
timeSeriesRevenue <- df %>%
group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>%
summarise(Sales=sum(Sales))
从这段代码中可以看出,我们首先要做的是计算每个订单的总销售额,计算方法是将UnitPrice与Quantity相乘。一旦我们计算并创建了这个Sales列,就可以使用group_by函数配合floor_date函数将数据按月进行分组。通过在summarise函数中使用sum作为聚合函数,我们可以得到每月的销售收入数据。最终得到的数据如下:

我们可以通过以下代码将这些数据可视化为折线图:
ggplot(timeSeriesRevenue, aes(x=InvoiceDate, y=Sales)) +
geom_line() +
ylim(c(0, max(timeSeriesRevenue$Sales) + 10000)) +
ylab("sales") +
xlab("date") +
ggtitle("Revenue over Time") +
theme(plot.title=element_text(hjust=0.5))
正如我们在前面章节中看到的,我们可以使用geom_line函数来绘制折线图。每月收入数据的折线图如下所示:

我们可以在这个每月收入随时间变化的图表中看到与之前的每月订单数量随时间变化图表相似的模式。从 2010 年 12 月到 2011 年 8 月,每月收入大约保持在 700,000 左右,之后从 2011 年 9 月开始显著增加。如前所述,为了验证这种销售和收入的显著增长是由于业务增长还是季节性因素,我们需要回顾销售历史,并将今年的销售与去年做对比。
这类一般性和广泛的时间序列分析可以帮助营销人员更好地了解业务的整体表现,并识别出可能存在的潜在问题。通常,先进行广泛的分析再深入挖掘业务中的具体部分进行进一步的产品分析是一个不错的做法。
回头客
成功企业的另一个重要因素是它如何留住客户,以及有多少重复购买和客户。在本节中,我们将分析每月的重复购买数量,以及这些重复购买和客户对每月收入的贡献。一个典型的强大且稳定的企业,来自现有客户的销售额会保持稳定。让我们看看我们目前在本章中分析的在线零售业务中,来自重复和现有客户的销售额有多少。
我们将查看每月的重复购买数量。这意味着客户在一个月内下了超过一个订单。让我们快速查看一下我们拥有的数据:

从这段数据快照中可以看出,一个购买订单(InvoiceNo)可能会有多条记录。然而,我们需要的是每个订单的汇总数据,以便每条DataFrame记录代表一个购买订单。我们可以通过以下代码对每个InvoiceNo的原始数据进行汇总:
invoiceCustomerDF <- df %>%
group_by(InvoiceNo, InvoiceDate) %>%
summarise(CustomerID=max(CustomerID), Sales=sum(Sales))
从这段代码可以看出,我们按InvoiceNo和InvoiceDate对DataFrame,df进行分组,并对所有Sales求和,同时取CustomerID的一个值。这样,新的DataFrame,invoiceCustomerDf,就有了每个购买订单的记录。结果DataFrame如下所示:

如你所见,DataFrame中的每条记录代表了我们所需的每个订单的所有信息。现在,我们需要将这些数据按月汇总,并计算每个月有多少客户进行了超过一次的购买。请查看以下代码:
timeSeriesCustomerDF <- invoiceCustomerDF %>%
group_by(InvoiceDate=floor_date(InvoiceDate, "month"), CustomerID) %>%
summarise(Count=n_distinct(InvoiceNo), Sales=sum(Sales))
与前一部分类似,我们使用group_by和floor_date函数将数据汇总到每个月。我们还按CustomerID进行分组,这样我们就可以计算每个客户在每个月下了多少订单,产生了多少销售额。现在的数据如下所示:

现在,为了得到重复客户的数量,我们需要做的就是筛选出在Count列中只有 1 的客户。执行此操作的代码如下所示:
repeatCustomers <- na.omit(timeSeriesCustomerDF[which(timeSeriesCustomerDF$Count > 1),])
新创建的DataFrame,reapeatCustomers,现在包含了每个月内有超过一次购买的所有客户。为了得到每月的重复客户总数,我们将运行以下代码:
timeSeriesRepeatCustomers <- repeatCustomers %>%
group_by(InvoiceDate) %>%
summarise(Count=n_distinct(CustomerID), Sales=sum(Sales))
从这段代码中可以看到,我们仅仅是按InvoiceDate进行分组,InvoiceDate是被舍入到每个月的第一天的日期,然后我们计算每个月内唯一或不同的客户数量,并求和总销售额。结果如下所示:

现在我们将这些重复客户的数量与每月总客户数进行比较。你可以使用以下代码来计算每月总客户数:
# Unique Customers
timeSeriesUniqCustomers <- df %>%
group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>%
summarise(Count=n_distinct(CustomerID))
结果如下所示:

最后,我们将分析每月收入中可以归因于重复客户的百分比。请看以下代码:
timeSeriesRepeatCustomers$Perc <- timeSeriesRepeatCustomers$Sales / timeSeriesRevenue$Sales*100.0
timeSeriesRepeatCustomers$Total <- timeSeriesUniqCustomers$Count
如代码所示,我们只是将timeSeriesRepeatCustomers DataFrame中的Sales列除以我们在前一节中创建的timeSeriesRevenue DataFrame中的Sales列。然后,我们将每月独立客户的数量附加到timeSeriesRepeatCustomers DataFrame中的新列Total。
让我们使用以下代码将所有这些数据可视化为一张图表:
ggplot(timeSeriesRepeatCustomers) +
geom_line(aes(x=InvoiceDate, y=Total), stat="identity", color="navy") +
geom_line(aes(x=InvoiceDate, y=Count), stat="identity", color="orange") +
geom_bar(aes(x=InvoiceDate, y=Perc*20), stat="identity", fill='gray', alpha=0.5) +
scale_y_continuous(sec.axis = sec_axis(~./20, name="Percentage (%)")) +
ggtitle("Number of Unique vs. Repeat & Revenue from Repeat Customers") +
theme(plot.title=element_text(hjust=0.5))
如代码所示,我们通过使用ggplot2库中的geom_line和geom_bar函数来绘制两条折线图和一条柱状图。第一条折线图表示每月总客户数Total,并将使用navy颜色绘制。第二条折线图表示每月重复客户数Count,将使用orange颜色绘制。最后,我们绘制了一条柱状图,表示来自重复客户的收入百分比Perc,并使用gray颜色绘制。这里需要注意的是,次要y 轴的缩放因子20。sec_axis函数定义了次要y 轴的缩放公式。这里我们使用的是~./20,意味着次要y 轴的范围从 0 到主轴最大值的 1/20。由于我们将次要y 轴按20缩放,因此我们将这个数字乘以Perc,并在geom_bar函数中进行计算,以便将数据的尺度与次要y 轴的范围匹配。结果如下所示:

如代码所示,我们在这张图中看到三个图表:一条表示每月总客户数的海军蓝折线图、一条表示每月重复客户数的橙色折线图,以及表示重复客户收入百分比的灰色柱状图。从这张图可以看到,次要y 轴(标记为百分比(%))的范围从 0 到主y 轴最大值的 1/20,符合我们设置的缩放因子20。
现在让我们仔细看看图表。从 2011 年 9 月开始,月度客户数量和重复客户数量似乎都有上升趋势,且重复客户大约占月度客户总数的 20-30%。然而,如果你查看来自这些重复客户的收入百分比,你会发现大约 40-50%的总收入来自重复客户。换句话说,约一半的收入来自 20-30%的重复客户群体。由于该在线零售业务有着大量来自重复客户的收入,这意味着它将从这些稳定的重复客户收入中获益。这也表明了留住现有客户的重要性。作为一名营销人员,关键在于如何留住现有客户,并建立起稳定的重复客户群体。
随着时间变化的畅销产品
到目前为止,我们已经分析了整体的时间序列模式以及客户如何与整体业务互动,但还没有分析客户如何与单个产品互动。在本节中,我们将探讨并分析客户如何与单个销售的产品互动。更具体地说,我们将查看这些产品的前五大畅销品随时间变化的趋势。
分析时间序列趋势项的第一个任务是统计每个产品在每个周期内的销售数量。请看以下代码:
popularItems <- df %>%
group_by(InvoiceDate=floor_date(InvoiceDate, "month"), StockCode) %>%
summarise(Quantity=sum(Quantity))
从这段代码中可以看出,我们按照月份和每个产品的唯一代码StockCode对数据进行了分组。然后,我们通过在summarise函数中使用sum函数,将给定月份和产品的所有销售数量Quantity加总起来。
由于我们只关注前五大畅销品,我们需要从DataFrame popularItems中选择这五个产品。请看以下代码:
top5Items <- popularItems[
which(popularItems$InvoiceDate == as.Date("2011-11-01")),
] %>%
arrange(desc(Quantity)) %>%
head(5)
timeSeriesTop5 <- popularItems[
which(popularItems$StockCode %in% top5Items$StockCode),
]
在这里,我们首先按 2011 年 11 月销售的商品数量Quantity对商品进行降序排序。使用which函数,我们可以从popularItems中选择 2011 年 11 月的数据,然后使用arrange函数按我们想要的列Quantity对数据进行排序。通过在arrange函数中使用desc,我们可以按降序排列数据。最后,我们使用head函数选取前五名商品。新创建的变量top5Items现在包含了 2011 年 11 月的前五大畅销品。最后一步是提取这五个商品的时间序列数据。通过使用which函数和%in%操作符,我们可以从top5Items中选择那些StockCode在其中的商品数据。
为了可视化这五个产品的时间序列趋势,我们可以使用以下代码:
ggplot(timeSeriesTop5, aes(x=InvoiceDate, y=Quantity, color=StockCode)) +
geom_line() +
ylab("number of purchases") +
xlab("date") +
ggtitle("Top 5 Popular Items over Time") +
theme(plot.title=element_text(hjust=0.5))
图表如下所示:

让我们仔细看看这个时间序列图。这五个产品的销售在 2011 年 11 月出现了激增,特别是股票代码为 85099B 的产品,其销售额从 2011 年 2 月到 2011 年 10 月几乎为零,直到 2011 年 11 月突然激增。值得深入探讨一下是什么原因导致了这个激增。这可能是一个对季节性变化高度敏感的商品,在 11 月变得非常流行,或者也可能是趋势发生了真正的变化,导致这个商品比之前更受欢迎。
其他五个热销产品 22086、22197、23084 和 84826 的流行度似乎在 2011 年 11 月之前的几个月里逐渐积累。作为营销人员,也值得仔细分析一下这种积累和这些商品流行度上升背后的潜在因素。你可以研究这些商品是否通常在寒冷季节更受欢迎,或者市场中这些特定商品的趋势是否正在增长。
分析产品的流行趋势和变化不仅帮助你了解客户最喜欢和最常购买的产品,还能帮助你定制营销信息。例如,你可以在营销邮件、电话或广告中推荐这些人气上升的商品,以提高客户的参与度。由于客户对这些商品更感兴趣,也更可能购买它们,因此在你增加这些商品的营销时,可能会获得更高的营销参与度,最终通过这些趋势商品吸引目标客户时,可能会得到更高的转化率。利用这些流行和趋势商品是一种构建产品推荐引擎的方式,我们将在下一章中进一步扩展并深入实验。
本节中 R 练习的完整代码可以在以下链接找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.5/R/ProductAnalytics.R。
总结
在本章中,我们讨论了产品分析的概念和重要性。我们简要讨论了产品分析如何从追踪事件和客户行为开始,例如网站或应用访问、页面浏览和购买。接着,我们讨论了一些常见的产品分析目标,以及如何利用它来生成可操作的洞察和报告。通过这些关于产品分析的讨论,我们探索了如何在编程练习中利用产品分析来实现客户和产品的留存,使用的是电子商务业务数据。首先,我们分析了收入和购买订单数量的时间序列趋势。然后,我们深入分析了月度重复客户的模式。从数据中我们发现,尽管月度重复客户占整体客户群体的比例相对较小,但他们却贡献了大约一半的月度总收入。这表明了客户留存的重要性,以及如何认真制定留存策略。最后,我们讨论了如何分析随着时间推移而流行和趋势化的商品。在这一部分,我们讨论了季节性效应的潜在影响,以及如何通过分析流行商品来制定营销策略和产品推荐。
在下一章,我们将扩展并应用本章所学的知识,构建产品推荐引擎。我们将学习协同过滤算法以及它如何用于产品推荐。
第六章:推荐合适的产品
在本章中,我们将深入探讨如何构建产品推荐系统,通过为个别客户量身定制的产品推荐来更好地定位客户。研究表明,个性化的产品推荐能够提高转化率和客户留存率。随着我们可以利用数据科学和机器学习进行目标营销的数据越来越多,定制化产品推荐在营销信息中的重要性和有效性显著增长。在本章中,我们将讨论常用的机器学习算法,以开发推荐系统,协同过滤,以及实现协同过滤算法进行产品推荐的两种方法。
在本章中,我们将涵盖以下主题:
-
协同过滤与产品推荐
-
使用 Python 构建产品推荐算法
-
使用 R 构建产品推荐算法
协同过滤与产品推荐
根据 Salesforce 进行的一项研究,那些收到个性化产品推荐的客户驱动了 24%的订单和 26%的收入。这表明,产品推荐对订单量和整体销售收入有多大的影响。在 Salesforce 发布的报告中,他们还发现,产品推荐会导致重复访问,通过推荐进行的购买会产生更高的平均订单价值,且客户确实会购买推荐的商品。你可以查看此报告:www.salesforce.com/blog/2017/11/personalized-product-recommendations-drive-just-7-visits-26-revenue。
产品推荐系统
产品推荐系统是一个旨在预测并编制出客户可能购买的商品列表的系统。推荐系统近年来已经获得了广泛的应用,并且为各种商业用例开发和实施。例如,音乐流媒体服务 Pandora 使用推荐系统为听众推荐音乐;电子商务公司 Amazon 使用推荐系统预测并显示客户可能购买的产品列表;媒体服务提供商 Netflix 使用推荐系统为个别用户推荐他们可能观看的电影或电视剧。推荐系统的使用不仅限于此,它还可以用来推荐相关的文章、新闻或书籍。由于可以应用于多种领域,推荐系统在许多行业中扮演着关键角色,特别是在电子商务和媒体行业,因为它们直接影响销售收入和用户参与度。
通常有两种方式来生成推荐列表:
-
协同过滤
-
基于内容的过滤
协同过滤方法基于用户之前的行为,例如他们浏览过的页面、购买过的产品或他们对不同物品给出的评分。协同过滤方法利用这些数据来寻找用户或物品之间的相似性,并向用户推荐最相似的物品或内容。协同过滤方法背后的基本假设是,过去浏览或购买过相似内容或产品的用户,未来也很可能会浏览或购买相似的内容或产品。因此,基于这一假设,如果一个人曾经购买过 A、B、C 三件物品,另一个人购买过 A、B、D 三件物品,那么第一个人很可能会购买 D,而另一个人则可能会购买 C,因为他们之间有很多相似之处。
另一方面,基于内容的过滤方法根据物品或用户的特征生成推荐列表。它通常会查看描述物品特征的关键词。基于内容的过滤方法背后的基本假设是,用户很可能会浏览或购买与他们过去购买或浏览过的物品相似的物品。例如,如果一个用户过去听过一些歌曲,那么基于内容的过滤方法会推荐与该用户已经听过的歌曲特征相似的其他歌曲。
在这一章节中,我们将使用协同过滤算法来构建一个产品推荐系统。接下来,我们将仔细看看如何在以下部分构建协同过滤算法。
协同过滤
如上一节所讨论的,协同过滤算法是根据用户行为的历史和用户之间的相似性来推荐产品的。实现协同过滤算法的第一步是构建一个用户到物品的矩阵。用户到物品的矩阵由行中的单个用户和列中的单个物品组成。通过一个示例来解释会更容易。请看以下矩阵:

该矩阵中的行代表每个用户,列代表每个物品。每个单元格中的值表示该用户是否购买了该物品。例如,用户1购买了物品B和D,而用户2购买了物品A、B、C和E。为了构建基于协同过滤的产品推荐系统,我们需要首先建立这种用户到物品的矩阵。在接下来的编程练习部分,我们将通过一个示例更详细地讨论如何通过编程构建这样的矩阵。
通过这个用户到物品的矩阵,构建基于协同过滤的产品推荐系统的下一步是计算用户之间的相似性。为了衡量相似性,余弦相似度通常被使用。计算两个用户之间余弦相似度的公式如下:

在这个公式中,U[1] 和 U[2] 分别代表用户 1 和用户 2。P[1i] 和 P[2i] 分别代表用户 1 和用户 2 购买的每一个产品 i。如果你使用这个公式,你将得到 0.353553 作为用户 1 和用户 2 之间的余弦相似度,以及 0.866025 作为用户 2 和用户 4 之间的余弦相似度。正如你所想,余弦相似度越大,两个用户的相似性越高。所以,在我们的例子中,用户 2 和用户 4 比用户 1 和用户 2 更为相似。我们将在接下来的编程练习部分讨论如何使用 Python 和 R 计算用户之间的余弦相似度。
最后,在使用协同过滤算法进行产品推荐时,你可以采取两种方法——基于用户的方法和基于物品的方法。顾名思义,基于用户的协同过滤方法使用用户之间的相似性。另一方面,基于物品的协同过滤方法使用物品之间的相似性。这意味着,当我们在基于用户的方法中计算两个用户之间的相似性时,我们需要构建并使用用户到物品的矩阵,正如我们之前所讨论的。然而,在基于物品的方法中,我们需要计算两个物品之间的相似性,这意味着我们需要构建并使用物品到用户的矩阵,这个矩阵可以通过简单地转置用户到物品的矩阵获得。在接下来的编程练习部分,我们将更详细地讨论这两种方法之间的区别,以及如何使用 Python 和 R 基于这两种方法构建推荐系统。
使用 Python 构建产品推荐算法
在本节中,我们将讨论如何使用 Python 构建产品推荐系统。更具体地说,我们将学习如何使用机器学习库 scikit-learn 在 Python 中实现协同过滤算法。对于那些想使用 R 而非 Python 完成本次练习的读者,可以跳过到下一节。本节的开始,我们将分析一些电子商务业务数据,然后讨论使用协同过滤构建产品推荐系统的两种方法。
对于本练习,我们将使用 UCI 机器学习库中的一个公开数据集,您可以通过以下链接找到它:archive.ics.uci.edu/ml/datasets/online+retail#。您可以点击该链接并下载名为Online Retail.xlsx的 Microsoft Excel 格式数据。下载完数据后,您可以通过运行以下命令将其加载到您的 Jupyter Notebook 中:
import pandas as pd
df = pd.read_excel(io='../data/Online Retail.xlsx', sheet_name='Online Retail')
类似于第五章,产品分析,我们在pandas包中使用read_excel函数来加载 Excel 格式的数据。我们将数据的路径传递给参数io=,并将 Excel 工作表的名称传递给参数sheet_name。
一旦你将数据加载到pandas的DataFrame中,它应该如下所示:

如果你还记得上一章的内容,Quantity列中有一些记录的值为负数,表示取消的订单。我们将忽略并移除这些记录。我们可以使用以下代码从DataFrame中过滤掉所有这些记录:
df = df.loc[df['Quantity'] > 0]
数据准备
在我们开始使用协同过滤算法构建产品推荐引擎之前,我们需要完成以下几件事情:
-
处理数据集中的
NaN值 -
构建客户-商品矩阵
首先,我们需要处理数据集中的NaN值,特别是在CustomerID字段中的NaN值。没有正确的CustomerID值,我们就无法构建一个正确的推荐系统,因为协同过滤算法依赖于单个客户的历史购买数据。
其次,在我们继续实现协同过滤算法进行产品推荐之前,我们需要构建客户-商品矩阵。客户-商品矩阵就是一张表格数据,其中每一列代表一个产品或商品,每一行代表一个客户,每个单元格中的值表示该客户是否购买了该商品。
处理CustomerID字段中的 NaN 值
仔细查看数据后,你会发现有一些记录没有CustomerID。由于我们需要构建一个客户-商品矩阵,其中每一行代表一个客户,因此我们不能将没有CustomerID的记录包含在数据中。让我们首先看看有多少条记录没有CustomerID。
看一下以下代码:
df['CustomerID'].isna().sum()
我们这里使用的isna函数可以检测缺失值,并为每个缺失值返回True。通过对这些值求和,我们可以计算出没有CustomerID的记录数。结果如下所示:

从输出结果可以看到,有133,361条记录没有CustomerID。缺少CustomerID的数据如下所示:

现在我们知道有一些记录缺少CustomerID,我们需要将它们排除在进一步分析之外。将它们从DataFrame中删除的一种方法是使用dropna函数,代码如下:
df = df.dropna(subset=['CustomerID'])
pandas包中的dropna函数会删除给定DataFrame中缺失值的记录。从这个代码片段可以看出,使用subset参数,我们可以基于特定的列来删除缺失值。在这里,我们删除了没有CustomerID的记录。一旦运行这段代码,DataFrame中的所有记录,df,将会有CustomerID值。删除缺失值前后,DataFrame的维度应该如下面的截图所示:

从这个输出中可以看出,原始DataFrame中没有CustomerID值的133,361条记录被删除了。
构建客户-商品矩阵
我们现在拥有的数据表示客户购买的单个商品。然而,为了使用协同过滤算法构建产品推荐系统,我们需要的数据格式是每条记录包含每个客户购买的商品信息。在本节中,我们将把数据转换成客户-商品矩阵,其中每一行代表一个客户,列对应不同的商品。
让我们看看下面的代码:
customer_item_matrix = df.pivot_table(
index='CustomerID',
columns='StockCode',
values='Quantity',
aggfunc='sum'
)
从这个代码片段可以看出,我们正在使用pivot_table函数将数据转换为客户-商品矩阵。在这里,我们将index定义为CustomerID,并用columns表示每个StockCode。通过将aggfunc设为sum,并使用Quantity字段作为values,我们可以对每个商品的购买数量进行求和。生成的customer_item_matrix的快照如下所示:

让我们更仔细地看看这些数据。CustomerID为12481的客户购买了StockCode为15036的商品36件。同样,CustomerID为12484的客户购买了StockCode为11001的商品16件,CustomerID为12488的客户购买了StockCode为10135的商品10件。通过这些数据,可以看出,我们现在有了一个矩阵,其中每一行表示每个客户购买的每个商品的总数量。
现在,让我们对数据进行0-1编码,使得1表示该商品已被给定客户购买,而0表示该商品未曾被该客户购买。请看下面的代码:
customer_item_matrix = customer_item_matrix.applymap(lambda x: 1 if x > 0 else 0)
从这段代码可以看出,我们正在使用applymap函数,该函数将给定的函数应用到 DataFrame 的每个元素上。我们在这段代码中使用的 Lambda 函数会将所有值大于0的元素编码为1,其余的编码为0。这个转换后的 DataFrame 快照如下所示:

现在我们有了一个客户-物品矩阵,可以用于协同过滤算法。接下来,我们将进入构建产品推荐引擎的步骤。
协同过滤
在这一部分中,我们将探索构建产品推荐引擎的两种方法——基于用户的和基于物品的。在基于用户的方法中,我们根据用户的物品购买历史计算用户之间的相似度。而在基于物品的方法中,我们根据哪些物品经常和其他物品一起购买来计算物品之间的相似度。
为了衡量用户之间或物品之间的相似度,我们将使用cosine_similarity方法,该方法来自scikit-learn包。你可以使用以下代码导入此函数:
from sklearn.metrics.pairwise import cosine_similarity
sklearn包中的cosine_similarity函数计算给定数据中每对样本之间的余弦相似度。现在让我们深入了解一下!
基于用户的协同过滤与推荐
为了构建基于用户的协同过滤算法,我们需要计算用户之间的余弦相似度。让我们看一下以下代码:
user_user_sim_matrix = pd.DataFrame(
cosine_similarity(customer_item_matrix)
)
从这段代码可以看出,我们正在使用sklearn包中metrics.pairwise模块的cosine_similarity函数。该函数计算样本之间的成对余弦相似度,并将结果输出为array类型。然后,我们用这个输出数组创建一个pandas的DataFrame,并将其存储在名为user_user_sim_matrix的变量中,这代表用户-用户相似度矩阵。结果如下所示:

从这个用户-用户相似度矩阵的快照中可以看到,索引和列名并不容易理解。由于每一列和每一行的索引代表的是个别客户,我们将使用以下代码重命名索引和列名:
user_user_sim_matrix.columns = customer_item_matrix.index
user_user_sim_matrix['CustomerID'] = customer_item_matrix.index
user_user_sim_matrix = user_user_sim_matrix.set_index('CustomerID')
现在结果如下所示:

让我们仔细看看这个用户与用户之间的相似度矩阵。正如你所想,客户与自己之间的余弦相似度是1,这是我们从这个相似度矩阵中可以观察到的。这个用户与用户之间的相似度矩阵中的对角线元素的值为1。其余的表示两个客户之间的成对余弦相似度。例如,客户12347和12348之间的余弦相似度为0.063022。另一方面,客户12347和12349之间的余弦相似度为0.046130。这表明,基于他们购买的产品,客户12348与客户12347更为相似,而客户12349与客户12347的相似度较低。通过这种方式,我们可以轻松地判断哪些客户彼此相似,哪些客户购买了与其他客户相似的商品。
这些成对的余弦相似度测量值就是我们将用于产品推荐的依据。我们通过选择一个客户作为示例来进行操作。我们将首先使用以下代码,对与客户 ID 为12350的客户最相似的客户进行排名:
user_user_sim_matrix.loc[12350.0].sort_values(ascending=False)
当你运行这段代码时,你将得到以下输出:

这些是与客户12350最相似的前 10 个客户。我们选择客户17935,并讨论如何根据这些结果推荐产品。策略如下。首先,我们需要确定客户12350和17935已经购买的商品。然后,我们将找到目标客户17935尚未购买,但客户12350已购买的商品。由于这两位客户过去购买了相似的商品,我们假设目标客户17935有很高的几率购买那些他或她尚未购买,但客户12350已经购买的商品。最后,我们将使用这份商品列表,并将其推荐给目标客户17935。
首先,我们来看一下如何检索客户12350过去购买的商品。代码如下所示:
items_bought_by_A = set(customer_item_matrix.loc[12350.0].iloc[
customer_item_matrix.loc[12350.0].nonzero()
].index)
从这段代码中可以看出,我们使用了pandas包中的nonzero函数。该函数返回非零元素的整数索引。我们可以通过在给定客户12350的customer_item_matrix上使用这个函数,得到该客户购买的商品列表。我们也可以对目标客户17935应用相同的代码,如下所示:
items_bought_by_B = set(customer_item_matrix.loc[17935.0].iloc[
customer_item_matrix.loc[17935.0].nonzero()
].index)
现在,我们有了客户12350和17935购买的两个商品集合。通过简单的集合操作,我们可以找到客户12350购买了,但客户17935没有购买的商品。代码如下所示:
items_to_recommend_to_B = items_bought_by_A - items_bought_by_B
现在,items_to_recommend_to_B变量中的商品是客户12350购买的商品,但客户17935尚未购买(可能还没有)。根据我们的假设,这些商品是客户17935可能会购买的。推荐给客户17935的商品列表如下所示:

为了获取这些项目的描述,你可以使用以下代码:
df.loc[
df['StockCode'].isin(items_to_recommend_to_B),
['StockCode', 'Description']
].drop_duplicates().set_index('StockCode')
正如你从这段代码中可以注意到的,我们使用isin运算符来获取与items_to_recommend_to_B变量中的商品匹配的记录。
一旦你运行这段代码,你将得到如下输出:

使用基于用户的协同过滤方法,我们已经讨论了如何为每个客户进行有针对性的产品推荐。你可以定制并包含每个目标客户可能购买的产品到你的营销信息中,这样可能会提高客户的转化率。正如之前所讨论的,使用基于用户的协同过滤算法,你可以轻松地为目标客户做产品推荐。
然而,使用基于用户的协同过滤存在一个主要的缺点。正如我们在这个练习中所看到的,推荐是基于个别客户的购买历史进行的。对于新客户,我们没有足够的数据将这些新客户与其他客户进行比较。为了解决这个问题,我们可以使用基于项目的协同过滤,接下来我们将讨论这一方法。
基于商品的协同过滤和推荐
基于商品的协同过滤与基于用户的方法相似,区别在于它使用的是商品之间的相似性度量,而不是用户或客户之间的相似性。之前我们需要计算用户之间的余弦相似性,但现在我们将计算商品之间的余弦相似性。请查看以下代码:
item_item_sim_matrix = pd.DataFrame(
cosine_similarity(customer_item_matrix.T)
)
如果你将这段代码与之前计算用户间相似度矩阵的代码进行比较,唯一的区别是我们在这里对customer_item_matrix进行了转置,这样行索引表示的是个别商品,而列表示的是客户。我们仍然使用sklearn包中的metrics.pairwise模块的cosine_similarity函数。为了正确命名带有产品代码的索引和列,你可以使用以下代码:
item_item_sim_matrix.columns = customer_item_matrix.T.index
item_item_sim_matrix['StockCode'] = customer_item_matrix.T.index
item_item_sim_matrix = item_item_sim_matrix.set_index('StockCode')
现在结果如下所示:

如之前所示,对角线元素的值为1。这是因为一个商品与其自身的相似度为1,意味着两者是完全相同的。其余的元素则包含基于余弦相似度计算的商品间相似度值。例如,查看前面的商品间相似度矩阵,StockCode 10002和StockCode 10120之间的余弦相似度为0.094868。另一方面,10002和10125之间的余弦相似度为0.090351。这表明,StockCode 10120的商品与StockCode 10002的商品更相似,而StockCode 10125的商品与StockCode 10002的商品则相对较不相似。
使用这种商品间相似度矩阵进行产品推荐的策略类似于我们在上一节中使用基于用户的推荐方法。首先,对于目标客户购买的给定商品,我们将从刚刚建立的商品间相似度矩阵中找到最相似的商品。然后,我们会推荐这些相似商品给客户,因为这些相似商品是其他购买了目标客户最初购买的商品的顾客购买的。让我们通过一个例子来说明。
假设一位新客户刚购买了StockCode 23166的产品,我们希望在营销邮件中包含一些该客户最有可能购买的产品。我们需要做的第一件事是找到与StockCode 23166产品最相似的商品。你可以使用以下代码获取与StockCode 23166产品最相似的前 10 个商品:
top_10_similar_items = list(
item_item_sim_matrix\
.loc[23166]\
.sort_values(ascending=False)\
.iloc[:10]\
.index
)
结果如下所示:

我们可以使用以下代码获取这些相似商品的描述:
df.loc[
df['StockCode'].isin(top_10_similar_items),
['StockCode', 'Description']
].drop_duplicates().set_index('StockCode').loc[top_10_similar_items]
如你所见,在这段代码中,我们使用isin操作符来筛选出与top_10_similar_items变量中的相似商品列表匹配的商品。一旦你运行这段代码,你将看到以下输出:

这里的第一个商品是目标客户刚购买的商品,其他九个商品是那些购买了第一个商品的顾客经常购买的商品。如你所见,购买陶瓷顶储物罐的人通常还会购买果冻模具、香料罐和蛋糕模具。通过这些数据,你可以在营销信息中为该目标客户提供这些商品作为进一步的产品推荐。通过有针对性的产品推荐个性化营销信息,通常能提高客户的转化率。利用基于商品的协同过滤算法,你现在可以轻松地为新老客户提供产品推荐。
这个 Python 练习的完整细节可以在以下网址找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.6/python/ProductRecommendation.ipynb
使用 R 构建产品推荐算法
在本节中,我们将讨论如何使用 R 构建产品推荐系统。更具体地说,我们将学习如何使用dplyr、reshape2和coop包在 R 中实现协同过滤算法。对于那些希望使用 Python 而非 R 进行此练习的读者,可以参考上一节。我们将从分析一些电子商务业务数据开始,然后讨论使用协同过滤构建产品推荐系统的两种方法。
在这个练习中,我们将使用来自 UCI 机器学习库的公开数据集之一,数据集可以在以下网址找到:archive.ics.uci.edu/ml/datasets/online+retail#。你可以访问此链接并下载数据,数据为 Microsoft Excel 格式,名为Online Retail.xlsx。下载数据后,你可以通过运行以下命令将其加载到你的 RStudio 中:
library(dplyr)
library(readxl)
df <- read_excel(
path="~/Documents/research/data-science-marketing/ch.6/data/Online Retail.xlsx",
sheet="Online Retail"
)
和上一章类似,我们使用readxl包中的read_excel函数加载 Excel 格式的数据。我们将数据的路径提供给path参数,将 Excel 表格的名称提供给sheet参数。
一旦你将这些数据加载到DataFrame中,它应该如下所示:

如果你还记得上一章的内容,Quantity列中有一些负值记录,表示取消的订单。我们将忽略并删除这些记录。我们可以使用以下代码在DataFrame中筛选出所有这些记录:
# ignore cancel orders
df <- df[which(df$Quantity > 0),]
数据准备
在我们开始构建使用协同过滤算法的产品推荐引擎之前,我们需要做几件事。首先,我们需要处理数据集中的NaN值,特别是那些在CustomerID字段中有NA值的记录。如果CustomerID字段中的值不正确,我们就无法构建一个正确的推荐系统,因为协同过滤算法依赖于单个客户的历史商品购买数据。其次,在实现产品推荐的协同过滤算法之前,我们需要构建客户与商品的矩阵。客户-商品矩阵实际上是表格数据,其中每列代表一个商品或项目,每行代表一个客户,每个单元格中的值表示该客户是否购买了给定的商品。
处理CustomerID字段中的NA值
如果您仔细查看数据,您会注意到一些没有CustomerID的记录。由于我们需要构建一个客户-商品矩阵,其中每行特定于每个客户,我们不能包括那些没有CustomerID的记录在我们的数据中。让我们首先看一下有多少条记录没有CustomerID。
请查看以下代码:
# there are 133,361 records with no CustomerID
sum(is.na(df$CustomerID))
我们在此使用的is.na函数用于检测缺失值,并对每个缺失值返回TRUE。通过使用sum函数对这些值进行求和,我们可以计算没有CustomerID的记录数。结果如下所示:

从此输出中可以看出,有133,361条没有CustomerID的记录。为了查看那些没有CustomerID的记录,您可以使用以下代码:
# sneak peek at records with no CustomerID
head(df[which(is.na(df$CustomerID)),])
输出如下所示:

现在我们知道有记录缺少CustomerID值,我们需要从进一步分析中排除它们。从我们的DataFrame中删除它们的一种方法是使用na.omit函数,如下所示:
# remove records with NA
df <- na.omit(df)
在 R 语言中,na.omit函数会从DataFrame中删除缺失值(NA)。运行此代码后,df的DataFrame中所有记录现在都将有CustomerID值。在删除缺失值之前和之后,DataFrame df的维度应如以下截图所示:

如您所见,从dim(df)命令的输出可以看出,原始DataFrame中133,361条没有CustomerID值的记录已被删除。
构建客户-商品矩阵
现在我们手头的数据表示客户购买的个别商品。然而,为了使用协同过滤算法构建产品推荐系统,我们需要有每条记录包含客户购买的哪个商品的数据。在本节中,我们将把数据转换成客户-商品矩阵,其中每行代表一个客户,列对应不同的产品。
为了将我们的数据转换成客户-商品矩阵,我们将使用reshape2包中的dcast函数。如果您的 R 环境中尚未安装此包,您可以运行以下命令来安装并包含此包:
install.packages("reshape2")
library(reshape2)
让我们看一下以下代码:
customerItemMatrix <- dcast(
df, CustomerID ~ StockCode, value.var="Quantity"
)
dcast函数来自reshape2包,使用公式将DataFrame重塑为另一种形式的DataFrame。在我们的案例中,我们希望数据的重塑方式是:行表示单个客户,列表示不同的产品。通过将公式定义为CustomerID ~ StockCode,dcast函数将重塑数据,使得每个StockCode的代码映射为列,而每一行则代表一个单独的客户。value.var参数定义了要取的值。在这里,我们告诉dcast函数将Quantity字段的值作为重塑后DataFrame中元素的值。结果如下所示:

让我们仔细看看这些数据。CustomerID为12731的客户购买了StockCode为10002的商品3件。同样,CustomerID为12748的客户购买了StockCode为10080的商品2件,而CustomerID为12735的客户购买了StockCode为10125的商品1件。正如您从中看到的,我们现在拥有一个矩阵,其中每一行表示每个客户购买的每个产品的总数量。
现在,让我们对这些数据进行0-1编码,使得1表示该产品被给定客户购买,而0表示该产品从未被该客户购买。请看以下代码:
# 0-1 encode
encode_fn <- function(x) {as.integer(x > 0)}
customerItemMatrix <- customerItemMatrix %>%
mutate_at(vars(-CustomerID), funs(encode_fn))
正如您从代码中看到的,我们首先定义了编码函数encode_fn。该函数简单地将每个值编码为1(如果大于0),否则编码为0。然后,我们使用dplyr包中的mutate_at函数,该函数将encode_fn编码函数应用于矩阵中的每个元素,除了CustomerID列。结果应如下所示:

我们现在拥有了一个客户-商品矩阵,可以用于协同过滤算法。接下来,让我们继续构建产品推荐引擎。
协同过滤
在本节中,我们将探索两种建立产品推荐引擎的方法——基于用户的与基于项目的。在基于用户的方法中,我们根据用户的商品购买历史计算用户之间的相似性。另一方面,在基于项目的方法中,我们计算项目之间的相似性,这些项目通常与其他商品一起购买。为了衡量用户或项目之间的相似性,我们将使用coop库中的cosine函数,这是一个在 R 中实现快速余弦相似度计算的库。您可以使用以下代码安装该 R 库:
install.packages("coop")
library(coop)
coop库中的cosine函数能够高效地计算 R 中的余弦相似度矩阵。现在让我们深入了解吧!
基于用户的协同过滤与推荐
为了构建基于用户的协同过滤算法,我们需要计算用户之间的余弦相似度。让我们来看一下下面的代码:
# User-to-User Similarity Matrix
userToUserSimMatrix <- cosine(
as.matrix(
# excluding CustomerID column
t(customerItemMatrix[, 2:dim(customerItemMatrix)[2]])
)
)
colnames(userToUserSimMatrix) <- customerItemMatrix$CustomerID
从这段代码中可以看出,使用coop库中的cosine函数,可以计算并构建余弦相似度矩阵。需要注意的是,在计算余弦相似度之前,我们对customerItemMatrix进行了转置。这是为了计算用户之间的相似度。如果没有转置,cosine函数将计算商品之间的相似度。最后,在代码的最后一行,我们通过顾客 ID 重命名了列名。
结果如下所示:

让我们仔细看看这个用户与用户之间的相似度矩阵。正如你可以想象的那样,一个客户与他或她自己的余弦相似度是1,这可以从这个相似度矩阵中看到。这个用户与用户相似度矩阵中的对角线元素的值为1。其余部分表示两个客户之间的成对余弦相似度。例如,客户12347和12348之间的余弦相似度为0.06302187。另一方面,客户12347和12349之间的余弦相似度为0.04612963。这表明,基于他们之前购买的商品,客户12348与客户12347比客户12349与客户12347更相似。通过这种方式,我们可以轻松地判断哪些客户与哪些其他客户相似,以及哪些客户购买了哪些其他客户也购买过的相似商品。
这些成对的余弦相似度度量将用于产品推荐。让我们通过选择一个客户作为例子来操作。我们首先使用以下代码对与客户 ID12350最相似的客户进行排序:
top10SimilarCustomersTo12350 <- customerItemMatrix$CustomerID[
order(userToUserSimMatrix[,"12350"], decreasing = TRUE)[1:11]
]
正如你从这段代码中看到的,我们使用order函数对userToUserSimMatrix中的12350列的值进行排序。通过设置decreasing = TRUE标志,我们可以按降序排序这些值。
当你运行这段代码时,你会得到以下输出:

这是与客户12350最相似的前 10 个客户。我们选择客户17935并讨论如何利用这些结果来推荐产品。策略如下。首先,我们需要识别客户12350和17935已经购买的商品。然后,我们要找出目标客户17935尚未购买但客户12350已购买的产品。由于这两位客户之前购买了相似的商品,我们假设目标客户17935有很大机会购买这些他或她尚未购买的商品,而客户12350已经购买了。最后,我们将使用这个商品清单并将其推荐给目标客户17935。
让我们首先来看一下如何获取客户12350过去购买的商品。代码如下所示:
itemsBoughtByA <- customerItemMatrix[
which(customerItemMatrix$CustomerID == "12350"),
]
itemsBoughtByA <- colnames(customerItemMatrix)[which(itemsBoughtByA != 0)]
如你所见,我们使用了which操作符来查找非零元素的列索引。该代码的输出结果如下:

使用以下代码,我们可以获取客户17935所购买的商品列表:
itemsBoughtByB <- customerItemMatrix[
which(customerItemMatrix$CustomerID == "17935"),
]
itemsBoughtByB <- colnames(customerItemMatrix)[which(itemsBoughtByB != 0)]
客户17935购买的商品如下:

现在我们有了客户12350和17935购买的两组商品。通过简单的集合操作,我们可以找到客户12350购买的但客户17935未购买的商品。代码如下所示:
itemsToRecommendToB <- setdiff(itemsBoughtByA, itemsBoughtByB)
现在在itemsToRecommendToB变量中的商品是客户12350购买的,但客户17935尚未购买的商品。根据我们的假设,这些是客户17935可能会购买的商品。推荐给客户17935的商品列表如下所示:

为了获取这些商品的描述,你可以使用以下代码:
itemsToRecommendToBDescriptions <- unique(
df[
which(df$StockCode %in% itemsToRecommendToB),
c("StockCode", "Description")
]
)
itemsToRecommendToBDescriptions <- itemsToRecommendToBDescriptions[
match(itemsToRecommendToB, itemsToRecommendToBDescriptions$StockCode),
]
如你从代码中看到的,我们使用了%in%操作符来获取与itemsToRecommendToB变量中的物品匹配的记录。运行此代码后,你将得到以下包含推荐商品描述的输出:

使用基于用户的协同过滤,我们已经讨论了如何为单个客户做出针对性的产品推荐。你可以根据客户的兴趣定制并在营销信息中包含每个目标客户可能购买的产品,这样可以有效地推动更多的转化。如前所述,使用基于用户的协同过滤算法,你可以轻松为目标客户创建产品推荐。
然而,使用基于用户的协同过滤方法有一个主要缺点。正如我们在这个练习中所看到的,推荐是基于单个客户的购买历史进行的。对于新客户,我们没有足够的数据与其他客户进行对比。为了应对这个问题,我们可以使用基于物品的协同过滤,接下来我们将讨论这个方法。
基于物品的协同过滤和推荐
基于物品的协同过滤与基于用户的方法类似,不同之处在于它使用的是物品之间的相似度度量,而不是用户或顾客之间的相似度。我们之前需要计算用户之间的余弦相似度,但现在我们要计算物品之间的余弦相似度。请看以下代码:
# Item-to-Item Similarity Matrix
itemToItemSimMatrix <- cosine(
as.matrix(
# excluding CustomerID column
customerItemMatrix[, 2:dim(customerItemMatrix)[2]]
)
)
如果将这段代码与之前计算用户间相似度矩阵的代码进行比较,唯一的区别在于这次我们没有转置customerItemMatrix。我们仍然使用coop库中的cosine函数。
结果如下所示:

与之前一样,对角线元素的值为1。这是因为一个商品与其自身的相似度为1,即这两个商品是完全相同的。其他元素则包含根据余弦相似度计算的商品间相似度值。例如,观察前面的商品间相似度矩阵,StockCode为10002的商品与StockCode为10120的商品之间的余弦相似度为0.09486833。另一方面,10002与10125之间的余弦相似度为0.09035079。这表明,StockCode为10120的商品与10002的商品比StockCode为10125的商品更为相似。
使用这个商品间相似度矩阵进行产品推荐的策略与我们在上一节中使用基于用户的方法类似。首先,对于目标客户购买的特定产品,我们将从刚刚构建的商品间相似度矩阵中找到最相似的商品。然后,我们将这些相似商品推荐给客户,因为这些相似商品是由其他购买了目标客户最初购买的产品的客户所购买的。让我们通过一个例子来说明。
假设一个新客户刚刚购买了一款StockCode为23166的商品,我们希望在营销邮件中推荐一些该客户最有可能购买的商品。我们首先需要做的是找到与StockCode为23166的商品最相似的商品。你可以使用以下代码来获取与StockCode为23166的商品最相似的前 10 个商品:
top10SimilarItemsTo23166 <- colnames(itemToItemSimMatrix)[
order(itemToItemSimMatrix[,"23166"], decreasing = TRUE)[1:11]
]
使用order函数并设置decreasing = TRUE标志,我们可以按降序排列相似商品。然后,借助这个反向排序的索引列表,我们可以找到与StockCode为23166的商品最相似的前 10 个商品。
结果如下所示:

我们可以使用以下代码获取这些相似商品的描述:
top10SimilarItemDescriptions <- unique(
df[
which(df$StockCode %in% top10SimilarItemsTo23166),
c("StockCode", "Description")
]
)
top10SimilarItemDescriptions <- top10SimilarItemDescriptions[
match(top10SimilarItemsTo23166, top10SimilarItemDescriptions$StockCode),
]
正如你从这段代码中看到的,我们使用%in%操作符来筛选与变量top10SimilarItemsTo23166中列出的相似商品匹配的商品。一旦运行这段代码,你将看到以下输出:

这里的第一个项目是目标客户刚刚购买的商品,剩余的 10 个项目是其他购买了第一个商品的顾客常买的物品。正如你所看到的,购买陶瓷顶存储罐的人通常会购买果冻模具、香料罐和蛋糕模具。有了这些数据,你可以在针对目标客户的营销信息中加入这些商品,作为进一步的产品推荐。通过个性化营销信息和定向产品推荐,通常能提高客户的转化率。通过基于商品的协同过滤算法,你现在可以轻松地为新客户和现有客户做产品推荐。
本 R 练习的完整代码可以通过以下链接找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.6/R/ProductRecommendation.R
摘要
在本章中,我们讨论了产品推荐系统。我们学习了根据 Salesforce 进行的一项研究,个性化产品推荐如何提高转化率和客户保持率。我们讨论了构建产品推荐系统的两种方法:协同过滤和基于内容的过滤;它们的不同之处,以及它们的假设。接着,我们深入探讨了如何构建基于协同过滤的推荐系统。正如你可能还记得的,构建基于协同过滤的推荐系统的第一步是建立一个用户与商品的矩阵,接下来的步骤是使用余弦相似度来计算用户之间的相似性。我们还讨论了利用协同过滤算法进行产品推荐的两种不同方法——基于用户的方法和基于商品的方法。
从下章开始,我们将转变方向,重点利用客户行为数据来提升我们的营销策略。在下一章中,我们将讨论进行客户分析的好处和重要性。
第四部分:个性化营销
在本节中,您将学习如何利用数据更好地理解客户行为,如何使用机器学习预测营销互动的可能性以及个别客户在其生命周期中的价值,以及如何运用数据科学来提升客户保持。
本节包括以下章节:
-
第七章,客户行为的探索性分析
-
第八章,预测营销互动的可能性
-
第九章,客户生命周期价值
-
第十章,数据驱动的客户细分
-
第十一章,客户保持
第七章:客户行为探索性分析
本章作为未来章节的第一步,我们将讨论什么是客户分析、分析并更好地理解客户群体的重要性和好处,以及客户分析在营销各个方面的使用案例。随着我们收集并跟踪更多关于客户及其在各个销售、营销平台和渠道中的行为的数据,营销人员可以更容易地分析和理解不同客户对不同营销策略的反应。客户分析通过利用这些数据帮助营销人员更好地理解他们的客户。此外,它还可以帮助营销人员制定更好的营销策略,从而提升客户参与度、留存率和转化率。
本章将涵盖以下主题:
-
客户分析:理解客户行为
-
使用 Python 进行客户分析
-
使用 R 进行客户分析
客户分析——理解客户行为
客户分析是通过分析客户行为数据来理解并获得客户行为洞察的过程。它涵盖了从简单的数据分析和可视化到更高级的客户细分和预测分析等多种内容。通过客户分析获得的信息和洞察可以用于制定营销策略、优化销售渠道以及做出其他关键的商业决策。
客户分析的重要性日益增加。因为许多企业更容易访问客户数据,而且客户现在也更容易获取关于其他竞争对手提供的类似产品和内容的数据和信息,所以能够理解和预测客户可能会购买或查看的内容对许多企业至关重要。你对客户的理解越深入,你就能在与竞争对手的竞争中拥有更强的竞争力。
客户分析的使用案例
客户分析可以在营销过程中的任何阶段使用。它可以用来监控和跟踪客户如何与产品互动或对不同的营销策略做出反应。这通常需要使用数据分析和可视化技术来生成报告或仪表板,便于展示关键绩效指标(KPIs)。
销售漏斗分析
客户分析的常见使用案例之一是销售漏斗分析。通过分析销售漏斗数据,我们可以监控和跟踪客户的生命周期,获取例如他们通过哪个营销渠道注册、他们多频繁登录系统、浏览和购买了哪些产品,或者他们在漏斗的每个步骤中如何流失等洞察。
客户细分
客户分析还可以用来根据客户的行为识别不同的客户群体。客户细分是客户分析的一个良好例子和结果。通过识别相似客户的子群体,你可以更好地理解目标群体。例如,低参与度客户的营销策略应该与高参与度客户的营销策略有所不同。通过根据参与度对客户群体进行有效细分,你可以更深入地了解不同客户群体如何对不同的营销策略做出反应。这进一步帮助你更好地定位特定的客户子群体。
预测分析
另一个客户分析的好用例是对客户数据进行预测分析。通过客户数据,你可以更深入地理解客户的哪些属性和特征与目标结果高度相关。例如,如果你希望提高响应率和参与率,可以分析数据,找出那些能导致更高响应率和参与率的客户特征。然后,你可以建立预测模型,预测客户对你的营销信息做出响应的可能性。
另一个预测分析的应用例子是营销渠道优化。通过从客户分析中获得的洞察,你可以建立预测模型来优化营销渠道。客户对不同的营销渠道会有不同的反应。例如,使用智能手机比其他群体更频繁的年轻群体,更有可能通过智能手机响应营销。另一方面,更年长的群体则更可能对传统媒体上的营销(如电视或报纸广告)做出更好的反应。通过客户分析,你可以识别客户的某些属性与不同营销渠道表现之间的关联。
正如我们到目前为止所讨论的,客户分析的应用范围广泛,可以在营销过程的任何阶段使用。在接下来的编程练习中,我们将讨论如何使用客户分析来监控和跟踪不同的营销策略,并查看一些细分和分析客户群体以获得洞察的方法。然后,在接下来的章节中,我们将探索客户分析的其他用例,例如优化参与率和留存率,以及客户细分。
使用 Python 进行客户分析
在本节中,我们将讨论如何使用 Python 进行客户分析。我们将主要使用 pandas 和 matplotlib 包来分析和可视化数据集中观察到的客户行为。对于那些希望使用 R 而非 Python 的读者,可以跳过到下一节。我们将从分析和理解已参与客户的行为开始,然后讨论通过某些标准对客户群体进行细分的简单方法。
在本次练习中,我们将使用 IBM 提供的公开数据集,可以通过以下链接找到:www.ibm.com/communities/analytics/watson-analytics-blog/marketing-customer-value-analysis/。您可以访问此链接并下载以 CSV 格式提供的数据,文件名为 WA_Fn UseC_ Marketing Customer Value Analysis.csv。下载数据后,您可以通过运行以下命令将其加载到 Jupyter Notebook 中:
import pandas as pd
df = pd.read_csv('../data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv')
类似于第六章,推荐合适的产品,我们使用 pandas 包中的 read_csv 函数来加载 CSV 格式的数据。一旦将数据加载到 pandas 的 DataFrame 中,数据应如下所示:

如从这份数据中所见,有一列名为 Response,包含客户是否响应了营销活动的信息。此外,Renew Offer Type 和 Sales Channel 列分别表示向客户提供的续约优惠类型和用于联系客户的销售渠道。还有许多其他列表示客户的社会经济背景以及客户当前拥有的保险类型。我们将利用这些信息更好地分析和理解客户行为,特别是在客户如何回应和参与营销及销售活动方面。
已参与客户分析
现在我们已经将数据加载到 Python 环境中,接下来我们将分析这些数据,以理解不同客户如何表现以及如何回应不同的营销策略。我们将按照以下步骤进行:
-
总体参与率
-
按优惠类型划分的参与率
-
按优惠类型和车辆类别划分的参与率
-
按销售渠道划分的参与率
-
按销售渠道和车辆大小划分的参与率
总体参与率
我们首先需要理解的是总体市场响应或参与率。我们可以使用以下代码获取总的响应客户数量:
df.groupby('Response').count()['Customer']
从数据中可以看到,Response 列包含了客户是否响应了营销呼叫的信息(Yes 表示响应的客户,No 表示没有响应的客户)。我们简单地使用 pandas DataFrame 中的 groupby 函数按此列进行分组,并使用 pandas 包中的 count 函数统计每个类别中的客户数量。
结果如下所示:

为了在图表中可视化这个结果,你可以使用以下代码:
ax = df.groupby('Response').count()['Customer'].plot(
kind='bar',
color='skyblue',
grid=True,
figsize=(10, 7),
title='Marketing Engagment'
)
ax.set_xlabel('Engaged')
ax.set_ylabel('Count')
plt.show()
图表如下所示:

从这些结果中可以看出,大多数客户没有响应营销呼叫。让我们用百分比的方式来看这些数字,使用以下代码:
df.groupby('Response').count()['Customer']/df.shape[0]
当你运行这段代码时,结果如下所示:

从这些结果中我们可以看出,只有大约 14% 的客户响应了营销呼叫。让我们更深入分析响应的客户,进一步了解哪些因素对他们最有效。
按优惠类型的参与率
不同类型的优惠对客户的效果不同。在这一部分,我们将查看哪些类型的优惠对参与的客户最有效。请看以下代码:
by_offer_type_df = df.loc[
df['Response'] == 'Yes'
].groupby([
'Renew Offer Type'
]).count()['Customer'] / df.groupby('Renew Offer Type').count()['Customer']
从这段代码中可以看出,我们正在按 Renew Offer Type 列进行分组,在该列中有四种不同类型的优惠。我们首先通过筛选 Response 列中为 Yes 的值来统计每种续约优惠类型中参与的客户数量。然后,我们将这些数字除以每种续约优惠类型中客户的总数,从而得到每种续约优惠类型的参与率。结果如下所示:

我们可以通过以下代码在条形图中可视化这些结果:
ax = (by_offer_type_df*100.0).plot(
kind='bar',
figsize=(7, 7),
color='skyblue',
grid=True
)
ax.set_ylabel('Engagement Rate (%)')
plt.show()
条形图如下所示:

从这个图表中你可以很容易地看到,Offer2 在客户中的参与率最高。在进行客户分析时,如前所述,我们通常希望了解每个事件中客户的 demographics 和属性,以便理解哪些方案对哪类客户最有效。这可以通过更好地定位那些客户子群体,进而在下一次营销活动中实现进一步的改进。让我们进一步分析这些数据。
按优惠类型和车辆类别的参与率
在上一节中,我们了解到Renewal Offer Type 2对客户的效果最好。营销人员可以从这一发现中受益,因为它提供了有关哪种类型的优惠最有效并且具有最高客户响应率的有用见解。然而,我们还可以获得更多关于不同背景或特征的客户如何对不同优惠类型做出反应的见解。在这一节中,我们将展示作为营销人员,您可以如何了解具有不同属性的客户如何对不同的营销信息做出不同的反应。
让我们看看针对不同车辆类别的客户,每种优惠类型的响应率是否有明显差异。我们将使用以下代码查看按每种优惠类型和Vehicle Class计算的参与率:
by_offer_type_df = df.loc[
df['Response'] == 'Yes'
].groupby([
'Renew Offer Type', 'Vehicle Class'
]).count()['Customer']/df.groupby('Renew Offer Type').count()['Customer']
从这段代码中可以看到,我们按两列数据进行分组,分别是Renew Offer Type和Vehicle Class,并计算每个组的参与率。
结果如下所示:

为了提高可读性,我们可以使用以下代码来转换这些数据:
by_offer_type_df = by_offer_type_df.unstack().fillna(0)
从这段代码中可以看到,我们在pandas的DataFrame中使用了unstack函数来透视数据,将内层分组提取并转换为列。这样查看结果会更加方便。结果如下所示:

如您所见,在应用unstack函数后,Vehicle Class现在变成了列。我们可以使用以下代码将此数据可视化为条形图:
ax = (by_offer_type_df*100.0).plot(
kind='bar',
figsize=(10, 7),
grid=True
)
ax.set_ylabel('Engagement Rate (%)')
plt.show()
图表如下所示:

让我们仔细看看这张图表。我们在上一节中看到,Offer2在客户中具有最高的响应率。在这里,我们可以看到不同车辆类别的客户如何对其他类型的续约优惠做出不同的反应。例如,拥有Four-Door Car的客户对所有优惠类型的响应频率最高。然而,拥有SUV的客户对Offer1的响应率明显高于Offer2。从这些结果中可以看出,通过进一步细分客户群体,我们可以获得更多的见解。如果我们在不同客户细分群体之间看到响应率的显著差异,我们可以对不同优惠类型的目标人群进行微调。在我们的示例中,如果我们认为拥有SUV的客户对Offer1的参与度明显高于对Offer2的参与度,那么我们可以将Offer1目标定向为SUV客户。另一方面,如果我们认为拥有Two-Door Car的客户对Offer2的参与度显著高于其他优惠类型,那么我们可以将Offer2目标定向为Two-Door Car车主。
按销售渠道的参与率
我们来看另一个例子。我们将分析不同销售渠道的参与率差异。请看以下代码:
by_sales_channel_df = df.loc[
df['Response'] == 'Yes'
].groupby([
'Sales Channel'
]).count()['Customer']/df.groupby('Sales Channel').count()['Customer']
结果如下所示:

使用可视化展示这些结果会更容易理解。你可以使用以下代码将这些数据可视化:
ax = (by_sales_channel_df*100.0).plot(
kind='bar',
figsize=(7, 7),
color='skyblue',
grid=True
)
ax.set_ylabel('Engagement Rate (%)')
plt.show()
图表如下所示:

从这个图表中你可以看到,Agent在获取客户回应方面表现最佳,其次是通过Web渠道的销售表现。和之前一样,我们可以进一步分析这一结果,看看不同特征的客户是否会有行为上的变化。
按销售渠道和车辆大小划分的参与率
本节将查看不同车辆大小的客户是否对不同销售渠道有不同的响应。请查看以下代码,计算每个销售渠道和车辆大小的参与率:
by_sales_channel_df = df.loc[
df['Response'] == 'Yes'
].groupby([
'Sales Channel', 'Vehicle Size'
]).count()['Customer']/df.groupby('Sales Channel').count()['Customer']
结果如下所示:

和之前一样,我们可以将这些数据通过以下代码unstack为一个更易于查看的格式:
by_sales_channel_df = by_sales_channel_df.unstack().fillna(0)
结果如下所示:

我们可以使用以下代码将这些结果可视化为条形图:
ax = (by_sales_channel_df*100.0).plot(
kind='bar',
figsize=(10, 7),
grid=True
)
ax.set_ylabel('Engagement Rate (%)')
plt.show()
现在图表如下所示:

从这个图表中可以看到,Medsize车辆的客户对所有销售渠道的反应最好。在不同销售渠道中,Large和Small车辆的车主在参与率上有所不同。例如,Small车辆的车主在通过Agent和Call Center渠道时反应更好,而Large车辆的车主则更倾向于通过Branch和Web渠道进行响应。正如之前讨论的,我们可以将这些见解运用到下一轮的营销中。例如,由于Small车主通过Agent和Call Center响应的可能性较高,我们可以在面向Small车主时更加重视这两个渠道。
客户群体细分
本节将简要讨论如何对客户群体进行细分。我们将在第十章《数据驱动的客户细分》中扩展这一概念,并深入讨论如何运用机器学习进行客户细分,但本节会让你对客户细分的基本概念和表现形式有一个大致了解。
本节中,我们将通过Customer Lifetime Value(客户生命周期价值)和Months Since Policy Inception(自保单生效以来的月份数)对客户群体进行细分。你可以尝试使用不同的特征来细分客户群体。请查看以下Customer Lifetime Value列的分布:

根据这些信息,我们将定义那些Customer Lifetime Value高于中位数的客户为高 CLV 客户,而那些 CLV 低于中位数的客户为低 CLV 客户。你可以使用以下代码进行编码:
df['CLV Segment'] = df['Customer Lifetime Value'].apply(
lambda x: 'High' if x > df['Customer Lifetime Value'].median() else 'Low'
)
我们将对Months Since Policy Inception字段进行相同的处理。请查看以下关于Months Since Policy Inception的分布:

类似地,我们将定义那些Months Since Policy Inception大于中位数的客户为高Policy Age Segment客户,而那些低于中位数的客户为低Policy Age Segment客户。你可以使用以下代码进行编码:
df['Policy Age Segment'] = df['Months Since Policy Inception'].apply(
lambda x: 'High' if x > df['Months Since Policy Inception'].median() else 'Low'
)
我们可以使用以下代码来可视化这些段:
ax = df.loc[
(df['CLV Segment'] == 'High') & (df['Policy Age Segment'] == 'High')
].plot.scatter(
x='Months Since Policy Inception',
y='Customer Lifetime Value',
logy=True,
color='red'
)
df.loc[
(df['CLV Segment'] == 'Low') & (df['Policy Age Segment'] == 'High')
].plot.scatter(
ax=ax,
x='Months Since Policy Inception',
y='Customer Lifetime Value',
logy=True,
color='blue'
)
df.loc[
(df['CLV Segment'] == 'High') & (df['Policy Age Segment'] == 'Low')
].plot.scatter(
ax=ax,
x='Months Since Policy Inception',
y='Customer Lifetime Value',
logy=True,
color='orange'
)
df.loc[
(df['CLV Segment'] == 'Low') & (df['Policy Age Segment'] == 'Low')
].plot.scatter(
ax=ax,
x='Months Since Policy Inception',
y='Customer Lifetime Value',
logy=True,
color='green',
grid=True,
figsize=(10, 7)
)
ax.set_ylabel('CLV (in log scale)')
ax.set_xlabel('Months Since Policy Inception')
ax.set_title('Segments by CLV and Policy Age')
plt.show()
让我们仔细看看这段代码。在第一个代码块中,我们使用pandas包中的plot.scatter函数为那些位于High CLV和High Policy Age段的客户创建散点图。通过使用logy=True标志,我们可以轻松地将尺度转换为对数尺度。对数尺度通常用于货币值,因为它们的值常常具有很大的偏斜性。我们对之前创建的四个段重复这个过程四次。
结果散点图如下所示:

从这个散点图中你可以看到,红色的数据点代表那些位于High CLV和High Policy Age段的客户。橙色代表High CLV和Low Policy Age组,蓝色代表Low CLV和High Policy Age组,最后,绿色代表Low CLV和Low Policy Age组。
现在我们已经创建了这四个段,让我们看看这些段之间的参与率是否存在明显差异。请查看以下代码:
engagment_rates_by_segment_df = df.loc[
df['Response'] == 'Yes'
].groupby(
['CLV Segment', 'Policy Age Segment']
).count()['Customer']/df.groupby(
['CLV Segment', 'Policy Age Segment']
).count()['Customer']
从这段代码中你可以看到,我们通过两个新创建的列CLV Segment和Policy Age Segment进行分组,并计算这四个段的参与率。结果如下所示:

在图表中查看差异会更加直观。你可以使用以下代码为这些数据创建一个条形图:
ax = (engagment_rates_by_segment_df.unstack()*100.0).plot(
kind='bar',
figsize=(10, 7),
grid=True
)
ax.set_ylabel('Engagement Rate (%)')
ax.set_title('Engagement Rates by Customer Segments')
plt.show()
现在,图表如下所示:

从此图中可以看出,高保单年龄段的参与度高于低保单年龄段。这表明那些在该公司投保较长时间的客户响应更好。还可以注意到,高保单年龄和低客户生命周期价值这一群体的参与率在四个群体中最高。通过根据客户属性创建不同的客户群体,我们可以更好地理解不同客户群体的行为差异。我们将在第九章《客户生命周期价值》中进一步深入探讨客户细分的概念,并进行实验。
本次 Python 练习的完整代码可以在以下链接中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.7/python/CustomerBehaviors.ipynb
使用 R 进行客户分析
在本节中,我们将讨论如何使用 R 进行客户分析。我们将主要使用dplyr和ggplot2库来分析和可视化数据集中观察到的客户行为。对于那些希望使用 Python 而非 R 进行练习的读者,可以参考前一节内容。我们将通过分析和理解活跃客户的行为开始本节内容,然后讨论通过某些标准对客户基础进行简单划分的方法。
对于本次练习,我们将使用IBM提供的公开数据集,您可以通过以下链接找到:www.ibm.com/communities/analytics/watson-analytics-blog/marketing-customer-value-analysis/。您可以访问该链接并下载名为WA_Fn UseC_ Marketing Customer Value Analysis.csv的 CSV 格式数据。下载数据后,您可以通过运行以下命令将其加载到 RStudio 中:
library(dplyr)
library(ggplot2)
#### 1\. Load Data ####
df <- read.csv(
file="~/Documents/data-science-for-marketing/ch.7/data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv",
header=TRUE
)
与上一章类似,我们在 R 中使用read.csv函数加载 CSV 格式的数据。一旦将数据加载到DataFrame中,它应如下所示:

从这些数据中可以看到,有一列名为 Response,其中包含了客户是否回应了营销活动的信息。此外,Renew.Offer.Type 和 Sales.Channel 列分别代表了呈现给客户的续约优惠类型和用于联系客户的销售渠道。还有许多其他列代表了客户的社会经济背景以及客户当前拥有的保险类型。我们将利用这些信息来分析和更好地理解客户行为,尤其是他们对营销和销售活动的响应和参与情况。
在我们深入数据之前,我们将 Response 列编码为数值——0 表示 No,1 表示 Yes。这样可以使我们未来的计算更简便。你可以使用以下代码进行编码:
# Encode engaged customers as 0s and 1s
df$Engaged <- as.integer(df$Response) - 1
已参与客户的分析
现在我们已经将数据加载到环境中,我们将对其进行分析,以了解不同客户如何对不同的营销策略做出反应。我们将按照以下步骤进行:
-
总体参与率
-
按优惠类型的参与率
-
按优惠类型和车辆类别的参与率
-
按销售渠道的参与率
-
按销售渠道和车辆大小的参与率
总体参与率
我们首先需要理解的是整体的营销响应或参与率。我们可以使用以下代码来获取总的响应客户数量:
## - Overall Engagement Rates ##
engagementRate <- df %>% group_by(Response) %>%
summarise(Count=n()) %>%
mutate(EngagementRate=Count/nrow(df)*100.0)
正如你从数据中看到的,Response 列包含了客户是否回应了营销呼叫的信息(Yes 表示有回应,No 表示没有回应)。我们通过在 dplyr 库中使用 group_by 函数对该列进行分组,并利用 n() 函数统计每个类别中的客户数量。接着,使用 mutate 函数,我们通过将 Count 除以 DataFrame 中的总记录数来计算参与率。
结果如下所示:

为了在图表中可视化这一点,你可以使用以下代码:
ggplot(engagementRate, aes(x=Response, y=EngagementRate)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('Engagement Rate') +
xlab("Engaged") +
ylab("Percentage (%)") +
theme(plot.title = element_text(hjust = 0.5))
绘图结果如下所示:

从这些结果中可以看到,大多数客户没有回应营销呼叫。数据表明,只有大约 14% 的客户回应了营销呼叫。接下来,我们将深入了解那些回应的客户,进一步了解对他们来说什么最有效。
按优惠类型的参与率
不同类型的优惠对不同客户的效果不同。在这一部分,我们将研究哪些类型的优惠对参与过的客户最有效。请看以下代码:
## - Engagement Rates by Offer Type ##
engagementRateByOfferType <- df %>%
group_by(Renew.Offer.Type) %>%
summarise(Count=n(), NumEngaged=sum(Engaged)) %>%
mutate(EngagementRate=NumEngaged/Count*100.0)
如你从这段代码中可以看到,我们按Renew.Offer.Type列进行分组,其中有四种不同类型的优惠。然后,在summarise函数中,我们使用n()函数统计记录的总数,并通过对编码列Engaged求和,统计参与的客户数量。最后,在mutate函数中,我们通过将NumEngaged除以Count并乘以100.0来计算EngagementRate。
结果如下所示:

我们可以通过以下代码将这些结果可视化为条形图:
ggplot(engagementRateByOfferType, aes(x=Renew.Offer.Type, y=EngagementRate)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('Engagement Rates by Offer Type') +
xlab("Offer Type") +
ylab("Engagement Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
现在的条形图如下所示:

从这个图中你可以轻松看出,Offer 2在客户中拥有最高的参与率。在进行客户分析时,如前所述,我们通常希望了解每个事件中客户的群体特征和属性,以便了解什么对哪类客户最有效。这将帮助我们在下一次营销活动中更好地针对这些客户子群体进行优化。让我们进一步分析这些数据。
按优惠类型和车辆类别的参与率
在上一节中,我们已经了解到Renewal Offer Type 2对客户最有效。市场营销人员可以从这一信息中受益,因为这一发现提供了有价值的见解,说明哪种类型的优惠最有效,并且收到了客户的最高响应率。然而,我们还可以进一步洞察不同背景或特征的客户如何对不同的优惠类型做出不同反应。在本节中,我们将展示作为市场营销人员,如何理解不同属性的客户如何对不同的营销信息做出不同的反应。
让我们看看对于不同车辆类别的客户,是否存在优惠类型响应率的显著差异。我们将使用以下代码查看每种优惠类型和车辆类别的参与率:
## - Offer Type & Vehicle Class ##
engagementRateByOfferTypeVehicleClass <- df %>%
group_by(Renew.Offer.Type, Vehicle.Class) %>%
summarise(NumEngaged=sum(Engaged)) %>%
left_join(engagementRateByOfferType[,c("Renew.Offer.Type", "Count")], by="Renew.Offer.Type") %>%
mutate(EngagementRate=NumEngaged/Count*100.0)
如你从这段代码中可以看到,我们按Renew.Offer.Type和Vehicle.Class两列进行分组,并统计每组中的参与客户数量。然后,我们通过Renew.Offer.Type列将此数据与engagementRateByOfferType变量进行连接,以获得每种优惠类型的参与客户总数。最后,在mutate函数中计算参与率。
结果如下所示:

为了使这更加易读,我们可以使用条形图来可视化这些数据。请看以下代码:
ggplot(engagementRateByOfferTypeVehicleClass, aes(x=Renew.Offer.Type, y=EngagementRate, fill=Vehicle.Class)) +
geom_bar(width=0.5, stat="identity", position = "dodge") +
ggtitle('Engagement Rates by Offer Type & Vehicle Class') +
xlab("Offer Type") +
ylab("Engagement Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
使用这段代码,我们可以生成如下所示的条形图:

让我们仔细看看这个图表。在上一节中我们看到Offer2在客户中具有最高的响应率。在这里,我们可以看到不同车辆类别的客户如何以不同方式响应其他类型的续约优惠。例如,拥有四门轿车的客户在所有优惠类型中反应最为频繁。然而,拥有SUV的客户对Offer1的反应几乎高于Offer2。从这些结果中可以看出,我们可以通过进一步细分客户的人口统计信息获得更多的洞察。如果我们看到不同客户群体之间的响应率有显著差异,我们可以细化不同优惠针对的目标群体。例如,如果我们认为拥有SUV的客户对Offer1的响应率明显高于Offer2,那么我们可以将Offer1针对SUV客户。另一方面,如果我们认为拥有两厢车的客户对Offer2的响应率明显高于其他优惠类型,那么我们可以将Offer2针对两厢车车主。
按销售渠道的参与率
让我们来看一个另一个例子。我们将分析不同销售渠道的参与率差异。请查看以下代码:
## - Engagement Rates by Sales Channel ##
engagementRateBySalesChannel <- df %>%
group_by(Sales.Channel) %>%
summarise(Count=n(), NumEngaged=sum(Engaged)) %>%
mutate(EngagementRate=NumEngaged/Count*100.0)
结果如下所示:

使用可视化图表可以更容易地理解这一结果。您可以使用以下代码来可视化这些数据:
ggplot(engagementRateBySalesChannel, aes(x=Sales.Channel, y=EngagementRate)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('Engagement Rates by Sales Channel') +
xlab("Sales Channel") +
ylab("Engagement Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
图表如下所示:

从这个图表中可以看到,Agent在获得客户反馈方面效果最好。其次是通过Web的销售。接下来,我们将深入分析这一结果,看看不同特征的客户之间的行为是否有所不同。
按销售渠道和车辆尺寸的参与率
在本节中,我们将探讨不同车辆尺寸的客户是否在不同销售渠道上的反应有所不同。请查看以下代码,以计算每个销售渠道和车辆尺寸的参与率:
## - Sales Channel & Vehicle Size ##
engagementRateBySalesChannelVehicleSize <- df %>%
group_by(Sales.Channel, Vehicle.Size) %>%
summarise(NumEngaged=sum(Engaged)) %>%
left_join(engagementRateBySalesChannel[,c("Sales.Channel", "Count")], by="Sales.Channel") %>%
mutate(EngagementRate=NumEngaged/Count*100.0)
结果如下所示:

和之前一样,我们可以通过条形图来可视化这些数据,以便更容易阅读。您可以使用以下代码来可视化这些数据:
ggplot(engagementRateBySalesChannelVehicleSize, aes(x=Sales.Channel, y=EngagementRate, fill=Vehicle.Size)) +
geom_bar(width=0.5, stat="identity", position = "dodge") +
ggtitle('Engagement Rates by Sales Channel & Vehicle Size') +
xlab("Sales Channel") +
ylab("Engagement Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
该图表现在如下所示:

从这个图表中可以看出,拥有Medsize车辆的客户对所有销售渠道的反应最佳。不同销售渠道之间,Large和Small车辆所有者的参与率略有不同。例如,Small车辆所有者更倾向于通过Agent和Call Center渠道做出反应,而Large车辆所有者则更倾向于通过Branch和Web渠道做出反应。如前所述,我们可以在下一步的营销工作中利用这些洞察。例如,既然Small车辆所有者通过Agent和Call Center渠道表现出更高的参与度,我们可以在这两个渠道上更密集地接触Small车辆所有者。
客户细分
在本节中,我们将简要讨论如何进行客户细分。我们将在第九章中进一步扩展这一概念,并讨论如何利用机器学习进行客户细分,客户生命周期价值,但本节将为你提供一些关于客户细分是什么以及它的表现形式的基本概念。
在本节中,我们将通过Customer.Lifetime.Value和Months.Since.Policy.Inception来进行客户细分。你可以尝试不同的特征来细分客户群体。首先,让我们来看一下Customer.Lifetime.Value列的分布:

- 基于这些信息,我们将 CLV 高于中位数的客户定义为高 CLV 客户,将 CLV 低于中位数的客户定义为低 CLV 客户。你可以使用以下代码进行编码:
clv_encode_fn <- function(x) {if(x > median(df$Customer.Lifetime.Value)) "High" else "Low"}
df$CLV.Segment <- sapply(df$Customer.Lifetime.Value, clv_encode_fn)
从这段代码中可以看到,我们定义了clv_encode_fn函数,将 CLV 高于中位数的客户编码为High,将 CLV 低于中位数的客户编码为Low。然后,使用sapply函数,我们对Customer.Lifetime.Value列中的值进行编码,并将编码后的值存储为一个名为CLV.Segment的新列。
我们将按照相同的过程处理Months.Since.Policy.Inception字段。请查看以下关于Months.Since.Policy.Inception的分布:

同样地,我们将Months.Since.Policy.Inception值高于中位数的客户定义为高Policy.Age.Segment客户,将值低于中位数的客户定义为低Policy.Age.Segment客户。你可以使用以下代码进行编码:
policy_age_encode_fn <- function(x) {if(x > median(df$Months.Since.Policy.Inception)) "High" else "Low"}
df$Policy.Age.Segment <- sapply(df$Months.Since.Policy.Inception, policy_age_encode_fn)
我们可以使用以下代码来可视化这些细分:
ggplot(
df[which(df$CLV.Segment=="High" & df$Policy.Age.Segment=="High"),],
aes(x=Months.Since.Policy.Inception, y=log(Customer.Lifetime.Value))
) +
geom_point(color='red') +
geom_point(
data=df[which(df$CLV.Segment=="High" & df$Policy.Age.Segment=="Low"),],
color='orange'
) +
geom_point(
data=df[which(df$CLV.Segment=="Low" & df$Policy.Age.Segment=="Low"),],
color='green'
) +
geom_point(
data=df[which(df$CLV.Segment=="Low" & df$Policy.Age.Segment=="High"),],
color='blue'
) +
ggtitle('Segments by CLV and Policy Age') +
xlab("Months Since Policy Inception") +
ylab("CLV (in log scale)") +
theme(plot.title = element_text(hjust = 0.5))
让我们仔细看一下这段代码。首先,我们将High CLV和High Policy Age群体绘制成红色的散点图。然后,我们重复相同的过程,分别为High CLV和Low Policy Age群体绘制橙色的散点图,Low CLV和Low Policy Age群体绘制绿色的散点图,最后为Low CLV和High Policy Age群体绘制蓝色的散点图。这里有一点需要注意的是我们在aes函数中是如何定义y值的。如代码所示,y=log(Customer.Lifetime.Value),我们将 CLV 值转换为对数刻度。对数刻度常用于货币数值,因为它们的值通常具有较高的偏态。
生成的散点图如下所示:

从这个散点图中可以看到,红色的数据点代表的是High CLV和High Policy Age群体的客户。橙色的数据点代表的是High CLV和Low Policy Age群体,蓝色的数据点代表的是Low CLV和High Policy Age群体,最后,绿色的数据点代表的是Low CLV和Low Policy Age群体。
现在我们已经创建了这四个群体,接下来我们来看看这些群体之间的参与率是否有明显差异。请查看以下代码:
engagementRateBySegment <- df %>%
group_by(CLV.Segment, Policy.Age.Segment) %>%
summarise(Count=n(), NumEngaged=sum(Engaged)) %>%
mutate(EngagementRate=NumEngaged/Count*100.0)
从这段代码中可以看到,我们正在根据两个新创建的列CLV.Segment和Policy.Age.Segment进行分组,并计算这四个群体的参与率。结果如下所示:

通过图表,理解差异会更加直观。你可以使用以下代码为这些数据创建条形图:
ggplot(engagementRateBySegment, aes(x=CLV.Segment, y=EngagementRate, fill=Policy.Age.Segment)) +
geom_bar(width=0.5, stat="identity", position = "dodge") +
ggtitle('Engagement Rates by Customer Segments') +
ylab("Engagement Rate (%)") +
theme(plot.title = element_text(hjust = 0.5))
现在图表看起来如下:

从这个图中可以看出,High Policy.Age.Segment的参与率高于Low Policy.Age.Segment。这表明那些与公司合作时间较长的客户回应更积极。还可以注意到,High Policy Age和Low CLV群体的参与率在四个群体中最高。通过根据客户属性创建不同的客户群体,我们可以更好地理解不同群体的客户行为差异。我们将在第九章中进一步扩展和深入实验客户细分的概念,客户生命周期价值。
这次 R 语言练习的完整代码可以在以下链接找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.7/R/CustomerBehaviors.R。
总结
在这一章中,我们讨论了客户分析。我们了解了客户分析的定义,以及进行客户分析的重要性和好处,还介绍了客户分析的不同应用场景。我们探讨了随着客户数据对企业的可及性提高以及关于客户的丰富数据的增多导致竞争加剧的现象,并强调了了解客户喜好的重要性。进行客户分析是获取客户行为洞察的重要步骤,有助于形成更好的营销策略,优化销售渠道以及做出其他关键业务决策。利用客户分析,您可以监测和追踪客户对不同产品和营销策略的反应指标(KPI),有效地构建类似客户群体,并建立预测模型以提高客户参与和保留率,同时优化营销渠道。
在下一章中,我们将学习如何使用预测分析来预测营销参与的可能性。我们将讨论一些常用的机器学习算法,用于构建预测模型,并在数据集上实验它们的预测性能。
第八章:预测营销互动的可能性
在本章中,我们将扩展上一章的知识以及我们在第七章中进行的客户分析练习,客户行为的探索性分析。为了实现更成功、更智能的营销策略,我们不能仅仅停留在分析客户数据这一层面。借助数据科学和机器学习的先进技术,我们现在可以对客户的未来行为做出智能的猜测和估计,例如哪些类型的客户更可能参与营销活动,客户可能购买多少商品,或者哪些客户可能会流失。这些基于历史客户数据建立的预测或智能猜测可以帮助你提升营销表现,并进一步为不同目标受众量身定制营销策略。在本章中,我们将学习如何利用数据科学和机器学习来预测未来的结果,以及这些预测如何帮助你的未来营销工作。
在本章中,我们将涵盖以下主题:
-
营销中的预测分析
-
评估分类模型
-
使用 Python 预测营销互动的可能性
-
使用 R 预测营销互动的可能性
营销中的预测分析
预测分析是一个分析并从历史数据中提取信息的过程,旨在识别模式并对未来结果做出预测。通常会使用大量的统计和机器学习模型,找出数据集中属性或特征与目标变量或行为之间的关系。这些预测分析可以在许多不同行业中得到应用。
例如,它常常被用于金融行业的欺诈检测,其中机器学习模型被训练来检测和防止潜在的欺诈交易。医疗行业也可以从预测分析中受益,帮助医生做出决策。此外,营销的各个方面也能从预测分析中获益,例如客户获取、客户保持以及追加销售和交叉销售等。
在预测分析中,广义上讲,存在两种类型的问题:
-
分类问题:分类问题是指观察值可以属于一组类别。例如,预测客户是否会打开一封营销邮件就是一个分类问题。只有两种可能的结果——打开营销邮件或不打开邮件。
-
回归问题:另一方面,回归问题是指结果可以取任何实数范围的情况。例如,预测客户终身价值就是一个回归问题。一个客户的终身价值可能是 $0,而另一个客户的终身价值可能是 $10,000。这种结果可以取连续值的问题,称为回归问题。
在本章中,我们将重点讨论营销行业中常见的分类问题之一——预测客户参与的可能性。在下一章,第九章,客户终身价值,我们将探讨营销行业中经常出现的回归问题。
预测分析在营销中的应用
如前所述,预测分析在营销中的应用方式多种多样。本节中,我们将讨论预测分析在营销中的四个常见应用案例:
-
客户参与的可能性:预测分析可以帮助营销人员预测客户与他们的营销策略互动的可能性。例如,如果你的营销活动主要通过电子邮件进行,你可以利用预测分析来预测哪些客户有较高的可能性打开你的营销邮件,并根据这些高可能性客户定制营销策略,从而最大化营销效果。再举一个例子,如果你在社交媒体上展示广告,预测分析可以帮助你识别那些可能点击广告的客户类型。
-
客户终身价值:预测分析可以帮助你预测客户的预期终身价值。通过历史交易数据,预测分析可以帮助你识别客户群体中的高价值客户。通过这些预测,你和你的公司可以更加专注于与这些高价值客户建立良好的关系。我们将在下一章中更详细地讨论如何为客户终身价值预测构建预测模型。
-
推荐正确的产品和内容:正如我们在第六章,推荐正确的产品中所讨论的那样,我们可以利用数据科学和机器学习来预测哪些客户可能购买产品或查看内容。通过这些预测,你可以通过向个人客户推荐合适的产品和内容来提高客户转化率。
-
客户获取与保持:预测分析也在客户获取和保持方面得到了广泛应用。根据你收集的潜在客户资料和现有客户的历史数据,你可以应用预测分析来识别高质量的潜在客户,或者根据潜在客户转化为活跃客户的可能性对其进行排名。另一方面,你可以使用客户流失数据以及现有客户的历史数据,开发预测模型来预测哪些客户可能会流失或取消订阅你的产品。我们将在第十一章《客户保持》中详细讨论如何将预测分析应用于客户保持,保持客户。
除了这四种常见的市场营销预测分析应用外,还有许多其他方法可以将预测分析应用于你的营销策略。你应该发挥创意,思考如何以及在哪些方面将预测分析运用于未来的营销策略。
评估分类模型
在开发预测模型时,了解如何评估这些模型非常重要。在本节中,我们将讨论五种评估分类模型性能的不同方法。第一个可用于衡量预测性能的指标是准确度。准确度就是所有预测中正确预测的百分比,如下所示的公式所示:

第二个常用于分类问题的指标是精确度。精确度定义为真正例的数量除以真正例和假正例的总数。真正例是指模型正确预测为正的情况,而假正例是指模型预测为正,但真实标签为负的情况。公式如下所示:

除了精确度,召回率也是评估分类模型性能的常用指标。召回率定义为真正例的数量除以真正例加上假负例的数量。假负例是指模型预测为负,但真实标签为正的情况。召回率可以看作是模型找回或发现正例的程度。公式如下所示:

我们将要讨论的最后两个指标是接收者操作特征(ROC)曲线和曲线下面积(AUC)。ROC 曲线展示了在不同阈值下,真正率和假正率的变化。AUC 只是 ROC 曲线下的总面积。AUC 的值范围从 0 到 1,AUC 数值越高,表示模型性能越好。随机分类器的 AUC 值为 0.5,因此任何 AUC 高于 0.5 的分类器都表明该模型的表现优于随机预测。典型的 ROC 曲线如下所示:

在接下来的编程练习中,我们将使用我们刚刚讨论的这五个指标来评估我们在 Python 和 R 中构建的模型的性能。现在让我们开始构建机器学习模型,以预测市场营销参与的可能性!
使用 Python 预测市场营销参与的可能性
在本节中,我们将讨论如何使用 Python 中的机器学习算法构建预测模型。更具体地,我们将学习如何使用随机森林算法构建预测模型,以及如何调整随机森林模型并评估模型性能。我们将主要使用 pandas、matplotlib 和 scikit-learn 包来分析、可视化和构建预测客户市场营销参与可能性的机器学习模型。对于那些希望使用 R 而非 Python 进行本次练习的读者,可以跳到下一节。
在本次练习中,我们将使用 IBM 提供的一个公开数据集,您可以通过以下链接找到该数据集:www.ibm.com/communities/analytics/watson-analytics-blog/marketing-customer-value-analysis/。您可以访问此链接并下载以 CSV 格式提供的数据,文件名为 WA_Fn UseC_ Marketing Customer Value Analysis.csv。下载数据后,您可以通过运行以下命令将其加载到您的 Jupyter notebook 中:
import pandas as pd
df = pd.read_csv('../data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv')
df 数据框如下所示:

如你所见,这是我们在上一章中使用的相同数据集,当时我们进行了客户分析。通过上一章所学到的关于该数据集的知识,我们将首先通过对目标变量和其他将用作机器学习模型特征的分类变量进行编码来准备我们的数据。
变量编码
为了使用 Python 中的scikit-learn包构建机器学习模型,数据集中的所有特征必须具有数值型值。然而,在数据集中,我们有许多列具有非数值型值。例如,目标变量Response,我们将使用机器学习模型预测的内容,是非数值型的。它包含两个字符串值——Yes和No。为了能够构建机器学习模型,我们需要用数值型值对这个Response目标变量进行编码。再举一个例子,Gender列,我们可以将其作为预测模型的特征之一,也没有数值型值。它包含两个字符串值——F表示女性,M表示男性。在本节中,我们将讨论如何对这些非数值型列进行编码,以便可以将它们用作机器学习模型的特征。
响应变量编码
我们要做的第一件事是对响应变量Response进行编码。我们将Yes值编码为1,No值编码为0。看看以下代码:
df['Engaged'] = df['Response'].apply(lambda x: 1 if x == 'Yes' else 0)
如你所见,从这段代码中,我们使用pandas DataFrame的apply函数来对Response列应用我们的lambda函数,以便将Yes值编码为1,将No值编码为0。然后,我们将这些编码后的值存储在新创建的列Engaged中。为了使用这个新创建的列来获取整体响应或参与率,你可以使用以下代码:
tdf['Engaged'].mean()
整体参与率如下所示:

分类变量编码
如果你仔细观察数据,以下变量是分类变量:
columns_to_encode = [
'Sales Channel', 'Vehicle Size', 'Vehicle Class', 'Policy', 'Policy Type',
'EmploymentStatus', 'Marital Status', 'Education', 'Coverage'
]
这些变量具有一组可以取值的不同值,而这些值之间不一定有区分顺序。
如果你还记得第四章《从参与到转化》中提到的内容,从参与到转化,其实有不止一种方法可以对分类变量进行编码。在本章中,我们将使用的方法是为每个单独的分类变量类别创建虚拟变量,方法是使用pandas包中的get_dummies函数。看看以下代码:
categorical_features = []
for col in columns_to_encode:
encoded_df = pd.get_dummies(df[col])
encoded_df.columns = [col.replace(' ', '.') + '.' + x for x in encoded_df.columns]
categorical_features += list(encoded_df.columns)
df = pd.concat([df, encoded_df], axis=1)
如你所见,在这段代码片段中,我们正在遍历定义在columns_to_encode中的分类变量列名列表。然后,对于每一列,我们使用pandas包中的get_dummies函数来构建虚拟变量。为了使事情更清楚并减少混淆,我们正在重命名新创建的和编码过的DataFrame encoded_df中的列,其中每一列包含关于原始列名和它所表示的类别的信息。例如,对于Sale Channel列,新创建的encoded_df将如下所示:

如您从这个例子中所见,新的DataFrame的每一列代表原始Sales Channel列中的每个类别,值是经过独热编码的,这意味着如果给定记录属于某个类别,则该列值为1,否则为0。
一旦我们为给定的列创建了虚拟变量,我们将把新创建的列存储到一个名为categorical_features的变量中。最后,我们使用pandas包的concat函数将这个新创建的DataFrame与原始的DataFrame进行拼接。concat函数的一个参数axis=1告诉pandas通过列来拼接这两个DataFrame。
到目前为止,我们已经成功编码了除了Gender之外的所有分类变量。由于Gender列只包含两种性别,因此我们不需要为该列创建两个虚拟变量。我们将创建一个包含给定记录性别信息的变量。请看下面的代码:
df['Is.Female'] = df['Gender'].apply(lambda x: 1 if x == 'F' else 0)
categorical_features.append('Is.Female')
如您从这段代码中所见,我们正在创建一个名为Is.Female的新列。我们使用pandas的DataFrame的apply函数,将所有女性编码为1,所有男性编码为0。
构建预测模型
我们几乎准备好开始构建和训练机器学习模型,以预测客户的响应或参与度了。在此之前,我们的数据还有一些需要清理的地方。请看下面的代码:
all_features = continuous_features + categorical_features
response = 'Engaged'
sample_df = df[all_features + [response]]
sample_df.columns = [x.replace(' ', '.') for x in sample_df.columns]
all_features = [x.replace(' ', '.') for x in all_features]
如您从这段代码中看到的,我们正在创建一个新的DataFrame sample_df,它包含了所有的特征all_features和响应变量response。然后,我们通过将列名和特征名中的所有空格替换为点号,来清理列名和特征名。经过这些清理后,DataFrame sample_df现在如下所示:

现在,我们有了一个可以用来训练和测试机器学习模型的样本集,接下来我们将把这个样本集分成两个子集——一个用于训练模型,另一个用于测试和评估训练好的模型。Python 机器学习包scikit-learn有一个函数,可以将给定的样本集拆分成训练集和测试集。请看下面的代码:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(sample_df[all_features], sample_df[response], test_size=0.3)
在scikit-learn包的model_selection模块中,有一个名为train_test_split的函数。这个函数接受样本集和所需的训练集和测试集大小的划分作为输入参数,并返回随机划分的训练集和测试集。从这段代码中可以看到,我们使用70%的样本集进行训练,剩下的30%用于测试。以下是样本集的训练集和测试集的划分情况:

如您所见,sample_df 中共有 9,134 条记录,x_train 中有 6,393 条记录,x_test 中有 2,741 条记录,这意味着大约 70% 的样本集用于训练集,剩余的 30% 样本集用于测试集。我们将在接下来的章节中使用这些训练集和测试集来构建和评估模型。
随机森林模型
基于我们目前准备好的数据,我们将构建一个预测模型,使用随机森林算法,预测客户是否会响应或参与市场营销活动。在 Python 的 scikit-learn 包中,随机森林算法被实现于 ensemble 模块,您可以使用以下代码导入随机森林类:
from sklearn.ensemble import RandomForestClassifier
您可以使用以下代码创建一个随机森林分类器:
rf_model = RandomForestClassifier()
然而,您可以调整许多超参数来优化随机森林模型。超参数是您在训练机器学习模型之前定义的参数。例如,在随机森林算法中,您可以定义您希望在随机森林模型中使用的树木数量。另一个例子是,您可以定义森林中每棵树的最大深度,从而限制每棵树的最大生长深度。
在 scikit-learn 的 RandomForestClassifier 类中,您可以定义许多超参数。我们将查看以下几个超参数的例子:
-
n_estimators:该参数定义了您希望在森林中构建的树木数量。一般来说,树木越多,性能表现越好。然而,随着森林中树木数量的增加,每增加一棵树带来的性能提升会逐渐减少。由于森林中树木的增加意味着训练额外树木的计算成本更高,您应该尝试找到平衡点,并在训练额外树木的计算成本超过性能提升时停止添加树木。 -
max_depth:该参数定义了单棵树的最大深度。深度越大,树可以从训练集捕获的信息越多,这意味着较大的树比较小的树学习训练集的能力更强。然而,树的深度越大,它就越可能会过拟合训练集。这意味着训练好的树在训练集中的表现和预测很好,但在未见过的数据集上预测较差。为了避免过拟合,我们希望限制树的深度,确保它不会对训练集过拟合,同时能够足够好地预测结果。 -
min_samples_split:此参数定义了拆分树节点所需的最小数据点数量。例如,如果您将min_samples_split设置为50,但节点只有40个记录,那么该节点将不会进一步拆分。另一方面,如果节点的样本数超过预定义的最小值,那么它将拆分成子节点。类似于max_depth超参数,这有助于管理树中发生的过拟合程度。 -
max_features:此参数定义了拆分节点时考虑的最大特征数。这个参数在随机森林模型中创造了随机性。在考虑拆分节点的最大特征数的情况下,随机森林算法会随机选择一个特征子集,最多为指定的最大特征数,并决定如何拆分树的某个节点。这样可以让每棵树从训练集中学习到不同的信息。当这些从略有不同特征集的训练集中学习到信息的树进行集成(bagging)或集成(ensemble)时,最终的森林将变得更加准确且具有鲁棒性。
若要获取更详细的描述和其他超参数的信息,您可以参考其官方文档,链接如下:scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html。
训练随机森林模型
使用scikit-learn训练随机森林模型非常简单。请查看以下代码:
rf_model = RandomForestClassifier(
n_estimators=200,
max_depth=5
)
rf_model.fit(X=x_train, y=y_train)
使用scikit-learn包的ensemble模块中的RandomforestClasifier类,您首先需要创建一个带有超参数的RandomforestClasifier对象。为了演示,我们指示模型构建200棵树,每棵树的深度最大为5。然后,您可以使用fit函数训练该模型,该函数接受两个参数,X和y,其中X是训练样本,y是训练标签或目标值。
当您运行此代码时,您将看到如下输出:

一旦随机森林模型训练或拟合完成,模型对象将包含许多有用的信息。您可以从训练好的scikit-learn随机森林模型中提取一个有用的属性,那就是关于森林中各个单独树的信息。通过使用estimators_属性,您可以检索森林中构建的单独树。请查看以下输出:

从这个输出中可以看出,estimators_ 属性返回的是一个子估计器列表,这些子估计器是决策树。通过这些信息,你可以模拟每个子估计器对每个输入的预测。例如,以下代码展示了如何获取森林中第一个子估计器的预测:
rf_model.estimators_[0].predict(x_test)
以下输出显示了前五个子估计器的一些预测:

从这个输出中可以看出,不同的树对测试集中的每一条记录的预测是不同的。这是因为每棵树是通过随机选择不同的特征子集进行训练的。我们来看一下这些单独子估计器的预测结果。第一棵树预测测试集中的第6条记录为1类,其他则为0类。而第二棵树则预测测试集前 10 条记录为0类。通过这些信息,你可以看到随机森林模型的最终预测是如何由这些独立的子估计器或树形成的。
从训练好的 RandomForestClassifier 对象中,我们还可以获得特征重要性信息,这能帮助我们理解每个特征对最终预测的影响。你可以通过以下代码获取每个特征的重要性:
rf_model.feature_importances_
这段代码的输出结果如下:

为了将这些特征重要性与对应的特征关联起来,你可以使用以下代码:
feature_importance_df = pd.DataFrame(list(zip(rf_model.feature_importances_, all_features)))
feature_importance_df.columns = ['feature.importance', 'feature']
结果如下所示:

从这个输出中可以看出,EmploymentStatus.Retired 特征似乎是做出最终预测的最重要因素,Income、Total.Claim.Amount 和 Customer.Lifetime.Value 特征依次排在第二、第三和第四重要特征的位置。
评估分类模型
在本章早些时候,我们讨论了五种不同的方式来评估分类模型的性能。在这一节中,我们将学习如何在 Python 中计算并可视化评估分类模型的指标,使用的是我们刚刚建立的随机森林模型。
我们将要查看的前三个指标是准确率、精确率和召回率。Python 的 scikit-learn 包已经实现了这些指标的函数。你可以通过以下代码行导入这些函数:
from sklearn.metrics import accuracy_score, precision_score, recall_score
从这段代码中可以看到,scikit-learn 包的 metrics 模块有一个 accuracy_score 函数用于计算模型的准确率,一个 precision_score 函数用于精确率计算,还有一个 recall_score 函数用于计算召回率。
在继续评估模型性能之前,我们需要模型的预测结果。为了让我们在前一部分中构建的随机森林模型对数据集进行预测,我们可以简单地使用模型的predict函数。看看以下代码:
in_sample_preds = rf_model.predict(x_train)
out_sample_preds = rf_model.predict(x_test)
利用这些预测结果,我们将评估随机森林模型在训练集和测试集上的表现。以下代码展示了如何在scikit-learn包中使用accuracy_score、precision_score和recall_score函数:
# accuracy
accuracy_score(actual, predictions)
# precision
precision_score(actual, predictions)
# recall
recall_score(actual, predictions)
如你所见,accuracy_score、precision_score和recall_score函数都接受两个参数——真实标签和预测标签。看看以下输出:

该输出简要概述了我们模型在预测响应方面的表现。对于训练集,整体预测的准确率为0.8724,这意味着模型预测正确的概率约为87%。对于测试集,整体预测的准确率为0.8818,大致与训练集中的预测准确率相当。你还可以看到,样本内和样本外预测的精度分别为0.9919和0.9423,召回率分别为0.1311和0.1324。由于随机性和可能使用的不同超参数,你可能会得到不同的结果。
接下来我们要查看的指标是 ROC 曲线和 AUC。scikit-learn包中的metrics模块为 ROC 曲线和 AUC 提供了方便的函数。看看以下代码行:
from sklearn.metrics import roc_curve, auc
scikit-learn包中的metrics模块中的roc_curve函数计算 ROC,而auc函数计算 AUC。为了使用这些函数计算 ROC 和 AUC,我们需要首先从我们的随机森林模型中获取预测概率。以下代码展示了如何获得随机森林模型在训练集和测试集上的预测概率:
in_sample_preds = rf_model.predict_proba(x_train)[:,1]
out_sample_preds = rf_model.predict_proba(x_test)[:,1]
如你所见,我们正在使用随机森林模型rf_model的predict_proba函数。该函数输出每个记录属于每个类别的预测概率。由于我们的情况只有两个可能的类别,0代表无响应,1代表有响应,因此predict_proba函数的输出有两列,其中第一列代表负类的预测概率,即每个记录无响应的概率,第二列代表正类的预测概率,即每个记录有响应的概率。由于我们只关心响应营销活动的概率,因此我们可以选择第二列作为正类的预测概率。
使用这两个训练集和测试集的正类预测概率,我们现在可以计算 ROC 曲线和 AUC。让我们首先看看如何通过以下代码使用 roc_curve 函数计算 ROC 曲线:
in_sample_fpr, in_sample_tpr, in_sample_thresholds = roc_curve(y_train, in_sample_preds)
out_sample_fpr, out_sample_tpr, out_sample_thresholds = roc_curve(y_test, out_sample_preds)
从这段代码中可以看出,roc_curve 函数接受两个参数——观测标签和预测概率。该函数返回三个变量,fpr、tpr 和 thresholds。fpr 值代表每个给定阈值的假阳性率,tpr 值代表每个给定阈值的真正阳性率。thresholds 值代表在这些阈值下测量的 fpr 和 tpr。
接下来,使用这些 fpr 和 tpr 值,我们可以通过以下代码计算 AUC:
in_sample_roc_auc = auc(in_sample_fpr, in_sample_tpr)
out_sample_roc_auc = auc(out_sample_fpr, out_sample_tpr)
print('In-Sample AUC: %0.4f' % in_sample_roc_auc)
print('Out-Sample AUC: %0.4f' % out_sample_roc_auc)
从这段代码中可以看到,auc 函数接受两个参数——fpr 和 tpr。利用之前通过 roc_curve 函数计算得到的 fpr 和 tpr 值,我们可以轻松计算出训练集和测试集的 AUC 数值。输出结果如下所示:

根据超参数和随机森林算法中的随机性,你的 AUC 数值可能与这些示例不同。然而,在我们的例子中,样本内训练集的 AUC 为 0.8745,样本外测试集的 AUC 为 0.8425。如果你看到这两个数字之间有很大的差距,那是过拟合的迹象,你应该通过修剪森林中的树木来调整超参数,例如最大深度和分裂所需的最小样本数,从而解决这个问题。
我们将在评估机器学习模型时,最后看一下实际的 ROC 曲线。通过 roc_curve 函数的输出,我们可以使用 matplotlib 包绘制实际的 ROC 曲线。请查看以下代码:
plt.figure(figsize=(10,7))
plt.plot(
out_sample_fpr, out_sample_tpr, color='darkorange', label='Out-Sample ROC curve (area = %0.4f)' % in_sample_roc_auc
)
plt.plot(
in_sample_fpr, in_sample_tpr, color='navy', label='In-Sample ROC curve (area = %0.4f)' % out_sample_roc_auc
)
plt.plot([0, 1], [0, 1], color='gray', lw=1, linestyle='--')
plt.grid()
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('RandomForest Model ROC Curve')
plt.legend(loc="lower right")
plt.show()
从这段代码中可以看出,我们绘制了三条折线图——一条是样本外测试集的 ROC 曲线,另一条是样本内训练集的 ROC 曲线,最后一条是基准的直线。结果如下所示:

从这个图中可以看出,通过 ROC 曲线,更容易观察和比较模型在训练集和测试集之间的整体表现。训练集 ROC 曲线和测试集 ROC 曲线之间的差距越大,模型越有可能对训练集过拟合,无法将发现推广到未知数据中。
这个 Python 练习的完整代码可以在以下链接找到: github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.8/python/PredictingEngagement.ipynb
使用 R 预测营销参与的可能性
在本节中,我们将讨论如何使用 R 中的机器学习算法构建预测模型。更具体地说,我们将学习如何使用随机森林算法构建预测模型,如何调整随机森林模型,并评估模型的性能。我们将主要使用 caTools、ROCR 和 randomForest 包来评估、可视化和构建预测客户营销参与度的机器学习模型。如果读者希望使用 Python 而非 R 来进行此练习,可以参考前一节内容。
在本练习中,我们将使用 IBM 提供的公开数据集,链接如下:www.ibm.com/communities/analytics/watson-analytics-blog/marketing-customer-value-analysis/。你可以点击这个链接下载以 CSV 格式提供的数据,文件名为 WA_Fn UseC_ Marketing Customer Value Analysis.csv。下载数据后,你可以通过运行以下命令将其加载到 RStudio 中:
#### 1\. Load Data ####
df <- read.csv(
file="~/Documents/data-science-for-marketing/ch.8/data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv",
header=TRUE
)
df 看起来如下:

正如你可能已经注意到的,这是我们在上一章中使用的相同数据集,在那一章中我们进行了客户分析。借助上一章中我们对该数据集的了解,我们将首先通过编码目标变量和其他作为特征的分类变量来准备数据,以便应用于我们的机器学习模型。
变量编码
为了在 R 中构建机器学习模型,数据集中的所有特征必须具有数值型数据。然而,在我们拥有的数据集中,有许多列包含非数值型数据。例如,目标变量 Response,即我们将尝试使用机器学习模型预测的内容,是非数值型的。它包含两个字符串值——Yes 或 No。我们需要将这个 Response 目标变量编码为数值,以便能够构建机器学习模型。另一个例子是 Gender 列,它可以作为预测模型的特征之一,也没有数值型数据。它包含两个字符串值——F 表示女性,M 表示男性。在这一节中,我们将讨论如何编码这些非数值列,以便我们能够将它们用作机器学习模型的特征。
响应变量编码
我们要做的第一件事是编码响应变量 Response。我们将 Yes 值编码为 1,将 No 值编码为 0。请看以下代码:
## 2.1\. Response Variable: Response
df$Engaged <- as.integer(df$Response) - 1
从这段代码可以看出,我们只是使用as.integer函数将Response列的值转换为整数值。之所以减去1,是因为它将值编码为1表示No,2表示Yes,而不是将No编码为0,Yes编码为1,这是我们所期望的。然后我们将这些编码后的值存储在新创建的列Engaged中。为了使用这个新创建的列获取整体响应或参与率,可以使用以下代码:
mean(df$Engaged)
整体参与率如下所示:

分类变量编码
如果仔细查看数据,以下列是我们数据集中的分类变量:
## 2.2\. Categorical Features
categoricalVars = c(
'Sales.Channel', 'Vehicle.Size', 'Vehicle.Class', 'Policy', 'Policy.Type',
'EmploymentStatus', 'Marital.Status', 'Education', 'Coverage', 'Gender'
)
这些变量具有一组不同的值,这些值之间不一定有顺序来区分它们。
如果你还记得第四章《从参与到转化》中的内容,我们讨论了如何在 R 中为这些分类变量创建因子变量。在本章中,我们将使用的方法是为每个类别的单独分类变量创建虚拟变量,使用 R 中的model.matrix函数。请看以下代码:
encodedDF <- model.matrix(~.-1, df[categoricalVars])
从这段代码可以看到,在 R 中为分类变量创建虚拟变量非常简单。你需要做的就是对 R DataFrame的分类变量列应用model.matrix函数。如果仔细查看代码,你会注意到我们在这里使用的~.-1公式。如果没有这个公式,model.matrix函数将在输出矩阵中创建一个名为Intercept的多余列。为了避免出现这个不必要的列,我们可以在这段代码示例中使用这个公式。现在,新创建的DataFrame encodedDf的前几列如下所示:

从这个输出可以看到,这个新DataFrame的每一列代表了原始列中的每个类别。例如,第一列Sales.ChannelAgent如果给定的记录或客户是由销售代理联系的,则编码为1,否则为0。再比如,第五列Vehicle.SizeMedsize如果给定的记录或客户拥有中型车辆,则编码为1,否则为0。
现在我们已经成功地用数值编码了所有的分类变量,我们需要将连续变量附加到这个新创建的DataFrame,encodedDF。请看以下代码:
## 2.3\. Continuous Features
continuousFeatures <- c(
'Customer.Lifetime.Value', 'Income', 'Monthly.Premium.Auto',
'Months.Since.Last.Claim', 'Months.Since.Policy.Inception',
'Number.of.Open.Complaints', 'Number.of.Policies', 'Total.Claim.Amount'
)
encodedDF <- cbind(encodedDF, df[continuousFeatures])
从这段代码可以看出,我们正在使用cbind R 函数,它通过列将两个 DataFrame 结合在一起。我们正在将之前创建的包含所有编码过的分类变量的 DataFrame encodedDF与包含连续变量的 DataFrame 结合在一起。然后,我们将这个合并后的DataFrame存回encodedDF变量中。
构建预测模型
我们已经几乎准备好开始构建和训练机器学习模型来预测客户的响应或参与情况。在我们开始训练随机森林模型之前,有一件事需要做。我们需要将样本集encodedDF变量拆分成两个子集——一个用于训练模型,另一个用于测试和评估训练好的模型。caTools R 包提供了一个便捷的函数,可以将给定的样本集拆分成训练集和测试集。如果您的 R 环境中未安装此库,可以使用以下命令进行安装:
install.packages('caTools')
现在,看看下面的代码,了解如何将样本集拆分为训练集和测试集:
library(caTools)
sample <- sample.split(df$Customer, SplitRatio = .7)
trainX <- as.matrix(subset(encodedDF, sample == TRUE))
trainY <- as.double(as.matrix(subset(df$Engaged, sample == TRUE)))
testX <- as.matrix(subset(encodedDF, sample == FALSE))
testY <- as.double(as.matrix(subset(df$Engaged, sample == FALSE)))
让我们仔细看一下这段代码。caTools包中的sample.split函数允许我们将数据集按我们希望的比例拆分。从这段代码可以看到,我们将SplitRatio定义为0.7,这意味着我们将70%的样本集作为训练集,剩余的30%作为测试集。生成的变量sample现在包含一个布尔值数组,TRUE或FALSE,其中70%的数组为TRUE,其余的30%为FALSE。
使用这些数据,我们可以创建训练集和测试集。如您所见,我们在 R 中使用subset函数来创建训练集和测试集。首先,我们将对应sample变量中TRUE值的记录作为训练集。然后,我们将对应sample变量中FALSE值的记录作为测试集。以下显示了样本集的训练集和测试集的划分:

如您所见,encodedDF中总共有9,134条记录,trainX中有6,393条记录,testX中有2,741条记录,这意味着大约70%的样本集进入了训练集,剩下的30%样本集进入了测试集。我们将在接下来的部分使用这些训练集和测试集来构建和评估模型。
随机森林模型
使用我们目前准备的数据,我们将使用随机森林算法构建一个预测模型,该模型预测客户是否会对营销活动做出回应或参与。我们将使用randomForest R 库。如果您的 R 环境中未安装此库,可以使用以下命令进行安装:
install.packages('randomForest')
一旦安装了这个包,您可以使用以下代码来构建随机森林模型:
library(randomForest)
rfModel <- randomForest(x=trainX, y=factor(trainY))
然而,随机森林模型有许多超参数可以调优。超参数是在训练机器学习模型之前你所定义的参数。例如,在随机森林算法中,你可以定义想要在随机森林模型中使用的树的数量。另一个例子是,你可以定义每棵树的最大终端节点数量,从而限制森林中每棵树的生长大小。
你可以定义并微调许多超参数。我们将看看其中的一些超参数:
-
ntree:此参数定义了你想要在森林中构建的树的数量。一般来说,更多的树意味着更好的性能。然而,随着树的数量增加,每增加一棵树带来的性能提升会逐渐减小。由于在森林中增加更多的树意味着在训练额外的树时需要更高的计算成本,因此你应该尝试找到平衡点,当训练额外树木的计算成本超过性能提升时,就停止增加树木。
-
sampsize:此参数定义了为训练每棵树所抽取的样本大小。在训练随机森林模型时,这会引入森林中的随机性。较大的样本大小会导致森林的随机性较小,且更容易发生过拟合。这意味着训练出来的树在训练集中的表现和预测效果良好,但在未见过的数据集上预测效果较差。减小样本大小可以帮助避免过拟合,但通常会导致模型性能下降。
-
nodesize:此参数定义了终端节点的最小样本数,意味着每个终端节点至少需要有多少样本。这个数字越大,树可以生长得越小。随着这个数字的增加,你可以缓解过拟合问题,但代价是模型性能的下降。
-
maxnodes:此参数定义了森林中每棵树可以拥有的最大终端节点数量。如果不设置此参数,算法会将树生长到最大程度。这可能导致训练集过拟合。减少终端节点的最大数量有助于解决过拟合问题。
有关更详细的描述和其他超参数的信息,你可以参考官方文档,文档地址为:www.rdocumentation.org/packages/randomForest/versions/4.6-14/topics/randomForest。
训练一个随机森林模型
使用randomForest包训练一个随机森林模型非常简单。请看以下代码:
rfModel <- randomForest(x=trainX, y=factor(trainY), ntree=200, maxnodes=24)
使用 randomForest 包中的 randomForest 函数,你可以轻松训练一个随机森林模型。你只需将训练集传递给该函数。为了说明,我们让模型构建 200 棵树,每棵树最多只能生长到 24 个终端节点。
当你运行这段代码时,你的模型对象将如下所示:

一旦随机森林模型被训练或拟合,模型对象会包含大量有用的信息。你可以从训练好的随机森林模型中提取出关于森林中单棵树的信息。通过使用 getTree 函数,你可以查看森林中各个树是如何构建的。看看以下示例:

这里我们查看的是关于森林中第一棵树的信息。这些信息告诉我们树的结构。left daughter 和 right daughter 列告诉我们该节点在树中的位置。status 列告诉我们该节点是否是终端节点(-1)或非终端节点(1)。prediction 列则告诉我们该节点的预测结果。
我们还可以从拟合的随机森林模型中获取每棵树的预测结果。看看以下代码:
predict(rfModel, trainX, predict.all=TRUE)
通过使用 predict.all=TRUE 参数,prediction 函数将返回森林中每棵树的预测结果。看看以下输出:

该输出展示了前 20 棵树对训练集前五个记录的预测结果。正如你从输出中看到的,森林中的第 10^(th) 棵树预测训练集中的第 5^(th) 条记录属于 1 类,但其他 19 棵树都预测该记录属于 0 类。从输出中可以看到,每棵树对测试集中的每个记录的预测不同。这是因为每棵树都是用随机选择的不同特征子集进行训练的。利用这些信息,你可以看到随机森林模型的最终预测是如何由这些单独的子估计器或树组成的。
从训练好的 randomForest 对象中,我们还可以获得特征重要性,从而了解每个特征对最终预测的影响或重要性。你可以使用以下代码获取每个特征的特征重要性:
# - Feature Importances
importance(rfModel)
这段代码的输出部分如下所示:

正如你从输出中看到的,EmploymentStatusRetired 特征似乎是最终预测中最重要的因素,而 Income、Total.Claim.Amount 和 Customer.Lifetime.Value 特征分别位列第二、第三和第四重要特征。
评估分类模型
在本章之前,我们讨论了五种不同的方法来评估分类模型的性能。在本节中,我们将学习如何使用我们刚刚构建的随机森林模型来计算和可视化评估分类模型的度量标准。
我们将要查看的前三个度量标准是准确率、精确度和召回率。在我们继续评估模型性能之前,我们需要模型的预测结果。为了让我们在前一节中构建的随机森林模型对数据集进行预测,我们可以简单地使用predict函数。请查看以下代码:
inSamplePreds <- as.double(predict(rfModel, trainX)) - 1
outSamplePreds <- as.double(predict(rfModel, testX)) - 1
有了这些预测结果,我们将评估我们的随机森林模型在训练集和测试集中的表现。以下代码展示了如何在 R 中计算准确率、精确度和召回率:
# - Accuracy
accuracy <- mean(testY == outSamplePreds)
# - Precision
precision <- sum(outSamplePreds & testY) / sum(outSamplePreds)
# - Recall
recall <- sum(outSamplePreds & testY) / sum(testY)
使用这种方法,我们可以将样本内训练集的accuracy(准确率)、precision(精确度)和recall(召回率)与样本外测试集的accuracy、precision和recall进行比较。请查看以下输出:

该输出为我们提供了模型在预测响应时表现的简要概述。对于训练集,整体预测的准确率为0.8756,意味着模型的预测在大约88%的时间内是正确的。对于测试集,整体预测的准确率为0.8636。你还可以发现样本内和样本外预测的精确度分别为0.9717和0.8980,召回率分别为0.1151和0.1065。由于随机性和你可能使用的不同超参数,你可能会得到不同的结果。
接下来我们要看的度量标准是 ROC 曲线和 AUC 值。我们将使用ROCR R 包。如果你的 R 环境中尚未安装该包,可以使用以下命令进行安装:
install.packages('ROCR')
首先查看以下关于 ROC 曲线和 AUC 值的代码:
library(ROCR)
inSamplePredProbs <- as.double(predict(rfModel, trainX, type='prob')[,2])
outSamplePredProbs <- as.double(predict(rfModel, testX, type='prob')[,2])
pred <- prediction(outSamplePredProbs, testY)
perf <- performance(pred, measure = "tpr", x.measure = "fpr")
auc <- performance(pred, measure='auc')@y.values[[1]]
plot(
perf,
main=sprintf('Random Forest Model ROC Curve (AUC: %0.2f)', auc),
col='darkorange',
lwd=2
) + grid()
abline(a = 0, b = 1, col='darkgray', lty=3, lwd=2)
我们需要做的第一件事是从我们构建的模型中获取预测概率。使用predict函数和type='prob'标志,我们可以从随机森林模型中获取预测概率。然后,我们使用ROCR包中的prediction函数。这个函数会计算在不同概率阈值下的真正例和假正例数量,这是我们绘制 ROC 曲线所需要的。使用prediction函数的输出,我们可以通过ROCR包中的performance函数,获取不同概率阈值下的真正例率和假正例率。最后,为了获得 AUC 值,我们可以使用相同的performance函数,只需使用不同的标志measure='auc'。
有了这些数据,我们现在可以绘制 ROC 曲线。使用plot函数和perf变量(它是performance函数的输出),我们可以绘制 ROC 曲线。绘图如下所示:

从这个图表中可以看到,我们的随机森林模型的 AUC 为0.76。与代表随机线的基准直线相比,该模型表现得要好得多,这表明模型的预测远优于随机预测。
该 R 练习的完整代码可以通过以下链接找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.8/R/PredictingEngagement.R。
概述
本章我们讨论了预测分析及其在营销中的应用。我们首先讨论了什么是预测分析,以及它在金融和医疗等其他行业中的应用。然后我们讨论了预测分析在营销中的四个常见应用场景——参与可能性、客户生命周期价值、推荐合适的产品和内容,以及客户获取和保持。预测分析在营销中还有许多其他应用场景,因此我们建议您跟进最新的新闻,了解预测分析在营销行业中的应用。接着我们讨论了评估预测模型表现的五种不同方法——准确度、精确度、召回率、ROC 曲线和 AUC。
在接下来的章节中,我们将扩展我们对预测分析的知识。我们将讨论衡量客户生命周期价值的概念和重要性,以及为客户生命周期价值预测构建机器学习模型。
第九章:客户生命周期价值
本章将重点讨论预测分析在营销中的第二个应用案例,即我们在上一章讨论的客户生命周期价值。在营销中,确定营销活动的预算总是一个挑战。我们不希望花费过多而导致负的投资回报率(ROI),但我们也不希望花费过少而没有显著的影响或结果。在确定营销策略的预算时,了解运行某一营销活动所期望的回报非常重要。了解客户生命周期价值(CLV)对于单个客户而言,可以帮助营销人员为其营销预算提供依据,并且能够有针对性地寻找潜在的高价值客户。在本章中,我们将更详细地讨论计算 CLV 的概念与优势,以及如何在 Python 和 R 中构建预测机器学习模型,以预测单个客户的预期 CLV。
在本章中,我们将涵盖以下主题:
-
CLV
-
回归模型的评估指标
-
使用 Python 预测 3 个月的 CLV
-
使用 R 预测 3 个月的 CLV
CLV
在营销中,CLV 是一个必须拥有并监控的关键指标。CLV 衡量的是客户在与公司关系的整个生命周期中,对企业的总价值。这个指标对于获取新客户尤其重要。获取新客户的成本通常比维持现有客户更高,因此了解客户生命周期价值以及获取新客户的相关成本对于制定具有正投资回报率的营销策略至关重要。例如,如果你的客户的平均 CLV 是$100,而获取一个新客户的成本仅为$10,那么随着新客户的获得,你的业务将创造更多收入。
然而,如果获取一个新客户的成本是$150,而你的客户平均 CLV 仍然是$100,那么你每获得一个客户就会亏损。简单来说,如果你的新客户获取营销支出超过了 CLV,你就会在每次获取客户时亏损,最好是只与现有客户合作。
计算 CLV 有多种方法。一种方法是找到客户的平均购买金额、购买频率和生命周期,然后通过简单计算得到 CLV。例如,假设有一个假设情况,某客户的平均购买金额是 100 美元,并且他或她每个月平均购买五次。那么,这个客户的每月平均价值是 500 美元,计算方法就是将平均购买金额与平均购买频率相乘。现在,我们需要知道该客户的生命周期。估算客户生命周期的一种方法是查看平均月流失率,即离开并终止与您业务关系的客户所占的百分比。您可以通过将 1 除以流失率来估算客户的生命周期。假设在我们的假设情况下流失率为 5%,那么估算的客户生命周期为 20 年。根据客户每月平均价值 500 美元和生命周期 20 年的数据,该客户的 CLV 为 120,000 美元。这个最终的 CLV 金额是通过将 500 美元(每月平均值)乘以 12 个月,再乘以 20 年的生命周期计算得出的。
因为我们通常不知道客户的生命周期,所以我们经常尝试估计在某一特定时期内的客户生命周期价值(CLV)。这可以通过估算客户的 12 个月 CLV、24 个月 CLV,或者也可以是 3 个月 CLV 来实现。除了我们通过示例讨论的方法外,CLV 还可以通过构建预测模型来估算。利用机器学习算法和客户的购买历史数据,我们可以构建预测客户在某一特定时期内 CLV 的机器学习模型。在本章的编程练习中,我们将学习如何构建一个回归模型来预测客户的 3 个月 CLV。
评估回归模型
我们在评估回归模型时需要使用一组与分类模型评估不同的指标。这是因为回归模型的预测输出是连续值,意味着它可以取任何值,并且不受限于预定义的值集合。而另一方面,正如我们在第八章《预测营销互动的可能性》中所见,分类模型的预测输出只能取有限的几个值。就像在之前的互动预测中,我们的分类模型只能取两个值——0 表示没有互动,1 表示有互动。由于这种差异,我们需要使用不同的指标来评估回归模型。
在本节中,我们将讨论四种常用的回归模型评估方法——均方误差(MSE)、中位数绝对误差(MAE)、R²和预测值与实际值散点图。顾名思义,MSE 衡量的是平方误差的平均值,其中误差是预测值与实际值之间的差异。MSE的公式如下所示:

这个方程中的Y值是实际值,Y'值是预测值。因为 MSE 是平方误差的平均值,这个度量对异常值非常敏感,容易受到异常值的强烈影响。
另一方面,MAE 对异常值的敏感性较低,因此被认为更为稳健,因为中位数受异常值或尾部极端值的影响远低于平均数。这个公式来自于scikit-learn文档页面,scikit-learn.org/stable/modules/model_evaluation.html#median-absolute-error,如下所示:

这个方程中的y值表示实际值,而
值表示预测值。
另一个常用的回归模型评估度量是R²,也称为决定系数。R²衡量拟合优度。换句话说,它衡量回归模型与数据的拟合程度。简而言之,R²是回归模型解释目标变量变异性的百分比。该公式如下所示:

R²的值通常在零和一之间。R²值为零意味着模型根本没有解释或捕捉到目标变量的变异性,且不适合数据。另一方面,R²值为一意味着模型捕捉到目标变量 100%的变异性,完美地拟合数据。R²值越接近一,模型的拟合度越好。
最后,预测值与实际值的散点图也用于可视化模型拟合的紧密程度。这个散点图的示例如下:

对于一个良好的拟合,你会在这个散点图中看到靠近对角线的点。如果模型的R²值较高,点会靠近对角线。另一方面,如果模型的R²值较低,点会分散在对角线附近。在接下来的编程练习中,我们将讨论如何在 Python 和 R 中计算和可视化这些度量,并利用这些度量来评估我们的回归模型。
用 Python 预测 3 个月的 CLV
在本节中,我们将讨论如何使用 Python 中的机器学习算法构建和评估回归模型。在本节结束时,我们将使用线性回归算法构建一个预测模型,来预测CLV,更具体地说,是预测 3 个月的客户价值。我们将主要使用pandas、matplotlib和scikit-learn这三个包来分析、可视化并构建预测 3 个月客户价值的机器学习模型。如果有读者希望使用 R 语言进行这个练习,你可以跳到下一节。
在本练习中,我们将使用来自 UCI 机器学习库的公开数据集之一,数据集可以通过这个链接访问:archive.ics.uci.edu/ml/datasets/online+retail。
你可以通过点击这个链接下载以 XLSX 格式提供的数据,文件名为Online Retail.xlsx。下载完数据后,你可以通过运行以下命令将其加载到你的 Jupyter Notebook 中:
import pandas as pd
df = pd.read_excel('../data/Online Retail.xlsx', sheet_name='Online Retail')
数据框df如下所示:

正如你可能注意到的,我们在前面的章节中已经多次使用了这个数据集。凭借我们从前几章学到的知识,我们将首先通过清理数据来准备它。
数据清理
如你所记得,数据集中有一些我们需要清理的内容。清理步骤如下:
- 处理负数量:数据中有些交易的
Quantity值为负,表示已取消的订单。为了这个练习,我们将忽略这些已取消的订单,因此需要将它们从pandas数据框中排除。排除Quantity列中负值的代码如下所示:
df = df.loc[df['Quantity'] > 0]
我们只是将所有Quantity值为正的行提取出来,并重新存储到df变量中。
- 删除
NaN记录:我们需要删除没有CustomerID的记录。因为我们要构建一个机器学习模型来预测 3 个月的客户价值,我们需要按CustomerID列对数据进行分组。如果没有CustomerID,我们就无法为这个项目正确构建模型。删除没有CustomerID的记录的代码如下所示:
df = df[pd.notnull(df['CustomerID'])]
从这段代码中你可以看到,我们使用了pandas包中的notnull函数。该函数返回一个数组列表,其中True表示给定索引处的值不是null,False表示给定索引处的值是null。我们将CustomerID列中非空值的记录重新存储回df变量中。
- 处理不完整数据:我们需要做的另一项清理工作是处理不完整的数据。如果你还记得之前章节的内容,上一月的交易数据是不完整的。请看一下下面的输出:

如您从此输出中看到的,数据集包含了 2010 年 12 月 1 日至 2011 年 12 月 9 日之间的所有交易。2011 年 12 月的数据是不完整的。为了正确构建 3 个月客户价值预测模型,我们将忽略最后一个月的交易。请查看以下代码,展示如何从我们的 DataFrame 中删除这些记录:
df = df.loc[df['InvoiceDate'] < '2011-12-01']
我们只是将 2011 年 12 月 01 日之前发生的所有交易提取出来,并存储回df变量中。
- 总销售额:最后,我们需要为每笔交易创建一个列来记录总销售额。请查看以下代码:
df['Sales'] = df['Quantity'] * df['UnitPrice']
我们将Quantity列与UnitPrice列相乘,以获得每笔交易的总购买金额。然后,我们将这些值存储到一个名为Sales的列中。我们现在已经完成了所有的清理任务。
现在我们已经清理了所有交易数据,接下来让我们总结每个订单或InvoiceNo的数据。请查看以下代码:
orders_df = df.groupby(['CustomerID', 'InvoiceNo']).agg({
'Sales': sum,
'InvoiceDate': max
})
如您从此代码中看到的,我们正在通过两列,CustomerID和InvoiceNo,对DataFrame df进行分组。然后,我们为每个客户和订单求和所有的Sales值,并取该订单的最后交易时间作为InvoiceDate。这样,我们就得到了一个DataFrame,orders_df,它包含了我们需要了解的每个客户的订单数据。数据如下所示:

在我们开始构建模型之前,先来仔细看看这个客户购买历史数据。
数据分析
为了计算 CLV,我们需要知道每个客户的购买频率、最近一次购买时间和总购买金额。我们将计算每个客户的平均购买金额、生命周期购买金额,以及每个客户的购买时长和购买频率。请查看以下代码:
def groupby_mean(x):
return x.mean()
def groupby_count(x):
return x.count()
def purchase_duration(x):
return (x.max() - x.min()).days
def avg_frequency(x):
return (x.max() - x.min()).days/x.count()
groupby_mean.__name__ = 'avg'
groupby_count.__name__ = 'count'
purchase_duration.__name__ = 'purchase_duration'
avg_frequency.__name__ = 'purchase_frequency'
summary_df = orders_df.reset_index().groupby('CustomerID').agg({
'Sales': [min, max, sum, groupby_mean, groupby_count],
'InvoiceDate': [min, max, purchase_duration, avg_frequency]
})
我们首先按CustomerID列进行分组,并根据Sales和InvoiceDate列对数据进行聚合。如果您仔细观察聚合函数,我们使用了四个客户聚合函数:groupby_mean、groupby_count、purchase_duration和avg_frequency。第一个函数groupby_mean简单地计算每个组的平均值,第二个函数groupby_count则计算每个组中的记录数。purchase_duration函数计算每个组中第一笔和最后一笔发票日期之间的天数,avg_frequency函数通过将purchase_duration除以订单数量来计算订单之间的平均天数。
结果DataFrame如下所示:

这些数据让我们了解了每个客户的购买情况。例如,ID 为 12346 的客户只在 2011 年 1 月 18 日进行了一次购买。然而,ID 为 12347 的客户在 2010 年 12 月 7 日至 2011 年 10 月 31 日之间共进行了六次购买,时间跨度为 327 天。该客户每次订单的平均花费为 680,平均每 54.5 天进行一次购买。
让我们更仔细地看看重复购买客户的购买次数分布情况。
看一下以下代码:
summary_df.columns = ['_'.join(col).lower() for col in summary_df.columns]
summary_df = summary_df.loc[summary_df['invoicedate_purchase_duration'] > 0]
ax = summary_df.groupby('sales_count').count()['sales_avg'][:20].plot(
kind='bar',
color='skyblue',
figsize=(12,7),
grid=True
)
ax.set_ylabel('count')
plt.show()
从这段代码可以看到,首先我们清理了 summary_df 数据框中的列名。然后,我们仅选择那些至少进行过两次或更多次购买的客户,这代表了重复购买的客户。最后,我们按 sales_count 列进行分组,并统计每个类别下的客户数量。结果图如下所示:

从这张图可以看出,大多数客户的历史购买次数不超过 10 次。接下来,我们来看一下这些重复购买客户之间购买的平均天数。首先看一下以下代码:
ax = summary_df['invoicedate_purchase_frequency'].hist(
bins=20,
color='skyblue',
rwidth=0.7,
figsize=(12,7)
)
ax.set_xlabel('avg. number of days between purchases')
ax.set_ylabel('count')
plt.show()
我们正在使用 pandas 包中的 hist 函数构建购买频率数据的直方图。bins 参数定义了要构建的直方图的区间数。结果如下所示:

这张图展示了重复购买客户历史购买频率的整体情况。从这张图可以看出,大多数重复购买客户每隔 20 到 50 天进行一次购买。
预测 3 个月的 CLV
在本节中,我们将使用 Python 中的 pandas 和 scikit-learn 包构建一个预测 3 个月客户价值的模型。我们将首先将数据切分为 3 个月为一组,并将最后 3 个月的数据作为预测目标,其余的数据作为特征。我们将首先为模型构建准备数据,然后训练一个线性回归模型,以预测 3 个月的客户价值。
数据准备
为了构建预测模型,我们首先需要准备数据,以便将相关数据输入模型。看看以下代码:
clv_freq = '3M'
data_df = orders_df.reset_index().groupby([
'CustomerID',
pd.Grouper(key='InvoiceDate', freq=clv_freq)
]).agg({
'Sales': [sum, groupby_mean, groupby_count],
})
data_df.columns = ['_'.join(col).lower() for col in data_df.columns]
data_df = data_df.reset_index()
由于我们想要预测未来三个月的客户价值,我们将数据拆分成每个客户的三个月时间段。如你所见,在groupby函数中,我们通过CustomerID和自定义的Grouper将之前构建的orders_df数据框按每三个月进行分组。然后,对于每个三个月的时间窗口,我们将所有销售额求和,以获得总购买金额,计算购买金额的平均值,并统计给定时间段内每个客户的购买总数。通过这种方式,我们得到了每个客户每三个月的购买信息的汇总数据。最后,我们对列名进行了清理。data_df中的数据现在如下所示:

为了简化操作,让我们对InvoiceDate列的值进行编码,使其比当前日期格式更易于阅读。请查看以下代码:
date_month_map = {
str(x)[:10]: 'M_%s' % (i+1) for i, x in enumerate(
sorted(data_df.reset_index()['InvoiceDate'].unique(), reverse=True)
)
}
data_df['M'] = data_df['InvoiceDate'].apply(lambda x: date_month_map[str(x)[:10]])
从这段代码中可以看到,我们正在将日期值编码为M_1、M_2、M_3等,其中较小的数字代表较近的日期。例如,日期2011-12-31现在被编码为M_1,日期2011-09-30被编码为M_2。结果如下所示:

现在我们已经准备好构建一个包含特征和目标变量的样本集。如前所述,我们将使用最近的三个月作为目标变量,其余的作为特征,这意味着我们将训练一个机器学习模型,用来预测未来三个月的客户价值,基于其余数据。为了训练这个模型,我们需要将数据转化为表格数据,其中行表示单个客户,列表示每个特征。请查看以下代码:
features_df = pd.pivot_table(
data_df.loc[data_df['M'] != 'M_1'],
values=['sales_sum', 'sales_avg', 'sales_count'],
columns='M',
index='CustomerID'
)
features_df.columns = ['_'.join(col) for col in features_df.columns]
从这段代码中可以看到,我们使用了pandas函数pivot_table,其中索引将是CustomerID,列将是每个三个月周期的sales_sum、sales_avg和sales_count。我们在这里创建的features_df数据框如下所示:

你可能会注意到这些数据中有NaN值。我们可以使用以下代码将这些NaN值编码为0.0:
features_df = features_df.fillna(0)
现在我们已经构建了特征数据框,接下来我们来构建目标变量。请查看以下代码:
response_df = data_df.loc[
data_df['M'] == 'M_1',
['CustomerID', 'sales_sum']
]
response_df.columns = ['CustomerID', 'CLV_'+clv_freq]
从这段代码中可以看到,我们正在选择最近的三个月时间段,即M_1组,作为目标变量。目标列将是sales_sum,因为我们想要预测下一个三个月的客户价值,也就是预测一个给定客户在接下来的三个月内可能进行的总购买金额。目标变量如下所示:

现在只剩下最后一步了,那就是构建一个样本集,用于构建机器学习模型,将特征和响应数据结合在一起。请查看以下代码:
sample_set_df = features_df.merge(
response_df,
left_index=True,
right_on='CustomerID',
how='left'
)
sample_set_df = sample_set_df.fillna(0)
如您所见,我们通过使用merge函数在CustomerID上将两个DataFrames进行连接。通过设置how='left'标志,我们将获取特征数据中的所有记录,即使响应数据中没有对应的数据。这是一个例子,其中给定的客户在过去 3 个月内没有进行任何购买,因此我们将其编码为零。最终的样本集现在如下所示:

有了这些数据,我们现在可以建立一个模型,通过历史购买数据预测未来 3 个月的客户价值。
线性回归
与上一章类似,我们将使用以下代码将样本集分割成训练集和测试集:
from sklearn.model_selection import train_test_split
target_var = 'CLV_'+clv_freq
all_features = [x for x in sample_set_df.columns if x not in ['CustomerID', target_var]]
x_train, x_test, y_train, y_test = train_test_split(
sample_set_df[all_features],
sample_set_df[target_var],
test_size=0.3
)
如您从这段代码中所见,我们使用样本集的 70%来训练模型,其余 30%用于测试和评估模型性能。在本节中,我们将使用线性回归模型。然而,我们建议您尝试其他机器学习算法,例如随机森林和支持向量机(SVM)。
关于如何使用scikit-learn包训练这些模型的更多细节,可以参考以下链接:scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html 和 scikit-learn.org/stable/modules/generated/sklearn.svm.SVR.html。
为了使用我们的数据集训练线性回归模型,您可以使用以下代码:
from sklearn.linear_model import LinearRegression
reg_fit = LinearRegression()
reg_fit.fit(x_train, y_train)
这就是最简单的实现方式。您导入scikit-learn包中的LinearRegression类,并初始化一个LinearRegression对象。然后,您可以使用fit函数和x_train特征与y_train目标来训练线性回归模型。
一旦训练了线性回归模型,您可以在LinearRegression对象中找到一些有用的信息。首先,您可以使用LinearRegression对象的intercept_属性来获取线性回归方程的截距,如下所示:
reg_fit.intercept_
同样,您可以使用coef_属性来查找拟合的线性回归模型的系数,如以下代码所示:
reg_fit.coef_
拟合回归模型中每个特征的系数如下所示:

从这个系数输出中可以看出,你可以轻松地找到与目标变量呈负相关和正相关的特征。例如,前 3 个月的平均购买金额 sales_avg_M_2 对接下来的 3 个月客户价值有负面影响。这意味着,前 3 个月的购买金额越高,接下来 3 个月的购买金额越低。另一方面,第二和第三最近的 3 个月期间的平均购买金额 sales_avg_M_3 和 sales_avg_M_4 与接下来 3 个月的客户价值呈正相关。换句话说,客户在 3 到 9 个月前的购买金额越多,接下来 3 个月的客户价值越高。查看系数是了解给定某些特征时,预期值如何变化的一种方式。
使用 3 个月客户价值预测的输出,你可以根据不同方式定制你的营销策略。由于你已经知道了来自单个客户在接下来的 3 个月内的预期收入或购买金额,你可以为你的营销活动制定更为合理的预算。预算应该足够高,以便能够接触到目标客户,但又不能超过预期的 3 个月客户价值,这样才能确保你有一个正向投资回报率的营销活动。另一方面,你也可以利用这些 3 个月客户价值的预测输出,专门针对这些高价值客户进行营销。这可以帮助你制定出具有更高投资回报率的营销活动,因为根据该模型预测,这些高价值客户可能带来的收入会比其他客户更多。
评估回归模型性能
现在我们已经有了一个拟合好的机器学习模型,用于预测 3 个月的客户价值。接下来,我们来讨论如何评估这个模型的性能。如前所述,我们将使用 R²、MAE 和预测值与实际值的散点图来评估我们的模型。我们首先需要从模型中获取预测结果,代码如下所示:
train_preds = reg_fit.predict(x_train)
test_preds = reg_fit.predict(x_test)
scikit-learn 包已经实现了用于计算 R² 和 MAE 的函数,这些函数位于其 metrics 模块中。你可以通过将它们导入到你的环境中来使用这些函数,代码如下:
from sklearn.metrics import r2_score, median_absolute_error
正如名称所示,r2_score 函数计算 R²,而 median_absolute_error 函数计算 MAE。你可以使用以下代码计算 R² 和 MAE 的数值:
r2_score(y_true=y_train, y_pred=train_preds)
median_absolute_error(y_true=y_train, y_pred=train_preds)
从这里可以看到,这两个函数都需要两个参数,y_true 和 y_pred。y_true 参数是实际的目标值,y_pred 参数是预测的目标值。使用这些代码,在我们的案例中,样本内和样本外的 R² 和 MAE 数值输出如下:

由于将样本集随机分割为训练集和测试集,您的结果可能与这些结果有所不同。在我们的情况下,训练集中的 R²为0.4445,测试集中的 R²为0.7947。另一方面,训练集中的 MAE 为178.2854,测试集中的 MAE 为178.7393。从这些数字来看,我们并没有看到明显的过拟合迹象,也没有在训练集和测试集的表现之间看到很大的差距。
最后,我们来看看预测值与实际值的散点图。您可以使用以下代码来生成该散点图:
plt.scatter(y_test, test_preds)
plt.plot([0, max(y_test)], [0, max(test_preds)], color='gray', lw=1, linestyle='--')
plt.xlabel('actual')
plt.ylabel('predicted')
plt.title('Out-of-Sample Actual vs. Predicted')
plt.grid()
plt.show()
结果图表如下所示:

正如您从这个图中看到的,x轴的值是实际值,y轴的值是预测值。如前所述,点越多位于直线附近,预测效果越好。这是因为直线上的点表明实际值和预测值非常接近。从这个图来看,点似乎围绕着直线分布,这表明预测值和实际值之间并没有太大的差距。
本次 Python 练习的完整代码可以在以下仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.9/python/CustomerLifetimeValue.ipynb。
使用 R 预测未来 3 个月的 CLV
在本节中,我们将讨论如何使用 R 中的机器学习算法构建和评估回归模型。到本节结束时,我们将使用线性回归算法构建一个预测模型,以预测CLV,更具体地说,预测未来 3 个月的客户价值。我们将使用一些 R 包,如dplyr、reshape2和caTools,来分析、转换和准备数据,以构建机器学习模型,预测未来 3 个月的客户价值。对于那些希望使用 Python 而非 R 来进行本次练习的读者,可以参考上一节。
对于本次练习,我们将使用一个来自 UCI 机器学习库的公开数据集,您可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/online+retail。您可以点击此链接并下载以 XLSX 格式提供的数据,文件名为Online Retail.xlsx。下载数据后,您可以通过运行以下命令将其加载到您的 R 环境中:
library(dplyr)
library(readxl)
#### 1\. Load Data ####
df <- read_excel(
path="~/Documents/data-science-for-marketing/ch.9/data/Online Retail.xlsx",
sheet="Online Retail"
)
数据框df如下所示:

正如您可能已经注意到的,我们在前几章中已经多次使用了这个数据集。凭借我们从前几章中获得的关于这个数据集的知识,我们将首先通过清理数据来准备数据。
数据清理
如你所记得,数据集中有一些需要清理的地方。清理步骤如下:
- 处理负数量:有些交易的
Quantity值为负数,表示已取消的订单。我们将忽略这些取消的订单,因此需要从我们的DataFrame中排除它们。排除这些负值的Quantity列的代码如下:
df <- df[which(df$Quantity > 0),]
我们只需要将所有Quantity值为正的行提取出来,并将它们存储回变量df中。
- 删除 NA 记录:我们需要删除在
CustomerID列中没有值的记录。因为我们将构建一个机器学习模型来预测 3 个月的客户价值,所以需要按CustomerID列对数据进行分组。没有这个字段,我们就无法为这个项目构建合适的模型。删除这些空值记录的代码如下:
df <- na.omit(df)
从这段代码中可以看到,我们在 R 语言中使用了na.omit函数。这个函数会返回一个去除了null或NA值的对象。然后,我们将输出结果重新存储到原始的DataFrame变量df中。
- 处理不完整数据:如果你还记得前几章提到的,最后一个月的交易数据是不完整的。请看以下输出:

从这个输出中可以看到,数据集包含了 2010 年 12 月 1 日到 2011 年 12 月 9 日之间的所有交易数据。2011 年 12 月的数据是不完整的。为了能够正确地构建 3 个月客户价值预测的模型,我们将忽略最后一个月的交易数据。请看以下代码,了解如何从我们的DataFrame中删除这些记录:
df <- df[which(df$InvoiceDate < '2011-12-01'),]
我们只需将 2011 年 12 月 1 日之前发生的所有交易提取出来,并将它们存储回变量df中。
- 总销售额:最后,我们需要为每笔交易创建一个列,表示每笔交易的总销售额。请看以下代码:
df$Sales <- df$Quantity * df$UnitPrice
我们仅仅是将Quantity列与UnitPrice列相乘,以得到每笔交易的总购买金额。然后,我们将这些值存储到名为Sales的列中。现在,我们已经完成了所有的清理任务。
现在我们已经清理了所有交易数据,让我们为每个订单或InvoiceNo总结一下这些数据。请看以下代码:
# per order data
ordersDF <- df %>%
group_by(CustomerID, InvoiceNo) %>%
summarize(Sales=sum(Sales), InvoiceDate=max(InvoiceDate))
从这段代码可以看到,我们正在按CustomerID和InvoiceNo两个列对df进行分组。接着,我们对每个客户和订单的Sales值进行求和,并将该订单的最后交易时间作为InvoiceDate。这样,我们就得到了一个名为ordersDF的DataFrame,它包含了每个客户所下的每个订单的相关信息。数据如下所示:

在我们深入构建模型之前,先来仔细查看一下这个客户购买历史数据。
数据分析
为了计算 CLV(客户终身价值),我们需要了解每个客户的购买频率、最近购买时间和总购买金额。我们将计算每个客户的平均购买金额和生命周期购买金额,以及每个客户的购买持续时间和频率。请查看以下代码:
# order amount & frequency summary
summaryDF <- ordersDF %>%
group_by(CustomerID) %>%
summarize(
SalesMin=min(Sales), SalesMax=max(Sales), SalesSum=sum(Sales),
SalesAvg=mean(Sales), SalesCount=n(),
InvoiceDateMin=min(InvoiceDate), InvoiceDateMax=max(InvoiceDate),
PurchaseDuration=as.double(floor(max(InvoiceDate)-min(InvoiceDate))),
PurchaseFrequency=as.double(floor(max(InvoiceDate)-min(InvoiceDate)))/n()
)
我们首先按照CustomerID列进行分组,并对Sales和InvoiceDate列进行聚合。通过使用 R 中的min、max、sum、mean和n函数,我们可以计算每个客户的最小、最大、总购买金额、平均购买金额以及购买次数。我们还使用min和max函数来获取每个客户的首次和最后一次订单日期。对于PurchaseDuration,我们计算的是每个客户最后一次和首次订单日期之间的天数。对于PurchaseFrequency,我们将PurchaseDuration的天数除以购买次数,从而得到平均购买间隔天数。
生成的DataFrame,summaryDF,如下所示:

这些数据让我们了解了每个客户的购买情况。例如,客户 ID 为12346的客户仅在 2011 年 1 月 18 日进行了单次购买。而客户 ID 为12347的客户则在 2010 年 12 月 7 日至 2011 年 10 月 31 日之间进行了六次购买,总计跨越了327天。该客户每次购买的平均金额约为681,而且平均每54.5天购买一次。
让我们更仔细地查看重复购买客户的购买次数分布。请查看以下代码:
summaryDF <- summaryDF[which(summaryDF$PurchaseDuration > 0),]
salesCount <- summaryDF %>%
group_by(SalesCount) %>%
summarize(Count=n())
ggplot(salesCount[1:19,], aes(x=SalesCount, y=Count)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('') +
xlab("Sales Count") +
ylab("Count") +
theme(plot.title = element_text(hjust = 0.5))
我们首先在代码的第一行中排除了仅进行过一次购买的客户。然后,我们统计了每个SalesCount对应的客户数量。最后,我们使用ggplot和geom_bar创建了一个条形图来展示这些数据。结果如下所示:

从这张图中可以看出,大多数客户的历史购买次数都在 10 次以下。接下来,我们来看看这些重复购买客户的平均购买间隔天数。首先,请查看以下代码:
hist(
summaryDF$PurchaseFrequency,
breaks=20,
xlab='avg. number of days between purchases',
ylab='count',
main=''
)
我们正在使用 R 中的hist函数构建购买频率数据的直方图。breaks参数定义了直方图的分箱数。结果如下所示:

该图展示了历史上重复购买客户的购买频率的总体情况。从图中可以看出,大多数重复购买客户每隔 20 到 50 天进行一次购买。
预测 3 个月的 CLV
在这一部分,我们将构建一个预测 3 个月客户价值的 R 模型。我们将首先将数据划分为 3 个月一组,并将最后 3 个月的数据作为预测的目标,其余的数据作为特征。我们将首先准备好数据来构建模型,然后训练一个线性回归模型来预测 3 个月的客户价值。
数据准备
为了构建预测模型,我们需要先准备数据,以便将相关数据输入模型。请查看以下代码:
# group data into every 3 months
library(lubridate)
ordersDF$Quarter = as.character(round_date(ordersDF$InvoiceDate, '3 months'))
dataDF <- ordersDF %>%
group_by(CustomerID, Quarter) %>%
summarize(SalesSum=sum(Sales), SalesAvg=mean(Sales), SalesCount=n())
如你所见,在这段代码中,我们使用了lubridate包,它可以帮助我们更轻松地处理日期数据。通过lubridate包中的round_date函数,我们首先将InvoiceDate四舍五入到最接近的季度。接着,我们按CustomerID和新创建的列Quarter对数据进行分组,以获取每个客户的季度销售数据。对于每个 3 个月的时间窗口,我们将所有销售额求和以获得总购买金额,并计算该期间每个客户的平均购买金额和总购买次数。通过这种方式,我们得到了每个客户每 3 个月的聚合数据。dataDF中的数据现在如下所示:

为了简化操作,我们将对Quarter列的值进行编码,使其比当前的日期格式更易于阅读。请查看以下代码:
dataDF$Quarter[dataDF$Quarter == "2012-01-01"] <- "Q1"
dataDF$Quarter[dataDF$Quarter == "2011-10-01"] <- "Q2"
dataDF$Quarter[dataDF$Quarter == "2011-07-01"] <- "Q3"
dataDF$Quarter[dataDF$Quarter == "2011-04-01"] <- "Q4"
dataDF$Quarter[dataDF$Quarter == "2011-01-01"] <- "Q5"
如你所见,在这段代码中,我们将日期值编码为Q1、Q2、Q3等,其中较小的数字表示较新的日期。例如,日期2012-01-01现在编码为Q1,而日期2011-10-01现在编码为Q2。结果如下所示:

现在我们准备好构建包含特征和目标变量的样本集。正如之前简要提到的,我们将使用过去 3 个月作为目标变量,其余的数据作为特征,这意味着我们将训练一个机器学习模型,该模型利用其他数据预测过去 3 个月的客户价值。为了训练这样的模型,我们需要将数据转换为表格数据,其中行表示单个客户,列表示每个特征。请查看以下代码:
# install.packages('reshape2')
library(reshape2)
salesSumFeaturesDF <- dcast(
dataDF[which(dataDF$Quarter != "Q1"),],
CustomerID ~ Quarter,
value.var="SalesSum"
)
colnames(salesSumFeaturesDF) <- c("CustomerID", "SalesSum.Q2", "SalesSum.Q3", "SalesSum.Q4", "SalesSum.Q5")
salesAvgFeaturesDF <- dcast(
dataDF[which(dataDF$Quarter != "Q1"),],
CustomerID ~ Quarter,
value.var="SalesAvg"
)
colnames(salesAvgFeaturesDF) <- c("CustomerID", "SalesAvg.Q2", "SalesAvg.Q3", "SalesAvg.Q4", "SalesAvg.Q5")
salesCountFeaturesDF <- dcast(
dataDF[which(dataDF$Quarter != "Q1"),],
CustomerID ~ Quarter,
value.var="SalesCount"
)
colnames(salesCountFeaturesDF) <- c("CustomerID", "SalesCount.Q2", "SalesCount.Q3", "SalesCount.Q4", "SalesCount.Q5")
featuresDF <- merge(
merge(salesSumFeaturesDF, salesAvgFeaturesDF, by="CustomerID"),
salesCountFeaturesDF, by="CustomerID"
)
featuresDF[is.na(featuresDF)] <- 0
正如您从这段代码中看到的,我们正在使用reshape2包来旋转数据。例如,使用reshape2包中的dcast函数,我们首先转换SalesSum数据,其中行索引代表每个客户或CustomerID,列表示每个季度,值则是给定客户和季度的总销售额或购买金额。我们对SalesSum、SalesAvg和SalesCount列分别执行这一过程三次,并最终合并这些数据。通过使用merge函数,我们可以按CustomerID索引合并这些 DataFrame。最后,我们使用is.na函数将null或NA值编码为 0。结果如下所示:

现在我们已经建立了特征DataFrame,接下来让我们建立目标变量。请查看以下代码:
responseDF <- dataDF[which(dataDF$Quarter == "Q1"),] %>%
select(CustomerID, SalesSum)
colnames(responseDF) <- c("CustomerID", "CLV_3_Month")
正如您从这段代码中看到的,我们将最后 3 个月的时间段,即Q1组,作为目标变量。目标列将是SalesSum,因为我们想预测下 3 个月的客户价值,即给定客户在接下来的 3 个月内可能的总购买金额。结果如下所示:

只剩下一件事需要完成,那就是构建一个样本集,将特征数据和响应数据结合在一起,用于构建机器学习模型。请查看以下代码:
sampleDF <- merge(featuresDF, responseDF, by="CustomerID", all.x=TRUE)
sampleDF[is.na(sampleDF)] <- 0
正如您在这里看到的,我们只是通过merge函数根据CustomerID将两个DataFrame连接起来。通过设置all.x=TRUE标志,即使响应数据中没有对应的数据,我们也会保留特征数据中的所有记录。这是一个示例,表示给定客户在过去 3 个月内没有进行任何购买,因此我们将其编码为 0。最终的样本集如下所示:

有了这些数据,我们现在可以建立一个模型,预测下 3 个月的客户价值,基于历史购买数据。
线性回归
与前一章类似,我们将使用以下代码将样本集拆分为训练集和测试集:
# train/test set split
library(caTools)
sample <- sample.split(sampleDF$CustomerID, SplitRatio = .8)
train <- as.data.frame(subset(sampleDF, sample == TRUE))[,-1]
test <- as.data.frame(subset(sampleDF, sample == FALSE))[,-1]
正如您从这段代码中看到的,我们将样本集的 80%用于训练模型,剩余的 20%用于测试和评估模型性能。在本节中,我们将使用线性回归模型。然而,我们建议尝试其他机器学习算法,例如随机森林和支持向量机(SVM)。您可以使用randomForest包训练一个随机森林模型,使用e1071包训练一个 SVM 模型。我们强烈建议查看它们的使用文档。
为了使用我们的数据集训练线性回归模型,您可以使用以下代码:
# Linear regression model
regFit <- lm(CLV_3_Month ~ ., data=train)
这非常简单。您只需要提供一个公式,即我们这个例子中的CLV_3_Month ~ .,以及训练数据,即我们这个例子中的train变量,传递给lm函数。这样就可以指示机器用给定的数据训练一个线性回归模型。
一旦线性回归模型训练完成,您可以从模型对象中找到一些有用的信息。您可以使用以下命令获取关于模型的详细信息:
summary(regFit)
输出如下所示:

从这个输出中可以看到,您可以轻松找到每个特征的系数,以及哪些特征与目标有正相关或负相关。例如,前一个三个月的总购买金额SalesSum.Q2对下一个三个月的客户价值有正面影响。这意味着,前一个三个月的总购买金额越高,接下来的三个月购买金额也越高。另一方面,第二和第四个最近的三个月的总购买金额SalesSum.Q3和SalesSum.Q5与下一个三个月的客户价值呈负相关。换句话说,客户在两季度或四季度前的购买金额越大,接下来三个月内他们带来的价值就越低。通过查看系数,您可以获得有关如何根据某些特征来预测期望值变化的洞察。
使用三个月客户价值的预测输出,您可以根据不同的方式定制您的营销策略。由于您知道每个客户在接下来的三个月内预期的收入或购买量,您可以为您的营销活动设定一个更有根据的预算。预算应该足够高,以便覆盖您的目标客户,但又要低于预计的三个月客户价值,以确保您能够实现正向投资回报率(ROI)的营销活动。另一方面,您还可以利用这些三个月客户价值的预测输出,专门针对这些高价值客户进行营销,这有助于您创建更高 ROI 的营销活动,因为这些通过模型预测的高价值客户,可能会带来比其他客户更多的收入。
评估回归模型的表现
现在我们已经有了一个训练好的机器学习模型,可以预测三个月后的客户价值,接下来我们来讨论如何评估这个模型的表现。正如前面所讨论的,我们将使用 R²、MAE 和预测值与实际值的散点图来评估我们的模型。我们首先需要从模型中获取预测输出,如下面的代码所示:
train_preds <- predict(regFit, train)
test_preds <- predict(regFit, test)
我们将使用miscTools包来计算样本内和样本外的 R²值。请看以下代码:
# R-squared
# install.packages('miscTools')
library(miscTools)
inSampleR2 <- rSquared(train$CLV_3_Month, resid=train$CLV_3_Month - train_preds)
outOfSampleR2 <- rSquared(test$CLV_3_Month, resid=test$CLV_3_Month - test_preds)
在我们的案例中,R²值看起来像以下输出:

由于在将样本集分割为训练集和测试集时存在随机性,你的结果可能与这些结果不同。在我们的案例中,样本内的 R²为0.4557,样本外的 R²为0.1235。样本内和样本外 R²值之间相当大的差距表明存在过拟合的情况,其中模型在训练集上的表现明显优于在测试集上的表现。遇到过拟合的情况时,你可以尝试不同的特征组合或使用更多的样本进行训练。
接下来,我们来看一下样本内和样本外预测的 MAE。请看下面的代码:
# Median Absolute Error
inSampleMAE <- median(abs(train$CLV_3_Month - train_preds))
outOfSampleMAE <- median(abs(test$CLV_3_Month - test_preds))
如你从这段代码中看到的,我们使用了median和abs函数来获取样本内和样本外预测的绝对误差的中位数。我们案例中的结果如下:

最后,让我们来看一下预测值与实际值的散点图。你可以使用以下代码生成这个散点图:
plot(
test$CLV_3_Month,
test_preds,
xlab='actual',
ylab='predicted',
main='Out-of-Sample Actual vs. Predicted'
)
abline(a=0, b=1)
结果图表如下所示:

如你从这个图表中看到的,x值是实际值,y值是预测值。正如之前讨论的,点越接近直线,预测效果越好。这是因为直线上的点表明实际值和预测值非常接近。看这个图表,点似乎没有分布在直线附近,这表明预测效果较差。这与我们之前观察到的低外样本R²值一致。预测值与实际值的散点图是可视化模型性能的好方法。
本次 R 语言练习的完整代码可以在以下仓库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.9/R/CustomerLifetimeValue.R
总结
在本章中,我们了解了什么是 CLV(客户生命周期价值),以及它在营销中的重要性和应用。特别是在验证客户获取成本时,了解每个新客户将为公司带来多少价值至关重要。我们讨论了 CLV 计算如何帮助营销人员制定正向投资回报率(ROI)的营销策略。接着,我们通过一个假设的例子展示了如何计算 CLV,使用了平均购买金额、购买频率和客户生命周期。我们还提到了另一种方法,即使用机器学习和预测模型来估算 CLV。
在编程练习中,我们学习了如何构建回归模型来预测 3 个月期间的客户终身价值(CLV)。在 Python 中,我们使用了scikit-learn包来构建LinearRegression模型。在 R 中,我们使用了内置的lm函数,利用我们的数据训练线性回归模型。在回归模型评估中,我们讨论了四个常用的指标:MSE、MAE、R²和预测值与实际值的散点图,并解释了这些指标分别衡量了什么,以及它们告诉我们回归模型的表现。在我们的编程练习中,我们讨论了如何在 Python 和 R 中计算和可视化 MAE、R²,以及预测值与实际值的散点图。
在接下来的章节中,我们将讨论客户细分。我们将探讨如何通过细分客户群体,帮助营销人员更好地了解他们的客户,并制定更加高效的营销策略。
第十章:数据驱动的客户细分
在营销中,我们通常试图理解客户群体中某些子群体的行为。尤其是在精准营销中,营销人员尝试以某些方式对客户群体进行细分,并专注于每个目标细分群体或客户群体。专注于特定目标客户群体有助于提升营销效果,因为目标群体内的客户需求和兴趣与业务的产品、服务或内容更为契合,从而带来更好的业绩表现。
在本章中,我们将深入探讨客户细分的概念。我们将讨论什么是客户细分,了解不同客户群体的细分的重要性和好处,以及如何利用客户细分分析结果制定不同的营销策略。除了传统的客户群体细分方法,通过查看客户某些属性的关键统计数据并手动将客户群体切分为多个细分群体,我们还可以使用机器学习,让机器找到将客户群体分割为期望数量细分群体的最佳方法。在本章中,我们将学习如何使用 k-means 聚类算法基于历史数据构建客户细分。
在本章中,我们将涵盖以下主题:
-
客户细分
-
聚类算法
-
使用 Python 进行客户细分
-
使用 R 进行客户细分
客户细分
鉴于当前市场中的竞争,理解客户的不同行为、类型和兴趣至关重要。尤其是在精准营销中,理解并分类客户是制定有效营销策略的必要步骤。通过细分客户群体,营销人员可以一次专注于一个客户群体。这也有助于营销人员将营销信息精准地传递给一个特定的受众。客户细分是成功精准营销的基础,通过细分,您可以以最具成本效益的方式,针对特定客户群体提供不同的定价选项、促销活动和产品布局,最大程度地吸引目标受众的兴趣。
任何行业或企业都可以通过更好地理解不同的顾客群体而受益。例如,针对全美播放的电视广告,如果是某个销售冬季服饰(如大衣、雪地靴和帽子)的外套品牌,这样的广告可能并不具有成本效益。居住在那些从不寒冷的地区,如佛罗里达、南加州或夏威夷的人们,可能不会对购买冬季服装感兴趣。然而,居住在寒冷冬季地区的人们,比如阿拉斯加、明尼苏达或北达科他州的人们,可能更倾向于购买能保暖的衣物。因此,对于这个外套品牌而言,与其向所有顾客发送营销邮件或电子邮件,不如基于顾客的地理信息,锁定那些生活在需要冬季服装的地方的顾客群体。
另一个例子是,如果你拥有一栋靠近大学的出租楼,可能希望根据顾客的年龄和教育背景来精准定位你的客户。将营销对象锁定在 20 到 30 岁之间并且就读于周边大学的顾客群体,回报将高于向其他人群体进行营销。对于酒店业来说,你可能希望将目标顾客定位为即将庆祝周年纪念的情侣,推出浪漫套餐。通过使用社交媒体平台,如 Facebook 或 Instagram,你可以精准定位这一部分顾客。
正如我们在这三个案例中简要讨论的那样,了解你的顾客以及哪一类顾客群体最能代表他们,有助于你制定有效和高效的营销策略。在将顾客群体细分为子群体时,你可以使用某些特征及其统计数据,如在第七章《顾客行为的探索性分析》中所示。然而,当你试图通过多个属性来细分顾客时,事情变得越来越复杂。在接下来的部分,我们将讨论如何使用机器学习进行顾客细分。
聚类算法
聚类算法在市场营销中经常用于顾客细分。这是一种无监督学习方法,通过数据来学习不同群体之间的共性。与有监督学习不同,有监督学习有明确的目标和标签变量,旨在预测这些目标和标签变量,而无监督学习则是在没有任何目标或标签变量的情况下从数据中学习。在众多聚类算法中,我们将在本章中探讨 k-means 聚类算法的使用。
k-means 聚类算法将数据记录划分为预定义数量的聚类,每个聚类内的数据点彼此接近。为了将相似的记录分组,k-means 聚类算法会尝试寻找聚类的中心点,即聚类的中心或均值,以最小化数据点与聚类中心之间的距离。目标方程(来自scikit-learn.org/stable/modules/clustering.html#k-means)如下所示:

这里n是数据集中的记录数,x[i]是第i个数据点,C是聚类的数量,µ[j]是第j个中心点。
使用 k-means 聚类进行客户细分的一个缺点或难点是,您需要事先知道聚类的数量。然而,通常情况下,您并不知道最优的聚类数量是多少。轮廓系数可以用来评估并帮助您做出关于细分问题最佳聚类数量的决策。简单来说,轮廓系数衡量的是数据点与其聚类之间的接近程度,与其他聚类的接近程度相比。公式如下:

这里b是某点与其最近的聚类之间的平均距离,而a是同一聚类内数据点之间的平均距离。轮廓系数的值范围从-1 到 1,其中值越接近 1,表示越好。在接下来的编程练习中,我们将使用 k-means 聚类算法和轮廓系数对我们的数据集进行客户群体划分。
使用 Python 进行客户细分
在本节中,我们将讨论如何使用 Python 中的聚类算法将客户群体划分为子群体。在本节结束时,我们将使用 k-means 聚类算法建立一个客户细分模型。我们将主要使用pandas、matplotlib和scikit-learn包来分析、可视化和构建机器学习模型。对于那些希望使用 R 语言而非 Python 的读者,可以跳过到下一节。
在本次练习中,我们将使用 UCI 机器学习库中一个公开可用的数据集,您可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/online+retail。您可以点击该链接并下载数据,数据为 XLSX 格式,名为Online Retail.xlsx。下载数据后,您可以通过运行以下命令将其加载到您的 Jupyter Notebook 中:
import pandas as pd
df = pd.read_excel('../data/Online Retail.xlsx', sheet_name='Online Retail')
DataFrame,df,如下所示:

如你所注意到的,我们在前面的章节中已经多次使用了这份数据集。正如你在前面章节中记得的,继续之前,我们需要清理一些数据。
数据清理
在我们开始构建聚类模型之前,有五个任务需要完成,以清理数据并为建模做准备。清理步骤如下:
- 删除已取消的订单:我们将删除
Quantity列中为负值的记录,使用以下代码:
df = df.loc[df['Quantity'] > 0]
- 删除没有
CustomerID的记录:有133,361条记录没有CustomerID,我们将使用以下代码删除这些记录:
df = df[pd.notnull(df['CustomerID'])]
- 排除不完整的月份:正如你在前面章节中记得的,2011 年 12 月的数据是不完整的。你可以用以下代码排除这些数据:
df = df.loc[df['InvoiceDate'] < '2011-12-01']
- 从
Quantity和UnitPrice列计算总销售额:对于我们的分析,我们需要总销售额,因此我们将Quantity和UnitPrice两列相乘,得到总销售额,具体代码如下:
df['Sales'] = df['Quantity'] * df['UnitPrice']
- 每个客户的数据:为了分析客户群体,我们需要转化数据,使每条记录代表单个客户的购买历史。请查看以下代码:
customer_df = df.groupby('CustomerID').agg({
'Sales': sum,
'InvoiceNo': lambda x: x.nunique()
})
customer_df.columns = ['TotalSales', 'OrderCount']
customer_df['AvgOrderValue'] =
customer_df['TotalSales']/customer_df['OrderCount']
如你从这段代码中看到的,我们通过CustomerID对DataFrame(df)进行分组,并计算每个客户的总销售额和订单数量。然后,我们还计算每个订单的平均值AvgOrderValue,方法是将TotalSales列除以OrderCount列。结果如下图所示:

如你从这份数据中看到的,TotalSales、OrderCount和AvgOrderValue这三列数据的尺度不同。TotalSales的取值范围从0到26,848,而OrderCount的取值范围是从1到201。聚类算法对数据的尺度非常敏感,因此我们需要将这些数据归一化,使其具有相同的尺度。我们将采取两个步骤来归一化数据。首先,我们将对数据进行排名,使每一列的值范围从1到4298(即记录的总数)。请查看以下代码:
rank_df = customer_df.rank(method='first')
结果显示在以下截图中:

接下来,我们将对这些数据进行归一化,使其集中在均值周围,均值为0,标准差为1。请查看以下代码:
normalized_df = (rank_df - rank_df.mean()) / rank_df.std()
结果显示在以下截图中:

查看每一列的统计数据,如下图所示:

如你所见,数据值集中在0周围,标准差为1。我们将使用这些数据进行接下来的聚类分析。
k-means 聚类
k-means 聚类算法是一个常用的算法,用于挖掘数据中的分布和分离。在营销中,它常被用来构建客户细分,并理解不同细分的行为。让我们深入了解如何在 Python 中构建聚类模型。
为了在scikit-learn包中使用 k-means 聚类算法,我们需要导入kmeans模块,如下所示的代码:
from sklearn.cluster import KMeans
接下来,你可以使用以下代码构建并拟合一个 k-means 聚类模型:
kmeans = KMeans(n_clusters=4).fit(normalized_df[['TotalSales', 'OrderCount', 'AvgOrderValue']])
从这段代码中可以看到,我们正在构建一个聚类模型,将数据分成四个部分。你可以通过n_clusters参数来更改所需的聚类数量。使用fit函数,你可以训练一个 k-means 聚类算法,让它学会分割给定的数据。在这段代码中,我们基于TotalSales、OrderCount和AvgOrderValue值构建了四个聚类。训练后的模型对象kmeans将聚类的标签和中心存储在模型对象的labels_和cluster_centers_属性中。你可以通过以下代码来获取这些值:
kmeans.labels_
kmeans.cluster_centers_
现在我们已经构建了第一个聚类模型,让我们来可视化这些数据。首先,看看以下代码:
four_cluster_df = normalized_df[['TotalSales', 'OrderCount', 'AvgOrderValue']].copy(deep=True)
four_cluster_df['Cluster'] = kmeans.labels_
我们将每条记录的聚类标签信息存储到一个新创建的 DataFrame four_cluster_df中。通过这个DataFrame,我们可以使用以下代码来可视化这些聚类:
plt.scatter(
four_cluster_df.loc[four_cluster_df['Cluster'] == 0]['OrderCount'],
four_cluster_df.loc[four_cluster_df['Cluster'] == 0]['TotalSales'],
c='blue'
)
plt.scatter(
four_cluster_df.loc[four_cluster_df['Cluster'] == 1]['OrderCount'],
four_cluster_df.loc[four_cluster_df['Cluster'] == 1]['TotalSales'],
c='red'
)
plt.scatter(
four_cluster_df.loc[four_cluster_df['Cluster'] == 2]['OrderCount'],
four_cluster_df.loc[four_cluster_df['Cluster'] == 2]['TotalSales'],
c='orange'
)
plt.scatter(
four_cluster_df.loc[four_cluster_df['Cluster'] == 3]['OrderCount'],
four_cluster_df.loc[four_cluster_df['Cluster'] == 3]['TotalSales'],
c='green'
)
plt.title('TotalSales vs. OrderCount Clusters')
plt.xlabel('Order Count')
plt.ylabel('Total Sales')
plt.grid()
plt.show()
如你所见,在这段代码中,我们正在通过散点图来可视化数据。结果如下所示:

让我们仔细看看这个图。蓝色聚类是低价值客户群体,这些客户购买我们的产品较少。另一方面,红色聚类是高价值客户群体,这些客户购买了最多的产品并且购买频率较高。我们还可以使用其他变量从不同角度来可视化聚类。请看以下图:

第一个图显示了基于AvgOrderValue和OrderCount可视化的聚类。另一方面,第二个图显示了基于AvgOrderValue和TotalSales可视化的聚类。从这些图中可以看出,蓝色聚类的每单平均值最低,订单数也最少。然而,红色聚类的每单平均值最高,订单数也最多。通过可视化聚类,你可以更加容易和清晰地理解不同聚类的特征。
选择最佳的聚类数量
通常,在构建 k-means 聚类模型时,我们并不知道最佳的聚类数。正如本章前面部分所讨论的,我们可以使用轮廓系数来确定将数据分割成最佳聚类数。在 scikit-learn 包中,你可以使用 sklearn.metrics 模块中的 silhouette_score 函数来计算轮廓分数,从而衡量聚类的质量。看看以下代码:
from sklearn.metrics import silhouette_score
for n_cluster in [4,5,6,7,8]:
kmeans = KMeans(n_clusters=n_cluster).fit(
normalized_df[['TotalSales', 'OrderCount', 'AvgOrderValue']]
)
silhouette_avg = silhouette_score(
normalized_df[['TotalSales', 'OrderCount', 'AvgOrderValue']],
kmeans.labels_
)
print('Silhouette Score for %i Clusters: %0.4f' % (n_cluster, silhouette_avg))
从这段代码中可以看到,我们正在尝试五个不同数量的聚类:4、5、6、7 和 8。对于每一个聚类数,我们都会衡量轮廓分数,并选择分数最高的聚类数。该代码的输出如下所示:

在我们的案例中,在我们尝试的五个不同的聚类数中,轮廓分数最高的最佳聚类数是 4。在接下来的部分中,我们将使用 4 作为聚类数,展示如何解释聚类分析的结果。
解释客户细分
在这一部分中,我们将讨论从前面的聚类分析结果中提取洞察的不同方法。首先,让我们构建一个包含四个聚类的 k-means 聚类模型。你可以使用以下代码:
kmeans = KMeans(n_clusters=4).fit(
normalized_df[['TotalSales', 'OrderCount', 'AvgOrderValue']]
)
four_cluster_df = normalized_df[['TotalSales', 'OrderCount', 'AvgOrderValue']].copy(deep=True)
four_cluster_df['Cluster'] = kmeans.labels_
从这段代码中可以看到,我们正在使用基于三个属性:TotalSales(总销售额)、OrderCount(订单数)和 AvgOrderValue(每单平均值)的 4 个聚类来拟合 k-means 聚类模型。然后,我们将聚类标签信息存储到一个 DataFrame four_cluster_df 中。这个 DataFrame 如下图所示:

我们首先要查看的是每个聚类的中心。你可以使用以下代码获取聚类中心:
kmeans.cluster_centers_
这段代码的输出如下面的截图所示:

让我们仔细看看这一点。第四个聚类在所有三个属性上都具有最低的值。这表明第四个聚类包含销售额最少、订单数量最少、每单平均值最低的客户。这组客户是低价值客户。另一方面,第三个聚类在所有三个属性上都有最高的数值。第三个聚类中的客户拥有最高的销售额、最多的订单数和最高的每单平均值。因此,第三个聚类中的客户购买高价商品,并为业务带来了最高的收入。你通常会希望将营销重点放在这一部分客户身上,因为这样会带来最高的回报。
第二个聚类的客户非常有趣。他们购买频率相对较高,因为他们的OrderCount的聚类中心值属于中高范围,但他们的每单平均消费较低,因为AvgOrderValue的聚类中心值较低。这些客户经常购买低价值商品。因此,向这一客户群体推广单价较低的商品将是非常合适的。第一个聚类的客户也很有意思。从聚类中心来看,他们对收入和订单数量的贡献属于中低水平,但他们的每单平均消费较高。这些客户购买昂贵商品的频率较低。因此,向这一客户群体推广昂贵商品将是非常合适的。
从这个例子中可以看出,观察聚类中心有助于我们理解不同类型和细分的客户,以及如何针对不同群体进行差异化营销。最后,我们还可以找出每个客户细分市场的畅销商品。看看以下代码:
high_value_cluster = four_cluster_df.loc[four_cluster_df['Cluster'] == 2]
pd.DataFrame(
df.loc[
df['CustomerID'].isin(high_value_cluster.index)
].groupby('Description').count()[
'StockCode'
].sort_values(ascending=False).head()
)
如我们之前所见,第三个聚类是高价值客户群体,我们将查看该群体的前五个畅销商品。此代码的输出如下:

对于这一高价值细分市场,最畅销的商品是JUMBO BAG RED RETROSPOT,第二畅销商品是REGENCY CAKESTAND 3 TIER。当你针对这一客户群体制定营销策略时,可以利用这些信息。在营销活动中,你可以向这一客户群体推荐与这些畅销商品相似的商品,因为他们对这类商品最感兴趣。
你可以在以下仓库找到本次练习的完整代码:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.10/python/CustomerSegmentation.ipynb。
使用 R 进行客户细分
在本节中,我们将讨论如何使用 R 语言中的聚类算法将客户群体划分为子群体。到本节结束时,我们将使用 k-means 聚类算法构建一个客户细分模型。对于那些希望使用 Python 而非 R 完成此练习的读者,请参阅上一节内容。
对于本次练习,我们将使用来自 UCI 机器学习库的一个公开数据集,数据集可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/online+retail。你可以点击此链接下载数据,数据以 XLSX 格式提供,名为Online Retail.xlsx。下载完数据后,你可以通过运行以下命令将其加载到 RStudio 中:
library(readxl)
#### 1\. Load Data ####
df <- read_excel(
path="~/Documents/data-science-for-marketing/ch.10/data/Online Retail.xlsx",
sheet="Online Retail"
)
以下截图显示了数据框df:

正如你可能已经注意到的,我们在前面的章节中多次使用了这个数据集。你可能还记得,在继续之前,我们需要清理一些数据。
数据清理
在我们开始构建聚类模型之前,有五个任务需要完成,以清理数据并为建模做好准备。清理步骤如下:
- 删除取消的订单:我们将使用以下代码删除
Quantity为负数的记录:
df <- df[which(df$Quantity > 0),]
- 删除没有 CustomerID 的记录:有
133,361条记录没有CustomerID,我们将使用以下代码删除这些记录:
df <- na.omit(df)
- 排除不完整的月份:你可能还记得,在前面的章节中,2011 年 12 月的数据是不完整的。你可以使用以下代码排除这些数据:
df <- df[which(df$InvoiceDate < '2011-12-01'),]
- 从 Quantity 和 UnitPrice 列计算总销售额:为了进行分析,我们需要总销售额,因此我们将
Quantity和UnitPrice列相乘,得到总销售额,如以下代码所示:
df$Sales <- df$Quantity * df$UnitPrice
- 按客户数据:为了分析客户细分,我们需要转换数据,使每条记录代表单个客户的购买历史。请看以下代码:
# per customer data
customerDF <- df %>%
group_by(CustomerID) %>%
summarize(TotalSales=sum(Sales),
OrderCount=length(unique(InvoiceDate))) %>%
mutate(AvgOrderValue=TotalSales/OrderCount)
如你所见,在这段代码中,我们将数据框 df 按 CustomerID 分组,计算每个客户的总销售额和订单数量。然后,我们还计算每个订单的平均订单值 AvgOrderValue,通过将 TotalSales 列除以 OrderCount 列来获得。结果如下所示:

如你所见,在这些数据中,TotalSales、OrderCount 和 AvgOrderValue 列的尺度不同。TotalSales 的值范围从 0 到 26,848,而 OrderCount 的值介于 1 和 201 之间。聚类算法高度依赖数据的尺度,因此我们需要对这些数据进行归一化处理,使其具有相同的尺度。我们将采取两步来归一化这些数据。首先,我们将对数据进行排名,使每一列的值范围从 1 到 4298,即记录的总数。请看以下代码:
rankDF <- customerDF %>%
mutate(TotalSales=rank(TotalSales), OrderCount=rank(OrderCount, ties.method="first"), AvgOrderValue=rank(AvgOrderValue))
结果如下所示:

接下来,我们将对这些数据进行归一化,使其围绕均值进行中心化,均值为 0,标准差为 1,使用 R 中的 scale 函数。请看以下代码:
normalizedDF <- rankDF %>%
mutate(TotalSales=scale(TotalSales), OrderCount=scale(OrderCount), AvgOrderValue=scale(AvgOrderValue))
结果如下所示:

查看每一列的统计数据,如下所示:

你可以看到这些值围绕 0 中心,并且标准差为 1。我们将使用这些数据进行接下来的聚类分析。
k-means 聚类
k-means 聚类算法是一个常用的算法,用于分析数据中的分布和分离。在营销中,它常被用于构建客户细分,并理解这些不同细分的行为。让我们深入了解如何在 R 中构建聚类模型。
你可以使用以下代码来构建和拟合一个 k-means 聚类模型:
cluster <- kmeans(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], 4)
如你所见,这段代码中我们正在构建一个聚类模型,将数据分成4个部分。kmeans函数的第一个参数用于指定进行 k 均值聚类的数据,第二个参数定义了期望的聚类数量。在这段代码中,我们构建了4个聚类,基于TotalSales、OrderCount和AvgOrderValue值。训练后的 k 均值聚类模型对象cluster将聚类标签和中心存储在模型对象的cluster和centers变量中。你可以通过以下代码来提取这些值:
cluster$cluster
cluster$centers
现在我们已经构建了第一个聚类模型,让我们来可视化这些数据。首先,我们将聚类标签存储为一个名为Cluster的单独列,放入normalizedDF变量中,如以下代码所示:
# cluster labels
normalizedDF$Cluster <- cluster$cluster
然后,我们可以使用以下代码来可视化聚类:
ggplot(normalizedDF, aes(x=AvgOrderValue, y=OrderCount, color=Cluster)) +
geom_point()
如你所见,这段代码中我们正在使用散点图来可视化数据。结果如以下截图所示:

让我们仔细看看这个图。左下角的聚类是低价值客户群体,这些客户购买我们的产品较少。另一方面,右上角颜色最深的聚类是高价值客户群体,这些客户购买的数量最多,而且购买频率很高。我们还可以使用其他变量,从不同的角度来可视化这些聚类。请看以下几个图:


第一个图展示了基于AvgOrderValue和OrderCount可视化的聚类。第二个图展示了基于AvgOrderValue和TotalSales可视化的聚类。正如你从这些图中看到的,左下角颜色第二浅的聚类,具有最低的每单平均值和最低的订单数量。然而,右上角颜色最深的聚类,具有最高的每单平均值和最多的订单数。聚类的可视化帮助你更轻松、更清晰地理解不同聚类的特征。
选择最佳的聚类数量
很多时候,在构建 k-means 聚类模型时,我们并不知道最佳的聚类数量。正如本章前面部分所讨论的那样,我们可以使用轮廓系数来确定最适合划分数据的聚类数量。在 R 中,你可以使用cluster库中的silhouette函数来计算轮廓系数并衡量聚类质量。看看下面的代码:
# Selecting the best number of cluster
library(cluster)
for(n_cluster in 4:8){
cluster <- kmeans(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], n_cluster)
silhouetteScore <- mean(
silhouette(
cluster$cluster,
dist(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], method = "euclidean")
)[,3]
)
print(sprintf('Silhouette Score for %i Clusters: %0.4f', n_cluster, silhouetteScore))
}
如你从这段代码中所见,我们正在实验五种不同数量的聚类:4、5、6、7 和 8。对于每种聚类数量,我们将衡量轮廓系数并选择得分最高的聚类数量。此代码的输出结果如下所示:

在我们的实验中,五种不同聚类数量中,得分最高的聚类数量是4。在接下来的部分中,我们将使用4作为聚类数量,展示如何解读聚类分析的结果。
解读客户细分
在本节中,我们将讨论从前面聚类分析结果中提取不同洞察的方法。首先,我们将构建一个4个聚类的 k-means 聚类模型。你可以使用以下代码:
# Interpreting customer segments
cluster <- kmeans(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], 4)
normalizedDF$Cluster <- cluster$cluster
如你从这段代码中所见,我们正在根据三个属性:TotalSales、OrderCount 和 AvgOrderValue,拟合一个4个聚类的 k-means 聚类模型。然后,我们将聚类标签信息存储到一个 DataFrame 中,命名为normalizedDF。该 DataFrame 的内容如下所示:

我们首先要查看的是每个聚类的中心。你可以使用以下代码来获取聚类中心:
# cluster centers
cluster$centers
此代码的输出结果如下所示:

让我们仔细看看这个。第三个聚类在所有三个属性上的数值都是最低的。这表明,第三个聚类包含的是销售额最低、订单数量最少、每单平均值最低的客户。这组客户是低价值客户。另一方面,第四个聚类在所有三个属性上的数值都是最高的。第四个聚类的客户拥有最高的销售额、最多的订单数量以及最高的每单平均值。这表明,这些客户购买了价格昂贵的商品,并为企业带来了最高的收入。你通常会希望将营销精力集中在这个客户群体上,因为这将带来最高的回报。
第一个聚类中的客户很有趣。他们的购买频率相对较高,因为他们在OrderCount上的聚类中心值处于中到高水平,但他们的每单平均消费较低,因为AvgOrderValue的聚类中心值较低。这类客户经常购买低价值商品。因此,向这一类客户推广单品价格较低的商品是非常合适的。第二个聚类的客户也很有意思。根据该聚类的中心,他们的收入贡献和订单数较低。然而,他们的每单平均消费较高。这类客户购买高价商品的频率较低。因此,向这一类客户推广昂贵的商品非常合适。
正如你从这个例子中看到的,通过查看聚类的中心,可以帮助我们理解不同类型和细分的客户,并了解如何采取不同的方式来针对他们。最后,我们还可以找到每个客户细分群体的畅销商品。请看以下代码:
# High value cluster
highValueCustomers <- unlist(
customerDF[which(normalizedDF$Cluster == 4),'CustomerID'][,1], use.names = FALSE
)
df[which(df$CustomerID %in% highValueCustomers),] %>%
group_by(Description) %>%
summarise(Count=n()) %>%
arrange(desc(Count))
如我们之前所见,第四个聚类是高价值客户群体,我们将查看这一群体的畅销商品。此代码的输出结果如下图所示:

对于这个高价值群体,最畅销的商品是JUMBO BAG RED RETROSPOT,第二畅销商品是REGENCY CAKESTAND 3 TIER。当你针对这一客户群体时,可以将这些信息用于营销策略。在你的营销活动中,你可以推荐与这些畅销商品类似的商品给这一群体的客户,因为他们对这些类型的商品最感兴趣。
你可以在以下仓库中找到本练习的完整代码:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.10/R/CustomerSegmentation.R。
总结
在本章中,我们深入了解了客户细分。我们探讨了客户细分如何帮助不同的企业形成更好、更具成本效益的营销策略的三种简单场景。我们讨论了了解不同客户细分的重要性,理解不同细分群体中的客户行为,以及他们的需求和兴趣如何帮助你更好地定位受众。我们还学习了 k-means 聚类算法,它是用于客户细分的最常用的聚类算法之一。为了评估聚类的质量,我们展示了如何使用轮廓系数。
在编程练习中,我们实验了如何在 Python 和 R 中构建 k-means 聚类模型。在 Python 中,我们可以使用 scikit-learn 包中的KMeans模块,而在 R 中,我们可以使用 kmeans 函数来构建聚类模型。通过使用 Python 中的 silhouette_score 函数和 R 中的 silhouette 函数,我们了解了如何利用轮廓系数来评估聚类的质量,并看到了如何通过查看轮廓分数来帮助我们确定最佳的聚类数量。最后,我们讨论了如何解读聚类分析结果,使用散点图和聚类中心,并看到如何为每个客户群体找出最畅销的商品。
在下一章,我们将讨论面临流失风险的客户以及如何留住这些客户。我们将共同在 Python 和 R 中使用 keras 包构建神经网络模型,以识别那些可能流失的客户。
第十一章:客户留存
由于客户有更多选择可以消费类似内容或购买类似产品和服务,许多企业发现留住客户变得越来越困难,且客户很容易被其他竞争对手吸引。因为获取新客户的成本通常高于留住现有客户的成本,所以客户流失问题比以往任何时候都更加令人担忧。为了保留现有客户并防止他们流失到竞争对手,企业不仅需要了解客户及其需求和兴趣,还应能够识别出哪些客户可能会流失,并采取措施留住这些面临流失风险的客户。
在本章中,我们将深入探讨客户流失及其对企业的影响,以及如何留住现有客户。我们将讨论一些客户离开企业的常见原因,并研究数据科学如何帮助减少客户流失的风险。作为预测客户流失的一种方法,我们将了解人工神经网络模型及其在不同领域的应用,并学习如何使用 Python 和 R 构建一个模型。
在本章中,我们将涵盖以下主题:
-
客户流失与客户留存
-
人工神经网络
-
使用 Python 预测客户流失
-
使用 R 预测客户流失
客户流失与客户留存
客户流失是指客户决定停止使用某公司提供的服务、内容或产品。正如我们在第七章中简要讨论过的,客户行为的探索性分析,当我们讨论客户分析时,保留现有客户的成本远低于获取新客户的成本,且来自重复客户的收入通常高于新客户的收入。在竞争激烈的行业中,企业面临众多竞争者,新客户获取的成本更高,因此,对于这些企业来说,保留现有客户变得尤为重要。
客户离开企业背后有很多原因。一些常见的客户流失原因包括服务质量差、产品或服务未能提供足够的价值、缺乏沟通和客户忠诚度低。留住这些客户的第一步是监控客户流失率的变化。如果流失率普遍较高或随着时间推移有所增加,那么分配一些资源来改善客户留存率将是一个好主意。
为了提高客户留存率,首要任务是更好地了解客户。你可以对已经流失的客户进行调查,了解他们离开的原因。你还可以对现有客户进行调查,了解他们的需求是什么,痛点在哪里。数据科学和数据分析方法是通过数据来寻找答案。例如,你可以查看客户的网页活动数据,了解他们花费最多时间的页面,是否在他们浏览的页面上出现了错误,或者他们的搜索结果是否没有返回好的内容。你还可以查看客户服务通话记录,了解他们的等待时间有多长,投诉了哪些问题,以及这些问题是如何被处理的。对这些数据点进行深入分析可以揭示企业在保持现有客户方面面临的挑战。
在分析客户流失时,你还可以利用本书中讨论的一些主题。你可以运用我们在第五章《产品分析》和第六章《推荐合适的产品》中学到的知识,了解哪些产品最能满足客户的需求和兴趣,并推荐合适的产品,从而提供更个性化的内容。你还可以运用我们在第七章《客户行为的探索性分析》和第十章《基于数据的客户细分》中学到的知识,更好地理解客户行为和不同的客户群体。另一种方法是构建一个机器学习模型,预测哪些客户可能流失,并针对这些流失风险较高的特定客户进行定向干预,争取留住他们。在接下来的章节中,我们将讨论如何构建一个神经网络模型,识别那些高流失风险的客户,以实现客户留存。
人工神经网络
人工神经网络(ANN)模型是一种受人脑运作启发的机器学习模型。近年来,ANN 模型在图像识别、语音识别和机器人技术等领域的成功应用,证明了它们在各个行业中的预测能力和实用性。你可能听说过“深度学习”这个术语。它是 ANN 模型的一种,其中输入层和输出层之间的层数较多。下面的图示可以更好地解释这一概念:

该图展示了一个具有单一隐藏层的人工神经网络(ANN)模型的简单示例。图中的圆圈代表人工神经元或节点,模拟的是人脑中的神经元。这些箭头表示信号如何从一个神经元传递到另一个神经元。正如该图所示,ANN 模型通过寻找每个输入神经元到下一层神经元之间的信号模式或权重来学习,从而最佳地预测输出。
在接下来的编程练习中,我们将使用的具体 ANN 模型是多层感知器(MLP)模型。简单来说,MLP 模型是一种至少有一个或多个隐藏层节点的神经网络模型。包括一个输入层和一个输出层,MLP 模型至少包含三个或更多层节点。我们刚才看到的图是 MLP 模型的最简单情况,其中只有一个隐藏层。
ANN 模型可以应用于许多营销领域。通过使用 BrainMaker 的神经网络模型,微软将其直邮响应率从 4.9% 提高到了 8.2%。这帮助微软在成本减少 35% 的情况下实现了相同的收入。同样地,在我们在第八章《预测营销参与可能性》中讨论的营销参与预测问题中,我们本可以使用神经网络模型,而不是随机森林模型。我们也可以使用神经网络模型解决我们在第十章《数据驱动的客户细分》中讨论的客户细分问题。在接下来的编程练习中,我们将讨论如何使用 ANN 模型预测哪些客户可能流失。
使用 Python 预测客户流失
在本节中,我们将讨论如何使用 ANN 模型预测有离职风险的客户,或者是那些高度可能流失的客户。在本节结束时,我们将使用 ANN 模型构建一个客户流失预测模型。我们将主要使用pandas、matplotlib 和 keras 包来进行分析、可视化以及构建机器学习模型。对于那些希望使用 R 而非 Python 进行此练习的读者,你可以跳到下一节。
对于本次练习,我们将使用来自 IBM Watson Analytics 社区的公开数据集之一,数据集链接如下:www.ibm.com/communities/analytics/watson-analytics-blog/predictive-insights-in-the-telco-customer-churn-data-set/。你可以点击该链接下载数据,数据以 XLSX 格式提供,文件名为 WA_Fn-UseC_-Telco-Customer-Churn.xlsx。下载数据后,你可以通过运行以下命令将其加载到 Jupyter Notebook 中:
import pandas as pd
df = pd.read_excel('../data/WA_Fn-UseC_-Telco-Customer-Churn.xlsx')
df数据框如下截图所示:

该数据集包含 21 个变量,其中我们的目标是预测目标变量Churn。
数据分析与准备
如你所见,在查看数据后,我们需要做一些工作,才能开始构建机器学习模型。在本节中,我们将转换具有货币值的连续变量,并编码目标变量Churn以及其他分类变量。为此,请执行以下步骤:
- 目标变量编码:正如你从数据中看到的,目标变量
Churn有两个值:Yes和No。我们将这些值编码为1代表Yes,0代表No。编码目标变量的代码如下所示:
df['Churn'] = df['Churn'].apply(lambda x: 1 if x == 'Yes' else 0)
要获得整体流失率,你可以简单地运行以下代码:
df['Churn'].mean()
该代码的输出约为 0.27,这意味着大约 27%的客户流失。27%的流失率并不是一个小数字;相反,它足够高,足以让企业担心整体客户流失并采取措施留住这些客户。在接下来的建模部分,我们将讨论如何利用这些数据预测可能流失的客户,并利用这些预测来保留客户。
- 处理 TotalCharges 列中的缺失值:如果你查看数据集中的
TotalCharges列,可能会注意到有些记录的TotalCharges值是缺失的。由于只有11条记录缺失TotalCharges值,我们将简单地忽略并删除这些缺失值的记录。请查看以下代码:
df['TotalCharges'] = df['TotalCharges'].replace(' ',
np.nan).astype(float)
df = df.dropna()
从这段代码可以看到,我们只是将空格值替换为nan值。然后,我们通过使用dropna函数删除所有包含nan值的记录。
- 转换连续变量:下一步是对连续变量进行缩放。以下是连续变量的汇总统计信息:

你可以使用以下代码获取这些汇总统计信息:
df[['tenure', 'MonthlyCharges', 'TotalCharges']].describe()
从汇总统计中可以看到,三个tenure、MonthlyCharges和TotalCharges连续变量的尺度不同。tenure变量的范围是从1到72,而TotalCharges变量的范围是从18.8到8684.8。通常,ANN 模型在使用缩放或归一化特征时表现更好。请查看以下代码,用于归一化这三个特征:
df['MonthlyCharges'] = np.log(df['MonthlyCharges'])
df['MonthlyCharges'] = (df['MonthlyCharges'] -
df['MonthlyCharges'].mean())/df['MonthlyCharges'].std()
df['TotalCharges'] = np.log(df['TotalCharges'])
df['TotalCharges'] = (df['TotalCharges'] -
df['TotalCharges'].mean())/df['TotalCharges'].std()
df['tenure'] = (df['tenure'] - df['tenure'].mean())/df['tenure'].std()
从这段代码可以看到,我们首先应用对数转换,然后通过减去均值并除以标准差来归一化连续变量。结果如下所示:

如你所见,所有变量现在的均值为0,标准差为1。我们将使用这些标准化的变量来进行未来的模型构建。
- 独热编码分类变量:从数据中可以看出,存在许多分类变量。我们首先来看一下每列有多少独特值。请查看以下代码:
for col in list(df.columns):
print(col, df[col].nunique())
你可以使用nunique函数来计算每一列的独特值数量。这段代码的输出如下所示:

正如这个输出所示,存在7032个独特的客户 ID,2个独特的性别,3个独特的MultipleLines值,以及6530个独特的TotalCharges值。我们已经在前一步处理了tenure、MonthlyCharges和TotalCharges变量,因此接下来我们将专注于那些具有2到4个独特值的变量。
让我们来看一下这些分类变量的一些分布情况。首先,为了查看数据在男性和女性之间的分布情况,你可以使用以下代码进行可视化:
df.groupby('gender').count()['customerID'].plot(
kind='bar', color='skyblue', grid=True, figsize=(8,6), title='Gender'
)
plt.show()
该图如下所示:

从这张条形图中可以看出,不同性别的数据分布大致均匀。你可以使用相同的代码来查看InternetService和PaymentMethod不同值之间的数据分布。请查看以下图表:


第一个图显示了数据在InternetService变量的三种不同类别之间的分布,第二个图显示了数据在PaymentMethod变量的四种不同类别之间的分布。从这些图中可以看出,我们可以通过条形图轻松地可视化和理解分类变量的分布情况。我们建议你为其他分类变量绘制条形图,以便更好地理解数据分布。
现在,我们将对这些分类变量应用独热编码。请查看以下代码:
dummy_cols = []
sample_set = df[['tenure', 'MonthlyCharges', 'TotalCharges', 'Churn']].copy(deep=True)
for col in list(df.columns):
if col not in ['tenure', 'MonthlyCharges', 'TotalCharges', 'Churn'] and df[col].nunique() < 5:
dummy_vars = pd.get_dummies(df[col])
dummy_vars.columns = [col+str(x) for x in dummy_vars.columns]
sample_set = pd.concat([sample_set, dummy_vars], axis=1)
如你所见,我们在这段代码中使用了pandas包中的get_dummies函数为每个分类变量创建虚拟变量。然后,我们将这些新创建的虚拟变量与sample_set变量合并,这些数据将用于接下来建模阶段的训练。结果如下所示:

一旦完成这四个步骤,就可以开始构建用于客户流失预测的人工神经网络(ANN)模型了。请进入下一部分进行 ANN 建模!
使用 Keras 的人工神经网络(ANN)
在 Python 中构建人工神经网络(ANN)模型,我们将使用keras包,这是一个高级的神经网络库。欲了解更多详细信息,我们建议您访问他们的官方文档:keras.io/。在我们使用这个包构建 ANN 模型之前,我们需要安装两个包:tensorflow和keras。keras包使用tensorflow作为构建神经网络模型的后台,因此我们需要先安装tensorflow。你可以通过以下pip命令在终端中安装这两个包:
pip install tensorflow
pip install keras
一旦你安装了这两个包,我们就可以开始构建我们的第一个神经网络模型了。在这个练习中,我们将构建一个具有一个隐藏层的神经网络模型。首先看看下面的代码:
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(16, input_dim=len(features), activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
让我们更详细地看一下这段代码。首先,我们这里使用的是Sequential模型,这是层按线性顺序堆叠的模型类型,类似于我们在前面关于 MLP 模型部分中看到的图示。第一层是输入层,其中input_dim仅仅是样本集中的特征数或列数,而输出单元的数量是16。我们为这个输入层使用的是relu激活函数。接下来,在隐藏层中,输出单元的数量是8,并且使用的激活函数是relu。最后,输出层有一个输出单元,表示客户流失的概率,我们在这个层中使用的是sigmoid激活函数。你可以尝试不同数量的输出单元和激活函数进行实验。
使用keras包构建神经网络模型的最后一步是编译这个模型。看看下面的代码:
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
在这里,我们使用的是adam优化器,这是最常用和频繁使用的优化算法之一。由于我们的目标变量是二元的,因此我们使用binary_crossentropy作为损失函数。最后,这个模型将使用accuracy作为评估训练过程中模型性能的指标。
在开始训练这个神经网络模型之前,我们需要将我们的样本集分为训练集和测试集。请看一下下面的代码:
from sklearn.model_selection import train_test_split
target_var = 'Churn'
features = [x for x in list(sample_set.columns) if x != target_var]
X_train, X_test, y_train, y_test = train_test_split(
sample_set[features],
sample_set[target_var],
test_size=0.3
)
从这段代码中可以看出,我们正在使用scikit-learn包中的train_test_split函数。在我们的练习中,我们将使用 70%的样本集进行训练,30%进行测试。现在,我们可以使用以下代码训练我们的神经网络模型:
model.fit(X_train, y_train, epochs=50, batch_size=100)
在这里,我们使用100个样本作为batch_size,即每次模型学习的样本数量,50作为epochs的数量,即通过整个训练集的完整遍历次数。运行这段代码后,你将看到类似如下的输出:

正如您从输出中看到的,loss通常会在每个周期减少,准确率(acc)会提高。然而,模型性能提升的速率随着时间的推移会逐渐减缓。正如您从输出中看到的,在前几个周期中,损失和准确率指标有显著改善,但随着时间的推移,性能提升的幅度逐渐变小。您可以监控这个过程,并决定在性能增益最小的时候停止训练。
模型评估
现在我们已经建立了第一个神经网络模型,让我们来评估它的性能。我们将查看总体准确率、精确度和召回率,以及接收者操作特征(ROC)曲线和曲线下面积(AUC)。首先,来看一下计算准确率、精确度和召回率的代码:
from sklearn.metrics import accuracy_score, precision_score, recall_score
in_sample_preds = [round(x[0]) for x in model.predict(X_train)]
out_sample_preds = [round(x[0]) for x in model.predict(X_test)]
# Accuracy
print('In-Sample Accuracy: %0.4f' % accuracy_score(y_train, in_sample_preds))
print('Out-of-Sample Accuracy: %0.4f' % accuracy_score(y_test, out_sample_preds))
# Precision
print('In-Sample Precision: %0.4f' % precision_score(y_train, in_sample_preds))
print('Out-of-Sample Precision: %0.4f' % precision_score(y_test, out_sample_preds))
# Recall
print('In-Sample Recall: %0.4f' % recall_score(y_train, in_sample_preds))
print('Out-of-Sample Recall: %0.4f' % recall_score(y_test, out_sample_preds))
您应该已经熟悉这段代码,因为我们在第八章《预测营销参与的可能性》中使用了相同的评估指标。对于我们这个案例,该代码的输出结果如下:

由于模型中存在一定的随机性,您的结果可能与这些数字有所不同。从这个输出中可以看到,测试集中预测客户是否流失的准确率约为0.79,这意味着模型大约有 80%的时间是正确的。样本外的精确度表明,模型在预测客户流失时大约有 66%的准确性,而样本外的召回率则表明,模型大约能捕捉到 52%的流失案例。
接下来,我们可以使用以下代码计算 AUC 值:
from sklearn.metrics import roc_curve, auc
in_sample_preds = [x[0] for x in model.predict(X_train)]
out_sample_preds = [x[0] for x in model.predict(X_test)]
in_sample_fpr, in_sample_tpr, in_sample_thresholds = roc_curve(y_train, in_sample_preds)
out_sample_fpr, out_sample_tpr, out_sample_thresholds = roc_curve(y_test, out_sample_preds)
in_sample_roc_auc = auc(in_sample_fpr, in_sample_tpr)
out_sample_roc_auc = auc(out_sample_fpr, out_sample_tpr)
print('In-Sample AUC: %0.4f' % in_sample_roc_auc)
print('Out-Sample AUC: %0.4f' % out_sample_roc_auc)
该代码的输出结果如下:

为了在 ROC 曲线中可视化这些数据,您可以使用以下代码:
plt.figure(figsize=(10,7))
plt.plot(
out_sample_fpr, out_sample_tpr, color='darkorange', label='Out-Sample ROC curve (area = %0.4f)' % in_sample_roc_auc
)
plt.plot(
in_sample_fpr, in_sample_tpr, color='navy', label='In-Sample ROC curve (area = %0.4f)' % out_sample_roc_auc
)
plt.plot([0, 1], [0, 1], color='gray', lw=1, linestyle='--')
plt.grid()
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.show()
输出结果如下:

除了之前查看的准确率、精确度和召回率指标外,AUC 和 ROC 曲线也表明该模型能够很好地捕捉并预测流失风险较高的客户。正如你从这些评估输出中看到的,与简单地猜测哪些客户可能流失相比,使用该模型的输出来识别可能流失的客户更为有效。通过在市场营销策略中重点关注该模型预测的高流失概率客户,您可以以更具成本效益的方式留住这些有流失风险的客户。
本练习的完整代码可以在此代码库中找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.11/python/CustomerRetention.ipynb。
使用 R 预测客户流失
在本节中,我们将讨论如何使用 ANN 模型预测有可能流失的客户或高度可能流失的客户。在本节结束时,我们将构建一个使用 ANN 模型的客户流失预测模型。我们将主要使用dplyr、ggplot2和keras库来分析、可视化和构建机器学习模型。对于那些希望使用 Python 而非 R 的读者,可以参阅前一节。
在这个练习中,我们将使用 IBM Watson Analytics 社区中公开的一个数据集,链接地址是:www.ibm.com/communities/analytics/watson-analytics-blog/predictive-insights-in-the-telco-customer-churn-data-set/。你可以通过这个链接下载数据,数据格式为 XLSX,文件名为WA_Fn-UseC_-Telco-Customer-Churn.xlsx。下载数据后,你可以通过运行以下命令将其加载到你的 RStudio 环境中:
library(readxl)
#### 1\. Load Data ####
df <- read_excel(
path="~/Documents/data-science-for-marketing/ch.11/data/WA_Fn-UseC_-Telco-Customer-Churn.xlsx"
)
DataFrame,df,应该像下面的截图一样:

这个数据集共有 21 个变量,我们的目标是预测目标变量Churn。
数据分析与准备
正如你可能从数据中看到的那样,在开始构建机器学习模型之前,我们需要做一些准备工作。在本节中,我们将转换具有货币值的连续变量,并编码目标变量Churn以及其他分类变量。为此,请执行以下步骤:
- 处理数据中的缺失值:如果你查看了数据集中的
TotalCharges列,你可能会注意到有些记录没有TotalCharges值。由于只有11条记录缺少TotalCharges值,我们将简单地忽略并删除这些缺失值的记录。请看下面的代码:
library(tidyr)
df <- df %>% drop_na()
正如你从这段代码中可以看到的,我们使用了tidyr包中的drop_na函数,这个函数会删除所有含有NA值的记录。
- 分类变量:如你从数据中看到的,有许多分类变量。我们首先来看一下每一列中有多少个唯一值。请看下面的代码:
apply(df, 2, function(x) length(unique(x)))
你可以使用unique函数获取每一列的唯一值。通过将这个函数应用于df中的所有列,代码输出结果如下:

从输出结果来看,数据集中有7032个唯一的客户 ID,2个唯一的性别,3个唯一的MultipleLines值,和6530个唯一的TotalCharges值。tenure、MonthlyCharges和TotalCharges变量是连续变量,每个变量可以取任意值,其他则是分类变量。
我们将查看一些分类变量的分布情况。首先,为了查看男性和女性之间的数据分布,你可以使用下面的代码进行可视化:
ggplot(df %>% group_by(gender) %>% summarise(Count=n()),
aes(x=gender, y=Count)) +
geom_bar(width=0.5, stat="identity") +
ggtitle('') +
xlab("Gender") +
ylab("Count") +
theme(plot.title = element_text(hjust = 0.5))
绘图如下所示:

从这张条形图中可以看出,两个性别的数据分布大致均衡。你可以使用相同的代码查看不同InternetService和PaymentMethod值下数据的分布。请查看下面的图:


第一个图展示了InternetService变量的三个不同类别下数据的分布,第二个图展示了PaymentMethod变量的四个不同类别下数据的分布。通过这些图,我们可以轻松地通过条形图可视化和理解分类变量的分布情况。我们建议你为其他分类变量绘制条形图,以便更好地了解数据分布。
- 变换和编码变量:下一步是转化连续变量并编码二元分类变量。请看下面的代码:
# Binary & Continuous Vars
sampleDF <- df %>%
select(tenure, MonthlyCharges, TotalCharges, gender, Partner,
Dependents, PhoneService, PaperlessBilling, Churn) %>%
mutate(
# transforming continuous vars
tenure=(tenure - mean(tenure))/sd(tenure),
MonthlyCharges=(log(MonthlyCharges) -
mean(log(MonthlyCharges)))/sd(log(MonthlyCharges)),
TotalCharges=(log(TotalCharges) -
mean(log(TotalCharges)))/sd(log(TotalCharges)),
# encoding binary categorical vars
gender=gender %>% as.factor() %>% as.numeric() - 1,
Partner=Partner %>% as.factor() %>% as.numeric() - 1,
Dependents=Dependents %>% as.factor() %>% as.numeric() - 1,
PhoneService=PhoneService %>% as.factor() %>% as.numeric() - 1,
PaperlessBilling=PaperlessBilling %>% as.factor() %>% as.numeric() - 1,
Churn=Churn %>% as.factor() %>% as.numeric() - 1
)
从这段代码可以看出,我们仅对那些只有两个类别的变量进行编码,分别是gender、Partner、Dependents、PhoneService、PaperlessBilling和Churn,使用0和1。然后,我们对两个具有货币值的连续变量,MonthlyCharges和TotalCharges,应用对数变换。同时,我们标准化所有三个连续变量,tenure、MonthlyCharges和TotalCharges,使这些变量的均值为0,标准差为1。这是因为 ANN 模型通常在特征经过缩放或归一化后表现更好。经过变换后,这三个连续变量的分布如下图所示:

如你所见,这三个变换后的变量的均值是0,标准差是1。而在此之前,这些分布看起来如下所示:

- 独热编码分类变量:我们需要转化的最后一组变量是:具有三个或更多类别的多类别分类变量。我们将应用独热编码,为这些变量创建虚拟变量。请看下面的代码:
# Dummy vars
# install.packages('dummies')
library(dummies)
sampleDF <- cbind(sampleDF, dummy(df$MultipleLines, sep="."))
names(sampleDF) = gsub("sampleDF", "MultipleLines", names(sampleDF))
如你所见,这段代码中我们使用了dummies库来创建虚拟变量。通过该包的dummy函数,我们可以应用独热编码并为每个多类别分类变量创建虚拟变量。由于dummy函数会将sampleDF前缀加到新创建的虚拟变量名称前,我们可以使用gsub函数将其替换为相应的变量名称。我们将对其余的分类变量应用相同的逻辑,如下所示:
sampleDF <- cbind(sampleDF, dummy(df$InternetService, sep="."))
names(sampleDF) = gsub("sampleDF", "InternetService", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$OnlineSecurity, sep="."))
names(sampleDF) = gsub("sampleDF", "OnlineSecurity", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$OnlineBackup, sep="."))
names(sampleDF) = gsub("sampleDF", "OnlineBackup", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$DeviceProtection, sep="."))
names(sampleDF) = gsub("sampleDF", "DeviceProtection", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$TechSupport, sep="."))
names(sampleDF) = gsub("sampleDF", "TechSupport", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$StreamingTV, sep="."))
names(sampleDF) = gsub("sampleDF", "StreamingTV", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$StreamingMovies, sep="."))
names(sampleDF) = gsub("sampleDF", "StreamingMovies", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$Contract, sep="."))
names(sampleDF) = gsub("sampleDF", "Contract", names(sampleDF))
sampleDF <- cbind(sampleDF, dummy(df$PaymentMethod, sep="."))
names(sampleDF) = gsub("sampleDF", "PaymentMethod", names(sampleDF))
结果显示在以下输出中:

完成这四个步骤后,就可以开始构建用于客户流失预测的 ANN 模型了。继续下一节,进行 ANN 建模!
使用 Keras 构建 ANN
在 R 中构建 ANN 模型时,我们将使用keras包,它是一个高级神经网络库。欲了解更多详细信息,建议访问其官方文档:keras.io/。在使用这个包构建 ANN 模型之前,我们需要安装两个库:tensorflow和keras。keras包使用tensorflow作为构建神经网络模型的后端,因此我们需要先安装tensorflow。你可以使用以下命令在 RStudio 中安装这两个包:
install.packages("devtools")
devtools::install_github("rstudio/tensorflow")
library(tensorflow)
install_tensorflow()
devtools::install_github("rstudio/keras")
library(keras)
install_keras()
安装完这两个库后,我们终于可以开始构建我们的第一个神经网络模型了。在这个练习中,我们将构建一个具有一个隐藏层的神经网络模型。首先看一下下面的代码:
model <- keras_model_sequential()
model %>%
layer_dense(units = 16, kernel_initializer = "uniform", activation = 'relu', input_shape=ncol(train)-1) %>%
layer_dense(units = 8, kernel_initializer = "uniform", activation = 'relu') %>%
layer_dense(units = 1, kernel_initializer = "uniform", activation = 'sigmoid') %>%
compile(
optimizer = 'adam',
loss = 'binary_crossentropy',
metrics = c('accuracy')
)
让我们仔细看看这段代码。首先,我们在这里构建了一个Sequential模型,keras_model_sequential,这是一种层次线性堆叠的模型,类似于我们在前面关于 MLP 模型部分看到的图示。第一层,layer_dense,是一个输入层,其中input_shape只是样本集中特征或列的数量,而输出单元的数量是16。我们在这个输入层使用的是relu激活函数。然后,在隐藏层中,输出单元的数量是8,并且使用的激活函数是relu。最后,输出层有一个输出单元,它表示客户流失的概率,我们在这一层使用sigmoid激活函数。你可以尝试不同数量的输出单元和激活函数进行练习。最后,我们需要编译这个模型,使用compile函数。在这里,我们使用adam优化器,它是最常用的优化算法之一。由于我们的目标变量是二元的,我们使用binary_crossentropy作为loss函数。最后,模型将使用accuracy指标来评估训练过程中的模型性能。
在开始训练这个神经网络模型之前,我们需要将样本集拆分成训练集和测试集。看一下下面的代码:
library(caTools)
sample <- sample.split(sampleDF$Churn, SplitRatio = .7)
train <- as.data.frame(subset(sampleDF, sample == TRUE))
test <- as.data.frame(subset(sampleDF, sample == FALSE))
trainX <- as.matrix(train[,names(train) != "Churn"])
trainY <- train$Churn
testX <- as.matrix(test[,names(test) != "Churn"])
testY <- test$Churn
从这段代码中可以看到,我们正在使用 caTools 包的 sample.split 函数。在我们的练习中,我们将使用 70% 的样本集用于训练,30% 用于测试。现在,我们可以使用以下代码训练神经网络模型:
history <- model %>% fit(
trainX,
trainY,
epochs = 50,
batch_size = 100,
validation_split = 0.2
)
在这里,我们使用 100 个样本作为 batch_size,每次模型都将从中学习预测,并且使用 50 作为 epochs 的数量,即整个训练集的完整遍历次数。运行这段代码后,你将看到以下输出:

从这个输出中可以看到,loss 通常在每个 epoch 中减少,准确率(acc)提高。然而,模型性能改进的速度随着时间的推移而减慢。从输出中可以看出,在前几个 epoch 中,损失和准确度指标有很大的改善,而随着时间推移,性能提升的幅度逐渐减少。你可以监控这个过程,并在性能提升最小时决定停止训练。
模型评估
现在我们已经构建了第一个神经网络模型,让我们评估其性能。我们将查看整体准确率、精度和召回率,以及 ROC 曲线和 AUC。首先,查看以下计算准确率、精度和召回率的代码:
# Evaluating ANN model
inSamplePreds <- as.double(model %>% predict_classes(trainX))
outSamplePreds <- as.double(model %>% predict_classes(testX))
# - Accuracy, Precision, and Recall
inSampleAccuracy <- mean(trainY == inSamplePreds)
outSampleAccuracy <- mean(testY == outSamplePreds)
print(sprintf('In-Sample Accuracy: %0.4f', inSampleAccuracy))
print(sprintf('Out-Sample Accuracy: %0.4f', outSampleAccuracy))
inSamplePrecision <- sum(inSamplePreds & trainY) / sum(inSamplePreds)
outSamplePrecision <- sum(outSamplePreds & testY) / sum(outSamplePreds)
print(sprintf('In-Sample Precision: %0.4f', inSamplePrecision))
print(sprintf('Out-Sample Precision: %0.4f', outSamplePrecision))
inSampleRecall <- sum(inSamplePreds & trainY) / sum(trainY)
outSampleRecall <- sum(outSamplePreds & testY) / sum(testY)
print(sprintf('In-Sample Recall: %0.4f', inSampleRecall))
print(sprintf('Out-Sample Recall: %0.4f', outSampleRecall))
你应该熟悉这段代码,因为我们在第八章《预测营销互动的可能性》中使用了相同的评估指标。我们在本例中的代码输出如下:

由于模型中的一些随机性,你的结果可能与这些数字有所不同。从这个输出中可以看到,预测客户是否会流失的准确率约为 0.83,表明该模型大约 83% 的时间是正确的。样本外精度表明,在预测客户会流失时,模型约有 72% 的正确率,样本外召回率表明,模型捕捉到大约 58% 的流失案例。
接下来,我们可以计算 AUC 并绘制 ROC 曲线,使用以下代码:
# - ROC & AUC
library(ROCR)
outSamplePredProbs <- as.double(predict(model, testX))
pred <- prediction(outSamplePredProbs, testY)
perf <- performance(pred, measure = "tpr", x.measure = "fpr")
auc <- performance(pred, measure='auc')@y.values[[1]]
plot(
perf,
main=sprintf('Model ROC Curve (AUC: %0.2f)', auc),
col='darkorange',
lwd=2
) + grid()
abline(a = 0, b = 1, col='darkgray', lty=3, lwd=2)
输出结果如下:

除了我们之前查看的准确率、精度和召回率指标外,AUC 和 ROC 曲线还表明该模型很好地捕捉和预测了那些面临流失风险的客户。从这些评估结果中可以看出,使用该模型的输出来识别可能流失的客户,比单纯地猜测他们是谁要好得多。通过将营销策略集中在该模型预测的高流失概率客户身上,你可以更具成本效益地留住这些有流失风险的客户。
本练习的完整代码可以在以下仓库找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.11/R/CustomerRetention.R。
总结
在本章中,我们已经学习了客户流失和客户保持的相关内容。我们讨论了客户流失对企业的危害。更具体地说,我们了解了保持现有客户比获取新客户的成本要低得多。我们展示了客户离开公司的常见原因,例如:糟糕的客户服务、没有在产品或服务中找到足够的价值、沟通不畅和缺乏客户忠诚度。为了了解客户流失的原因,我们可以通过调查或分析客户数据来更好地了解他们的需求和痛点。我们还讨论了如何训练人工神经网络(ANN)模型来识别那些有流失风险的客户。通过编程练习,我们学习了如何使用keras库在 Python 和 R 中构建和训练 ANN 模型。
在接下来的章节中,我们将学习 A/B 测试及其如何帮助确定在不同选项中最佳的营销策略。我们将讨论如何在 Python 和 R 中计算统计显著性,帮助营销人员决定在多个营销策略中选择哪一个。
第五部分:更好的决策制定
在这一节中,你将学习一种测试不同营销策略并选择最有效策略的方法。
本节包含以下章节:
-
第十二章,A/B 测试以优化营销策略
-
第十三章,接下来是什么?
第十二章:更好的营销策略的 A/B 测试
在构建不同的营销策略时,是否能够成功通常无法确定。通常,在提出新的营销想法时,涉及大量的猜测,而且常常缺乏工具、资源,甚至动机去测试你的营销创意是否可行。然而,这种直接将营销策略付诸实践的方式是有风险的,并且可能非常昂贵。假设你在新的营销活动上花费了大量资金,但它根本没有帮助你实现营销目标怎么办?如果你花费了数百小时来精炼你的营销信息,而这些信息却从未吸引到潜在客户与之互动呢?
在本章中,我们将讨论一种在完全投入营销创意之前对其进行测试的方法。更具体地说,我们将了解什么是 A/B 测试,为什么进行 A/B 测试很重要,以及它如何帮助你以更高效且成本更低的方式实现营销目标。
在本章中,我们将涵盖以下主题:
-
营销中的 A/B 测试
-
统计假设检验
-
使用 Python 评估 A/B 测试结果
-
使用 R 评估 A/B 测试结果
营销中的 A/B 测试
A/B 测试在各行业的决策过程中起着至关重要的作用。A/B 测试本质上是一种比较和测试两种不同商业策略效果与利益的方法。它可以被看作是一种实验,在实验中,两个或多个变体在一段时间内进行测试,然后评估实验结果,以找到最有效的策略。在完全选择某一方案之前进行 A/B 测试,帮助企业将决策过程中的猜测去除,节省宝贵的资源,如时间和资金,如果选定的策略没有奏效,这些资源本可能会被浪费。
在典型的 A/B 测试环境中,您会创建并测试两种或更多种营销策略版本,以评估它们在实现您的营销目标方面的效果。考虑一个情况,假设您的目标是提高营销邮件的打开率。如果您的假设是电子邮件主题行 B 的打开率会高于电子邮件主题行 A,那么您将进行一个 A/B 测试,使用这两个主题行。您将随机选择一半用户,向他们发送主题行 A 的营销邮件。另一半随机选择的用户将收到主题行 B 的邮件。您将运行这个测试,持续一个预定的时间段(例如一周、两周或一个月),或者直到有足够数量的用户(每个版本至少需要 1,000 个用户)收到这两个版本的邮件为止。一旦测试完成,您将分析并评估实验结果。在分析结果时,您需要检查两个版本之间的结果是否存在统计学上的显著差异。我们将在接下来的章节中详细讨论统计假设检验和统计显著性。如果实验结果显示两个版本的主题行中有明显的赢家,您可以在未来的营销邮件中使用获胜的主题行。
除了前面提到的电子邮件主题行场景,A/B 测试还可以应用于营销的许多不同领域。例如,您可以对社交媒体上的广告进行 A/B 测试。您可以制作两种或更多广告版本,并进行 A/B 测试,看看哪种版本在点击率或转化率方面效果更好。另一个例子是,您可以使用 A/B 测试来检验网站上的产品推荐是否能带来更高的购买率。如果您开发了不同版本的产品推荐算法,那么您可以将初始版本的产品推荐算法展示给一些随机选择的用户,而将第二个版本展示给其他随机选择的用户。您可以收集 A/B 测试结果,并评估哪一版本的产品推荐算法能为您带来更多收入。
从这些示例用例中可以看出,A/B 测试在决策中扮演着重要角色。在您完全投入某个方案之前,通过测试不同的情境,您可以节省精力、时间和资本,避免在完全投入后却因为失败而浪费这些资源。A/B 测试还可以帮助您消除猜测,并量化未来营销策略的表现提升(或下降)。每当您有新的营销创意想要进行迭代时,应该考虑首先进行 A/B 测试。
统计假设检验
当你进行 A/B 测试时,测试你的假设并寻找测试组之间的统计显著差异非常重要。学生 t 检验,简称 t 检验,常用于检验两个测试之间的差异是否具有统计显著性。t 检验比较两个平均值,并检查它们是否显著不同。
在 t 检验中有两个重要的统计量——t 值和 p 值。t 值衡量数据变异性相对于差异的程度。t 值越大,两个组之间的差异越大。另一方面,p 值衡量结果发生的概率是否由随机因素引起。p 值越小,两个组之间的统计显著差异就越大。计算 t 值的公式如下:

在这个公式中,M[1] 和 M[2] 分别是组 1 和组 2 的平均值。S[1] 和 S[2] 是组 1 和组 2 的标准差,N[1] 和 N[2] 分别是组 1 和组 2 的样本数。
有一个零假设和备择假设的概念,你应该对此有所了解。一般来说,零假设认为两个组之间没有统计学意义上的差异。而备择假设则认为两个组之间有统计学意义上的差异。当 t 值大于某个阈值且 p 值小于某个阈值时,我们说可以拒绝零假设,两个组之间存在统计学意义上的差异。通常,0.01 或 0.05 被用作测试统计显著性的 p 值阈值。如果 p 值小于 0.05,则表示两个组之间的差异发生的概率小于 5%,换句话说,这个差异不太可能是由偶然造成的。
使用 Python 评估 A/B 测试结果
在这一节中,我们将讨论如何评估 A/B 测试结果,以决定哪种营销策略最有效。到本节结束时,我们将覆盖如何进行统计假设检验并计算统计显著性。我们将主要使用 pandas、matplotlib 和 scipy 包来分析和可视化数据,并评估 A/B 测试结果。
对于那些希望使用 R 而非 Python 来进行本次练习的读者,可以跳到下一节。
在本次练习中,我们将使用 IBM Watson Analytics 社区中的一个公开数据集,数据集链接为:www.ibm.com/communities/analytics/watson-analytics-blog/marketing-campaign-eff-usec_-fastf/。你可以访问该链接并下载数据,数据以 XLSX 格式提供,文件名为WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.xlsx。下载数据后,你可以通过运行以下命令将数据加载到你的 Jupyter Notebook 中:
import pandas as pd
df = pd.read_excel('../data/WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.xlsx')
df 数据框如下所示:

数据集中总共有七个变量。你可以在 IBM Watson Analytics Community 页面上找到这些变量的描述,但我们将在以下内容中再次强调:
-
MarketID:市场的唯一标识符 -
MarketSize:按销售额划分的市场区域大小 -
LocationID:店铺位置的唯一标识符 -
AgeOfStore:店铺的年龄(以年为单位) -
Promotion:测试的三种促销中的一种 -
week:促销进行的四周中的一周 -
SalesInThousands:特定LocationID、Promotion和week的销售额
数据分析
让我们更深入地了解数据。在这一部分,我们将专注于理解用于测试不同促销的销售额、市场规模、店铺位置和店铺年龄的分布情况。分析的目的是确保每个促销组的控制和属性是对称分布的,以便不同组之间的促销效果可以进行比较。
不同促销下的总销售分布可以通过以下代码进行可视化:
ax = df.groupby(
'Promotion'
).sum()[
'SalesInThousands'
].plot.pie(
figsize=(7, 7),
autopct='%1.0f%%'
)
ax.set_ylabel('')
ax.set_title('sales distribution across different promotions')
plt.show()
从这段代码可以看出,我们通过Promotion列对数据进行分组,并通过对SalesInThousands列求和来聚合总销售额。使用饼图,我们可以轻松地可视化每个组占整个销售额的比例。
结果饼图如下所示:

从这个饼图中可以清楚地看到,促销组3在三组中拥有最大的总销售额。然而,每个促销组在促销周期间的销售额大致占总销售额的三分之一。类似地,我们还可以可视化每个促销组中不同市场规模的构成。请看以下代码:
ax = df.groupby([
'Promotion', 'MarketSize'
]).count()[
'MarketID'
].unstack(
'MarketSize'
).plot(
kind='bar',
figsize=(12,10),
grid=True,
)
ax.set_ylabel('count')
ax.set_title('breakdowns of market sizes across different promotions')
plt.show()
条形图如下所示:

如果你认为堆叠条形图会更容易查看,你可以使用以下代码将这些数据以堆叠条形图的形式显示:
ax = df.groupby([
'Promotion', 'MarketSize'
]).sum()[
'SalesInThousands'
].unstack(
'MarketSize'
).plot(
kind='bar',
figsize=(12,10),
grid=True,
stacked=True
)
ax.set_ylabel('Sales (in Thousands)')
ax.set_title('breakdowns of market sizes across different promotions')
plt.show()
你可能会注意到,这段代码与之前的代码唯一的不同在于stacked=True标志。结果如下所示:

从这张柱状图中可以看到,中等市场规模在所有三个促销组中占据了最大比例,而小市场规模占据的比例最小。我们可以通过这张图验证三个促销组中不同市场规模的组成是相似的。
另一个属性,AgeOfStore,以及它在所有不同促销组中的总体分布,可以通过以下代码进行可视化:
ax = df.groupby(
'AgeOfStore'
).count()[
'MarketID'
].plot(
kind='bar',
color='skyblue',
figsize=(10,7),
grid=True
)
ax.set_xlabel('age')
ax.set_ylabel('count')
ax.set_title('overall distributions of age of store')
plt.show()
结果在下图中的柱状图中显示:

从这张图中你可以看到,大量商店的年龄为1岁,而大多数商店的年龄为10岁或更小。然而,我们更感兴趣的是三个不同促销组中的商店是否有相似的商店年龄分布。请查看以下代码:
ax = df.groupby(
['AgeOfStore', 'Promotion']
).count()[
'MarketID'
].unstack(
'Promotion'
).iloc[::-1].plot(
kind='barh',
figsize=(12,15),
grid=True
)
ax.set_ylabel('age')
ax.set_xlabel('count')
ax.set_title('overall distributions of age of store')
plt.show()
使用此代码,你将得到以下输出:

三个不同促销组中的商店年龄分布似乎彼此一致,但从这个图表中很难消化所呈现的信息。通过查看三组促销中商店年龄的汇总统计数据,会更容易理解这些信息。请看以下代码:
df.groupby('Promotion').describe()['AgeOfStore']
这段代码的输出结果如下:

从这个输出中,你可以注意到,从这些汇总统计数据来看,商店年龄的总体分布更加容易理解。我们可以看到,所有三组测试组的商店年龄分布似乎相似。这三组的商店平均年龄在 8 至 9 年之间,大多数商店年龄为 10 到 12 年或更年轻。
通过分析每个促销或测试组的组成,我们可以验证商店档案之间的相似性。这表明样本组得到了很好的控制,A/B 测试结果将具有意义且值得信赖。
统计假设检验
不同营销策略的 A/B 测试的最终目标是找出哪种策略最有效,哪种策略在其他策略中表现最好。如前文简要讨论的那样,响应数量较高的策略并不一定代表它优于其他策略。我们将讨论如何使用 t 检验来评估不同营销策略的相对表现,并查看哪种策略在统计学上显著优于其他策略。
在 Python 中,计算 t 检验的 t 值和 p 值有两种方法。我们将在本节中演示这两种方法,你可以根据自己的需求选择最适合的方式。计算 t 检验的 t 值和 p 值的两种方法如下:
- 通过公式计算 t 值和 p 值:第一种方法是通过我们在上一节中学到的公式手动计算 t 值。你可能记得,我们需要计算三项内容才能得到 t 值——均值、标准差和样本数。看看以下代码:
means = df.groupby('Promotion').mean()['SalesInThousands']
stds = df.groupby('Promotion').std()['SalesInThousands']
ns = df.groupby('Promotion').count()['SalesInThousands']
如你所见,使用代码中的 mean、std 和 count 函数,你可以轻松计算每个测试组的均值、标准差和样本数。通过这些数据,我们可以利用之前讨论过的公式计算 t 值。看看以下代码:
import numpy as np
t_1_vs_2 = (
means.iloc[0] - means.iloc[1]
)/ np.sqrt(
(stds.iloc[0]**2/ns.iloc[0]) + (stds.iloc[1]**2/ns.iloc[1])
)
使用这段代码,我们可以计算出促销 1 和促销 2 的性能比较的 t 值。运行代码后得到的 t 值是 6.4275。通过这个 t 值,我们可以使用以下代码计算 p 值:
from scipy import stats
df_1_vs_1 = ns.iloc[0] + ns.iloc[1] - 2
p_1_vs_2 = (1 - stats.t.cdf(t_1_vs_2, df=df_1_vs_1))*2
如你所见,代码中首先计算自由度,这是两个组样本数之和减去二。利用之前计算的 t 值,我们可以使用 scipy 包的 stats 模块中的 t.cdf 函数来计算 p 值。运行此代码得到的 p 值是 4.143e-10。这是一个极其小的数字,接近 0。如前所述,p 值越接近 0,意味着反对零假设的证据越强,且两组测试结果之间的差异具有显著性。
促销组 1 的平均销售额(千元)约为 58.1,而促销组 2 的平均销售额约为 47.33。通过我们的 t 检验,我们已经证明这两个组的营销表现存在显著差异,并且促销组 1 的表现优于促销组 2。然而,如果我们将促销组 1 和促销组 3 进行 t 检验,我们将看到不同的结果。
表面上,促销组 1 的平均销售额 (58.1) 看起来高于促销组 2 (55.36)。然而,当我们对这两个组进行 t 检验时,我们得到 t 值为 1.556,p 值为 0.121。计算出的 p 值远高于 0.05,这是一个普遍接受的临界值。这表明促销组 1 的营销表现与促销组 2 的营销表现没有统计学上的显著差异。因此,尽管促销组 1 的平均销售额高于促销组 2,但这种差异在统计上并不显著,我们不能得出促销组 1 的表现远好于促销组 2 的结论。从这些评估结果来看,我们可以得出结论,促销组 1 和促销组 3 的表现优于促销组 2,但促销组 1 和促销组 3 之间的差异在统计上并不显著。
- 使用 scipy 计算 t 值和 p 值:另一种计算 t 值和 p 值的方法是使用
scipy包中的stats模块。看看以下代码:
t, p = stats.ttest_ind(
df.loc[df['Promotion'] == 1, 'SalesInThousands'].values,
df.loc[df['Promotion'] == 2, 'SalesInThousands'].values,
equal_var=False
)
如您所见,在这段代码中,scipy包中的stats模块有一个名为ttest_ind的函数。该函数根据数据计算 t 值和 p 值。使用这个函数,我们可以轻松计算 t 值和 p 值,以比较不同促销或测试组的营销表现。无论我们是使用之前手动计算 t 值和 p 值的方法,还是使用scipy包中的ttest_ind函数,得到的结果是一样的。我们用来比较促销组 1 与组 2、促销组 1 与组 3 的 t 值分别为6.4275和1.556,而得到的 p 值分别是4.29e-10和0.121。当然,这些 t 检验结果的解释与之前相同。
我们已经展示了两种计算 t 值和 p 值的方法。虽然使用scipy包的现成解决方案来计算这些值可能看起来更简单,但始终牢记方程是有帮助的。
该 Python 练习的完整代码可以通过以下链接查看:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.12/python/ABTesting.ipynb。
使用 R 评估 A/B 测试结果
在本节中,我们将讨论如何评估 A/B 测试结果,以决定哪种营销策略最有效。到本节结束时,我们将涵盖如何进行统计假设检验并计算统计显著性。我们将主要使用dplyr和ggplot2来分析和可视化数据,并评估 A/B 测试结果。
对于那些希望使用 Python 而非 R 进行本次练习的读者,您可以参考前一节内容。
对于本次练习,我们将使用 IBM Watson Analytics 社区中公开可用的数据集,您可以通过以下链接找到该数据集:www.ibm.com/communities/analytics/watson-analytics-blog/marketing-campaign-eff-usec_-fastf/。您可以通过该链接下载数据,该数据以 XLSX 格式提供,文件名为WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.xlsx。下载数据后,您可以通过运行以下命令将其加载到 RStudio 中:
library(dplyr)
library(readxl)
library(ggplot2)
#### 1\. Load Data ####
df <- read_excel(
path="~/Documents/data-science-for-marketing/ch.12/data/WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.xlsx"
)
df数据框如下所示:

数据集中共有七个变量。您可以在 IBM Watson Analytics 社区页面找到这些变量的描述,我们将在以下内容中再次说明:
-
MarketID:市场的唯一标识符 -
MarketSize:市场区域的销售规模 -
LocationID:商店位置的唯一标识符 -
AgeOfStore:商店的年龄(以年为单位) -
Promotion:测试的三种促销活动之一 -
week:促销活动进行的四个星期之一 -
SalesInThousands:特定LocationID、Promotion和week的销售额
数据分析
让我们深入看看数据。在这一部分,我们将专注于理解销售、市场规模、门店位置和门店年龄的分布,这些数据用于测试不同的促销活动。本分析的目标是确保每个促销组的控制变量和属性呈对称分布,以便不同组之间的促销效果可以进行可比性比较。
不同促销活动的总销售分布可以通过以下代码进行可视化:
salesPerPromo <- df %>%
group_by(Promotion) %>%
summarise(Sales=sum(SalesInThousands))
ggplot(salesPerPromo, aes(x="", y=Sales, fill=Promotion)) +
geom_bar(width=1, stat = "identity", position=position_fill()) +
geom_text(aes(x=1.25, label=Sales), position=position_fill(vjust = 0.5), color='white') +
coord_polar("y") +
ggtitle('sales distribution across different promotions')
从这段代码中可以看出,我们通过Promotion列对数据进行了分组,并通过对SalesInThousands列求和来聚合总销售额。通过饼图,我们可以轻松地看到每个组所占的比例。
生成的饼图如下所示:

从这张饼图中可以清晰地看出,促销组 3 在三组中拥有最大的总销售额。然而,每个促销组大致占据了促销期间总销售额的三分之一。同样,我们也可以通过以下代码可视化每个促销组中不同市场规模的构成:
marketSizePerPromo <- df %>%
group_by(Promotion, MarketSize) %>%
summarise(Count=n())
ggplot(marketSizePerPromo, aes(x=Promotion, y=Count, fill=MarketSize)) +
geom_bar(width=0.5, stat="identity", position="dodge") +
ylab("Count") +
xlab("Promotion") +
ggtitle("breakdowns of market sizes across different promotions") +
theme(plot.title=element_text(hjust=0.5))
条形图如下所示:

如果你认为堆叠条形图更容易查看,你可以使用以下代码将这些数据展示为堆叠条形图:
ggplot(marketSizePerPromo, aes(x=Promotion, y=Count, fill=MarketSize)) +
geom_bar(width=0.5, stat="identity", position="stack") +
ylab("Count") +
xlab("Promotion") +
ggtitle("breakdowns of market sizes across different promotions") +
theme(plot.title=element_text(hjust=0.5))
你可能会注意到,这段代码和之前的代码之间唯一的区别是position="stack"标志在geom_bar函数中的使用。结果如下所示:

从这个条形图中可以看到,中等市场规模在三种促销组中占比最大,而小市场规模占比最小。我们可以通过这个图验证,不同市场规模的构成在三种促销组之间是相似的。
另一个属性AgeOfStore及其在所有不同促销组中的总体分布,可以通过以下代码可视化:
overallAge <- df %>%
group_by(AgeOfStore) %>%
summarise(Count=n())
ggplot(overallAge, aes(x=AgeOfStore, y=Count)) +
geom_bar(width=0.5, stat="identity") +
ylab("Count") +
xlab("Store Age") +
ggtitle("overall distributions of age of store") +
theme(plot.title=element_text(hjust=0.5))
结果如下所示的条形图:

从这个图中可以看到,很多门店的年龄为1岁,并且大多数门店的年龄为10岁或以下。然而,我们更感兴趣的是三种不同促销组中的门店年龄分布是否相似。请看以下代码:
AgePerPromo <- df %>%
group_by(Promotion, AgeOfStore) %>%
summarise(Count=n())
ggplot(AgePerPromo, aes(x=AgeOfStore, y=Count, fill=Promotion)) +
geom_bar(width=0.5, stat="identity", position="dodge2") +
ylab("Count") +
xlab("Store Age") +
ggtitle("distributions of age of store") +
theme(plot.title=element_text(hjust=0.5))
使用这段代码,你将获得如下输出:

三个不同促销组的商店年龄分布似乎彼此对齐,但从这个图表中获取信息相当困难。查看三个促销组的商店年龄汇总统计数据会更加容易。请看以下代码:
tapply(df$AgeOfStore, df$Promotion, summary)
这段代码的输出结果如下所示:

从这个输出结果中,你可能会注意到,通过这些汇总统计数据,理解整体商店年龄分布要容易得多。我们可以看到,三个测试组似乎有相似的商店年龄分布。三个组的商店平均年龄为 8-9 岁,大多数商店年龄在 10-12 岁或更年轻。
通过分析每个促销或测试组的组成方式,我们可以验证商店档案是否彼此相似。这表明样本组得到了良好的控制,A/B 测试结果将是有意义且可信的。
统计假设检验
不同营销策略的 A/B 测试的最终目标是找出哪种策略最有效,并在其他策略中表现最好。如前文简要讨论过的,响应数量较高的策略不一定意味着它优于其他策略。我们将讨论如何使用 t 检验来评估不同营销策略的相对表现,看看哪种策略在统计上显著优于其他策略。
在 R 中,计算 t 检验的 t 值和 p 值有两种方法。本节将演示这两种方法,具体使用哪一种,取决于你自己决定哪种方法更方便。计算 t 检验的 t 值和 p 值的两种方法如下:
- 根据公式计算 t 值和 p 值:第一种方法是手动计算 t 值,使用我们在前一节中学到的公式。如你所记得的,我们需要计算三项内容来得到 t 值:均值、标准差和样本数量。请看以下代码:
promo_1 <- df[which(df$Promotion == 1),]$SalesInThousands
promo_2 <- df[which(df$Promotion == 2),]$SalesInThousands
mean_1 <- mean(promo_1)
mean_2 <- mean(promo_2)
std_1 <- sd(promo_1)
std_2 <- sd(promo_2)
n_1 <- length(promo_1)
n_2 <- length(promo_2)
正如你从这段代码中看到的,你可以通过分别使用mean、sd和length函数轻松计算每个测试组的均值、标准差和样本数。有了这些数据,我们就可以使用之前讨论过的公式来计算 t 值。请看以下代码:
t_val <- (
mean_1 - mean_2
) / sqrt(
(std_1**2/n_1 + std_2**2/n_2)
)
使用这段代码,我们可以计算比较促销 1 和促销 2 表现的 t 值。运行代码得到的 t 值为6.4275。通过这个 t 值,我们可以使用以下代码来获取 p 值:
df_1_2 <- n_1 + n_2 - 2
p_val <- 2 * pt(t_val, df_1_2, lower=FALSE)
如您从这段代码中可以看到,我们首先计算自由度,即两个组样本数量之和减去 2。使用之前计算的 t 值,我们可以通过pt函数计算 p 值。这个函数根据 t 值和自由度返回来自 t 分布的概率值。运行这段代码得到的 p 值是4.143e-10。这是一个非常小的数字,接近 0。正如之前讨论的,接近 0 的 p 值意味着我们有强有力的证据反对零假设,表明两个测试组之间的差异是显著的。
促销组 1 的平均销售额(单位:千)大约是58.1,而促销组 2 的平均销售额大约是47.33。通过我们的 t 检验,已经证明这两个组的市场表现有显著差异,且促销组 1 的表现优于促销组 2。然而,如果我们进行促销组 1 和促销组 3 之间的 t 检验,结果会有所不同。
表面上,促销组 1(58.1)的平均销售额看起来高于促销组 2(55.36)。然而,当我们对这两个组进行 t 检验时,得到的 t 值为1.556,p 值为0.121。计算得到的 p 值远高于0.05,这是一条通常接受的分界线。这表明促销组 1 的市场表现与促销组 2 的市场表现在统计学上没有显著差异。因此,尽管促销组 1 的平均销售额在 A/B 测试中高于促销组 2,但这一差异在统计上并不显著,我们不能得出促销组 1 明显优于促销组 2 的结论。从这些评估结果来看,我们可以得出结论:促销组 1 和促销组 3 的表现优于促销组 2,但促销组 1 和促销组 3 之间的差异在统计上并不显著。
- 使用
t.test计算 t 值和 p 值:计算 t 值和 p 值的另一种方法是使用 R 中的t.test函数。请看以下代码:
# using t.test
t.test(
promo_1,
promo_2
)
如您从这段代码中可以看到,R 具有一个t.test函数,给定数据后,它可以计算 t 值和 p 值。使用这个函数,我们可以轻松地计算 t 值和 p 值,以比较不同促销活动或测试组的市场表现。无论我们使用之前手动从公式中计算 t 值和 p 值的方法,还是使用scipy包中的ttest_ind函数,这两种方法得到的结果都是相同的。比较促销组 1 与促销组 2,以及促销组 1 与促销组 3 时,我们得到的 t 值分别是6.4275和1.556;而得到的 p 值分别是4.29e-10和0.121。当然,这些 t 检验结果的解释与之前相同。
我们展示了两种计算 t 值和 p 值的方法。使用 R 中的t.test函数看起来更简便,但始终记住公式是非常有帮助的。
本 R 练习的完整代码可以在以下链接找到:github.com/yoonhwang/hands-on-data-science-for-marketing/blob/master/ch.12/R/ABTesting.R。
总结
在本章中,我们学习了营销中最常用的测试方法之一,用于做出未来营销策略决策。我们讨论了什么是 A/B 测试,为什么在完全投入某一营销策略之前进行 A/B 测试很重要,以及它如何帮助你以更高效且成本更低的方式实现营销目标。通过一个示例案例,其中你的目标是选择最佳的邮件主题行,我们了解了 A/B 测试的典型流程。A/B 测试不必仅进行一次。当你不断通过实验测试新的想法与当前正在运行的策略或其他想法时,A/B 测试效果最佳。简单来说,每当有新的想法时,都应该进行 A/B 测试。利用我们在本章中学到的 t 检验以及 Python 和 R 工具,你应该能够轻松评估 A/B 测试结果,并识别出哪一策略是获胜的策略。
本章是最后一个包含案例研究和编程练习的技术章节。在下一章中,我们将总结和回顾本书中所有涉及的主题。然后,我们将讨论一些在营销中常见的数据科学和机器学习应用,以及一些未在本书中涉及的 Python 和 R 库,这些库将在你未来的项目中带来益处。
第十三章:接下来会是什么?
我们已经走了很长一段路。我们从数据科学的基础知识及其在营销中的应用开始,逐步探讨了数据科学在营销中的众多使用案例。在此过程中,我们进行了描述性分析,利用数据科学技术分析和可视化数据,以识别模式。我们还进行了解释性分析,使用机器学习模型从数据中得出洞察,例如找出某些客户活动背后的驱动因素,以及客户属性与其行为之间的关联。最后,我们还探讨了预测性分析,通过训练各种机器学习算法,预测客户的某些行为。
本书中所涉及的主题并非琐碎,它们都针对数据科学在营销中的实际应用而设计。每一章的目的都是展示如何在实际的营销用例中运用数据科学和机器学习技术,并指导你如何将讨论过的概念应用到你特定的商业案例中。随着营销分析领域的不断发展和拓展,我们希望通过这一章让你了解一些可能面临的挑战,并介绍一些常用的技术,同时回顾我们在本书中讨论的内容。
在这一章中,我们将涵盖以下主题:
-
本书所涵盖主题的回顾
-
现实生活中的数据科学挑战
-
更多的机器学习模型和软件包
本书所涵盖主题的回顾
本书从一开始就涵盖了大量的内容,包括讨论营销趋势、数据科学和机器学习如何成为构建营销策略的关键组成部分,以及建立各种预测性机器学习模型以实现更高效的营销。在结束本书之前,回顾一下我们已经覆盖的内容并刷新记忆是很有价值的。
营销趋势
正如你可能还记得的那样,我们在第一章《数据科学与营销》中讨论的第一件事是营销的最新趋势。理解并跟上你所在行业中正在发生的趋势非常重要,尤其是在营销领域,对数据驱动的定量营销的需求非常大,而且还需要利用最新、最智能的技术来开发更具成本效益的营销策略。
根据 2018 年 2 月的首席营销官(CMO)调查(www.forbes.com/sites/christinemoorman/2018/02/27/marketing-analytics-and-marketing-technology-trends-to-watch/#4ec8a8431b8a),在过去的五年里,营销分析的依赖度从 30%上升到了 42%。可以很容易观察到的三个主要营销趋势如下:
-
数字营销日益重要:如今,许多营销活动更倾向于通过数字渠道进行,例如搜索引擎、社交媒体、电子邮件和网站,而不再局限于传统的大众媒体,如电视、广播和公交车站的广告牌。随着各种数字营销渠道作为营销选择越来越受欢迎,了解如何在社交网络上进行受众定向(例如 Facebook 和 Instagram),或如何在搜索引擎和视频流媒体服务(如 Google 和 YouTube)上投放广告变得尤为重要。
-
营销分析:营销分析是一种监控和量化过去营销活动结果和表现的方式。在第二章,关键绩效指标和可视化中,我们了解了各种关键绩效指标(KPI),可以用来跟踪和量化不同营销活动的回报。营销分析不仅仅局限于分析 KPI,它还可以应用于产品和客户分析,这些我们在第五章,产品分析,和第七章,客户行为的探索性分析中都有讨论。
-
个性化和精准营销:随着数据科学和机器学习的可访问性变得更加容易,另一个营销趋势应运而生:面向个人的精准营销。通过使用预测分析,我们现在可以预测出单个客户可能喜欢哪些类型的产品,这一点我们在第六章,推荐合适的产品中已经讨论过。我们还看到,如何通过构建预测机器学习模型来针对那些可能流失的客户,这一点在第十一章,留住客户中有详细阐述。由于精准营销能够带来更高的投资回报率(ROI),因此有许多软件即服务(SaaS)公司,例如 Sailthru 和 Oracle,提供个性化和精准营销的平台。
随着新策略和技术的发展,趋势注定会发生变化。我们在本书中讨论的趋势可能在 20-30 年后不再适用。作为一名营销专业人士,跟踪并了解同行的做法,以及正在开发和使用的其他方法或技术以实现更高的投资回报率(ROI)是至关重要的。
数据科学工作流
作为一名营销专业人士或有志于成为营销领域数据科学家的你,可能会发现很难知道从哪里开始进行数据科学项目。在第一章《数据科学与营销》中,我们讨论了一个典型的数据科学项目工作流。在开始未来的营销数据科学项目之前,回顾一下这些步骤是非常有意义的。你应该熟悉以下工作流图:

让我们更详细地讨论这六个步骤:
-
问题定义:任何数据科学和机器学习项目都应该有一个明确的问题定义。你需要深入理解问题本身、项目的范围以及解决方案的思路。这时你需要集思广益,决定使用哪些分析方法和数据科学技术。
-
数据收集:对于任何数据科学项目来说,拥有数据是成功的关键。在这个步骤中,你需要收集所有所需的数据以供数据科学项目使用。通常,你可能需要为内部数据实现数据收集流程,购买第三方数据,或者从不同的网站抓取数据。根据情况的不同,数据收集步骤可能是简单的,也可能是繁琐的。
-
数据准备:在数据收集步骤获得数据后,下一步是清理和准备数据。正如我们在本书中所看到的,我们的编程练习总是从数据清理和准备开始。在数据准备步骤中,我们处理了缺失值、编码了类别变量,或者转换了其他变量,以便机器学习算法能够理解这些数据。
-
数据分析:正如你可能记得的那样,我们在本书的编程练习中通过数据分析步骤发现了有价值的见解。通过分析数据,我们更好地理解了不同变量的总体分布,通常来说,通过不同的图表可视化数据,有助于识别出明显的模式。
-
特征工程:正如我们在整本书中看到并讨论过的那样,针对机器学习模型进行特征工程有许多不同的方式。对于货币值,我们应用了对数变换。在某些情况下,我们对数据进行了归一化,使得变量处于相同的尺度上。我们还使用了独热编码(one-hot encoding)来编码类别变量。特征工程是构建机器学习模型中最重要的步骤之一,因为算法将尝试从这些特征中学习,以正确预测目标。
-
模型构建:典型数据科学工作流程中的最后一步,当然是模型构建。利用前面步骤中清理过的数据和构建的特征,这里是训练机器学习模型的地方。在本书中,我们讨论了如何评估模型。对于分类模型,我们通常使用准确率、精确率、召回率、ROC 曲线和 AUC。对于回归模型,我们使用了 MSE、R²,或者是预测值与实际值的散点图来评估模型。
在我们的编程练习中,我们的工作流程几乎与我们刚才讨论的工作流程相同。当不确定下一步该做什么时,我们希望这个工作流程图能为你提供下一步的一些提示。
机器学习模型
如你所记得,我们在本书中构建了许多机器学习模型。例如,在第八章,《预测营销参与的可能性》中,我们训练了一个随机森林模型来预测每个客户参与营销电话的可能性。在第十一章,《客户保留》中,我们使用了人工神经网络(ANN)模型来识别哪些客户可能会流失。在本节中,我们将回顾本书中使用过的机器学习模型:
- 逻辑回归:在第三章,《营销参与背后的驱动因素》中,我们使用了逻辑回归模型来提取哪些因素使客户更可能参与营销活动。在 Python 中,我们使用了
statsmodels包来构建逻辑回归模型,训练逻辑回归模型的代码如下所示:
import statsmodels.formula.api as sm
logit = sm.Logit(
target_variable,
features
)
logit = logit.fit()
从这个训练好的模型中,我们可以通过运行logit_fit.summary()查看特征与目标变量之间的细节和相关性。另一方面,在 R 中,我们使用了以下命令来训练逻辑回归模型:
logit.fit <- glm(Target ~ ., data = DF, family = binomial)
类似于我们在 Python 中使用summary函数的方式,我们可以运行summary(logit.fit)命令来获取逻辑回归拟合的细节以及特征与目标变量之间的相关性。
- 随机森林:如你所记得,我们在第八章《预测营销参与的可能性》中使用了随机森林算法来预测哪些客户可能会响应营销电话。在 Python 中,我们使用了
scikit-learn包来构建随机森林模型。训练随机森林模型的代码如下所示:
from sklearn.ensemble import RandomForestClassifier
rf_model = RandomForestClassifier()
rf_model.fit(X=x_train, y=y_train)
如你所记得,随机森林算法有许多超参数可以调整。我们已经讨论了如何微调森林中的估计器数量n_estimators,树的最大深度max_depth,以及分支分割所需的最小样本数min_samples_split。另一方面,在 R 中,我们使用了randomForest库来构建随机森林模型。训练随机森林模型的 R 代码如下所示:
library(randomForest)
rfModel <- randomForest(x=trainX, y=factor(trainY))
使用randomForest包,你可以微调超参数。你可以使用ntree来调整森林中树的数量,sampsize来调整训练每棵树时所需的样本大小,以及maxnodes来定义树中终端节点的最大数量。
- 人工神经网络(ANN):如你所记得,在第十一章《保持客户》中,我们使用了 ANN 模型来预测可能会流失的客户。为了构建 ANN 模型,我们在 Python 和 R 中都使用了
keras包。在 Python 中,训练 ANN 模型的代码如下所示:
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(16, input_dim=
len(features), activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam', metrics=['accuracy'])
model.fit(X_train, y_train, epochs=50, batch_size=100)
如你已经知道的,我们首先需要向模型中添加输入层、隐藏层和输出层。然后,我们可以编译并训练 ANN 模型。在 R 中,概念是相同的,但语法稍有不同。使用keras包训练 ANN 模型的 R 代码如下所示:
library(keras)
model <- keras_model_sequential()
model %>%
layer_dense(units = 16, kernel_initializer =
"uniform", activation = 'relu', input_shape=ncol(train)-1) %>%
layer_dense(units = 8, kernel_initializer =
"uniform", activation = 'relu') %>%
layer_dense(units = 1, kernel_initializer =
"uniform", activation = 'sigmoid') %>%
compile(optimizer = 'adam',
loss = 'binary_crossentropy',
metrics = c('accuracy')
)
history <- model %>% fit(
trainX,
trainY,
epochs = 50,
batch_size = 100,
validation_split = 0.2
)
- k-means 聚类:在第十章《基于数据的客户细分》中,我们使用了 k-means 聚类算法来程序化地构建不同的客户细分群体。我们已经看到,分析这些不同客户细分群体的属性可以帮助我们理解客户的不同行为,并找到更好的方法来定位不同的客户群体。在 Python 中,我们可以使用
scikit-learn包来构建 k-means 聚类算法。代码如下所示:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=4)
kmeans = kmeans.fit(data)
如你所记得,你需要定义你希望从数据中构建的聚类数量,使用n_clusters参数。为了获取每条记录的聚类标签和聚类中心,我们可以使用kmeans.labels_和kmeans.cluster_centers_。同样,在 R 中,我们使用了kmeans函数来构建聚类模型,R 代码如下所示:
cluster <- kmeans(data, 4)
为了获取标签和聚类中心,我们可以使用cluster$cluster和cluster$centers。
有了这些算法,我们能够轻松地为营销中的不同使用场景构建各种机器学习模型。希望这些简短的机器学习模型构建语法回顾能帮助你刷新记忆。
现实生活中的数据科学挑战
如果我们能够针对不同的营销场景构建并使用各种机器学习模型,那么应用数据科学和机器学习到营销中看起来会非常光鲜亮丽且完美无缺。然而,通常情况并非如此。实际上,端到端的机器学习模型构建过程往往是繁琐的,过程中会有许多障碍和瓶颈。接下来,我们将讨论一些在现实生活中最常见的数据科学挑战,包括以下内容:
-
数据方面的挑战
-
基础设施方面的挑战
-
选择合适模型的挑战
数据方面的挑战
在营销中使用数据科学和机器学习的一个最大挑战是获取正确的数据。虽然这听起来可能很显而易见,但没有数据,就没有数据科学或机器学习。而且,如果数据的质量不好,那么训练出来的机器学习模型质量也会很差。
在这一部分,我们将讨论一些许多数据科学家在获取正确数据时常遇到的挑战:
-
数据的存在性:有时候你可能会想出一个很好的点子,打算使用数据科学技术来解决你在营销中遇到的某个问题。然而,你需要的数据可能根本不存在。例如,假设你的点子是识别热门网页内容,比如哪些网页是用户浏览最多、点赞最多的。然而,如果没有在你的网站上实现网页浏览跟踪功能,那么你可能没有相关的页面浏览数据。在这种情况下,你需要在你的网站上实现跟踪功能,以便跟踪哪些用户浏览了或点赞了哪些内容。这样,你只能在一定时间之后,收集到足够的数据来进行分析,才能开展你的想法。类似的情况在现实中发生得比较频繁,因此,理解你如何追踪用户活动以及缺少了哪些部分是至关重要的。如果可能的话,当数据在内部不存在时,获取第三方数据也是一个选择。有很多数据供应商出售你可能需要的数据。如果使用第三方数据供应商是一个选择,那么当你的项目没有数据时,这可能是一个好的解决方案。另外,还有很多可以自由使用的公开数据。始终值得看看你需要的数据是否是公开的。
-
数据的可访问性:数据的可访问性可能会成为数据科学项目的障碍,尤其是在大型企业中,某些数据集的访问权限被严格限制,只开放给选定的小组。在这种情况下,即使所需的数据集存在,数据科学家或市场营销专业人士也可能很难甚至无法访问和使用这些数据。数据的来源也可能导致数据可访问性问题。例如,如果数据流入其他应用程序而没有被存储或归档,那么在数据流失之后,它可能会丢失。数据文件的位置也可能成为访问所需数据的障碍。如果数据无法通过网络共享,或者你无法访问数据存储的位置,那么这也可能妨碍你使用数据。这就是为什么数据工程和数据工程师的责任和重要性日益上升的原因。数据工程师与其他数据科学家或软件开发人员合作,专门构建数据管道,帮助有可访问性问题的数据流转到业务的其他部分。如果你遇到数据可访问性的问题,首先找出障碍所在并考虑与数据工程师合作,建立数据管道,以使数据在未来的项目中可访问,至关重要。
-
杂乱的数据:你可以假设,在实际的数据科学项目中,你将面对的大部分数据都是杂乱无章的。数据可能是你无法轻易理解的格式,可能被分割成更小的部分,难以轻松地将它们连接在一起。或者,数据中也可能有太多缺失值或重复记录。数据集的杂乱程度会显著增加你需要花费的时间,以清理原始数据并使其可用。在这些杂乱的数据上进行深入的数据分析,对于使数据在未来步骤中可用至关重要。有时,与数据工程师合作修复数据源中的杂乱问题,进而使未来的数据更加清洁,可能是值得的。
基础设施挑战
在应用数据科学技术并使用机器学习模型进行市场营销项目的过程中,使用的系统基础设施可能会遇到一些挑战。通常,数据集太大,无法容纳在你的笔记本电脑或计算机中。随着数据量的日益增长,将来你很可能会遇到在笔记本电脑上开发数据科学模型的问题,即使你现在没有这个问题。
在进行数据科学项目时,有两件事可能会拖慢你的进度:CPU 或处理能力不足,以及 RAM 或内存不足。如果没有足够的处理能力,你的分析可能会花费很长时间。尤其是在训练机器学习模型时,模型训练可能需要几天、几周甚至几个月。在另一方面,如果内存不足,你可能会在运行分析时遇到Out of Memory错误。例如,基于树的模型,如决策树或随机森林,可能需要大量内存,训练这些模型可能会因为内存不足而在几个小时的训练后失败。
随着云计算的兴起和发展,解决这些问题的方案也随之出现。通过使用如 AWS、Google 或 Microsoft Azure 等云计算服务提供商,你理论上可以获得无限的计算能力和内存。当然,一切都有代价。如果没有正确规划,在这些云平台上运行大型数据科学任务可能会花费一大笔钱。在处理大型数据集时,明智的做法是考虑你需要多少处理能力和内存,才能成功运行任务。
选择合适模型的挑战
为某个数据科学项目选择机器学习算法比听起来更复杂。有些算法更像一个黑箱,你无法知道一个算法是如何做出预测或决策的。例如,理解一个训练好的随机森林模型是如何根据输入做出预测的非常困难。决策是由数百棵不同的决策树做出的,每棵树根据不同的决策标准进行工作,这使得数据科学家很难完全理解输入与输出之间发生了什么。
另一方面,线性模型,如逻辑回归模型,能够准确地告诉我们它们是如何做出决策的。一旦逻辑回归模型被训练,我们就能知道每个特征的系数,并从这些系数中推断出预测的输出是什么。根据你的使用场景,你可能需要这种可解释性,你需要能够向业务合作伙伴解释每个特征如何作用以及如何影响预测输出。通常情况下,更高级的模型更像一个黑箱,你需要在预测准确性和可解释性之间做出权衡。
更多的机器学习模型和包
在本书中,我们主要使用了以下五种最适合我们营销应用场景的机器学习算法:逻辑回归、随机森林、人工神经网络(ANN)、k-means 聚类和协同过滤。然而,还有许多其他现成的机器学习算法,您可能会在未来的数据科学和机器学习项目中找到它们的应用。我们将介绍一些其他常用的机器学习算法,以及在 Python 和 R 中使用的包,另外,我们还会提供更多关于这些算法的资源链接。
在您未来的项目中,您可能需要考虑的其他机器学习算法如下:
-
最近邻:这是一个机器学习算法,用于找到与新数据点最接近的预定义数量的样本。尽管这个算法的概念听起来很简单,但最近邻算法在包括图像识别在内的多个领域已被成功应用。在 Python 的
scikit-learn包中,您可以使用neighbors模块中的KNeighborsClassifier类来构建分类模型,或使用KNeighborsRegressor类来构建回归模型。有关使用的详细信息,我们建议您查看以下文档页面:scikit-learn.org/stable/modules/neighbors.html。另一方面,在 R 中,您可以使用class库中的knn函数。有关该函数的 R 文档,请参考此页面:www.rdocumentation.org/packages/class/versions/7.3-15/topics/knn。 -
支持向量机(SVM):SVM 是另一种您可能会用到的机器学习算法。SVM 算法试图找到一个超平面,将数据最佳地划分为不同的类别或组。它在高维空间中特别有效。在 Python 的
scikit-learn包中,您可以使用SVC和SVR类来实现分类和回归模型。有关文档页面,请访问以下链接:scikit-learn.org/stable/modules/svm.html。在 R 中,e1071库提供了svm函数,您可以用它来训练 SVM 模型。有关其用法的更多文档,请参考此页面:www.rdocumentation.org/packages/e1071/versions/1.7-0.1/topics/svm。 -
梯度提升树(GBT):GBT 是一种基于树的机器学习算法。与随机森林算法不同,GBT 算法按顺序学习和训练每一棵树,每棵树都从前一棵树的错误中学习。因其预测准确性和稳健性而广为人知并频繁使用。在 Python 中,你可以使用
scikit-learn包中的GradientBoostingClassifier类来解决分类问题,使用GradientBoostingRegressor类来解决回归问题。关于scikit-learn中 GBT 的更多细节可以在这里找到:scikit-learn.org/stable/modules/ensemble.html#gradient-tree-boosting。同样,在 R 中,gbm包实现了适用于分类和回归问题的 GBT 算法。你可以使用gbm包中的gbm函数来训练 GBT 模型。更多信息可以在以下链接中找到:www.rdocumentation.org/packages/gbm/versions/2.1.5/topics/gbm
总结
在本章中,我们回顾了本书中讨论的主题。我们简要地概述了在营销行业中可以观察到的趋势,以及数据科学和机器学习在营销中的重要性日益增加。然后,我们回顾了一个典型的数据科学工作流,其中你从问题定义开始,然后进入数据收集、准备和分析,最后进行特征工程和模型构建。在未来的数据科学项目中,值得将我们所看到的工作流图放在脑海中,当你卡住不知道接下来做什么时,可以参考这个图来获取灵感。我们还分享了在处理真实世界数据集时可能遇到的一些挑战。我们讨论的三个主要挑战是数据问题、基础设施问题和选择合适的模型。更具体地说,我们讨论了可解释性与模型准确性之间的权衡。我们提出了一些解决这些挑战的替代方案和解决办法,希望它们能在你面对类似挑战时提供帮助。最后,我们讨论了在未来项目中可能有用的其他常用机器学习模型。我们简要展示了每个模型所需的 Python 和 R 包,以及你可以在哪里找到有关这些模型使用的更多信息。
在本书的 13 章中,我们涵盖了可以应用于营销的各种数据科学和机器学习技术,重点强调实用性。通过本书中多个不同营销场景的示例,我们希望你已经对应用数据科学技术和构建机器学习模型以开发更智能、更高效的营销策略充满信心。我们希望你在本书的学习旅程中收获满满,获得了许多新的、有用的技能。


浙公网安备 33010602011771号