利用数据对抗客户流失-全-

利用数据对抗客户流失(全)

原文:Fighting Churn with Data

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

前言

这本书非常罕见。尽管它主要面向对编码和数据有一定了解的技术人员,但它也恰好清晰、引人入胜,有时甚至(惊讶!)有趣。特别是第一章节,对于任何对成功运营基于订阅的业务感兴趣的人来说,都是必读的。为你的老板买一本吧。

想象一下,所有这些不同的公司都将从这些页面中的敏锐分析中受益。来自全球经济各个领域的,从流媒体服务到工业制造商的数据专家们,都将密切关注卡尔的书。如今,整个世界都在“作为服务”运行:交通、教育、媒体、医疗保健、软件、零售、制造,等等。

所有这些新的数字服务都在产生大量数据,导致巨大的信号与噪声挑战,这就是为什么这本书如此重要的原因。我以此为生,没有人写过这样一本既实用又权威的指南,有效地过滤所有这些信息以减少流失并保持订阅者满意。在运营订阅业务时,流失率是生死攸关的大事!

数千名企业家已经对卡尔·戈尔德的工作非常熟悉。他是《订阅经济指数》的作者,这是一项每半年进行一次的基准研究,反映了数百个分布在各个行业的订阅公司的增长指标。作为 Zuora 的首席数据科学家,卡尔与订阅经济中最及时、最准确的数据集一起工作。他是 Zuora 不仅是一家成功的软件公司,而且是一位受人尊敬的思想领袖的重要原因。

如果你正在阅读这本书,你很快就能为公司成功做出即时的实质性贡献。但正如卡尔在书中广泛讨论的那样,仅仅进行分析是不够的;你还需要能够向整个企业传达你的结果。

所以,无论如何,使用这本书来学习如何进行适当的分析,但也用它来学习如何分享、执行,并在工作中表现出色。这里有大量的例子、案例研究、技巧和基准。我们有多幸运?我们能在订阅经济的早期工作,我们还能读到关于流失的第一本里程碑式的书。

——田志宇,Zuora 创始人兼首席执行官

序言

对于提供在线产品或服务的每家公司来说,客户流失(取消订阅)和参与度都是生死攸关的问题。随着数据科学和分析的广泛应用,现在请数据专家帮忙减少流失已成为标准做法。但理解流失有许多挑战和陷阱,这些在其他数据应用中并不常见,而且到目前为止,还没有一本书能帮助数据专家(或学生)在这个领域入门。

在过去的六年里,我参与了数十个产品和服务的客户流失问题研究,并在一家名为 Zuora 的公司担任首席数据科学家。Zuora 为订阅型公司提供了一个平台,用于管理他们的产品、运营和财务,你将在本书的案例研究中看到一些 Zuora 的客户。在那段时间里,我尝试了不同的方法来分析客户流失并反馈结果给那些正在与客户流失作斗争的公司。事实是,我在早期犯了很多错误,并因此受到启发,写这本书以帮助其他人避免犯我犯过的同样错误。

本书是从数据人员的角度来写的:无论这个人被期望做什么,是数据科学家、数据分析师还是机器学习工程师,或者是其他人,他们可能对数据和代码有些了解,并被要求填补这些角色。本书使用 Python 和 SQL,因此它假设数据人员是程序员。尽管我在书中详细阐述了使用电子表格进行演示和共享数据(正如我在书中所详细说明的),但我并不推荐在电子表格中尝试进行客户流失分析的主要分析任务:许多任务必须按顺序执行,其中一些任务并不简单。此外,还需要多次“清洗和重复”这个过程。这种工作流程非常适合短程序,但在电子表格和图形工具中很难实现。

由于本书是为数据人员编写的,因此它没有详细说明产品和服务可以采取的客户流失减少措施。所以这本书不包含如何进行电子邮件和电话活动、创建客户流失应对手册、设计定价和包装等内容的细节。相反,本书是战略性的,它教授了一种数据驱动的制定对抗客户流失的战斗计划的方法:选择哪些客户流失减少活动去追求,针对哪些客户,以及预期哪些结果。尽管如此,我将在必要时以高层次介绍各种客户流失减少策略,以便理解使用数据的背景。

致谢

有许多人的帮助,我才能为你们创作这本书。

从一开始,我要感谢 Ben Rigby 带我进入第一个客户流失案例研究,以及所有在 Sparked 工作的同事(Chris Purvis、Chris Mielke、Cody Chapman、Collin Wu、David Nevin、Jamie Doornboss、Jeff Nickerson、Jordan Snodgrass、Joseph Pigato、Mark Nelson、Morag Scrimgoeur、Rabih Saliba 和 Val Ornay)以及 Retention Radar 的所有客户。接下来,我要感谢 Tien Tzuo 和 Marc Aronson 带我来到 Zuora,并感谢 Tom Krackeler、Karl Goldstein 以及 Frontleaf 的每一个人(Amanda Olsen、Greg McGuire、Marcelo Rinesi 和 Rachel English)欢迎我加入他们的团队。继续按时间顺序,我还要感谢所有在 Zuora Insights 团队工作或与之合作的人(Azucena Araujo、Caleb Saunders、Gail Jimenez、Jessica Hartog、Kevin Postlewaite、Kevin Suer、Matt Darrow、Michael Lawson、Patrick Kelly、Pushkala Pattabhiraman、Shalaka Sindkar 和 Steve Lotito),我团队中负责客户流失的数据科学家 Dashiell Stander,以及所有 Zuora Insights 的客户。所有这些人都是我学习现在所知客户流失知识的项目的一部分;通过这种方式,他们使我能够为你们写这本书。并且我要感谢 Zuora 的每一个人,无论是帮助推广还是编辑这本书:Amy Konary、Gabe Weisert、Helena Zhang、Jayne Gonzalez、Kasey Radley、Lauren Glish、Peishan Li 和 Sierra Dowling。

接下来是我的出版社 Manning,我要感谢我的第一位收购编辑 Stephen Soehnlen,是他让我加入团队;我的主要开发编辑 Toni Arritola 和我的临时 DE Becky Whitney,他们耐心地教我如何撰写 Manning 风格的书籍;以及我的第二位 AE Michael Stephens,他帮助这本书顺利完成了最后阶段。我还感谢我的技术和代码编辑 Mike Shepard、Charles Feduke 和 Al Krinkler,以及在早期访问期间在 liveBook 论坛上评论的每一个人。我的感谢也延伸到 Deirdre Hiam,我的项目编辑;Pamela Hunt,我的校对员;以及 Frances Buran、Tiffany Taylor 和 Keir Simpson,我的校对员。我还想感谢所有的审稿人:Aditya Kaushik、Al Krinker、Alex Saez、Amlan Chatterjee、Burhan Ul Haq、Emanuele Piccinelli、George Thomas、Graham Wheeler、Jasmine Alkin、Julien Pohie、Kelum Senanayake、Lalit Narayana Surampudi、Malgorzata Rodacka、Michael Jensen、Milorad Imbra、Nahid Alam、Obiamaka Agbaneje、Prabhuti Prakash、Raushan Jha、Simone Sguazza、Stefano Ongarello、Stijn Vanderlooy、Tiklu Ganguly、Vaughn DiMarco 和 Vijay Kodam。你们的建议帮助使这本书更加完善。

特别感谢三家允许我展示他们案例研究数据选择的公司,使书中的材料生动起来:Matt Baker 和 Broadly 的每一个人;Yan Kong 和 Klipfolio 的每一个人;以及 Jonathan Moody、Tyler Cooper 和 Versature 的每一个人。

最后,我要感谢我的妻子 Anna 和孩子们 Clive 和 Skylar,在充满挑战但成果丰硕的时间里,他们给予的支持和耐心。

关于这本书

本书旨在使任何有一定编程和数据背景的人都能对在线产品或服务的客户流失进行颠覆性的分析。如果您在编程和数据分析方面有经验,本书包含了关于客户流失和客户参与度的技巧和窍门,您在其他地方找不到这些内容。

谁应该阅读这本书

本书的主要受众是数据科学家、数据分析师和机器学习工程师。当您被要求帮助理解和对抗在线产品或服务的客户流失时,您会需要这本书。此外,本书非常适合计算机科学和数据科学的学生,或者任何知道如何编码并希望了解典型现代公司数据科学重要领域的人。因为本书从原始数据开始,并为描述的每个分析任务提供了必要的背景,所以它读起来就像是一门完整的手动数据科学课程,以一个一致的项目进行教学:分析小型公司的客户流失。(提供了一个样本数据集。)

话虽如此,本书第三部分第八章和第九章关于预测和机器学习客户流失的内容,对于没有相关经验的人来说可能需要较高的学习曲线。如果您没有这方面的背景,我认为您仍然可以在第八章和第九章中学习到所需的所有知识,但您可能需要额外花时间阅读一些推荐的在线资源。

这本书也应该被非编码的商业专业人士阅读。本书包含了一组独特的关于真实公司客户流失的案例研究观察结果。本书解释了分析客户流失通常可用的数据,将数据转化为可操作情报的实践,以及最典型的发现。本书的一个重点是向商业人士传达数据结果的方法;因此,所有重要的要点都使用通俗易懂的语言(无行话!)进行解释。所以,如果您关心客户流失但不是程序员,您应该浏览本书的要点(明确标注),跳过编码和数学部分。然后,与您的开发者分享这本书,以获得将概念付诸实践的帮助。

本书是如何组织的:一个路线图

本书组织结构旨在逐步引导您通过一个特定过程:在线公司中的数据人员在使用原始数据来对抗客户流失时应该经历的过程。因此,本书最好按顺序,逐章阅读。尽管如此,本书的内容在前两个方面进行了重点介绍:

  • 在每一章中,最重要的主题首先讲解,而关于较少见场景的细节则放在章节的末尾。

  • 最重要的课程在最早章节中介绍,而后续章节的主题更加专业化。

因此,如果您发现自己接近一个似乎与您的场景无关的章节的结尾,跳到下一章通常没有害处。另外,如果您时间紧迫,需要掌握基础知识,可以尝试采取以下简化的阅读路径之一:

  • 要打好基础,请阅读第 1-3 章以及第 4.5 节,这相当于阅读了第一部分的大部分内容(跳过了第四章除了一个节以外的所有内容)。

  • 要获得没有最专业主题的高级课程,请阅读第 1-7 章,这相当于阅读了第一部分和第二部分。

关于这些简化的阅读课程以及如何应用所学知识的更多细节在第十一章中给出。

本书分为三个部分。第一部分解释了什么是流失以及如何衡量它,公司通常有哪些可用数据来帮助他们理解和减少流失,以及如何准备数据使其有用:

  • 第一章是对该领域的总体介绍,包括对案例研究的介绍,强调本书将帮助您为自己的产品和服务实现哪种类型的智能。

  • 第二章解释了如何识别流失客户以及以各种方式衡量流失。SQL 代码从本章开始。

  • 第三章介绍了如何从大多数在线公司收集的关于其用户的事件数据中创建客户指标。

  • 第四章解释了如何将第二章中的流失数据与第三章中的指标结合起来,创建一个用于理解和对抗流失的分析数据集。

第二部分包含了本书的核心技术,致力于理解客户行为与流失和保留之间的关系,并利用这些知识来推动降低流失的策略:

  • 第五章教授了一种群体分析形式,这是理解和解释行为与流失之间关系的主要方法。第五章还包括了许多案例研究示例,代码使用 Python 编写。

  • 第六章探讨了如何处理以不理想方式庞大的数据:大多数公司的数据集都有与同一基本行为密切相关的一系列测量。如何处理这种某种程度上重复的信息非常重要。

  • 第七章回到指标创建的主题,并使用第五章和第六章的信息来设计高级指标,这些指标有助于解释复杂客户行为,如价格敏感性和效率。

第三部分涵盖了使用回归和机器学习进行预测。在减少流失方面,预测的重要性不如拥有一套良好的指标,但它仍然很有用,并且需要一些特殊技术来正确实现:

  • 第八章教授了如何使用回归预测客户流失概率以及如何解释这些预测的结果,包括计算客户终身价值。

  • 第九章是关于机器学习以及衡量和优化流失预测准确性的内容。

  • 第十章涵盖了在客户流失的背景下分析人口统计或企业统计数据,以及寻找最佳客户的相似者。

大多数读者应该从开头开始阅读第一部分和第二部分。如果您在学习和应用这些技术之后需要做出预测或寻找相似客户,请继续阅读第三部分。如果您已经使用高级分析,您可能能够跳过第一部分,直接从第二部分和/或第三部分开始。就本书而言,高级分析意味着您已经拥有一套良好的客户指标,并且可以识别和衡量流失客户。否则,从第一部分开始。

关于代码

本书包含 SQL 和 Python 的代码列表。每个列表代表在准备数据、理解客户流失原因以及减少客户流失过程中的一个小步骤。

  • 书中的所有代码都可以在作者的 GitHub 仓库中找到,仓库地址为github.com/carl24k/fight-churn

  • GitHub 仓库还提供了一个 Python 包装程序,可以运行 SQL 和 Python 列表。这是运行代码的推荐方式。

  • 本书包含可以在模拟的客户数据集上运行的示例,这些数据集设计得类似于小型在线服务(如拥有 10,000 名客户的社交网络)用户生成的数据。

  • GitHub 仓库的 README 文件包含了设置编程环境和运行模拟以创建示例数据的说明。

liveBook 讨论论坛

购买《用数据对抗客户流失:客户流失的科学和策略》包括免费访问由曼宁出版社运行的私人网络论坛,您可以在论坛上对本书发表评论,提出技术问题,并从作者和其他用户那里获得帮助。要访问论坛,请访问livebook.manning.com/#!/book/fighting-chum-with-data/discussion。您还可以在livebook.manning.com/#!/discussion了解更多关于曼宁论坛和行为准则的信息。

曼宁出版社对读者的承诺是提供一个平台,让读者之间以及读者与作者之间可以进行有意义的对话。这并不是对作者参与特定数量活动的承诺,作者在论坛上的贡献是自愿的(且未付费)。我们建议您尝试向他提出一些挑战性的问题,以免他的兴趣转移!只要本书有售,论坛和以前讨论的存档将可通过出版社的网站访问。

其他在线资源

我维护了一个网站,fightchurnwithdata.com,该网站托管了我的博客,并提供链接到其他资源和信息。

关于作者

金 卡尔·戈尔德是 Zuora, Inc.(纽约证券交易所:ZUO)的首席数据科学家。Zuora 是一个全面的订阅管理平台,是一家新近上市、拥有超过 1000 名全球客户的硅谷独角兽公司。Zuora 的客户来自广泛的行业,包括软件(软件即服务,或 SaaS)、媒体、旅游服务、消费品、云服务、物联网(IoT)和电信。Zuora 在所有与订阅和持续收入相关的事物中都被广泛认为是领导者。卡尔于 2015 年加入 Zuora,担任首席数据科学家,并为 Zuora 的订阅分析产品 Zuora Insights 创建了预测分析系统。

关于封面插图

《用数据对抗竞争:保持客户关系的科学与策略》封面上的图像被标注为“苏黎世州的农家妇女”,或“苏黎世州的农夫妻子”。这幅插图由法国艺术家希波利特·莱科特(1781-1857)创作,于 1817 年出版。这幅插图手工绘制并着色精细,生动地提醒我们,仅仅 200 年前,世界的地区、城镇、村庄和邻里之间的文化差异有多么大。他们彼此孤立,说着不同的方言和语言。在街道或乡村,仅凭他们的服饰就能轻易识别他们居住的地方以及他们的职业或社会地位。

自那时以来,着装规范已经发生了变化,而当时区域间的多样性已经变得丰富多彩,现在却难以区分不同大陆的居民,更不用说不同的城镇或地区了。也许我们是以文化多样性换取了更加丰富多彩的个人生活——当然,是更加丰富多彩和快节奏的技术生活。

在难以区分一本计算机书与另一本计算机书的时代,Manning 通过基于两百年前丰富多样的区域生活所设计的封面,庆祝了计算机行业的创新精神和主动性,这些封面通过如这一系列图片等收藏品被重新赋予了生命。

第一部分. 构建你的武器库

在你能够用数据来对抗客户流失之前,你需要准备数据。知识将成为你在对抗客户流失的战斗中的武器,但对于大多数产品和服务来说,原始数据是无用的。尽管你永远不会停止构建和磨练你的数据,但这一部分将教你如何打下基础。本部分的目标是向你展示如何完成一些基础任务:衡量客户流失、为客户创建指标,以及将客户数据组合成数据集以进行进一步分析和与业务同事共享。

第一章包含了关于在线产品和服务行业的背景信息。本章还介绍了公司案例研究,并展示了本书将教你如何创造的结果类型。最后,第一章介绍了将在本书的例子中使用的模拟数据案例研究。

第二章教授使用 SQL 计算客户流失率。这项技能是必要的,这样你就可以在开始对抗客户流失之前正确地衡量它。本章还为本书后面部分的一些高级 SQL 技术奠定了基础。

第三章是关于客户指标计算的第一个章节,这是本书的主要主题之一。正如你将看到的,精心设计的客户指标将是你在对抗客户流失的战斗中使用的最主要的武器。

第四章介绍了数据集的概念,并展示了如何创建数据集以理解你的原始数据中的客户流失。本章结合了第二章和第三章的技术,是第二部分技术的基础。

1 客户流失的世界

什么是客户流失?我们为什么要与之抗争?数据如何帮助?简而言之,你为什么在读这本书?如果你在读这本书,你很可能

  • 数据分析师、数据科学家或机器学习工程师

  • 为一个提供具有重复客户或用户的产品或服务的组织工作

或者,也许你正在学习以获得那些工作之一,或者即使这不是你的工作,你也在填补这样的角色。

这些服务通常通过订阅销售,但你的组织不需要销售订阅来利用这本书。你所需要的是拥有重复客户或用户的产品,以及让他们不断回归的愿望。这本书教授了许多与订阅相关的技术,但在每种情况下,我都展示了相同的概念如何应用于零售和其他非订阅场景。

为了最大限度地利用这本书,你应该具备数据分析编程的背景。如果你是这样的,那么准备好在思考客户和数据的方式上实现突破性的突破。这不是你通常关于数据分析和数据科学的书籍,因为,正如你将学到的,通常的方法不适用于客户流失。但你不需要数据科学学位就能利用这本书:我会回顾足够的基础知识,以便任何有少量编程经验的人都能取得很好的成果。考虑到这一点,我称你,读者,为数据人员,因为这本书是从与数据工作的人的角度来写的。话虽如此,这本书充满了来自现实案例研究的商业洞察,所以即使你不编程,你也可以从阅读这本书中获得很多,然后在将理论付诸实践时将这本书交给你的开发者。这本书提供了关于客户流失和数据的手动方法。

如果你与一个提供现场服务的组织合作,你可能对客户流失了如指掌,并想继续进行防止流失的斗争。但我在此需要为那些刚开始的人提供背景信息;即使你已经了解客户流失,我在我们开始之前也需要澄清一些常见的误解。

本章的组织结构如下:

  • 第 1.1-1.3 节为本书的其余部分提供了背景:什么是客户流失,如何对抗它,为什么对抗客户流失很难,以及为什么我选择了本书的主题。

  • 第 1.4-1.6 节使理论具体化。我描述了这些策略适用的商业环境以及不同公司需要处理的数据。

  • 第 1.7-1.8 节通过审视书中提到的案例研究来使理论生动起来。到本书结束时,你将准备好为自己的产品或服务创造那些类型的成果。

1.1 你为什么在读这本书

任何服务的首要目标是通过营销和销售增加客户或用户数量以实现增长。(这对盈利性和非盈利性企业都适用。)当客户离开时,它会抵消公司的增长,甚至可能导致收缩。

定义:流失——当客户停止使用服务或取消订阅。

大多数服务提供商专注于获取。但为了成功,服务还必须努力最小化流失。如果不持续、主动地解决流失问题,产品或服务就无法达到其全部潜力。

“流失”这个词起源于“流失率”这个术语,它指的是在一定时期内离开客户的比例,我们将在后面更详细地讨论。这导致客户或用户群体随时间变化,这也是为什么“流失”这个词有道理的原因。这个词最初的意思是“激烈地移动”(例如搅拌黄油)。在商业环境中,现在“流失”既可以用作动词——“客户正在流失”或“客户流失了”——也可以用作名词——“客户是流失者”或“报告上季度的流失情况”。

如果你更愿意看到事情积极的一面,那么没有从服务中流失的客户也可以用积极的方式来表述。在这种情况下,人们会谈论客户保留。

定义:客户保留——保持客户使用服务并续订他们的订阅(如果有订阅)。客户保留是流失的对立面。

降低流失率等同于提高客户保留率,这两个术语在很大程度上是可以互换的。当目标被表述为保留更多客户更长时间时,除了挽救那些有流失风险的客户外,还应该关注保持客户的参与度。甚至有可能向最积极参与的客户推销更高级的服务版本,通常价格更高。挽救流失、增加参与度和提升销售都是具有重复客户互动的服务的重要目标。这些目标之间的区别在于关注点的不同,而不是意图上的差异。

吸收:尽管有各种具有重复客户的产品和服务,但使用数据来对抗流失、增加参与度、保留和提升销售的技巧是单一的。

本书为您提供了解决参与度和提升销售以及有效地使用数据在任何类型的重复用户互动场景中对抗流失的技能。

1.1.1 典型的流失场景

如果你在一个创建订阅产品的组织中工作,你的情况可能类似于图 1.1 顶部的示例。关键要素如下:

  • 产品或服务是定期提供和使用的。

  • 客户与产品互动。

  • 客户可能订阅产品或服务。订阅通常(但不总是)需要付费。

  • 订阅可以被终止或取消,这被称为演退。如果没有订阅,当客户停止使用产品时,他们就会发生演退。

  • 客户和订阅(如果有)的时机、价格和付款被记录在数据库中,通常是事务数据库。

  • 当客户使用或与产品或服务互动时,这些事件通常会被跟踪并存储在数据仓库中。

在第 1.4 节中,我们将探讨各种符合这种描述的产品。如果你的场景并不完全像这样,但有一些相似元素,那也是可以的。如第 1.5 节所述,本书中的技术也适用于相关情况。所描述的只是最常见的情况。

在整本书中,我交替使用订阅者、客户和用户这些术语。它们有略微不同的含义,但通常,相同的概念适用(一个订阅者有订阅,一个客户支付,一个用户可能两者都不做,但你仍然希望他们回来)。本书中的技术适用于你与客户关系的任何情况。如果我用一个与你产品不相关的角色举例,那么你应该在心理上替换一个适合你产品的角色。

1.1.2 本书的内容

图 1.1 展示了本书中的技术是如何协同工作的。以下描述了过程中的每一步:

  1. 演退测量——使用订阅数据来识别演退并创建演退指标。演退率是演退指标的一个例子。订阅数据库还允许识别已经演退和续订的客户以及他们确切的续订时间;这些数据对于进一步分析是必需的。

  2. 行为测量——使用事件数据仓库来创建总结每个订阅者相关事件的指标。创建行为指标是一个关键步骤,它允许在数据仓库中的事件得到解释。

  3. 演退分析——使用识别的演退和续订的行为指标。演退分析确定了哪些订阅者行为预示着续订,哪些预示着演退,并且可以为每个订阅者创建演退风险预测。

    在这个阶段,除了订阅者数据库和事件数据仓库之外,还可以将其他信息来源纳入分析(图 1.1 中未显示)。这些包括关于个人消费者(年龄、教育等)的客户或用户的人口统计信息,以及关于企业订阅者(行业、员工数量等)的企业信息统计。

图片

图 1.1 演退数据对抗演退的思维模型

  1. 分段——根据他们的特征和风险,将客户划分为组合了他们的风险水平、他们的行为和任何其他显著特征的组或细分市场。这些细分市场针对干预措施,旨在最大化订阅者生命周期和服务参与度。

  2. 干预——利用从客户流失分析中得出的见解和订阅者细分规则,制定和执行降低客户流失率的干预措施,包括电子邮件营销、电话营销和培训。另一种长期干预措施是对产品或服务进行更改,客户流失分析的信息对此也很有用。

    这是推动期望结果(增长!)的关键步骤。关于干预措施类型的更多信息将在下一节开始,并在本书的其余部分提供,但我只以一般方式介绍干预措施。这就是为什么图 1.1 将干预措施部分地显示在本书的范围之外。

我将在每一章中回溯到图 1.1,以明确说明本章涵盖的过程的哪个部分。

1.2 打击客户流失

写这本书的一个动机来自于尝试降低客户流失率的挑战。话虽如此,我的座右铭是“少承诺,多交付”。我将从降低客户流失率可能有多么困难的角度开始提醒。稍后,我会展示那些不完美的选项仍然可以对你的客户流失率和用户参与度产生重大影响。

1.2.1 降低客户流失率的干预措施

公司使用五种主要策略来降低客户流失率。我在这里总结它们,并在本书的其余部分进行更深入的讨论:

  • 产品改进——产品经理和工程师(对于软件)以及制作人、人才和其他内容创作者(对于媒体)通过改变产品功能或内容来降低客户流失率,从而提高客户获得的效用或享受。这可能包括添加新功能和新内容,或者重新包装以确保用户找到产品或服务的最佳部分。这是降低客户流失率的主要、最直接的方法。

    另一种(软件)方法是增加粘性,这大致意味着修改产品以增加客户转向替代产品的成本。通过提供难以复制或难以从一个系统转移到另一个系统的有价值功能来增加转换成本。

  • 参与活动——营销人员通过将订阅者引导到最受欢迎的内容和功能的大规模通讯来降低客户流失率。这更多的是营销的教育功能,而不是传统营销。记住,订阅者已经可以访问并了解服务的情况,所以承诺不会有帮助。尽管如此,营销人员经常使用这个功能,因为他们擅长制作有效的通讯。

  • 一对一客户互动——客户成功和支持代表通过确保客户采用产品并在他们需要时提供帮助来防止客户流失。而客户支持是传统上帮助客户的部门;在许多组织中,客户成功是一个新的、独立的职能:它被明确设计为更具主动性。客户支持在客户请求帮助时帮助客户;客户成功试图检测需要帮助的客户,并在他们请求之前与他们取得联系。客户成功还负责客户入职并确保他们完成所有必要的操作以充分利用产品。

  • 适当定价——如果服务不是免费的,销售部门(如果有的话)可能是阻止客户流失的最后一招。账户经理可以降低价格或更改订阅条款,管理客户可以降价到更便宜版本的过程。对于没有销售部门的消费产品,拥有类似权限的客户支持代表通常承担这一角色。一种更主动的方法是在一开始就适当调整销售:更好地销售对客户最优的产品版本,而不是尽可能销售最昂贵的版本。这可能会损害每次销售的短期收益;但如果做得正确,它可以减少客户流失并最终提高客户的终身价值。

  • 定位收购——你获取客户的渠道可能产生具有不同保留率和客户流失质量的客户。如果是这样,专注于最好的渠道是有意义的。与其试图让现有客户保持更长时间,不如尝试找到更好的客户来替代他们。这是减少客户流失最不直接的方法,并且受到限制,因为大多数产品无法从其首选渠道获得无限数量的客户。尽管如此,它是一个重要的工具,如果你能利用它,你应该这样做。

所有这些方法在数据驱动下最为有效,这意味着你的组织根据对可用数据的正确解读来选择目标和定制策略。以数据驱动为导向并不要求你拥有一定数量或类型的数据或特定的技术。本书的重点是正确使用可用数据,无论你从事哪种类型的产品,或最终采用何种干预措施来减少客户流失。

吸取经验:在与客户流失作斗争时,以数据驱动为导向意味着基于对可用数据的合理解读来设计产品变更、客户干预和收购策略。

有一点需要注意:干预和服务修改是实现降低客户流失和延长客户保留目标的关键最后一步。然而,如何执行干预超出了本书的范围。与数据分析技术不同,影响订阅者行为的干预措施通常特定于订阅服务的类型。没有一种适合所有情况的干预措施。此外,通常不是数据人员而是其他人(例如产品设计师或营销人员)进行这些干预。

吸收要点:有一些关于减少客户流失干预的一般原则,但这些需要根据每个产品的具体情况来定制。

形成干预的环境不仅包括产品或内容的特定特征,还包括用于实施干预的技术和资源。要充分涵盖干预措施,可能需要另一本书(甚至每个行业都有一本单独的书),而且这将是一本面向商业经理的书,而不是像这本书这样的技术书籍。感兴趣的读者可以在商业部分寻找“客户成功”相关的书籍,或者更具体地说,在产品设计、营销、客户支持等部分下寻找。本书中的工具和技术将彻底改变你在这些领域的每一个产品表现,但不要期望数据人员能做所有的事情!

1.2.2 为什么客户流失难以对抗

现在你已经知道了目标和可用的策略,我将向你介绍你将面临的困难。这些困难促使我在下一节中提出如何使用数据来对抗客户流失的建议。

客户流失难以预防

坏消息是人们(大多数情况下)是理性的且自私的,你的客户已经了解你的产品。为了长期且可靠地减少客户流失,你必须要么提高你产品提供的价值,要么降低成本。回想一下你上次流失的原因,什么会阻止你流失?更好的内容和服务?也许吧。更低的价格?可能。一个改进的用户界面呢?可能不会,除非用户界面一开始就非常糟糕。而且,更频繁的产品相关电子邮件通知会阻止你流失吗?再次,可能不会,除非它们包含你认为有价值的信息。(那个价值词又出现了!)

要减少客户流失,你需要增加价值,但这样做比最初让人们注册要困难得多。因为你的客户已经知道服务是什么样的,营销或销售代表做出的承诺不会引起太大的反响。作为数据人员,你可能会被要求提供“一劳永逸的方法”来减少客户流失,但这里有一个坏消息。

吸收要点:如果“一劳永逸的方法”意味着低成本且可靠的方法,那么没有一劳永逸的方法可以减少客户流失!

用著名创业公司首席执行官和风险投资家本·霍洛维茨的话来说,“对于这个问题,没有银弹,只有铅弹。”他在他的创业回忆录《艰难的事业的艰难之处》(Harper Business,2014 年)中谈到的是在创业公司中提供有竞争力的软件功能,但我认为这同样适用于对抗客户流失。这意味着通常没有快速的“一劳永逸”的解决方案;你持续不断地需要做艰苦的工作,提高你提供给订阅者的价值。我并不是说订阅服务的问题的简单解决方案从不存在。但这类问题通常由产品经理和内容制作人等人员解决。当服务转向数据人员寻求帮助以减少流失时,低垂的果实通常已经被摘取。如果数据人员发现了简单的解决方案,这表明那些创建服务的人没有做好他们的工作。(你可能会找到简单的解决方案,但你不应该。)

当然,另一种选择是降低服务的成本。但降低货币成本对于付费服务来说是一种核选项;收入流失或降级销售可能比完全流失要好,但这仍然是流失。

警告:降价是针对客户流失的“钻石子弹”:它总是有效,但你负担不起。

正如你在下一章中将会看到的,大多数服务将降级销售视为另一种形式的流失。

预测客户流失并不奏效(很好)

现在我们来谈谈数据科学家工具箱中的常用工具:使用机器学习系统进行预测。预测客户流失不奏效有两个原因。首先,也是最重要的,预测流失风险对于大多数减少流失的干预措施没有帮助。因为没有一种适合所有情况的干预措施,流失干预措施需要根据除流失可能性之外的因素进行针对性。这与像垃圾邮件或欺诈检测等其他领域不同,那里的是/否预测足以让你选择行动。如果你将一封电子邮件分类为垃圾邮件,你将其放入垃圾邮件文件夹——完成!但如果你预测一个客户有流失风险,那么你该怎么办呢?

为了减少流失,你可以运行一场电子邮件活动来推广产品功能的用途。但这样的活动应该针对不使用该功能的用户,而不是发送给所有有流失风险的用户。用不适当的内容填满用户的收件箱只会将他们赶走,而不是拯救他们!流失风险预测可以作为客户成功团队选择一对一干预客户的变量,但即便如此,它也只定义目标的一个变量。

这可能会让你感到失望。为了减少客户流失,仅仅部署一个能够赢得数据科学竞赛的人工智能系统是不够的。如果你提供了一份预测客户流失的分析,但没有提供更多可操作的信息,企业将无法轻松地使用它,甚至可能根本无法使用。当我说预测客户流失并不是用数据对抗客户流失的重点时,请相信我。这是我刚开始在这个领域工作时必须学习的重要课程之一。

TAKEAWAY 并不存在一种适合所有情况的客户流失干预措施,因此预测有流失风险的客户对于减少客户流失只有一点帮助。

第二个原因是,即使是最先进的机器学习,也很难以高精度预测客户流失。如果你回想起你上次流失行为时的行为,原因很容易理解:你可能没有充分利用产品,但由于你太忙或者花了一些时间研究替代方案,所以取消订阅花费了很长时间。也许你无法下定决心,或者你忘记了。如果一个预测系统在那个时期观察你的行为,它可能会标记你为有风险,并在你下定决心和找到时间取消订阅的整个过程中都是错误的。流失的时刻受到太多外部因素的影响,难以预测。

除了影响时间的外部因素外,由于效用或享受是一种根本上的主观体验,客户流失也难以预测。即使在相同的情况下,流失的可能性也会因人而异。这对于消费者服务尤为重要,因为客户流失通常最难预测。对于商业产品,客户往往比较理性。但无论是客户还是你,都没有足够的信息来对他们使用产品的成本效益进行精确分析。

最后,与保留相比,客户流失通常较为罕见;对于任何保持运营的付费订阅来说,都必须如此。因为客户流失罕见,无论你如何预测,误报预测都很常见。

考虑到所有这些因素,客户流失预测不可避免地相对粗糙。如果你在一个过去预测客户流失的项目上工作,并发现它很容易以高精度预测,那么你可能是在流失发生得太晚时进行预测,那时它已经不可操作(参见第四章)。在第九章中,我将提供关于客户流失预测准确性的数据,以及构成准确与不准确客户流失预测的因素。现在,我希望我已经给出了足够的轶事证据,以表明高度准确的预测通常是不可能的。

TAKEAWAY 外部因素、主观性、不完整的信息和稀缺性使得准确预测客户流失变得困难。

减少客户流失是团队努力的结果。

防止客户流失最困难的一点是,这并不是任何人的职责,也就是说,没有人或职能可以单独完成这项工作。考虑一下上一节中描述的客户流失减少策略:产品改进、参与活动、客户成功与支持、销售和定价。这些职能涵盖了典型组织超过一半的部门!这意味着客户流失减少将面临沟通和协调的问题。如果放任不管,不同团队可能会采取不协调的方法来降低客户流失率。例如,产品和营销团队决定专注于推动不同功能或内容的利用率,这将是适得其反的。而且,这些方法可能基于有限或错误的信息。因为他们不是数据专家(记住,那是指你),所以不能保证独立团队做出的选择是正确数据驱动的。

要点 多个团队之间的沟通不畅和缺乏协调可能会使客户流失减少的努力陷入风险。

此外,在典型情况下,数据人员无法单方面采取措施来降低客户流失率。降低客户流失率取决于业务不同领域的专家采取的行动,而不是那些处理数据的人员。这些同事各不相同,我暂且称他们为“业务人员”,因为没有更好的称呼。我并不是暗示数据人员不属于业务团队;但数据人员通常没有直接责任对具体的业务成果(如收入)负责,而那些其他角色的人通常有。从数据人员的角度来看,业务是数据分析结果的最终用户。

要点 数据人员的目的是让业务人员在减少客户流失的干预措施上更加有效。

1.2.3 优秀的客户指标:对抗客户流失的武器

客户流失难以对抗,因为业务的不同部分以不同的方式负责减少客户流失。所有这些团队都有不同的工具和方法,他们可能不会在情况和策略上达成一致。此外,每种减少客户流失的方法都需要企业针对最有可能做出反应的客户进行干预。因此,为了对抗客户流失,企业需要一套共享的事实或规则,以理解客户及其与产品的互动。

将数据变成对抗客户流失的武器的最佳方式是利用数据产生有效的客户测量,并将这些测量结果交给业务中的客户流失斗士。正如我们将在第三章中全面探讨的那样,客户的测量被称为指标。

定义 客户指标——对每位客户单独进行的任何测量。

以一个简单的例子来说,一个指标可以是这样:每个月每位客户使用软件功能或观看某个系列节目的次数。但并非每个指标都适合用于对抗客户流失。

要点:为了使客户指标在对抗客户流失方面变得出色,它应该具备以下特点:(1)易于业务理解;(2)与客户流失和保留明显相关,因此可以清楚地知道一个健康的客户是什么样的;(3)以有助于针对干预措施提高参与度的客户细分方式;(4)对业务的多项功能(产品、营销、支持等)都有用。

继续使用简单的例子,你可能会发现像“每月使用(查看)产品功能超过五次的客户,其流失率是每月只使用一次或更少的客户的一半。”这样的业务规则。使用或查看更多特定功能的事情并不复杂,关于流失的发现使得一个健康的客户显而易见。每个业务部分都可以用这样的事实以不同的方式使用。产品创造者会知道该功能正在提供价值,可以复制它或使其更容易找到。营销可以设计一场活动来吸引用户使用该功能。而当客户成功/支持人员与客户交谈时,他们可以询问客户是否在使用该功能,如果客户没有使用,可以鼓励他们尝试使用。

这听起来可能很简单,但提出看似简单却具有可操作性的发现实际上比听起来更难。有些发现可能会误导(例如,参见 1.8.2 节中的例子),更常见的问题是存在太多的潜在指标和规则。那么挑战就在于为业务找到一组简洁的指标。所以,仅仅因为你在寻找易于理解的事实和规则,并不意味着你的工作会容易!

我是通过经历那些指标并不出色的情境,得出了对提供优秀客户指标的重视。当我刚开始时,情况通常是这样的:在我们开始分析客户流失之前,公司选择了客户指标,并将它们用作预测模型的输入。我经常发现客户指标设计得不好,并不适合预测和理解客户流失,因此预测模型的结果不佳,没有人使用它。但他们继续使用这些平庸的指标,因为他们需要用于细分的测量。那时,我脑海中突然有了灵感。数据分析应该专注于确保指标对客户流失非常出色,因为这就是人们将用来完成他们工作的事情。我知道作为一个数据专家,我可以比业务人士更好地创建客户指标——你也可以。

我将教授你的方法类似于传统的统计或科学分析。受过统计训练的数据人员可能会发现这种方法比计算机科学家更自然。过程是迭代测试不同的客户指标,分析它们与客户流失以及彼此之间的关系,并评估它们的可解释性和在细分和干预方面的有用性。你找到最佳的指标集,这就是向业务交付的主要成果。你也将处于良好的位置来运行预测模型,如后续章节所述的附加用例。

取得成果:数据分析项目向业务交付的主要成果是一套客户指标。

1.3 为什么这本书不同

到现在为止,你可能已经怀疑这不是你通常关于数据科学或数据分析的书。我现在会解释这些差异,让你知道可以期待什么。

1.3.1 实用且深入

表 1.1 总结了本书与该领域典型书籍的不同之处。

表 1.1 本书与其他数据分析书籍的比较

本书 大多数关于数据分析的书籍
从开始到结束的一个场景,包括应用 涉及许多不同的场景,但省略了实际细节
专注于理解数据和设计指标(即特征工程) 专注于算法
通过迭代过程从原始数据创建数据集 使用固定的基准数据集
强调可解释性、简洁性和敏捷性 强调最大化准确性或其他技术指标

本书专注于一件事:使用数据来对抗客户流失的实用方法。相比之下,大多数数据分析书籍涵盖了广泛的使用案例,强调统计和计算机科学算法。尽管如此,第 1.5 节解释说,有一系列与客户流失相似的使用案例,并且可以使用相同的技巧。但本书专注于客户流失,以便学习变得容易;一旦你成为专家,修改技巧到相关场景就不会很难。

在典型的数据分析书籍中,重点是教授算法。用于展示技术的数据集是已给出的,被称为基准数据集。本书从原始数据开始,创建一个分析数据集,这是工作的很大一部分。我将解释几个统计/机器学习算法,达到可以使用它们的水平,但不会教授很多理论。相反,重点是教授整个过程,包括在现实世界场景中应用结果。

现实世界中的数据问题与训练数据的不同之处在于,在现实世界中,工作永远不会结束:一旦一项关于客户流失的分析完成,就会创建新的产品功能或内容,需要重新分析。或者,可能出现一种全新的数据来增强原始数据。此外,商业环境也在不断变化,如竞争和不断变化的经济状况。这些变化可能需要重新分析,即使产品没有修改。

要在这个环境中取得成功,使用数据的过程必须是简洁和敏捷的。简洁意味着使用最少的数量数据以及最少的分析步骤来完成工作。敏捷意味着快速有效地应对变化。实现简洁和敏捷具有重要的后果。我将在整本书中回到这些主题,但就目前而言,这里有两大要点。

要点:你的目标是向商业人士提供可操作的知识。倾听他们的意见,并首先尝试回答他们的问题。不要对每个假设或评估指标进行详尽的测试。

要点:编写代码来自动化这个过程。这使得适应不可避免的修正或变更请求变得容易得多。

由于需要与商业人士沟通以应对客户流失,当数据分析提供可操作的知识时,数据分析师的分析达到最大影响。因此,本书将告诉你如何以非技术人员能够理解的方式传达分析结果。这意味着使用相对简单的可视化,并避免使用技术术语,而是使用通用语言。我甚至建议在解释分析时进行简化(但在分析过程中不要走捷径)。在整个书中,我给出了我发现的有效的解释策略的具体例子。

数据和指标设计(又称特征工程)的重要性

许多在数据科学或数据分析课程中学习的人,当他们开始在一家公司或真实的研究项目中工作时,会感到惊讶:他们需要的不是在 CSV 文件或数据库表中等待他们的数据,而是需要通过算法运行的数据。大多数现实世界项目涉及从多个数据库或系统中定位和合并数据,这个过程是项目工作中很大的一部分(通常超过一半)。在学术数据科学中,这个过程被称为特征工程,但鉴于需要与商业人士沟通,我将坚持使用“指标设计”这个术语(更多关于这一点在第三章中介绍)。

另一个常见的误解是,算法或分析方法的选取是影响模型准确性的最重要因素。允许数据进行分析的总结指标(在学术界称为数据特征)是整个过程中最重要的部分,即使在关注准确性的情况下也是如此。

一些学术倾向的数据人员可能会认为准备数据既低效又不值得他们去做,将其视为苦差事。但在准备数据时,必须做出许多小决策,其中一些决策可能会对结果产生巨大影响,尤其是如果这些决策没有正确做出,或者不是数据人员期望的方式。

警告:将数据准备任务委托给另一个团队风险极高。

最后,我想补充说,根据我的经验,理解和设计指标是整个过程中最有乐趣、最具创造性的部分!在我看来,这确实是数据科学中的“真正科学”:通过实验从你的数据中学习,而不仅仅是运行别人的算法。在我的书中,这绝不是苦差事!

1.3.2 模拟案例研究

为了实现其目标,本书围绕一个深入的案例研究展开。你将从客户和事件数据的数据库开始,并使用它来完成实际客户流失斗争过程中的所有步骤:计算客户流失率,通过创建和分析指标来理解客户行为,发现客户行为与流失之间的关系,并利用所有这些知识来设计可用于针对性干预的细分市场(或者如果真的是你的公司,可以用于针对性干预)。

由于客户数据敏感,我无法分发真实的客户和产品数据以支持你的学习。相反,本书包含一个高度逼真的客户模拟,你可以使用它来生成自己的数据。我还会经常将案例研究与真实公司(在第 1.7 节中描述)进行比较。你会发现你从模拟中获得的结果与实际情况惊人地相似。模拟的代码以及设置和运行它的说明可以在本书的网站上找到(www.manning.com/books/fighting-churn-with-data)和本书的 GitHub 仓库中(github.com/carl24k/fight-churn)。

注意:设置开发环境、运行模拟和代码的最新说明始终位于本书 GitHub 仓库根目录下的 README 页面。设置说明不包括在本书中。

我将在介绍真实案例研究之后,在第 1.7.4 节中告诉你更多关于你将要模拟和研究的公司的信息。但首先,为了使案例研究具有背景,我想更广泛地谈谈本书适用的公司类型和数据。

1.4 具有重复用户交互的产品

对于那些还不熟悉这个领域的人来说,我将总结当前具有重复用户交互或订阅的产品世界的情况。订阅和重复付款商业模式绝对不是新事物;订阅新闻服务至少自 16 世纪以来就存在了,而保险费的重复支付是在 17 世纪确立的。20 世纪见证了第二次工业革命带来的各种公用事业重复付款服务的普及:首先是水、煤气、电力和电话服务;而在 20 世纪末,有线电视、移动电话服务和当然还有互联网服务。这些服务都是基于消费者和提供商之间的重复付款关系。所有这些关系都可以被称为订阅。

当我们想到订阅时,我们通常想到的是定期支付的固定费用,尽管订阅服务可以在消费者和提供商之间收取三种类型的付款:

  • 重复付款——每个服务期间相同金额的固定付款

  • 基于使用量的付款——根据某些计量单位支付所使用的服务费用

  • 一次性付款——通常包括设置费用,但也包括对服务或一次性(应用内)购买的临时(非重复)升级费用

这些服务有时是预先付款(每个服务期开始时)和有时是事后付款(服务提供后)。但所有这些服务都有一个共同点,那就是消费者和服务提供商之间的持续关系。

21 世纪见证了订阅服务的新一轮爆炸式增长,这些服务大多通过互联网提供,并使用云计算平台创建(或至少管理)。这些新产品的其中一个重要特征是它们通常具有更多的选择性。虽然 20 世纪的重复付款服务选择很少或没有(许多公用事业仍然是受监管的垄断),但在 21 世纪的订阅方面,我们有各种各样的选择。通常有可供选择的其他服务,比如从一家流媒体音乐服务切换到另一家。此外,还可以有实现相同目的的替代手段,例如企业开发自己的软件而不是通过订阅购买。最后,许多现代订阅是我们可以不用的东西(当你可以在商店购物时,你还需要那个食品配送订阅吗?)。

在以下章节中,我将描述今天存在的各种订阅方式的广泛多样性。我们将在本书的其余部分考虑这些商业模式。

1.4.1 付费消费者产品

大多数人都熟悉消费者订阅服务。这些产品通常每月费用适中(低于一顿昂贵的餐食),价格通常以“99”结尾(9.99、49.99、99.99 等)。如今,大多数消费者通过这种方式获得很多娱乐,并且出现了各种额外的产品:

  • 桌面软件(文字处理器、电子表格、图形创建工具、防病毒程序等),以前通过永久许可证销售

  • 新类型的软件即服务(身份盗窃保护、云存储、家庭安全视频监控等)

  • 物理产品盒(剃须和个人护理用品、餐食、手工艺品、礼物等)

  • 个人服装商品(包括服装和手表)

这些产品通常被称为企业对消费者服务(B2C)。另一个相关术语是直接面向消费者(D2C),它通常指的是在不捆绑其他有线或卫星娱乐套餐的情况下向消费者销售视频娱乐。(在撰写本文时,许多电视频道只能通过有线或卫星订阅获得,但流媒体服务是 D2C。)

1.4.2 企业对企业服务

从市场价值来看,企业订阅服务是一个巨大的市场细分。这些通常被称为企业对企业(B2B)产品。从 Salesforce 开始,它在 2000 年代创建了第一个基于云的客户关系管理(CRM)系统,这个市场已经爆炸式增长。现在,几乎所有面向企业的新的软件产品都提供作为服务(软件即服务,简称 SaaS)。同时,由于云部署和升级的效率,现有的本地软件产品已经开始转向新的模式。这些产品因为商业产品不回避复杂的合同(实际上,它们似乎更倾向于复杂的合同)而展现出令人眼花缭乱的支付条款。最知名的 B2B 订阅类别包括

  • CRM —SaaS 用于协调销售团队和市场营销互动

  • 企业资源规划(ERP)—SaaS 用于会计、物流和生产

  • 订阅式业务管理(SBM)—SaaS 用于管理订阅(也可按订阅方式销售)

  • 人力资源管理(HR)—SaaS 用于管理员工,包括招聘

  • 支持问题跟踪系统(ITS)—SaaS 用于跟踪和管理客户支持互动或工单

  • 桌面软件—电子表格、文字处理器、电子邮件、插图程序等,通过多用户订阅销售

  • 云计算资源—云服务器、存储、数据库和内容分发网络(CDNs)

  • 商业智能(BI)—用于查询和可视化各种类型数据的工具

  • 安全产品—病毒防护、密码管理器、网络监控以及其他确保个人计算机和公司系统安全的工具

这个简短的列表并不能公正地反映当今企业使用的各种 SaaS 产品。几乎每家现代 SaaS 公司都依赖于一系列其他 SaaS 产品来提供运行运营非核心部分的软件。典型的 SaaS 公司仅使用内部软件工程师来创建服务中独特的东西;几乎运营的每个其他部分都是使用由其他 SaaS 企业提供的软件来运行的,并且基于订阅付费。这包括之前列出的标准应用程序,以及为特定垂直领域的公司设计的 SaaS 产品:

  • 大多数在线新闻或媒体服务使用软件来管理评论和讨论,但这种服务并非由这些公司创建——他们专注于创造内容。

  • 支持特定垂直领域的信息服务过去只在金融和法律行业很常见,但现在房地产、能源、制造和农业等行业也有垂直特定的信息服务。

  • 服务可用于管理特定行业或垂直领域的发票和应付账款功能。

这些只是来自一个非常大且多样化的类别中的几个例子。

1.4.3 广告支持的媒体和应用程序

自从互联网的早期,最常见的商业模式(如果不是最常见的)就是提供免费媒体内容(阅读材料、视频、音乐等),但在之前、期间和/或之后显示广告。使用此类产品不需要正式订阅,因此这种场景与图 1.1 中的场景有一些重要区别。没有订阅数据库,尽管另一个数据库可能跟踪用户配置文件信息,用于选择广告。虽然从消费者那里获取价值的方式不同,但这些服务与常规付费消费者服务具有相同的自由裁量性质,并且对流失有同样的明显担忧:服务希望他们的用户(广告观看者)继续回来。

正如我将解释的,没有订阅的情况下,流失可以简单地定义为客户长时间消失。这类产品也可以从本书中教授的流失分析技术中受益,只要某种形式的事件数据仓库能够跟踪用户在多个会话中的活动。

吸取经验:在多个会话中跟踪用户事件是使用本书中技术的一个基本要求。

1.4.4 消费者信息订阅

另一种新颖的订阅类型是在订阅中提供免费信息,例如 YouTube 订阅或电子邮件更新。这是广告支持媒体模型的变体,其中内容仍然是广告支持的,但提供商创建了通过免费订阅来升级体验的选项。这种订阅通常意味着消费者同意通过推送到他们的收件箱或视频流的新信息来接收产品更新。在这种情况下,关系的结构符合图 1.1 中的典型场景,但订阅没有固定支付;相反,广告收入来自事件。

1.4.5 免费增值商业模式

免费增值提供是指任何具有免费版本和付费或高级服务级别的订阅服务。对于某些服务,免费版本可能是时间限制的,给用户一个“先试后买”的机会。对于其他服务,可能可以永久使用免费服务级别。另一种常见类型是免费版本带有广告(如 1.4.3 节所述)和没有广告的付费版本。

就流失而言,免费增值服务就像没有免费级别的服务一样,但有两种不同的流失类型:来自高级服务的流失和来自服务免费级别的流失。免费级别使用非订阅、基于活动的流失分析技术进行分析。还有从免费服务级别到付费服务级别的过渡,称为免费试用转换,可以使用与流失相同的分析技术(见 1.5 节)。

1.4.6 应用内购买模型

免费增值模型的一种变体是那些可以永久免费使用(或只需支付相对较小的一次性费用即可永久使用)的产品,但提供多种方式在使用过程中通过一次性支付来升级体验。这正在成为在线游戏中的主导模式。玩游戏是免费的,但如果你想为你的角色获得一个酷炫的外观、更好的武器或快速提升到更高等级的捷径,那将需要你付费!

这是一种符合图 1.1 模型但无需订阅的场景。相反,一个事务型数据库跟踪一次性购买(以及如果存在初始费用,则应用程序的原价购买)。流失可以定义为用户(客户)变得不活跃;只要用户行为可以在使用会话之间追踪,本书中提到的所有常规技术都适用。

1.5 非订阅流失场景

本书重点关注来自订阅的显性流失,但相同的方法适用于各种其他常见商业场景。我将在下面简要解释这些场景,但为了简单起见,我将主要教授针对订阅流失的技术。在你掌握这些技术之后,你应该能够轻松地将所学知识应用于其他场景。

1.5.1 不活跃作为流失

对于免费层级的免费增值服务,用户的不活跃状态可以被视为流失,同样的情况也适用于没有明确订阅的 APP 或广告支持的产品。您可以选择一个时间窗口,在这个时间窗口内,用户必须与服务互动(例如一个月或三个月)。那么,流失就被定义为长时间不活跃的用户。与图 1.1 中的典型场景相比,没有订阅交易数据库,只有事件数据仓库。但只要满足一个关键要求:用户行为必须在不同的活动或会话中持续跟踪。

1.5.2 免费试用转换

如 1.4.5 节所述,免费增值模型提供免费和付费两个级别的服务。由于免费级别和付费订阅级别都有相似的行为数据,因此分析从免费服务到付费服务的转换与从付费服务到流失的分析一样容易。这本质上与流失相反,但场景看起来与图 1.1 中的相似,因此可以使用相同的分析技术。

1.5.3 升级/降级销售

添加新服务或升级到更高成本的计划被称为升级销售,而移除服务或升级到更低成本的计划(不导致流失)被称为降级销售。与流失一样,行为数据和用户特征可以分析以确定最可能导致升级销售或降级销售的场景。然而,额外的挑战是可能存在不同的升级/降级销售选项需要分析。在实践中,然而,对于升级/降级销售进行额外的分析通常是多余的。大多数情况下,最有可能购买某种形式升级销售的客户是那些通过流失分析确定的最佳客户,而那些最有可能转移到更低成本计划(降级销售)的客户也最有可能流失。

升级销售也常常与跨越特定的使用阈值相关,例如企业软件许可证销售的用户数量(座位数)或移动电话服务中的数据量(千兆字节)。在这种情况下,深入分析是不必要的:只需查看一个相关的使用指标,你就能知道谁是升级销售的候选人。

1.5.4 其他是/否(二元)客户预测

客户退订是一个存在是或否答案的预测问题的例子。在统计学和数据科学中,这被称为二元结果,指的是两种可能性。本书中的方法可以不加修改地应用于几乎任何涉及预测未来客户状态且可以表述为是/否问题的情境。例如,任何类型的保险政策是否会导致索赔(医疗、汽车等),以及借款人是否会违约。这里的一个注意事项是,本书倾向于关注罕见的结果(这也适用于保险索赔和贷款违约)。如果是/否结果同样常见,可以使用略微不同的方法,主要是在准确度测量方面(见第九章)。

1.5.5 客户活动预测

假设一个订阅者继续使用服务,分析该订阅者未来可能的行为是合理的。这对于产生收入的用户行为尤为重要,例如,订阅者将使用预付费功能的程度;或者用户将消费多少内容,以及为广告支持服务产生的广告收入。

本书中的大多数技术也适用于此类分析,但有一个主要的前提是,对于退订,我们使用建模两种状态或二元结果(退订与继续)的技术,而对于活动预测,我们使用建模数值结果的技术。如果你已经在数据科学或统计学方面受过训练,那么将本书中的方法适应于预测实值并不太难,但这种扩展不会在本书中涵盖。

1.5.6 非退订用例

一个与退订非常不同且本书不会涵盖的用例是产品推荐系统。这些是具有广泛产品或内容选择场景,目标是根据之前的选项推荐最合适的产品的场景。然而,本书中的技术也适用于客户在基本、标准和高级订阅计划等少量产品中进行选择的情况。对于物理或媒体产品的庞大目录,你应该查阅关于推荐系统专门书籍或资源。

1.6 客户行为数据

考虑到包括周期性客户/用户交互在内的产品和服务种类繁多,这些交互可能采取的形式也更为多样。一个章节中的一节不可能列出所有可能性的详尽列表,所以这只是一个介绍。几乎在软件中发生或可以被软件跟踪的任何事情都可以被认为是交互或事件。

定义:事件——在对抗退订的背景下,数据仓库跟踪的任何用户交互或结果。事件是带时间戳的,并涉及单个账户或用户。

1.6.1 常见产品类别的客户事件

我将通过列出常见产品类别的典型客户事件来具体讨论数据。它们共同的特点是它们都指的是在任何时间可能发生给单个客户或用户的个别事件。对于某些事件,事件在特定时间对特定用户发生的事实可能就是唯一可用的信息;对于其他事件,事件的相关细节会与事件一起跟踪。以下是一些典型的客户事件:

  • 软件——指的是任何软件产品(SaaS),也可以指具有软件界面的其他类型的产品:

    • 登录——登录应用程序通常被视为一个事件。

    • 用户界面(UI)交互——用户界面中的几乎所有点击或输入都可以被视为一个事件。事件通常包括对 UI 部分的详细引用。

    • 文档/记录操作——包括创建、编辑、更新和删除记录或文档,这些在应用程序数据库中跟踪。事件可以包括有关文档类型和具体文档字段的详细信息,如果适用。

    • 批量处理——许多应用程序包括用户定期运行的过程。每个处理的项都可以被视为一个事件,或者批量作业本身也可以是一个事件。

  • 社交网络——专门的社会网络以及具有社交功能的产品:

    • 点赞——表示用户喜欢他们看到的内容是与社会网络互动中最普遍的一种。

    • 发布——分享网络支持的任何类型媒体。

    • 分享——通常是一个专门的文章,指的是另一个用户的帖子。

    • 连接——与其他用户建立联系通常是参与的最重要形式,因为它丰富了未来的用户体验。

  • 电信(简称 telco)——提供移动或固定位置电信产品和服务的服务提供商:

    • 通话——进行语音或视频通话。通常,通话事件会跟踪通话时长和通话类型。

    • 数据——数据使用通常与数据量一起跟踪。

    • 应用程序——使用应用程序,特别是使用哪个应用程序。

    • 添加/移除设备——更新设备是使用服务生命周期中的一个重要事件。

  • 物联网(IoT)——由连接的设备组成的产品:

    • 地理空间——关于设备移动的事件,包括位置和速度。

    • 传感器——传感器接收到的数据可以包括来自传感器的几乎所有类型的信息。

    • 设备——与传感器类似,设备活动可以指几乎任何类型的活动,包括特定于设备的信息。

  • 媒体——任何提供任何类型预先录制或实时流媒体的产品,包括视频、音频、图像和文本,不仅用于娱乐,也用于教育和专业培训:

    • 查看/播放——在特定媒体服务上播放媒体是最常见的事件,通常包括播放了什么媒体以及播放了多少的详细信息。这包括阅读新闻和书籍的文章或页面。

    • 浏览时间 —查看页面或其他内容并明确捕捉所花费的时间。

    • 点赞 —通过点赞(或给出差评)来表示媒体偏好是一个重要的媒体事件。

  • 游戏 —任何游戏产品:

    • 游戏 —在玩游戏时通常会生成许多事件,包括确切玩过的部分、持续时间等信息。

    • 等级和分数 —许多游戏包括分数或其他形式的“等级”,达到这些等级通常会被作为一个事件进行跟踪。

  • 零售 —允许购买单独选择商品的购物网站或服务,这些商品可以是实物或数字产品:

    • 查看 —查看产品可以作为一个事件进行跟踪,包括查看的产品详情。

    • 搜索 —搜索产品目录可以作为一个事件进行跟踪,包括用于搜索的关键词。

    • 添加到购物车 —将产品添加到购物车可以作为一个事件进行跟踪。

    • 退货 —退货也被视为一个事件,并包括产品详情。

  • 盒装配送 —定期向客户配送精选(通常是实物)产品的服务:

    • 配送 —每个盒子的成功配送是一个重要事件,以及配送过程中出现的任何失败或困难。跟踪的信息通常包括盒子的类型(当有多个时)和到达所需的时间。

    • 退货 —许多盒装服务允许退货,这是一个重要的用户不满意的事件。

    • 零售 —大多数盒装配送产品也包括零售选项,因此所有零售事件也都相关。

一些事件类别会跨越各种产品和服务。以下是一些例子:

  • 金融 —所有非免费产品和服务都会发生金融事件:

    • 定期付款 —这些事件很常见,通常被视为非事件,但它们会被跟踪,并且当它们不发生时,通常比发生时更重要。

    • 非定期购买 —零售网站上所有的销售,包括在游戏或订阅服务中进行的任何额外或应用内购买。

    • 超额费用 —当用户超过阈值时产生的费用。

  • 支持 —无论客户通过电话、电子邮件、聊天还是搜索支持/帮助文档来寻求帮助时:

    • 工单 —支持工单或案例,通常带有开启和关闭时间以及广泛的详细信息。

    • 电话/电子邮件/聊天 —客户与支持代表之间的任何互动,可能包括互动的全文。

    • 文档 —使用在线文档资源可以作为一个 UI 事件进行跟踪。

  • 计划 —任何具有实际订阅的产品或服务的订阅计划相关事件:

    • 计划变更 —计划变更的时间/日期可以作为一个事件进行跟踪。

    • 账单变更 —更改信用卡或其他支付方式的事件,以及切换账单详情,如月度与年度账单。

    • 取消——是的,取消产品或服务也被视为一个事件。但请注意,当我们谈论将取消作为事件时,我们是在谈论取消更改被输入系统的时间,而不是服务合同结束的时间,即使用户还有剩余时间。因此,取消事件并不等同于流失。流失发生在订阅者完成当前期限而没有注册新期限时,通常允许一个短暂的宽限期。因此,取消事件并不一定意味着将发生流失,因为客户仍然可能在很长时间过去之前重新签约。这种情况经常发生,因此,取消行为应被视为一个表明可能发生流失的事件,但这仍然不是确定的结论。

注意,许多产品或服务包含来自多个类别的活动;例如,任何具有软件用户界面的产品都会收集软件事件,而许多产品即使不是专门或主要的社会网络产品,也具有社交网络功能(例如,游戏)。

1.6.2 最重要的活动

在讨论了这么多事件类型之后,你可能想知道哪些类型是最重要的,这是有充分理由的。在如此多的多样性中,保持专注是很重要的。对此没有硬性规则,但我可以提供一些一般性指南,以为本书的其余部分提供讨论框架。明确地说,确定哪些事件是最重要的,是本书进行分析的主要目的之一,因为这对每个产品或服务都是不同的。这只是一个预览,我们将在稍后深入探讨。

核心观点是,最重要的活动是那些最接近客户实现服务目标或目的的活动。这听起来可能有些模糊,但一些例子应该能使其变得清晰:

  • 软件产品通常有一个目标(例如,编写文档)。因此,创建文档比仅仅登录更重要。一般来说,登录事件远不如直接参与实现产品目标的事件重要。

  • 许多 B2B 软件产品用于赚钱,因此,如果有可能衡量从事件中可能赚取多少收入,那么这些事件就是最重要的。例如,如果一个产品是用于跟踪销售的 CRM 系统,那么关闭的交易及其价值可能是最重要的事件类型。通常,产品与客户赚钱的业务并不那么接近,但你仍然应该关注与商业成功相关的事件。例如,如果产品是电子邮件营销工具,打开的电子邮件是重要的事件。

  • 对于大多数媒体服务来说,目的是享受媒体,因此播放内容通常很重要,更具体地说,是享受内容的指标,如观看整个内容、点赞或分享。但你永远无法直接衡量享受,因为它是一种主观状态。

  • 对于约会服务来说,目的是去约会,因此实际会面可能比搜索、查看个人资料或在线互动等重要。这提出了一个挑战,因为服务上的成功定义明确,但事件发生在线下。

  • 对于游戏来说,目的是为了娱乐。与媒体一样,主观感受难以衡量,因此最重要的可能事件可能是达到分数和等级或与朋友的社会互动。

与此要点相关的许多重要注意事项,我只提到了几个,但事实依然如此。

要点:寻找尽可能接近使用服务所创造价值的的事件,即使这些价值无法直接衡量。

本书剩余部分是关于将严谨性带入这种简单直觉。

1.7 案例研究:对抗客户流失

几家公司出现在本书的案例研究中。它们都使用数据以有区别的方式解决其客户流失问题。本节介绍了它们,并提供了例子,使上一节的数据讨论更加具体。

1.7.1 Klipfolio

Klipfolio 是一个数据分析云应用,可在网络浏览器、电视监视器和移动设备上构建和共享实时业务仪表板和报告。Klipfolio 通过提供关键绩效指标(KPI)和最重要指标的可视化,帮助公司保持对业务的了解和控制。Klipfolio 相信赋予人们随时随地使用和理解数据的能力,消除未知,使他们更具竞争力。Klipfolio 的在线应用程序订阅销售给企业。像大多数 B2B SaaS 产品一样,价格取决于用户数量和多种额外功能。大多数订阅按月或按年计费,直到订阅者取消。

作为一家极其数据驱动的公司(其产品都关乎数据!),Klipfolio 对使用数据来对抗客户流失和增加客户参与度充满热情。它很早就发现,通过降价和折扣来吸引客户保持关系并不值得——客户在折扣到期后往往会流失。通过分析使用和流失模式,Klipfolio 发现,如果组织中的只有一个人使用该产品,客户就有流失的风险,因此关键指标变成了每个账户的活跃用户数。公司还发现,如果客户没有完全采用产品,在订阅初期就有很高的流失风险,因此它通过其支持团队实施了入职电话和前三个月的免费支持。Klipfolio 还意识到,一些客户的终身价值太低,不足以证明其合理性。为了解决这个问题,它重新配置了其定价和包装,以反映其计划和服务功能的更盈利版本。

你将在本书中了解到 Klipfolio 如何使用他们的数据来实现这些成果。现在,我们先快速查看一些公司的活动数据。由于该产品允许创建 Klips 和仪表板,最常见的事件是查看仪表板、编辑 Klip 和保存 Klip。此外,还有一些与 Klips 相关的社会功能事件,如分享。捕获了超过 80 个不同的事件;最常见的事件列在表 1.2 中。Klipfolio 的事件还识别了每个公司内的个人用户,其中一些事件,如会话时长,包含额外的数据。

表 1.2 最常见的 Klipfolio 客户事件

查看仪表板 切换方向
切换标签页 今日活跃账户
Klip 编辑器 退出 Klip 编辑器
从仪表板编辑 Klip 添加 Klip 遮罩
在编辑器中保存 Klip 重新配置数据源

1.7.2 Broadly

广泛地改变了本地服务企业的发展方式。它通过强大的客户体验,帮助成千上万的本地服务企业每天吸引、保留并“惊艳”他们的客户。该公司致力于通过帮助他们吸引和捕捉潜在客户、通过电子邮件和短信简化沟通、监控其在线存在以及通过一个应用程序收集评论,将本地企业与现代消费者连接起来。像大多数 B2B SaaS 公司一样,Broadly 根据购买者的规模以及用户数量等因素提供不同计划的订阅服务。订阅服务可以按月或按年销售,并包括各种附加产品。

由于 Broadly 的产品促进了与客户的沟通和联系,它重视与客户互动的重要性。Broadly 拥有一支客户成功经理(CSMs)团队,他们与在产品上遇到困难客户提供帮助。CSMs 使用指标来引导与客户的对话:由指标提出的优势和劣势成为谈话的要点。Broadly 还使用的一种策略是关注客户参与度和风险中等的客户:公司不试图帮助那些尚未登录的客户,而是专注于那些显示出一些使用迹象但低于目标使用水平的客户。Broadly 还发现,影响客户保留的最重要因素之一是客户将 Broadly 与其公司预订系统集成。现在,CSMs 在可能的情况下帮助进行集成。Broadly 还使用电子邮件活动来推动对新产品功能的兴趣和采用。

Broadly 产品的最重要的方面之一是寻找客户并说服客户对业务进行评价。因此,客户数据包括添加客户、请求客户评价以及客户评价是正面还是负面的事件。SaaS 产品捕获了 60 多个不同的事件,其中最常见的 10 个事件列于表 1.3 中。

表 1.3 最常见的 Broadly 客户事件

交易添加 跟进电子邮件发送
请求展示 评价请求决策
客户添加 客户推广者
感谢电子邮件发送 请求完成
精准定位更新 页面浏览(路径:/add_customer)

1.7.3 Versature

Versature 通过为商业提供基于云的统一通信解决方案,正在颠覆加拿大的电信行业。在全国范围内受到客户和合作伙伴的信任,Versature 是一家获奖公司,它通过卓越、成本效益的技术和基于加拿大的支持来提高行业标准。企业利用 Versature 的统一通信套餐,套餐根据用户数量、通话量和其它在线通信服务来制定。大多数客户都订阅了每月续订的服务。

对客户流失的系统研究和对抗始于 20 世纪末的电信公司,紧随去监管化之后。作为一个在去监管化市场中诞生的电信提供商,Versature 从第一天起就专注于客户成功,重点是防止客户流失。由于每年都有负的净流失率,它经历了稳定增长。在客户生命周期早期就识别出有风险的客户,大大减少了可控的流失,并允许 Versature 的 CSMs 在与客户达到无法挽回的点之前进行价值驱动的对话。

Versature 的服务结合了传统的电信呼叫功能以及额外的数字功能,如名为 Sonar 的客户可访问的行政门户,名为 Insights 的呼叫数据管理服务,以及与 Google Chrome 和 Salesforce 等数字产品的集成。客户事件是传统电信呼叫与软件事件(如登录和页面浏览)的组合。Versature 前 10 个最常见的活动列于表 1.4 中。

表 1.4 最常见的 Versature 客户事件

本地电话 Sonar 登录
Sonar 页面浏览 国际电话
加拿大电话 Sonar 呼叫中心支持
免费电话 会议电话号码
美国电话 会议电话

淘汰案例研究和隐私保护

本书描述的公司都慷慨地允许自己被分析,并提供了他们的数据以展示技术。然而,这些公司在撰写本书时都在营业,并且有保护其运营信息战略需求的。因此,可能对许多读者有吸引力的详细信息仍然被保留。特别是,本书任何地方都没有报告任何实际公司的淘汰率:

  • 所有关于淘汰计算的数值示例(如第二章中的示例)都是用于说明目的而随机生成的数据。

  • 所有通过分析找到与淘汰相关的关系的图表(如下一节和后续章节中的图表)显示的是相对淘汰率,而不是实际淘汰率。

本书其他被掩盖的信息类型包括描绘或可用于推导有关案例研究公司客户基础或定价的信息的事实和数字,当此类信息尚未通过其他公共信息来源获得时。

还请注意,在本书的生产过程中,没有访问案例研究公司客户的任何个人可识别信息(PII)。本书的重点是匿名行为分析,不涉及 PII。尽管某些 PII(如地理信息)在某些淘汰分析中可能有用(见第十章),但本书的这些部分仅使用模拟数据来展示。

1.7.4 社交网络模拟

通过实例学习新技术是最好的方法,因此本书围绕一系列示例组织,这些示例将引导你通过淘汰案例研究。但是,真实的淘汰案例研究涉及关于产品和客户敏感的数据,因此无法在书中找到真实的数据集。相反,你将通过模拟客户和产品数据集的案例研究来学习使用数据对抗淘汰的技术。

模拟的产品是一个简单的社交网络。模拟中有八个事件,它们列在表 1.5 中。这些包括您在任何广告支持的社会网络服务中都能找到的最常见事件:交朋友、发布、点赞、点踩等等——当然,还有查看广告。模拟设计得如此之好,以至于模拟客户这些事件的发生与客户的模拟流失和续订之间有现实的关系。您将在整本书中发现这些关系。

表 1.5 社交网络模拟中的事件

查看广告 新朋友
点踩 发布
点赞 回复
消息 取消好友

警告:不要将本书中社交网络模拟的任何结果作为您自己产品或服务的预期指南。这些示例使用一组看起来很真实的数据,目的是为了演示在真实数据上使用的方法,但仅此而已。它们不能被期望预测任何真实产品或服务的任何结果。

更多关于如何运行生成社交网络案例研究数据的模拟的信息可以在本书 GitHub 仓库的 README 页面找到:github.com/carl24k/fight-churn

1.8 优秀客户指标案例研究

在进入第二章的技术细节之前,我将向您展示一些使用本书中技术得到的结果示例。我必须警告您,您将无法立即得出这样的发现;您必须学习这些技术来准备您的数据(第 2-4 章),然后您才能开始第五章中的客户流失分析。

正如我在事件讨论中提到的,与服务提供的价值最相关的行为最为重要。但选择要进行的测量也非常关键。以下是我发现特别有效的三个指标,用于对抗客户流失:

  • 利用率——指标显示客户使用了多少服务。如果服务对某些类型的用途有限制,则利用率指标显示客户占用了允许量的百分比。

  • 成功——指标显示用户在具有不同结果的活动中有多成功。

  • 单位成本——与客户为消耗或使用的服务数量支付的价格相关的指标。

如果您没有完全理解这些案例研究示例中的每个细节,请不要担心;这是对本书其余部分的一个快速预览!详细信息将在后面的章节中介绍。

1.8.1 利用率

在上一节中介绍,Klipfolio 是一个用于构建和共享实时业务仪表板的云数据分析应用程序。这些仪表板可以由多个用户创建,任何允许一个订阅上有多个用户的产品的常见指标是活跃用户数量。图 1.2 展示了 Klipfolio 客户每月活跃用户数量与流失率之间的关系。

图 1.2 使用了一种称为指标群体的技术来展示行为与流失率之间的关系。你将在本书中看到很多这样的图表,并学习如何创建它们,但到目前为止,我将简要解释这种技术是如何工作的。

图片 1-2

图 1.2 Klipfolio 流失率与活跃用户

给定一组客户和每月活跃用户数量等指标,客户根据该指标上的测量结果被组织成不同的群体。通常使用 10 个群体,因此第一个群体包含该指标方面排名后 10% 的客户,第二个群体包含下一个 10%,直到最后一个群体,它包含该指标方面排名前 10% 的客户。一旦形成群体,你就可以计算每个群体中流失的客户百分比。结果以图 1.2 所示的图表形式显示。图表中的每个点对应一个群体,点的 x 值由群体中客户的指标平均值给出,点的 y 值由群体中的流失百分比(流失率)给出。

如侧边栏中提到的隐私保护所述,本书中的案例研究流失率图表不显示实际的流失率,只显示群体之间的相对差异。然而,指标群体图表的底部始终设置为零流失率,因此可以用来比较相对流失率。例如,如果一个点比另一个点离图表底部近一半,这意味着该群体中的流失率是另一个群体的二分之一。

转到细节及其含义,图 1.2 显示,最低群体每月的活跃用户数不到 1 人(多个月份的平均值),而最高群体每月的平均活跃用户数超过 25 人。在流失率方面,每月活跃用户数最低的群体中的流失率大约是每月活跃用户数最高的群体中流失率的 8 倍。同时,大多数的流失率差异发生在每月大约 1 到 5 个活跃用户之间。

虽然测量活跃用户数量是应对流失的好指标,但图 1.3 展示的甚至更好。这是通过将活跃用户数除以用户已购买的座位数计算出的许可证利用率指标。许多 SaaS 产品是按“座位”销售的,这意味着允许的用户数量(这被称为许可座位数)。如果将活跃用户数除以许可座位数,得到的指标衡量的是客户对座位许可证的利用率百分比。

图 1.3 的结果显示,许可证利用率是应对流失的有效指标。许可证利用率最低的人群的平均利用率略高于 0,而最高的人群的许可证利用率约为 1.0。最低的人群的流失率大约是最高人群的 7 倍,且流失率在各个群体中或多或少连续变化。与图 1.2(显示流失率和每月活跃用户数)相比,没有达到一个利用率更高就不再有差别的水平。这使得许可证利用率在理解客户健康状况方面比单独的活跃用户更有效。

图片 1-3

![图 1.3 Klipfolio 流失率与许可证利用率]

如将在后续章节中进一步解释,每月活跃用户数在区分流失风险方面效果较差,因为它混淆了与流失相关的两个不同潜在因素:销售给客户的座位数有多少以及典型用户活跃的频率。利用率是衡量用户相对活跃程度的指标,与销售的座位数无关。许可证利用率通常有助于根据客户的参与度和流失风险对客户进行细分。

1.8.2 成功率

在上一节中介绍,Broadly 是一个在线服务,帮助企业管理其在线存在感,包括评论。Broadly 客户的一个重要指标是业务被正面评论或推广的次数。图 1.4 展示了 Broadly 客户每月促进者数量与流失率之间的关系。在图中,每月促进者数量最少的人群(平均每月仅略高于零个促进者)的流失率大约是拥有最多促进者人群的 4 倍;大多数的流失减少发生在每月 0 到 20 个促进者之间。这对于一个重要事件来说是一个明显的关联,也容易理解为什么拥有促进者的客户更有可能继续使用 Broadly 服务:获得正面评论是使用 Broadly 的企业的主要目标之一!

图片 1-4

![图 1.4 Broadly 流失率与客户促进者数量]

对于 Broadly 的客户来说,与推广者数量相关的一个重要事件是负面评论的数量,或者说业务被负面评论的次数。图 1.5 显示了 Broadly 客户每月负面评论数量与流失率之间的关系。每月负面评论数量最少(略高于零)的群体,其流失率大约是每月负面评论数量最多(平均略少于 5 条)的群体的 2 倍;大多数的流失减少发生在每月 0 到 1 条负面评论之间。

图片 1-5

图 1.5 Broadly 流失率与客户负面评论者

虽然这种关系看起来与图 1.4 中显示的客户推广者关系非常相似,但似乎有什么地方不对劲?获得负面评论是件坏事,并且可能是 Broadly 客户不希望看到的结果,那么为什么有负面评论与降低的流失率相关联呢?

要理解为什么更多的负面事件(如负面评论)与更低的流失率相关联,查看 Broadly 客户另一个更好的指标可能有所帮助。如果你将负面评论的数量除以总评论数(推广者加负面评论),那么结果就是负面评论者的百分比,我称之为负面评论率。图 1.6 显示了流失率与负面评论率之间的关系。这可能是你期望的产品事件与客户负面体验之间的关系:负面评论率越高,流失率越高,并且这种关系非常显著。

图片 1-6

图 1.6 Broadly 流失率与负面评论率

那么,为什么在图 1.5 中查看负面评论数量时,关系表明更多的负面评论者是有益的,而在图 1.6 中查看负面评论率时,更多的负面评论者却是不利的呢?答案是,图 1.5 中的总负面评论数量与图 1.4 中显示的总推广者数量相关,因为总体上收到很多评论的 Broadly 客户很可能同时收到很多正面和负面评论。当你以图 1.5 中的简单方式查看负面评论数量与流失率之间的关系时,它混淆了两个驱动该指标的基本因素:有很多评论(这是好的)和有高比例的负面评论(这是不好的)。当单独分析负面评论的比例时,你得到图 1.6 中显示的更有用的结果。这说明了为什么成功率和失败率对于理解流失率如此有效。

1.8.3 单位成本

在上一节中介绍的 Versature 为企业提供电信服务。作为一个统一通信提供商,其许多重要事件都是带有存储在每个事件附加字段中的通话时长。图 1.7 显示了 Versature 客户在通话总时长与客户流失率之间的关系。在本地通话方面,最低群体实际上几乎没有通话,其流失率大约是每月有数千次本地通话的客户群体的三倍。

图片 1-7

图 1.7 Versature 流失率与本地通话

当试图理解客户流失时,重要的是不仅要考虑客户使用的服务量,还要考虑他们支付的金额。月度经常性收入(MRR)是计算客户为使用订阅服务支付金额的标准指标:它是客户每月为使用服务支付的可重复金额,但不包括任何设置费或非正常费用。(我将在第三章中详细介绍 MRR 及其计算方法。)客户的支付金额也可以通过指标群体方法进行分析,以寻找与客户流失的关系,这在图 1.8 中为 Versature 所示。

图片 1-8

图 1.8 Versature 流失率与月度经常性收入(价格)分数

图 1.8 中的指标群体图做了一些新的尝试。它不是直接显示群体的平均 MRR,而是在每次 MRR 测量转换为分数后显示平均数。如果你熟悉曲线评分的概念,指标分数就是同样的想法:测量值从一种尺度转换到另一种尺度,但顺序保持不变。如果一个群体,比如指标底部的 10%,如果将其转换为分数,那么这个群体仍然是同一组客户,并且该群体的流失率完全相同。这意味着将指标转换为分数只会影响群体在群体图水平轴上的位置,而不会影响点的垂直位置,即流失率。当在水平轴上重新缩放以使结果更容易理解时,将指标转换为分数。我将在第三章中详细介绍指标分数,并教你如何计算它们。

图 1.8 中的队列客户流失率表明,月均收入(MRR)也与客户流失有关,尽管其关联性不如打电话那么强。不同队列的客户流失率并不完全一致,最低队列的客户流失率仅比最高流失队列低约一半或三分之一。但这也是一个让你停下来思考图表所展示内容的情况:支付更多的人流失得更少。这是你预期的吗?这可能会令人惊讶,但实际上相当普遍,尤其是在商业产品中。这是因为商业产品以更高的价格卖给大客户,而大客户由于相互关联的原因流失得更少。他们有更多的员工,所以当涉及到像打电话或使用软件这样的产品使用时,支付更多产品费用的客户通常也会更多地使用产品。图 1.8 中显示的支付更高 MRR(月均收入)的客户较低的流失率与图 1.7 中显示的通话次数更多的客户的较低流失率有关。

图 1.9 展示了另一个衡量客户支付金额与客户流失之间关系的指标:将月均收入(MRR)指标除以每月通话次数的指标。这产生了一个衡量指标,即客户每次通话的成本。我称这个指标为单位成本指标,因为它解释了客户为他们的钱所获得的服务量。

图片

图 1.9 Versature 客户流失与每月每通话价格对比

与图 1.8 一样,图 1.9 以分数而不是美元为单位显示队列平均数。单位成本指标的客户流失率图表明,当支付与所使用服务的数量相关时,支付更多的客户确实流失得更多。在单位成本最高的队列中,其流失率大约是单位成本最低队列的六倍。这类价值指标对于理解客户流失的原因至关重要,也是将在后续章节中深入探讨的重要主题。

这些例子应该能让你对本书的走向有一个大致的了解。在下一章中,你将从基础知识开始,学习如何识别客户流失并计算客户流失率。

摘要

  • “客户流失”这个术语是在订阅产品背景下出现的,意味着用户停止使用或取消服务。

  • 客户流失也适用于所有客户或用户在较长时间内反复与产品互动的产品和服务,无论是否存在正式的订阅或任何形式的支付。

  • 近年来,消费者和企业可选择的在线服务数量急剧增加。这些服务的可选择性意味着客户流失是一个需要不断解决的问题。

  • 数据分析能力较强的人经常被召集来帮助理解导致客户流失的原因以及可以采取哪些措施来减少客户流失。

  • 在线产品和服务的跟踪广泛涵盖了用户与服务之间的各种互动,通常统称为事件。这些事件的历史是应对顾客流失的主要数据来源之一。

  • 顾客流失(churn)并不是仅靠预测模型就能帮助实现目标(减少流失)的情况,因为减少流失的干预措施取决于了解流失的原因。

  • 为了帮助减少顾客流失,数据人员应该创建尽可能好的顾客指标。

  • 优秀的顾客指标具有以下特点:

    • 易于被商业人士理解

    • 与顾客流失和保留明显相关,因此一个健康的顾客形象显而易见

    • 以有助于针对性干预的方式细分顾客,这样可以增加参与度

    • 在商业的多个职能中都有用(例如,产品、营销、支持等)

2 测量流失

本章涵盖

  • 识别流失账户和计算流失率

  • 根据月度经常性收入计算净留存率和流失率

  • 在月度和年度测量之间转换流失率

你已经了解到,流失率是衡量每月或每年退出客户的比例的度量。如果你没有正确测量流失,那么采取任何措施都会更加困难。本章教你多个适合不同业务场景的流失定义,以及如何从订阅数据库中高效地计算它们。回顾第一章中引入的整体书籍场景,本章重点介绍图 2.1 中突出的过程。

图片

图 2.1 本章主题在利用数据对抗流失的过程中

计算流失率实际上并不是火箭科学,但你确实需要了解一些中级 SQL 和几个代数方程。有几个因素使得计算流失率变得非同寻常。部分挑战在于复杂性,另一个挑战则是后勤。计算流失率的复杂性在于一个账户在其生命周期内可能有多个订阅,包括以下内容:

  • 账户可以多次流失并再次注册。这对于所有订阅产品和服务都是正确的。

  • 对于许多订阅服务,账户可以同时持有多个不同产品的订阅。这在 B2B 产品和服务中尤为常见。

通常,订阅或账户上没有字段或标志表明“这是流失”。相反,流失是每个账户在特定时间点的动态状态,必须在此时刻确定。就此而言,如果账户或订阅上有字段或标志表明它是流失,那么你很可能是需要计算它的人,因为你就是数据人员——对吗?

在计算流失率时也存在后勤挑战:首先,数据是敏感的。对于任何订阅产品或服务,订阅数据库是其最有价值的资产之一。订阅数据库通常包含订阅者的个人可识别信息(PII)和公司的敏感财务信息。这不是你希望提取并留存在不安全位置的数据。第二个陷阱是随着成功而来的:如果你的产品或服务成功,这些数据量会很大。出于这两个原因,从数据库中提取数据进行处理并不是一个好主意。

那么问题就是,计算客户流失需要动态逻辑,但你最好在数据库中完成需要做的事情,而不是提取数据。因此,我将向你展示如何使用多部分 SQL 语句来计算客户流失,并将结果作为 SELECT 语句的输出(公平地说,我称这些 SQL 程序)。这是本书中反复出现的一个最佳实践:尽可能在数据库或数据仓库中完成工作,仅在必要时提取减少的数据。如果你习惯于在 Python 这样的过程语言中进行这种逻辑和计算,这可能会显得有些陌生,甚至令人沮丧。虽然可以在 Python 中完成这些任务,但这种方法与本书中教授的数据库方法相比,可扩展性差得多。一旦你习惯了使用多部分 SQL 程序在数据库中处理数据,你可能会 wonder how you ever got by without it!

本章的组织结构如下:

  • 第 2.1 节在你开始编码之前,通过图表和几个方程式展示了客户流失的概念。

  • 第 2.2 节展示了典型的订阅数据库的外观;你将在客户流失计算中使用该示例数据库结构。

  • 第 2.3 节从净保留率开始进行客户流失计算,这是一个与客户流失相关的常见指标。(还有一个相关的净流失率测量,但正如我将解释的,人们很少使用它。)净保留率首先出现,因为它在概念上很简单,计算起来最容易,并且对于简单的订阅场景来说足够了。

  • 第 2.4 节教你标准客户流失率的计算方法,它更普遍适用,但比净保留率稍微复杂一些。

  • 第 2.5 节考虑了在没有订阅但客户对产品或服务有重复使用的情况下的客户流失计算。这些技术适用于广告或应用内购买产品。

  • 第 2.6 节涵盖了收入流失率的测量,称为月度经常性收入(MRR)流失率;它适用于复杂的订阅服务。

  • 第 2.7 节解释了如何将一年的客户流失率转换为月度流失率,反之亦然。

用于数据驱动的客户流失应对代码

本书的所有源代码都可在本书的网站上找到(www.manning.com/books/fighting-churn-with-data)和本书的 GitHub 仓库中(github.com/carl24k/fight-churn)。本书的列表在文件夹/listings 中。请参阅仓库的 README 页面以获取详细的设置说明。总之,你需要执行以下步骤来运行本书中的代码列表:

  1. 安装 Python 和 Postgres。我还推荐安装免费的 GUI 工具来与之一起使用。如果你已经是这些工具的专家,那么使用你喜欢的工具;如果不是,请严格按照 README 中的设置说明操作。

  2. 使用 fight-churn/data-generation/churndb.py 创建数据库模式。

  3. 使用 fight-churn/data-generation/churnsim.py 生成模拟数据并将其保存到 Postgres 模式中。

模拟通常运行大约 10 分钟,在 6 个月内为虚构社交网络生成约 15,000 名客户。模拟创建了他们所有的订阅和事件,例如交朋友、发布帖子、查看广告以及点赞和不喜欢帖子。关于模拟的更多细节将在本书中提供。

在创建数据库并生成数据后,有一个名为 run_churn_listing.py 的 Python 包装程序,您可以使用它来运行本书中的所有列表。这对于第 2-4 章中的 SQL 列表很有帮助,因为脚本会处理变量和连接到数据库等细节;包装程序也用于第五章开始的 Python 函数。命令行参数控制运行哪个列表。例如,要运行列表 2.1,您使用此程序:

fight-churn/listings/run_churn_listing.py —chapter 2 —listing 1

或者,如果您想用另一种方法运行列表,所有列表都按章节组织在 fight-churn/listings/ 下的文件夹中。例如,列表 2.1 位于文件 fight-churn/listings/chap2/listing_2_1_net_retention.sql 中。SQL 列表都存储为包含以 % 开头的绑定变量的模板;它们不包含特定参数,如日期或事件名称。在运行查询之前,您需要替换这些变量的值。包装脚本 run_churn_listing.py 会为您处理这些。

在 SQL 列表中使用绑定变量,以便它们可以轻松重新配置以在除提供的模拟以外的数据集上运行。如果您想使用自己的数据而不是模拟,首先执行以下操作(在按照 README 中解释的说明安装 Python 和 Postgres 之后):

  1. 使用 fight-churn/data-generation/churndb.py 创建一个模式。通过编辑该可执行文件并设置文件顶部附近的变量 schema_name 来设置模式名称。

  2. 加载您的订阅、事件和模式事件表。(如何操作的细节超出了本书的范围,但各种免费工具使将数据加载到 Postgres 数据库变得相当容易。)

  3. 列表包装程序从存储在 JSON 文件 fight-churn/listings/conf/churnsim_listings.json 中的参数运行。要使用不同的参数运行列表,您必须创建该 JSON 文件的自己的版本。对于每个章节和列表,都有参数块,参数以键值对的形式存储。您可以将这些设置为适合您自己的数据集的适当值。

  4. 使用包装程序和额外的参数 —schema <your_schema> 运行列表。

关于安装和使用的更多详细信息请参阅 README。

2.1 损耗率的定义

图 2.2 阐述了客户流失的概念:两个圆代表不同时间点的订阅者池。每个圆的面积代表订阅者的数量或他们支付的总金额;后者在订阅者支付不同金额时使用。但无论客户流失是基于订阅者数量还是收入金额,概念是相同的。客户流失是顶部的向下新月形:起始圆中不与底部(结束)圆重叠的部分,即那些不再使用服务的订阅者。为了完整地描述,两个圆之间的重叠部分是保留的订阅者,而结束圆底部向上新月形(不与顶部圆重叠)代表新获得的订阅者。请注意,通常两个圆的大小并不完全相同。

图片

图 2.2 客户流失维恩图,显示一个增长中的订阅服务,其中获取量大于流失量

2.1.1 计算客户流失率和客户保留率

接下来,我将向您展示如何将图 2.2 中的定性图像转换为公式。客户流失率定义为起始订阅者(起始圆)离开服务(顶部新月形)的比例。在方程中,这表示为

图片 公式 2.1

其中 #StartCustomers 表示起始圆的面积,而 #ChurnedCustomers 表示流失新月形的面积。图 2.3 展示了一个简单的示例。

图片

图 2.3 客户流失率计算简化示例

该产品在 1 月份只有五名客户,所有客户都在每月的同一日进行续订。在 1 月份,一名客户没有续订,两名新客户注册,因此 2 月份有六名客户。

公式 2.2 显示了 1 月份的客户流失率计算:

图片 公式 2.2

注意,客户流失率不使用最终的总订阅者数或新获得的订阅者数。查看图 2.2 以了解原因:客户流失率是起始圆中未被结束圆覆盖的部分,但结束圆的大小取决于保留和新订阅者获取的数量。新订阅者的获取是一个极其重要的主题,但它是一个独立的问题,因为它来自不同的过程集合,所以它不包含在本书中。因此,客户流失率仅基于与起始订阅者的流失相关。如果客户流失率是按最终订阅者池的面积来划分的,那么它将是不连贯的,因为这会混合两个池的部分,并混淆获取和保留对订阅者池大小的贡献。

客户保留率在公式 2.3 中定义:

图片 公式 2.3

使用图 2.3 中的示例,客户保留率为

方程式 2.4

2.1.2 流失率和保留率之间的关系

这里有一个关于流失率和保留率的重要事实:它们以非常精确的方式相互关联,是同一枚硬币的两面。看看图 2.2 中起始圆的部分。整个起始圆要么是流失部分,要么是保留部分,由这个方程式表示:

方程式 2.5

现在进行一些代数操作。如果你将方程式 2.5 的两边除以起始订阅者,你会得到

方程式 2.6

接下来,将方程式 2.6 中的流失率和保留率的定义替换为方程式 2.1 和 2.3,并记住任何数除以自身等于 1,或 100%。这个方程式显示了以下关系:

最后,这些术语可以被重新排列以得出结论:

方程式 2.7

方程式 2.7 也可以通过查看图 2.2 来理解。流失和保留共同构成了起始圆,因此这两个比例的总和必须等于整个圆,即 100%。

摘要:保留率可以从流失率中轻松计算出来,反之亦然。你查看哪个测量值是个人偏好的问题。

大多数组织使用流失率进行减少流失的内部讨论。保留率通常用于向外界(例如,投资者)报告,当重点是要积极(“杯子是半满的”)时。

2.2 订阅数据库

正如我在第一章中描述的,订阅产品或服务通常有一个数据库来跟踪订阅的开始和结束时间,本书中的例子通常假设正在使用订阅数据库。如果你的业务没有订阅数据库,因为它是一个免费或广告支持的产品,第 2.5 节展示了如何在没有订阅的情况下计算流失率,但我建议你阅读这一节以及随后的章节,因为它们构建了必要的概念和技术。

表 2.1 显示了典型订阅数据库表的关键元素。

表 2.1 一个典型的订阅数据表

数据类型 是否必需
subscription_id integer or char Yes
account_id integer or char Yes
product_id integer or char Yes
start_date date Yes
end_date date No
mrr double precision Maybe

实际上,通常有更多的字段,但为了说明目的,这个订阅数据模型包含了一些你可能在生产环境中期望找到的核心字段:

  • 订阅 ID——每个订阅的唯一标识符。

  • 账户 ID——账户持有者或用户的标识符。这些在账户级别上是唯一的,但在订阅表中并不假定是唯一的。通常,账户可以持有多个订阅。

  • 产品 ID —— 订阅的唯一产品的标识符。此数据模型使用每个订阅一个产品,但如前所述,账户可以持有多个产品。如果订阅服务只提供单一产品,可能没有产品字段;但公平地说,这应该被视为一个必填字段,因为计划增长的单一产品服务通常会推出新的产品。

  • 开始日期 —— 每个订阅都必须在某个日期开始。这些是简单的日期,没有时间。

  • 结束日期 —— 订阅可能有或没有结束日期。如果没有结束日期,则假定订阅将持续到明确取消。

  • 每月周期性收入(MRR)——付费订阅关联着一定数量的周期性每月收入。

总结来说,订阅是一种向客户销售的产品,它从特定的开始日期开始,并按指定周期性费用进行。正如所讨论的,可能会有一个结束日期,也可能没有。

注意:带有结束日期的订阅通常被称为期限订阅,从开始日期到结束日期之间的时间称为期限。持续无限期(直到取消)的订阅通常被称为常青订阅。

注意,包含此信息的数据库表可能不被称为“订阅”。如果你在一家使用客户关系管理(CRM)系统跟踪交易的 B2B 公司工作,你的公司可能将此信息存储为“机会”;或者,如果你的公司使用旨在跟踪多产品订阅提供的订阅业务管理(SBM)产品,该表可能被称为“产品”“费率”“计划”。但只要所有必需的数据元素都可用,你就有了计算流失所需的一切。注意,对于任何销售付费订阅服务的公司,MRR 可以被视为一个必填字段,尽管如果存在折扣或免费试用(例如,基本级别的免费增值订阅),它可能为零。

杂乱的订阅数据库

在这本书的整个过程中,你将反复看到一个主题,那就是算法被设计来处理数据库中保存的订阅的不规则性,也称为杂乱或未清理的订阅数据。杂乱的订阅数据可以有多种形式:同一账户的重复项、不属于真实账户的条目、在所谓的连续订阅条款(如时长和价格)中的不一致性、订阅之间的意外间隔,以及结束日期在开始日期之前的情况,仅举几例。

如果你仍然是一名学生或者从未在商业环境中工作过,那么这可能会让你感到惊讶,甚至恼怒,因为一些我使用的算法可能比你预期的或认为必要的要复杂。但我可以向你保证,这些复杂性在大多数情况下是必要的。

在现实世界中,干净的订阅数据库很少见;杂乱的订阅数据库是常态。你可能还没有意识到的一个问题是,只需要一小部分杂乱数据就足以使某些算法出错,其影响范围可能远不止是数据有问题的账户。

(继续)

如果你处理的是干净的数据,请对帮助处理杂乱数据的其他人的技术有耐心,并且可以自由地简化算法以适应更简单的情况。如果你处理的是杂乱的订阅数据,请放心:你并不孤单。本书中的技术应该能帮助你理解订阅者参与度和降低流失率。(但不要期望我为你清理数据!我是一个数据科学家,不是魔术师……)

2.3 基本流失计算:网络保留

我从网络保留开始,因为它是最容易计算的流失指标,尽管我也会向你展示它并不总是最有用的。图 2.2 展示了当圆圈代表收入时的保留率。像所有流失和保留度量一样,网络保留是在特定时期内(通常是一年)测量的。网络保留率(NRR)是指公司在期末从最初存在的订阅者那里仍然收到的重复性收入的比率。

注意:如果你的订阅产品是免费的(没有付费的重复性收入),你仍然应该阅读这一节。网络保留计算可以用来计算常规(基于账户)的流失,并且它比第 2.4 节中提出的更通用的流失公式更简单。

像所有流失度量一样,网络保留忽略了在时间期间通过注册获得的新收入。另一方面,关于网络保留的一个重要事实是,如果发生这种情况,它包括保留的订阅者的收入变化。这可以发生在任何具有多个产品计划、临时折扣或定价计划变化(大多数付费订阅)的产品或服务中。我将忽略这些细节,专注于教授网络保留和流失的计算;你将在接下来的章节中了解这些不同的情况。

2.3.1 网络保留计算

网络保留在方程 2.8 中定义:

方程 2.8 方程 2.8

这是对保留率的定义与方程 2.2 中的定义略有不同。图 2.4 将图 2.3 中的例子扩展到包括两种不同计划类型和不同的 MRR:每月 9.99 美元的标准计划和每月 29.99 美元的豪华计划。在图中,一位豪华计划的客户流失,两位新客户注册,一位客户从标准计划转为豪华计划。这个例子显示了与图 2.3 中基于计数的流失计算的重要差异:

  • 因为流失计算是基于 MRR 的,所以当客户流失时,付费更多的客户对比率的影响更大。

  • 对于没有流失的客户,MRR 的变化也会影响这个比率。

图片 2-04

图 2.4 净保留率计算的简化示例

参考图 2.4 和方程式 2.8 的示例:净保留率的分子是所有剩余客户的 MRR,包括切换到高级计划的客户,或$79.96。起始时所有客户的 MRR 是$89.95,因此净保留率由以下公式给出

图片 2-04_E09 方程式 2.9

此外,还有一个与净保留率相关的流失度量,定义为 100%减去净保留率。方程式 2.10 显示了从净保留率定义净流失:

图片 2-04_E10 方程式 2.10

净保留率是唯一更常以保留率而不是流失率引用的与流失相关的度量。这在一定程度上是由于多价格订阅中出现的场景,特别是出现负净流失的可能性(另一方面,净保留率始终为正)。在下一节中,你将学习如何使用 SQL 计算净保留率,以及本书中关于 PostgreSQL 使用的更多细节。

2.3.2 SQL 净保留率计算

作为本书中的第一个 SQL 程序,我将简要介绍常见的表表达式(CTEs)。本书中的这个 SQL 程序以及所有其他程序都使用 CTEs,这是 ANSI SQL 的一个相对较新的扩展。CTEs 允许在查询中定义中间的、临时的表,按照它们出现的顺序。与其他临时表的语法相比,CTEs 更加简洁。数据库中的临时表是SELECT语句的结果,这些结果被保存在数据库中,可以在同一整体 SQL 语句或程序中的进一步SELECT中使用。然而,临时结果不会在当前执行之外持久化。

我使用 CTEs 来教授这些技术,因为它们允许清晰、逐步展示程序逻辑。 (我将其称为 SQL 程序,而不是常用术语 SQL 语句,后者暗示逻辑更短、更简单。)以下是对列表 2.1 的高层次概述,该列表展示了我们的第一个 SQL 程序,与图 2.2 中的流失图相关:

  1. 设置测量的起始和结束时间。

  2. 确定起始时的订阅者和总收入(如图 2.2 中的顶部圆圈)。

  3. 确定结束时的订阅者和总收入(如图 2.2 中的底部圆圈)。

  4. 确定保留的订阅者和他们的收入(如图 2.2 中两个圆圈的交集)。

  5. 将保留的订阅者收入除以起始订阅者收入(方程式 2.2)。

关于此程序(以及其他所有程序)的另一点是,通常我会查看你可能遇到的难度最大或最复杂的使用案例。这也意味着它可能对于某些场景来说功能过于强大,但我更愿意让一些用户根据自己的判断简化程序,而不是省略对许多其他人有帮助的指导。特别是,该程序假设订阅者可以持有多个具有不同持续收入的订阅产品。如果你的订阅只有一个产品和一个价格以及/或没有付费的持续收入,你可以使用相同的 SQL,但将 MRR 的总和替换为账户数量的计数。如果你的服务只有定期订阅,你可以删除关于空结束日期的情况。(如第 2.2 节所述,定期订阅在订阅创建时有一个定义的结束日期。)该程序的 SQL 在列出 2.1 中显示。

注意:列出 2.1 中的日期变量使用配置参数设置,因此在书籍的可下载代码中,它们以以%开头的绑定变量形式出现。列出显示了变量绑定后的 SQL。对于书籍中的所有 SQL 列表也是如此。

列出 2.1:净保留 SQL 程序

WITH
date_range AS (                                                            ①
    SELECT '2020-03-01'::date AS start_date, '2020-04-01'::date AS end_date
), 
start_accounts AS                                                          ②
(
    SELECT  account_id, SUM(mrr) AS total_mrr                              ③
    FROM subscription s INNER JOIN date_range d ON
        s.start_date<= d.start_date                                        ④
        AND (s.end_date>d.start_date or s.end_date is null)
    GROUP BY account_id                                                    ⑤
),
end_accounts AS                                                            ⑥
(
    SELECT account_id, SUM(mrr) AS total_mrr                               ③
    FROM subscription s INNER JOIN date_range d ON
        s.start_date<= d.end_date                                          ⑦
        AND (s.end_date>d.end_date or s.end_date is null)
    GROUP BY account_id                                                    ⑤
), 
retained_accounts AS                                                       ⑧
(
    SELECT s.account_id, SUM(e.total_mrr) AS total_mrr                     ⑨
    FROM start_accounts s 
    INNER JOIN end_accounts *e* ON s.account_id=e.account_id                 ⑩
    GROUP BY s.account_id                                                  ⑤
),
start_mrr AS (                                                             ⑪
    SELECT SUM (start_accounts.total_mrr) AS start_mrr
    FROM start_accounts
), 
retain_mrr AS (                                                            ⑫
    SELECT SUM(retained_accounts.total_mrr) AS retain_mrr
    FROM retained_accounts
)
SELECT 
retain_mrr /start_mrr  AS net_mrr_retention_rate,                          ⑬
    1.0 - retain_mrr /start_mrr AS net_mrr_churn_rate,                     ⑭
start_mrr,                                                                 ⑮
retain_mrr
FROM start_mrr, retain_mrr

① 设置程序计算流失的周期。

② 包含所有在开始日期活跃的账户 ID 及其在开始日期的总 MRR 的 CTE。

③ 使用聚合求和,以便当存在多个订阅时,SELECT 返回总数。

④ 在指定日期活跃的准则:开始日期在或早于该日期,结束日期在或晚于该日期或为空。

⑤ 使用聚合的 GROUP BY 函数,因此 SELECT 对每个账户的总 MRR 进行求和。

⑥ 包含所有在结束日期活跃的账户 ID 及其在结束日期的总 MRR 的 CTE。

⑦ 在指定日期活跃的准则:开始日期在或早于该日期,结束日期在或晚于该日期或为空。

⑧ 包含所有未流失(保留)的账户的 CTE。

⑨ 使用聚合求和,以便当存在多个订阅时,SELECT 返回总数。

⑩ 内连接的结果是包含在开始和结束日期都活跃的账户,这意味着它们被保留了。

⑪ 求和所有在开始日期活跃的账户的总 MRR。

⑫ 求和所有保留的账户的总 MRR。

⑬ 净保留公式:保留账户的 MRR 除以开始时的 MRR。

⑭ 净 MRR 流失公式:1.0 减去净保留率。

⑮ 包含结果组成部分,以显示 MRR 是如何产生的。

以下列表描述了程序中的每个 CTE 以及最终的SELECT语句,以及每个 CTE 在计算中扮演的角色,编号根据在列出 2.1 之前概述的计算策略步骤:

  1. date_range—一个包含计算开始和结束日期的单行表。

  2. start_accounts—一个表,其中每行对应于在开始时活跃的每个账户。此表是通过根据账户在流失测量开始时活跃的条件从订阅表中选择的,这意味着它持有订阅(其中订阅开始日期在流失测量开始日期之前,订阅结束日期在流失开始日期之后,或者没有订阅结束日期)。

  3. end_accounts—一个表,其中每行对应于在流失测量结束时活跃的每个账户。被认为是活跃的条件与起始账户相同,使用流失测量结束日期作为标准。

  4. retained_accounts—一个表,其中每行对应于在开始和结束时都活跃的每个账户。此表是通过在start_accounts表和end_accounts表之间基于账户 ID 进行标准连接创建的。

  5. start_mrr—一个包含起始流失测量时总 MRR 的单一行表,以清晰起见。

  6. retained_mrr—一个包含所有保留账户总 MRR 的单一行表,以清晰起见。

  7. 最终SELECT语句——从start_mrrretained_mrr表中获取结果,并通过将值插入方程 2.2 和 2.3 来计算最终结果。

这就是如何从典型的订阅数据库中通过 SQL 计算净保留率和净流失率(由方程 2.5 和 2.6 描述)的方法。

此 SQL 程序在本书网站上的代码生成的模拟数据集(www.manning.com/books/fighting-churn-with-data)和 GitHub 仓库(github.com/carl24k/fight-churn)中进行了测试,并生成了图 2.5 所示的结果。你应该按照 README 中的说明运行列表 2.1。如果你没有自己产品或服务的数据,你可以在模拟数据集上运行代码;说明在仓库根目录的 README 中。如果你已经生成了模拟数据,那么可以通过执行包装程序run_churn_listing.py并使用如下参数来运行列表 2.1:

fight-churn/listings/run_churn_listing.py —chapter 2 —listing 1

包装程序会打印出它正在运行的 SQL 语句,最终结果看起来与图 2.5 相似。此图以及本章中所有 SQL 输出均以表格格式显示,就像在 SQL 工具中运行查询时会出现的结果一样。如果你使用 GitHub 上提供的 Python 框架运行列表 2.1,结果将以一行文本的形式打印出来。此外,请注意,你的结果可能会有所不同,因为底层数据是随机模拟的。

图片

图 2.5 在模拟数据集上运行列表 2.1 的结果

本书中对 PostgreSQL(又称 Postgres)的使用

本书展示了所有使用经过测试和运行在 PostgreSQL 11 上的代码的示例。我选择 PostgreSQL 11 是因为它在撰写本书时是 PostgreSQL 的最新版本,它是一个具有现代功能且易于展示所教授概念的流行开源数据库。如果你在一家使用其他数据库的公司工作,将本书中的 SQL 转换为你使用的版本不应该太难。主要问题可能是 CTEs,它们将不得不转换为临时表或子查询。如果你是学生或刚开始学习这些技术并且可以自己选择数据库,我强烈建议你使用 PostgreSQL 来简化你在学习过程中使用本书代码的方式。

当然,PostgreSQL 没有大数据仓库的能力;虽然公共表表达式易于阅读,但它们可能在计算上很昂贵。因此,这种安排仅适用于具有相对较少客户的服务和产品。本书中的公司案例研究都有数万到大约 10 万客户,PostgreSQL 容易满足需求。根据你使用的硬件和你投入的性能调优工作量,PostgreSQL 应该能够很好地扩展到数百万客户的分析。如果你有 1000 万或更多的客户,你可能会最终使用专门为大数据架构的数据仓库产品。幸运的是,大多数现代数据仓库(如 Redshift 和 Presto)都支持带有公共表表达式的 SQL,因此本书中的技术可以直接转换。尽管如此,如果你是第一次学习本书中的技术,我强烈建议你在笔记本电脑上适合的 PostgreSQL 数据库上进行学习。

2.3.3 解释净留存率

以下场景可能会发生并影响你对净留存率的解释:

  • 所有订阅(包括免费订阅)支付相同。

  • 多种订阅价格,有两种情况:

    • 标准情况——流失和降价销售超过提升销售。

    • 负增长率案例——提升销售超过流失和降价销售。

如果所有订阅者支付完全相同,并且没有任何订阅者改变他们支付的金额,本节中计算的 MRR 流失和留存率与第 2.1 节中描述的基于订阅者数量的流失和留存率完全相同。看看图 2.2 中的圆圈:如果每个人都支付完全相同,那么基于订阅者 MRR 的圆面积与基于订阅者数量的圆面积完全成比例,这种比例在开始和结束时都是相同的。如果服务是免费的且 MRR 为零,情况也是一样的:净留存率和派生流失与基于订阅者数量的流失计算相同,尽管在这种情况下,你将不得不修改列表 2.1 中的 SQL 以使用计数聚合而不是 MRR 的总和。

另一方面,当订阅者支付金额变化时,净留存率和基于收入的客户流失率与基于订阅者计数的留存率和客户流失率不同。这是因为随着时间的推移,个别订阅者支付的金额可能会变化,因此如果图 2.2 中的圆圈代表收入,圆圈的大小有四种变化方式。这种更复杂的场景在图 2.6 中得到了说明,该图展示了包含升级销售和降级销售的 MRR 留存率和客户流失。订阅者收入可能变化的四种方式是

  • 订阅者获取

  • 订阅者流失

  • 升级销售(保留的订阅者转为更高重复收入)

  • 降级销售(保留的订阅者转为更低重复收入)

图片

图 2.6 升级销售和降级销售下的收入留存率和客户流失

基于收入的净留存率和净客户流失率与基于订阅者计数的(标准)客户流失率不同。保留订阅者中的升级销售的影响可以有效地抵消客户流失,而保留订阅者中的降级销售可以有效地增加客户流失。

要点 净留存率之所以被称为“净”,是因为它结合了客户流失、升级销售和降级销售的影响。

净留存率的这种净化特性使其在对抗客户流失方面不如其他客户流失定义那样有用。这是因为包括降级销售和升级销售并不相同,将它们混淆会混淆问题。将降级销售纳入客户流失计算是合理的,因为那是最初存在的收入部分,但由于客户流失而丢失。升级销售的收入来自“不同的蛋糕”——如图 2.6 所示的最终收入循环——因此,净留存率衡量标准不再是整体部分之间的比率。此外,折扣的到期是合同价格的变化,与客户满意度无关。

警告 升级销售和折扣到期会降低净留存率中的明显客户流失率,这使得它成为客户流失的更不具体测量,对抗客户流失的作用也较小。下文所述的标准(基于计数)客户流失和 MRR 客户流失是更具体的客户流失测量方法,更受欢迎。

净留存率并不是对抗客户流失的最有用指标,但它仍然引人注目,因为以下关于客户流失报告实践的事实。

提示 净留存率是向订阅公司外部投资者报告客户流失的首选指标。

为什么在报告中偏好净保留率?有两个原因:一个善良的原因是,作为一个运营指标,净保留率将流失、升级和降级总结成一个方便的数字,这个数字可以说是对外部投资者最重要的。一个稍微有些牵强的原因是,当升级高于降级时,净流失率(从 100%减去净保留率得到)小于忽略收入变化的(基于计数)标准流失率。正如我刚才解释的,净收入变化(升级和折扣到期减去降级)实际上隐藏了真正的潜在流失率。对于许多公司来说,报告净保留率而不是更具体的流失率测量是一种更好的投资者关系和轻微的业务基础模糊的做法。在极端情况下,来自升级的收入池的增加可能大于降级和流失减少收入池的综合负面效应。这是一个罕见但高度理想的场景,被称为负流失。

定义 负流失——当升级带来的收入增加大于降级和流失的综合负面效应时。

这意味着保留的订阅者的收入大于起始订阅者的收入,即使在考虑到流失和降级之后。因此,净保留率大于 100%。如果根据第 2.1.2 节中的方程 2.6 计算净流失率,结果将是负数。请注意,这并不是标准流失率的真正负值——标准流失率只能是一个正数(或者可能是零,对于没有订阅者取消服务的服务)。如前所述,这不是与数据对抗流失的最有用流失率测量,因为它掩盖了实际流失的真正程度,但向投资者报告时却非常令人印象深刻!

2.4 标准账户流失

标准的基于账户的流失率具有最简单的含义,因为它不受升级、降级和折扣到期的影响。它总是简单地指完全取消服务的客户比例。

定义 标准流失——基于客户数量的流失率测量。也简称为流失率。

标准流失率通常被称为账户流失率,因为它指的是可能持有多个订阅的账户持有者的完全流失。因此,对于标准流失率来说,一个取消了一个订阅但保留了另一个订阅的账户持有者不被视为流失。(这将被视为降级,我将在下一节中重新讨论。)在 B2B 领域,标准流失率也被称为标志流失率,因为每个账户持有者都是一个公司(一个标志)。

在本节中,我将展示如何直接计算流失率,而不是从留存率计算。直接计算需要使用称为外连接的 SQL 功能。确实,流失率可以通过使用类似于用于净留存的内连接来从账户留存率计算,但能够使用外连接识别流失账户是一项你以后需要的技能。因为外连接不是基本的 SQL 功能,所以我将在 2.4.2 节中回顾外连接,在那之前,我将在 2.4.1 节中概述查询。

2.4.1 标准流失率定义

在考虑细节之前,让我们首先根据图 2.2 和公式 2.1 回顾计算标准流失率的步骤:

  1. 设置测量的起始和结束时间。

  2. 识别并计数起始时的订阅者(如图 2.2 中的顶部圆圈)。

  3. 识别结束时的订阅者(如图 2.2 中的底部圆圈)。

  4. 识别并计数流失的订阅者(如图 2.2 中的向上向下凸起的月亮形状)。

  5. 将流失数量除以起始账户数量(公式 2.1)。

2.4.2 用于流失计算的外连接

从起始和结束账户中通过外连接选择了流失账户。我将简要回顾外连接,以便让不熟悉它们的读者了解。

内连接是更常见的连接类型,例如在account_id上连接以创建列表 2.1 中的retained_accounts CTE。它返回所有匹配的行(根据连接字段),并为匹配返回两个表中的所需字段。外连接不同,因为它不仅返回匹配的行,还返回一个表中的所有行。连接返回来自第二个表的匹配行,但对于第一个表中没有匹配行的行,它使用 null 填充第二个表的字段。这被称为左外连接,因为总是选择第一个表中的所有行,这在join语句的左侧。(还有一个右外连接,它在保持所有行的表方面与左外连接相反,甚至还有一个全外连接,它返回两个表的所有行。但对于流失来说,左外连接就足够了。)

使用外连接来查找流失账户,因为目的是找到起始时存在但结束时不再存在的账户。如果你进行内连接,它会匹配起始和结束时都存在的账户,而这些正是需要移除的账户。左外连接返回起始时存在的所有账户,而不仅仅是流失的账户,这就是为什么选择流失账户的 CTE 也需要一个WHERE子句。它只选择来自连接的、end_accounts中的account_id为 null 的账户,这意味着它只选择那些在start_accounts CTE 中匹配的account_id不在end_account CTE 中的行。

TAKEAWAY 外连接可以用来查找不满足连接条件的行,这使得它们在识别客户流失时非常有用。

再次查看图 2.2 中的客户流失图:它还提供了内部和外部连接逻辑的说明(这显示在图 2.7 中)。保留账户是开始和结束账户的交集,通过内部连接选择。流失账户,左外部连接,通过从起始账户中移除所有在末尾与 WHERE 子句匹配的记录来找到,选择 WHERE 子句中 end account_idNULL 的记录。(因此,获得的账户将是右外部连接,WHERE 子句选择为空起始 account_id,但本书中的技术不需要这样做。)

图 2.7 显示内部和外部连接的客户计算

2.4.3 使用 SQL 进行标准客户流失率计算

标准客户流失率计算的 SQL 代码如列表 2.2 所示。对于前三个 CTE,它与列表 2.1 中的净保留查询相同,它创建了包含测量开始和结束账户的临时表。但找到账户后,它创建了一个流失账户的表,而不是保留账户的表。

列表 2.2 标准(基于账户)客户流失 SQL 程序

WITH
date_range AS (                                                            ①
    SELECT  '2020-03-01'::date AS start_date, '2020-04-01'::date AS end_date
), 
start_accounts AS                                                          ②
(
    SELECT DISTINCT account_id                                             ③
    FROM subscription s INNER JOIN date_range d ON
        s.start_date<= d.start_date                                        ④
        AND (s.end_date>d.start_date or s.end_date is null)
),
end_accounts AS                                                            ⑤
(
    SELECT DISTINCT account_id                                             ③
    FROM subscription s INNER JOIN date_range d ON
        s.start_date<= d.end_date                                          ④
        AND (s.end_date>d.end_date or s.end_date is null)
), 
churned_accounts AS                                                        ⑥
(
    SELECT s.account_id
    FROM start_accounts s 
    LEFT OUTER JOIN end_accounts *e* ON                                      ⑦
    s.account_id=e.account_id                                              ⑧
    WHERE e.account_id is null                                             ⑨
),
start_count AS (                                                           ⑩
    SELECT     COUNT(*) AS n_start FROM start_accounts
), 
churn_count AS (                                                           ⑪
    SELECT     COUNT(*) AS n_churn FROM churned_accounts
)
SELECT 
n_churn::float/n_start::float 
        AS churn_rate,                                                     ⑫
    1.0-n_churn::float/n_start::float 
        AS retention_rate,                                                 ⑬
n_start,                                                                   ⑭
n_churn                                                                    ⑭
FROM start_count, churn_count

① 设置 SQL 计算客户流失率的周期

② 包含开始时所有活跃账户 ID 的 CTE

③ 使用唯一查询,因为不能假设每个账户只有一个订阅

④ 在给定日期上活跃的准则:开始日期在或早于该日期,结束日期在日期之后或为 NULL。

⑤ 包含结束时所有活跃账户 ID 的 CTE

⑥ 包含所有流失账户账户 ID 的 CTE:那些在开始时活跃但在结束时不再活跃的账户

⑦ 外连接包括开始时的所有记录,因为它是一个左外部连接。

⑧ 连接操作查找与账户 ID 匹配的记录,对于没有匹配的末尾账户,用 NULL 填充。

⑨ 移除没有流失的记录,因为这些记录的 e.account_id 不是 NULL。只有那些在 start_accounts CTE 中但不在 end_accounts CTE 中的账户保留。

⑩ 计算开始时活跃的账户数量

⑪ 计算流失账户的数量

⑫ 流失率公式:流失数量/起始数量

⑬ 保留率公式:1 − churn rate

⑭ 打印计算组件以显示如何产生流失率

以下描述程序中的每个 CTE 以及最终的 SELECT 语句,以及每个 CTE 在与 2.4.1 节中描述的步骤相关的计算中所起的作用:

  1. date_range—一个包含计算开始和结束日期的行的表。这是步骤 1。

  2. start_accounts—一个包含在开始时活跃的每个账户的行。这个表是通过根据账户在流失测量开始时活跃的条件从订阅表中选择的创建的。这是步骤 2。

  3. end_accounts—一个包含流失测量结束时每个活跃账户的行。被认为是活跃的条件与开始账户相同,使用流失测量结束日期作为标准。这是步骤 3。

  4. churned_accounts—一个包含在开始时活跃但在结束时不再活跃的每个账户的行。这个表是通过在 start_accounts 表和 end_accounts 表之间基于账户 ID 的外连接创建的,并使用 WHERE 子句删除了结束 account_id 不为 null 的账户。这是步骤 4。

  5. start_count—一个包含一行,汇总了流失测量开始时总账户数的表,以清晰起见。

  6. churn_count—一个包含在测量期间流失的总账户数的行,以清晰起见。

  7. 最终 SELECT 语句 —从 start_countchurn_count 表中获取结果,通过将值代入方程 2.1 和 2.2 来计算最终结果。这是步骤 5,程序中的最后一步。

现在你已经知道了如何从典型的订阅数据库中通过 SQL 计算由方程 2.1 和 2.2 描述的流失率和留存率。列表 2.2 在书籍网站上的代码生成的模拟数据集(www.manning.com/books/fighting-churn-with-data)和 GitHub 仓库(github.com/carl24k/fight-churn)上进行了测试,并生成了图 2.7 所示的结果。你应该按照 README 中的说明运行列表 2.2。在设置好环境(详情见 README)后,使用以下命令运行列表:

fight-churn/listings/run_churn_listing.py —chapter 2 —listing 2

与图 2.5 相比,图 2.5 显示了使用列表 2.1 计算的净留存结果,图 2.8 显示了相同的流失率,但计算的不是收入,而是从账户中计算得出的。这是预期的,因为模拟为每个客户使用相同的 MRR。

图 2.8 运行列表 2.2 在模拟数据集上的结果

2.4.4 何时使用标准流失率

当所有订阅者支付相似金额或订阅免费时,标准流失率用作主要运营指标。如果所有订阅者支付完全相同(意味着没有折扣或任何变化,或产品是免费的),则可以使用净留存查询或标准流失率查询来计算标准流失率。但如果存在适度的定价变化,或者有临时折扣,那么你应该使用本节中给出的标准流失计算方法。

如您稍后所见,标准客户流失率在客户流失分析中也扮演着特殊角色。因为客户流失分析使用的是旨在预测客户(订阅持有者)流失的模型,一个正确校准的预测客户流失模型应该能够重现标准账户客户流失率。然而,对于客户支付金额有显著差异的订阅或广泛使用折扣的订阅,标准客户流失率并不是最佳客户流失率;对于这些场景,你应该使用 MRR 客户流失率,这在第 2.6 节中介绍。

总结:如果存在适度的定价差异,包括折扣,那么你应该使用标准的客户流失计算方法。为此,适度的定价差异意味着大多数客户支付的价格接近相同,但可能会有一些客户处于较旧的计划、折扣或货币转换效应。

2.5 非订阅产品的基于事件的活动客户流失

在第一章中,我告诉你们,对抗客户流失不仅仅是针对订阅服务;它适用于任何有重复客户的任何产品或服务。现在,你们将学习专门针对非订阅场景的主要技术:基于客户活动计算客户流失。

基于事件的活动客户流失的概念与标准订阅客户流失相同,但你需要一个非正式的定义来区分客户是活跃的还是流失的。为了使用这些技术,你只需要一个事件数据仓库,这些事件是关于客户产品使用的时间戳事实。我将在第三章中更多关于事件数据的讨论,但为了简单起见,本节中的示例假设了一个具有事件时间戳的数据模型。

2.5.1 定义基于事件的活跃账户和客户流失

对于非订阅产品,最常见活跃客户的定义是客户在最近的时间窗口内使用了产品,通常是一到两个月。这一概念在图 2.9 中得到了说明。用户活动往往趋于集中,因此将活跃期视为一系列事件,任意两个连续事件之间没有大的间隔是自然的。如果超过了最大时间限制,则被视为客户流失。这样的时间限制应该足够长,以至于大多数超过限制的客户至少在一段时间内不会回来。

图片

图 2.9 事件发生频率定义了活跃期和客户流失。

基于事件的活跃客户的其他标准可能包括以下内容:

  • 只有当客户发生某些特定事件时,才认为他们是活跃的。

  • 只有当客户拥有一定数量的事件时,才认为他们是活跃的。

  • 只有当客户在最近的时间段内计算了特定的事件指标时,才认为他们是活跃的。使客户活跃(并在测试失败时流失)的特定指标示例包括零售购买的最小(或任何)支出或从广告中产生的最小收入。

这些不同的选择并没有在很大程度上改变 SQL 程序。

2.5.2 使用 SQL 进行活动型流失计算

计算事件引起的流失的 SQL 程序在列表 2.3 中展示。它与列表 2.2 几乎相同,只是没有使用订阅表。相反,测量开始时和结束时账户的 CTE 基于一个事件表(一个名为event的表)。如前所述,一个账户被认为是活跃的简单标准是它有一些最近的事件。与列表 2.2 一样,控制日期范围的参数在一个 CTE 中。

因为没有订阅的产品通常使用时间较短,列表 2.3 展示了为期一个月的流失(月度流失)。在这种情况下,另一个不同之处在于还有一个参数控制最近度的阈值。除了这些变化之外,程序的主要逻辑与列表 2.2 相同,所以这里我只是总结一下:

  1. 找到在流失测量开始时活跃的账户。在开始时活跃的账户是在一段时间内有事件,这个时间窗口的结束时间是流失测量的(名义)开始时间。

  2. 找到在流失测量结束时活跃的账户。这些账户在一段时间内有事件,这个时间窗口的结束时间是流失测量的结束。

  3. 外连接这两组账户以找到那些在开始时活跃但在结束时不再活跃的账户。这些是流失账户。

  4. 将流失账户的数量除以开始时活跃的账户数量来计算流失率。

列表 2.3 在书籍网站上的代码生成的模拟数据集(www.manning.com/books/fighting-churn-with-data)和 GitHub 仓库(github.com/carl24k/fight-churn)上进行了测试,并生成了图 2.10 所示的结果。在设置好环境后,使用以下命令运行列表:

fight-churn/listings/run_churn_listing.py —chapter 2 —listing 3

图 2.10 运行列表 2.3 在模拟数据集上的结果

与图 2.7 相比,图 2.7 展示了使用列表 2.2 计算的标准流失的结果,这个结果显示了类似的流失率,但并不完全相同。这是可以预料的,因为这些是相同的客户流失,但列表 2.3 使用稍微不同的标准来确定流失发生的时间。

列表 2.3 活动型(基于事件)流失 SQL 程序

WITH
date_range AS (                                                       ①
    SELECT  '2020-03-01'::TIMESTAMP AS start_date,                    ②
        '2020-04-01'::TIMESTAMP AS end_date,                          ②
        interval '1 months' AS inactivity_interval
), 
start_accounts AS                                                     ③
(
    SELECT DISTINCT account_id
    FROM event *e* INNER JOIN date_range d ON
        e.event_time>start_date-inactivity_interval
        AND e.event_time<= start_date)                                ④
start_count AS (                                                      ⑤
    SELECT     COUNT(start_accounts.*) AS n_start FROM start_accounts
), 
end_accounts AS                                                       ⑥
(
    SELECT DISTINCT account_id
    FROM event *e* INNER JOIN date_range d ON
        e.event_time>end_date-inactivity_interval
        AND e.event_time<= end_date                                   ⑦
), 
end_count AS (                                                        ⑧
    SELECT COUNT(end_accounts.*) AS n_end FROM end_accounts
), 
churned_accounts AS 
(
    SELECT DISTINCT s.account_id
    FROM start_accounts s 
    LEFT OUTER JOIN end_accounts *e* ON s.account_id=e.account_id
    WHERE e.account_id is null
),
churn_count AS (
    SELECT     COUNT(churned_accounts.*) AS n_churn
    FROM churned_accounts
)
SELECT 
n_churn::float/n_start::float AS churn_rate,
    1.0-n_churn::float/n_start::float AS retention_rate,
n_start,
n_churn
FROM start_count, end_count, churn_count

① 设置 SQL 计算流失的时间段

② 使用时间戳作为日期,因为事件使用时间戳

③ 此 CTE 与列表 2.2 中的相同 CTE 相似。

④ 选择在开始日期时间限制内有事件的账户

⑤ 此 CTE 与列表 2.2 中的相同 CTE 相同。

⑥ 此 CTE 与列表 2.2 中的相同 CTE 相似。

⑦ 选择在截止日期时间限制内的有事件的账户

⑧ 代码的其余部分与列表 2.2 相同。

基于订阅的流失和基于活动的流失之间的重要区别之一是,基于活动的流失需要等待每个客户知道他们是否已经流失。在订阅中,你知道订阅结束后没有替代品的第二天就是一个流失;但对于没有订阅的系统用户,你永远不知道一个事件是否是定义流失的最后一个事件,直到一段时间之后。话虽如此,在第四章中,你将看到,如果你允许订阅之间有很短的间隔(通常是几天),不计算流失,同样的逻辑也可以应用于订阅。

TAKEAWAY 你可以根据活动的最近日期来计算非订阅产品的流失率。

2.6 高级流失:月度经常性收入(MRR)流失

在前面的章节中,你了解到标准流失率在多价格订阅产品中可能存在问题。标准流失率忽略了降级销售,而降级销售应该是流失的一部分,而净保留包括降级销售,但也包括不应被视为流失的升级销售。为此,还有一种为这种情况设计的流失度量:MRR 流失。这是流失计算中最复杂的计算,但在存在多个订阅产品和价格时,它是最准确的。

TIP 如果你有支付广泛价格范围的客户:也就是说,你最有价值的客户支付的是最低价值客户的两倍或更多,请使用 MRR 流失。在企业 B2B 软件中,最有价值的客户可能支付的是最低价值客户的 100 倍左右,在这种情况下,MRR 流失是绝对必要的。

2.6.1 MRR 流失的定义和计算

MRR 流失再次是损失与起始状态之比,但现在流失率的分子是来自流失和降级销售的总体损失,而分母是客户起始时的收入。参考图 2.6,它说明了包含升级和降级销售的流失计算,MRR 流失包括完全流失导致的 MRR 直接损失(图 2.6 中的顶部向下凸起的月牙形)以及由于降级销售导致的损失(图 2.6 中的第二个向下凸起的月牙形)作为分子。它包括保留的 MRR 作为分母,但不包括升级销售的 MRR。因此,它是多价格订阅产品流失的最准确度量。

在一个方程中,MRR 流失被定义为方程 2.11 所示,而 MRR 保留则显示在方程 2.12 中。

方程 2.11 的图片 方程 2.11
方程 2.12 的图片 方程 2.12

在方程中,MRRchurned_accounts 表示所有流失账户的总 MRR,而 MRRdownsell 表示所有降级销售账户的 MRR 总减少。

图 2.11 展示了通过扩展 2.3.1 节中图 2.4 的示例来进行的 MRR churn 计算的一个例子。在图中,一位在 Premier 计划上的客户流失,两位新客户注册,两位客户更改了他们的计划。一位客户从 Standard 升级到 Premier,一位从 Premier 降级到 Standard。这个例子显示了 MRR churn 和净保留之间的重要区别:降价进入 MRR churn,但升价不进入。

使用方程式 2.11 并填写图 2.11 中示例的值,可以得到方程式 2.13:

图 2-10_E13 方程式 2.13

图 2-11

图 2.11 MRR churn 计算数据集

2.6.2 使用 SQL 进行 MRR churn 计算

MRR churn 计算的 SQL 代码显示在列表 2.4 中。它包括来自净保留 SQL 和标准 churn SQL 计算的元素。首先,我概述了计算步骤以及它们与 MRR churn 方程式(方程式 2.7)和降价/升价收入图(图 2.6)的关系。之后,我解释了这些步骤如何在 SQL 程序的 CTEs 中实现。计算步骤如下:

  1. 设置测量的起始和结束时间。

  2. 确定起始时的订阅者和总收入(图 2.6 中的顶部圆圈)。

  3. 确定结束时的订阅者(图 2.6 中的底部圆圈)。

  4. 确定流失的订阅者和他们的收入(图 2.6 中的顶部向下凸起的月牙形)。

  5. 确定降价订阅者和降价金额(图 2.6 中的第二个向下凸起的月牙形)。

  6. 将流失收入和降价订阅者收入的总额除以起始订阅者收入(方程式 2.11)。

这些步骤在 SQL 程序(列表 2.4)中作为以下 CTEs 实现:

  1. date_range—一个包含计算起始和结束日期的行表。这是步骤 1。

  2. start_accounts—一个包含在 churn 测量开始时每个活跃账户的行表。此表通过从订阅表中选择基于账户在 churn 测量开始时活跃的条件创建。这是步骤 2。

  3. end_accounts—一个包含在 churn 测量结束时每个活跃账户的行表。被视为活跃的条件与起始账户相同,使用 churn 测量结束日期作为标准。这是步骤 3。

  4. churned_accounts—一个包含在起始时活跃但在结束时不再活跃的每个账户的行表。此表通过在 start_accounts 表和 end_accounts 表之间进行外连接,并使用 WHERE 子句删除结束 account_id 不为 null 的账户创建。这是步骤 4。

  5. downsell_accounts—一个表,其中每行对应一个在开始和结束时都活跃但最终 MRR 低于开始的账户。这是通过在 start_accounts 表和 end_accounts 表之间进行 ID 连接以及一个 WHERE 子句来创建的,该子句仅选择那些最终 MRR 低于开始 MRR 的记录。这是第 5 步。

  6. start_mrr—一个包含挂账测量开始时总 MRR 的单行表。

  7. churn_mrr—一个包含挂账账户总 MRR 的单行表。

  8. downsell_mrr—一个包含所有保留账户总 MRR 的单行表。

  9. 最终 SELECT 语句 — 从单行结果表 start_mrrchurn_mrrdownsell_mrr 中获取结果,通过将值插入方程 2.7 中来计算最终结果。这是第 6 步,程序中的最后一步。

列表 2.4 MRR 挂账 SQL 程序

WITH
date_range AS (                                                            ①
    SELECT '2020-03-01'::date AS start_date, '2020-04-01'::date AS end_date
), 
start_accounts AS                                                          ②
(
    SELECT account_id, SUM(mrr) AS total_mrr                               ③
    FROM subscription s INNER JOIN date_range d ON
        s.start_date<= d.start_date                                        ④
        AND (s.end_date>d.start_date or s.end_date is null)
    GROUP BY account_id                                                    ⑤
),
end_accounts AS                                                            ⑥
(
    SELECT account_id, SUM(mrr) AS total_mrr                               ⑦
    FROM subscription s INNER JOIN date_range d ON
        s.start_date<= d.end_date                                          ④
        AND (s.end_date>d.end_date or s.end_date is null)
    GROUP BY account_id                                                    ⑧
), 
churned_accounts AS                                                        ⑨
(
    SELECT s.account_id, SUM(s.total_mrr) 
        AS total_mrr                                                       ⑩
    FROM start_accounts s 
    LEFT OUTER JOIN end_accounts *e* ON                                      ⑪
    s.account_id=e.account_id
    WHERE e.account_id is null                                             ⑫
    GROUP BY s.account_id                                                  ⑩
),
downsell_accounts AS                                                       ⑬
(
    SELECT s.account_id, s.total_mrr-e.total_mrr
        AS downsell_amount                                                 ⑭
    FROM start_accounts s 
    INNER JOIN end_accounts *e* ON s.account_id=e.account_id                 ⑮
    WHERE e.total_mrr<s.total_mrr                                          ⑯
),
start_mrr AS (                                                             ⑰
    SELECT SUM (start_accounts.total_mrr) AS start_mrr
    FROM start_accounts
), 
churn_mrr AS (                                                             ⑱
    SELECT     SUM(churned_accounts.total_mrr) AS churn_mrr
    FROM churned_accounts
), 
downsell_mrr AS (                                                          ⑲
    SELECT coalesce(SUM(downsell_accounts.downsell_amount),0.0) 
        AS downsell_mrr                                                    ⑳
    FROM downsell_accounts
)
SELECT 
    (churn_mrr+downsell_mrr) /start_mrr AS mrr_churn_rate,                 ㉑
start_mrr,                                                                 ㉒
churn_mrr, 
downsell_mrr
FROM start_mrr, churn_mrr, downsell_mrr

① 设置 SQL 计算挂账的时间段

② 包含活跃账户及其开始时 MRR 的 CTE

③ 在存在多个订阅的情况下使用汇总总和

④ 在给定日期上活跃的准则

⑤ 对总和进行 GROUP BY 聚合

⑥ 包含活跃账户及其最终 MRR 的 CTE

⑦ 在存在多个订阅的情况下使用汇总总和

⑧ 对总和进行 GROUP BY 聚合

⑨ 包含所有挂账账户 ID 的 CTE

⑩ 在存在多个订阅的情况下汇总总 MRR

⑪ 通过账户 ID 匹配记录,并在不匹配时填充 null。

⑫ 移除存在非空 e.account_id 的记录

⑬ 包含所有降级账户的 CTE

⑭ 根据定义,降级金额是正数。

⑮ 内连接选择开始和结束时都活跃的账户。

⑯ WHERE 条件选择最终支付更少的账户。

⑰ 汇总所有在开始时活跃的账户的总 MRR

⑱ 汇总所有挂账账户的总 MRR

⑲ 包含降级导致的 MRR 总减少的 CTE

⑳ 如果没有降级,则 Coalesce 用零填充。

㉑ MRR 挂账公式

㉒ 打印计算组件

列表 2.4 在书籍网站上代码生成的模拟数据集(www.manning.com/books/fighting-churn-with-data)以及书籍的 GitHub 仓库(github.com/carl24k/fight-churn)上进行了测试,并生成了图 2.12 所示的结果。您应按照 GitHub 上的 README 指示运行列表 2.4。在设置好环境(详情见 README)后,使用以下命令运行列表:

fight-churn/listings/run_churn_listing.py —chapter 2 —listing 4

与图 2.7 所示的标准流失率计算结果相比,这个结果显示了相同的流失率。这是可以预料的,因为模拟为每位客户使用了完全相同的 MRR,所以模拟没有降价销售或导致其与标准计算不同的原因。尽管如此,模拟代码可以被扩展以包括可变的 MRR,这将使这个练习更有趣。我鼓励你作为练习这样做。

图片

图 2.12 在模拟数据集上运行列表 2.4 的结果

2.6.3 MRR 流失率 vs. 账户流失率 vs. 净(留存)流失率

到目前为止,你已经了解了三种不同的流失率公式:

  • 从净留存率计算得出的净流失率

  • 标准的(基于账户的)流失率

  • MRR 流失率

你也已经了解了每种通常适用于哪种情况:

  • 净留存率和流失率——作为向投资者报告的操作指标。基于净留存的流失率在所有订阅者支付相同(或所有订阅者不支付)时等同于标准流失率。

  • 标准流失率——用于应对订阅者支付金额大致相同,但价格和折扣可能存在一些差异,这可能会使净留存率的解释复杂化。

  • MRR 流失率——用于应对不同订阅者支付金额差异较大的情况。

有一种相当常见的情况不适用于使用 MRR 流失率,那就是对于具有低于月度计划的年度计划的订阅。订阅者可以锁定一个低利率,但通过预付全年来承诺整个年份。这通常对订阅业务是有益的,因为如果做得正确,它会导致订阅者的终身价值更高,这一点将在第八章中解释。然而,当订阅者从月度计划切换到年度计划时,这会被视为降价销售,此类变化会对报告的流失率产生负面影响。在这种情况下,可能最好使用标准流失率。

当不同类型的账户的 MRR 之间存在真正巨大的差异时,MRR 流失率最为合适:在 B2B 软件销售中,大账户可以轻松支付比小账户多 10 倍或更多的金额。对于具有此类价格差异的公司,这三个流失率指标之间通常存在一致的关系。

总结:标准流失率 > MRR 流失率 > 净流失率

你可能会预期 MRR 流失率通常会比标准流失率指标高,因为 MRR 流失率包括了降价销售的影响,而标准流失率则不包括。然而,正如我将在后面的章节中讨论的,几乎总是情况是,支付更多费用的账户比支付较少费用的多价格产品账户流失得更少。如果你不从事这样的产品,这可能会显得有些矛盾,因为按照付费更多的逻辑,应该会让客户更不满意。然而,在 B2B 产品中,更高的价格往往属于使用产品更多(有更多用户)的大型公司订阅者,而大型公司几乎总是比小型公司更稳定。此外,支付更多费用的公司往往更愿意长期使用产品,因为他们在购买前有更长的考虑过程,并且在订阅产品的设置和运营上投入更多。因此,计算所有订阅者平等的标准化流失率几乎总是高于 B2B 产品的 MRR 流失率。

从净留存率计算出的净流失率几乎总是所有流失率指标中最小的。这是因为,除了反映大型公司订阅者的低流失率外,它还将保留账户中的升级销售计入流失率。正如之前提到的,当升级销售超过降价销售和流失时,从净留存率计算出的净流失率甚至可能是负数。

2.7 流失率测量转换

到目前为止,计算假设你想要计算一个月的流失率。正如我提到的,可以计算任何时间段的流失率,大多数 B2B 产品订阅将流失率计算为年度数字,以更好地反映订阅的典型长度。这很好,因为计算是相同的,使用相同的代码计算年度流失率就像改变一年中相隔一年的起始和结束日期一样简单。但假设一家公司想要计算年度流失率,但运营时间不到一年,或者由于其他原因在数据库中只有不到一年的数据?这不是问题,因为你可以将较短时间(如一个月)的流失率转换为较长时间(如一年)的流失率。但是,一个月内测量的流失率和一年内测量的流失率之间的关系并不完全直接。

揭秘:年度流失率不是月度流失率的 12 倍。本节展示了不同时间框架下流失率测量之间的关系,以及如何将一个月内的流失率测量转换为年度流失率,反之亦然。

2.7.1 生存分析(高级)

注意,本节包含很多方程式。如果你不喜欢数学,可以跳到下一节开始处显示的答案。

理解每月和年度流失率之间关系的关键是,将保留客户视为幸存者,并观察他们在多个月份中的存活情况。术语“幸存者”来自生物学中的种群研究,这正是这种分析起源的地方,但将保留客户视为在流失如死亡的过程中的幸存者是合理的。这如图 2.13 所示,展示了如果存在每月流失率,以及等价的每月保留率(为流失率的倒数),如图中所述,初始订阅者池会发生什么。图 2.13 展示了左侧的一个简单具体例子,以及右侧的相同过程用代数方程表示。

下面是如何在一年中演变流失过程,从 100 个账户和 10%(0.1)的流失率开始,如图 2.13 的左侧所示:

  1. 在第一个月初,有 100 个订阅者。在第一个月中,100 × 0.1 = 10 个客户流失,剩下 90 个。这相当于 100 乘以保留率 0.9。

  2. 在第二个月初,有 90 个订阅者,在第二个月中,90 × 0.1 = 9 个客户流失,剩下 81 个。这相当于原始的 100 乘以保留率的平方,因为 81 = 100 × 0.81 = 100 × 0.9²。

    注意,因为流失率是流失账户的百分比,如果一开始账户较少,那么流失的账户也会较少,尽管流失率是相同的。

  3. 在第三个月初,有 81 个订阅者,在第三个月中,81 × 0.1 ≈ 8 个客户流失,剩下 73 个。这相当于原始的 100 乘以保留率的立方,因为 73 = 100 × 0.73 ≈ 100 × 0.9³。

  4. 在随后的几个月中持续的模式是,12 个月后,剩下 100 × 0.9¹² = 28 个客户。

图 2.13 每月流失率下账户一年的存活情况

从方程(图 2.13 的右侧)来看,这是前几个月每月的过程,然后展示这种一般模式:

  1. 在第一个月初,有 N 个订阅者,但根据流失率的定义,在第一个月中,cN 个客户流失。月底剩余的订阅者数为(1 − c)N或 rN。

  2. 在第二个月初,有 rN = (1 − c)N个账户,在一个月中流失c(1 − c)N个。两个月后剩余的账户数为起始数(1 − c)N减去流失数c(1 − c)N,即(1 − c)Nc(1 − c)N

    工作出代数并证明从保留率的定义来看,这并不太难。

    (1 − c)N − [c(1 − c)N = (1 − c)N × (1 − c) = (1 − c)² N

    (1 − c)² N = r² N

  3. 在第三个月初,有 r² N = (1 − cN 的账户,并且在一个月内有 c(1 − cN 的账户流失。三个月后剩余的账户数是起始数 (1 − cN 减去流失的账户数 c(1 − cN,或者 (1 − cNc(1 − cN

    计算代数并不困难,可以证明从保留率的定义

    (1 − cN − [c(1 − c)²]N = (1 − cN × (1 − c) = (1 − cN

    并且

    (1 − cN = r³ N

  4. 在随后的月份中持续的模式是,经过 x 个月后,有

    r^(x) N = (1 − c)^(x) N

这表明在多个月份中保留的账户数量等于保留率的次方乘以起始账户数量。

2.7.2 流失率转换

保留率的跨月关系是将月度流失率转换为年度测量的关键。在前一节中,我向你展示了在一个月流失率为 c,月度保留率为 r = (1 − c) 的情况下,一年后保留的客户数量是

N[year] = r¹²N

因此,一年的保留率,用 R 表示,是

R = r¹² 方程式 2.14

方程式 2.14 展示了如何将月度保留率 r 转换为年度保留率 R。这直接来自定义,因为一年后保留的客户数量必须等于起始数量乘以年度保留率。同样,从定义中,你知道年度流失率,我用 C 表示,必须等于 100% 减去年度保留率。因此,年度流失率 C 必须等于

C = 100% − R**C = 100% − r¹²C = 100% − (1 − c)¹² 方程式 2.15

简而言之,年度流失率等于 1 减去 1 减去月度流失率的十二次方。这听起来很复杂,但用保留率来理解就很容易了。年度保留率是月度保留率的十二次方。

要点:要将月度流失率转换为年度,使用年度保留率是月度保留率的十二次方的这一事实,以及保留率是 1 减去流失率。

注意,年度保留率低于月度保留率,因为将小于 1 的数提高到任何次方都会进一步减小它。这很合理,因为一年中流失的账户必须比一个月中多:订阅者有 12 倍更长的时间流失。另一方面,年度流失率必须大于月度流失率,因为有更多的时间流失。

那么如何将年度流失率的测量转换为月度呢?我不会深入所有细节,但反向关系是相同的。如果你参考方程式 2.14 并对等式的两边取十二次方根,你会得到

方程式 2.16

公式 2.16 展示了如何将年度流失率 R 转换为月度流失率 r。我使用符号表示一个数的十二次方根是通过将该数提高到十二次方(1/12)的幂来实现的。如果您不熟悉根运算,请回忆一下,一个数x的平方根是那个数,当它平方时给出 x。十二次方根定义类似:一个数x的十二次方根是那个数,当它提高到十二次方时给出 x。不用担心,没有人会在头脑中计算十二次方根和幂,但在任何编程语言中这都是小菜一碟。取十二次方根等同于将一个数提高到 1/12 的幂,这是大多数编程语言中实现此类根的方式。

对于年度留存率的测量,等效月度留存率只是年度测量的十二次方根。同样,此方程从年度测量计算等效月度流失率:

图 2-13_E17 公式 2.17

公式 2.17 展示了如何将年度流失率 C 转换为月度流失率 c。

总结:要将年度客户流失率转换为月度流失率,可以使用以下事实:月度留存率是年度留存率的十二次方根,而留存率是 1 减去流失率。

2.7.3:在 SQL 中转换任何流失率测量窗口

您可以轻松地将任何流失率测量窗口转换为任何其他测量窗口的等效流失率。如果您需要测量订阅数据库中数据少于一年的公司的流失率,这是一个很好的技巧。您可以使用可用的所有数据(无论是 2 个月、6 个月还是 10 个月)来计算流失率,然后将结果转换为年度流失率。我不会深入细节,但对于任何流失率测量,c'在 p 天内超过任何时间段的等效年度流失率 C:

C = 100% − (1 − c')^(365/p) 公式 2.18

在这种情况下,所测量的留存率(1 − c')被提高到的是 365 除以时间周期长度 p。如果 p 是 1 个月,这将减少到(大约)12。同样,可以从任何 p 天的时间段的流失率 c'中计算出月度流失率 c,使用公式 2.19:

c = 100% − (1 − c')^((365/12)[p]) 公式 2.19

回到从订阅数据库中计算流失率的计算,从任何时间段的流失率测量中很容易计算出月度和年度流失率。列表 2.5 显示了必要的 SQL SELECT 语句,假设与列表 2.2 中常规流失率计算相同的公共表表达式,除了date_range CTE 中的开始和结束日期可以是任何日期(即结束日期在开始日期之后)。此 SQL 通过动态计算时间周期 p 作为SELECT语句的一部分来实现公式 2.13 和 2.14。

列表 2.5:不均匀时间段的流失率 SQL SELECT 语句

SELECT 
    n_start,                                                                ①
    n_churn,                                                                ②
n_churn::float/n_start::float AS measured_churn,                            ③
end_date-start_date AS period_days,                                         ④
1.0-POWER(1.0-n_churn::float/n_start::float,365.0/(end_date-start_date)::float) 
        AS annual_churn,                                                    ⑤
1.0-POWER(1.0-n_churn::float/n_start::float,(365.0/12.0)/(end_date-start_date)::float)
        AS monthly_churn                                                    ⑥

① 如列表 2.2 所示的起始订阅者数量

② 如列表 2.2 所示的客户流失订阅者数量

③ 标准客户流失率计算(公式 2.1)

④ 显示开始日期和结束日期之间的天数差异

⑤ 公式 2.13

⑥ 公式 2.14

列表 2.5 中使用 SELECT 语句的结果示例显示在图 2.14 中。在这种情况下,因为测量周期在一个月到一年之间,等效年度客户流失率高于测量的客户流失率,而等效月度客户流失率低于测量的客户流失率。

列表 2.5 在书籍网站上提供的代码生成的模拟数据集上进行了测试(www.manning.com/books/fighting-churn-with-data)以及在 GitHub 仓库(github.com/carl24k/fight-churn)中,并生成了图 2.14 中所示的结果。你应该通过使用这个列表包装脚本(与章节早期相同的命令),将列表参数更改为 —listing 5 来运行列表 2.5:

fight-churn/listings/run_churn_listing.py —chapter 2 —listing 5

与图 2.8 相比,图 2.8 显示了使用列表 2.2 计算的标准客户流失率的结果,这个结果衡量了 92 天的客户流失率(这是一个假设的例子,假设有 92 天的数据来计算年度客户流失率)。结果包括月度客户流失率和年度化客户流失率。月度客户流失率与图 2.8 中的结果相似,但并不完全相同——这个月度客户流失率是整个 92 天的平均月度流失率。

图 2.14 运行列表 2.5 在模拟数据集上的结果

2.7.4 选择客户流失率测量窗口

既然你有选择,你可能想知道应该选择什么时间段来衡量客户流失率。一般来说,你应该选择一个接近典型订阅续订期限的时间段来衡量客户流失率(如果有订阅的话)。然后,如果你需要以其他方式报告客户流失,你可以使用本节中的方法来扩展测量。

吸收要点:消费者订阅的客户流失率通常按月度率衡量,而商业订阅的客户流失率按年度率衡量。

如果你以与典型订阅不同的时间段来衡量客户流失率,可能会出现问题。如果你的订阅主要是按月或按年进行,问题会有所不同。

首先考虑年度订阅的情况。如果你的订阅主要是年度的,并且你在一月内测量客户流失率,你必须确保你选择的月份大约有年度续订的十二分之一。否则,客户流失率的测量将会存在偏差。对于许多企业来说,大多数续订都集中在特定季节;如果情况如此,并且你在一年中的其他时间测量客户流失率,你将看到人为降低的客户流失率。相反,如果你在恰好有大量年度客户需要续订的月份测量客户流失率,你可能会通过在短时间内看到大部分年度的客户流失而计算出人为高的客户流失率。

如果你的订阅主要是月度的,并且你在一年的时间里测量客户流失率,那么客户流失率的计算将错过在两个日期之间开始和流失的账户。为了说明这一点,请注意,本章中所有的客户流失率计算都只检查了两个日期的账户,而在两个日期之间活跃的账户将被忽略。这导致了对客户流失率的低估。一般来说,如果你使用的时间周期长于最短的订阅,这会成为一个问题。

警告:在不同于典型订阅长度的时间窗口内测量客户流失率可能会导致客户流失率计算错误。

2.7.5 季节性和客户流失率

在上一节中,我警告过你,使用不同于典型订阅长度的时间框架来计算客户流失率可能会引起问题。在计算客户流失率时,还有另一种类型的问题需要注意,这主要适用于月度客户流失率的计算:季节性。

定义:季节性——在一年中的特定时间发生的变动。

许多订阅业务在客户流失率上有季节性变化,如果你使用月度时间窗口来测量客户流失率,你可能会发现由于季节性影响,客户流失率在一年中上下波动。挑战在于,当你开始尝试降低客户流失率时,如果你不纠正季节性影响,你可能很难知道你在客户流失率上看到的变化是由于你的客户流失率降低努力还是季节性变化。

警告:如果你使用月度客户流失率,季节性可能会使评估客户流失率降低措施的影响变得困难。

对于年度测量,季节性问题较少,因为年度流失率测量总是包括一年中的每个季节。如果你在 1 月份进行一次年度流失率测量,然后在 2 月份进行另一次年度流失率测量,两次年度流失率测量都包括每个季节:两个相隔一个月的年度流失率测量之间的差异反映了该月流失率与一年前同一月的流失率之间的差异,因此流失率的变动已经控制了季节性。(如果许多年度续订发生在某个季节,那么这个季节可能是流失率显著变化的时期;在其余年份,流失率的变动较小,因为续订次数较少。但业务中合同续订的关键季节并不等同于季节性。)

如果你使用月度流失率,你会怎么做?首先,尽可能多地测量过去几年的月度流失率,并尝试确定是否存在显著的季节性模式。你需要至少两年的历史数据来查看是否存在季节性模式;仅有一年的数据,你无法判断一个模式是否为季节性的,或者它是否有其他原因。

如果你确实有季节性模式,那么你有几种选择来纠正它。处理季节性的临时方法是只是意识到季节性趋势,并寻找其他导致流失率变化的变化,这些变化来自减少流失的努力或商业环境的改变,这些变化比通常的季节性变化要大得多。如果你受过统计学训练,你可以通过使用时间序列分析的正确技术来严格地做到这一点。这种高级统计学超出了本书的范围,但如果你对《Ruey S. Tsay 的金融时间序列分析》(第 3 版,Wiley,2010)感兴趣,可以参考。有一些处理流失率季节性的方法不需要高级统计学,但它们涉及稍微复杂一些的流失率计算,并且至少需要两年的数据。在这里详细讨论这些内容太多,但我可以给你一些想法。

计算一个控制季节性的流失率的方法之一是在按月订阅上进行年度流失率计算,但使用按月订阅的年度流失率来解决问题。回想一下,在上一节中,你了解到如果你在按月订阅上进行年度流失率计算,你会错过年中注册并流失的账户。一个解决方案是,你不再查看两年间每个日期活跃的所有账户,而是查看每个日期前一年内活跃的所有账户。这个过程如下:

  1. 找出在第一年任何时间活跃的所有账户。

  2. 找出在第二年任何时间活跃的所有账户。

  3. 通过比较这两个集合使用外部连接来找出流失账户。

  4. 将失去的账户数量除以第一年的账户数量。

这样计算的流失率是一个年度流失率,不会错过年中流失,并包括所有季节。如果你一个月(或一个季度)后重复计算,流失率的变化反映了该月(或该季度)的流失与一年前同一时间的流失之间的差异。它考虑了季节性。

这种方法的一个缺点是它允许在任何一年内的新注册,并且客户不会被计算为流失。如果你有很多账户流失后又重新注册,你应该坚持使用常规的月度流失率计算,并不同样处理季节性。

另一种处理月度流失率季节性的相对简单方法是,每月计算月度流失率,然后为了比较目的对季度(或一年)进行平均。而不是比较一个月的流失率与下一个月,这可能会受到季节性的影响,而是比较去年 12 个月的月度流失率的平均值与上一年 12 个月的月度流失率的平均值。或者比较过去三个月的平均流失率与一年前同一三个月的平均流失率。这样你就可以看到你去年为改善流失率所做的一切是否对一年前同一季度的流失率产生了影响。因为你在比较同一季度,所以控制了季节性。你可以用这种方法来比较一个月,将一个月与一年前同一个月进行比较,这也控制了季节性。此外,请注意,将一个日历月的流失率与一年前同一期间的流失率进行比较,只需要 13 个月的数据,所以这可能是新公司最好的选择。

摘要

  • 流失率衡量在一定时间内有多少订阅者和/或多少收入从一项服务中流失。

  • 流失率和留存率根据留存加流失等于 100%的关系可以互换。

  • 可以使用 SQL 在订阅数据库上计算不同版本的流失率。

  • 外连接在 SQL 中用于识别流失的账户。

  • 标准流失率衡量取消订阅的账户数量,不受促销和降价的影响。标准流失率对于价格相差不远或可能有折扣的订阅产品最有用。

  • 净留存率包括促销和降级的双重影响,这使得它对于对抗流失不那么有用,但它是一个流行的报告指标。

  • 如果所有订阅者支付相同(或什么都不支付),净留存率等于标准留存率,净流失率等于标准流失率。

  • MRR 流失率包括降级的影响,但不包括升级,是衡量订阅者支付广泛价格时的流失率的最佳指标,这在 B2B 产品中很常见。

  • 对于非订阅产品,可以通过定义客户在最近时间段内发生事件时为活跃客户,基于事件数据来衡量挂失率。然后,挂失率计算为两个不同日期上活跃账户集合之间的差异。

  • 在任何时间段内测量的挂失率都可以转换为任何其他时间段的等效挂失率。

  • 挂失率通过保留率的生存分析从一段时间转换为另一段时间,然后保留率再转换回挂失率。

  • 要转换挂失率,将相应的保留率提高到某个幂次以增加时间段;使用保留率的根来减少时间段。

  • 挂失率通常按月度比率衡量消费者产品,按年度比率衡量商业产品。

  • 如果你在与典型订阅长度不同的时间尺度上测量挂失率,可能会出现问题。

  • 对于月度挂失率,在解释挂失率的变化时,季节性可能成为一个问题。

3 测量客户

本章涵盖

  • 测量客户事件的计数、平均值和总和

  • 在指标上运行 QA 测试

  • 选择指标的时间段和时间戳

  • 测量客户使用服务的时间长度

  • 测量订阅指标

如果你正在运营一个与用户或客户有重复互动的产品或服务,那么你应该在数据仓库中收集这些互动的数据。在这个背景下,互动指的是用户与产品、服务或平台之间的互动。(也可以包括通过平台介导的其他用户之间的互动。)通常将这些互动简称为事件,因为数据仓库中跟踪的互动通常都有一个时间戳,告诉你它们发生的时间。

定义:事件——关于用户行为的任何事实,存储在数据仓库中,并带有特定的时间戳。

我不会教你如何收集这些数据,但我会教你如何有效地利用这些数据。使用原始数据对抗流失的第一步是将事件数据转换为一系列总结事件并共同描绘用户行为轮廓的测量值。这些测量值通常被称为行为指标,或简称指标。

定义:指标——对用户行为的任何总结性测量。指标也有时间戳,尽管它们总结的是超过一个时间点的行为。

将相关事件转换为测量是必要的,因为每个事件就像一个大画面中的一个微小点:单独来看,一个事件通常意义不大。但尽管我们需要从每个单独的互动中退出来,我们不会退得太远。每个测量都是针对每个客户单独进行的,并且在他们作为客户的整个生命周期中反复进行。这是因为用户参与度和流失是每个个体的动态过程,你需要观察这些指标在订阅者生命周期中的变化,以了解订阅者参与度。

为了教授这个主题,我假设你已经收集了事件,但还没有对它们进行行为测量。如果你没有自己的数据,这本书的可下载代码中有一个模拟程序为你生成人工数据,以便你在代码上运行。请参阅www.manning.com/books/fighting-churn-with-datagithub.com/carl24k/fight-churn/tree/master/data-generation中的代码以及 README 文件中的详细说明。运行脚本安装模式(fight-churn/ data-generation/churndb.py),然后运行在虚构社交网络上生成客户、订阅和事件的模拟(fight-churn/data-generation/churnsim.py)。如果你已经生成了用于运行第二章示例的数据,那么你已经准备好了!

如果你正在与一个活的产品或服务合作,并且已经进行行为测量,你将学习一些关于指标和检查数据质量的技术的新想法;将现有的测量应用于分析不会很难。如果你在一个活的服务中工作,但尚未收集数据,你将获得关于收集何种数据的卓越理解。与本书的整体场景相关,本章涵盖了从事件数据仓库计算行为指标(图 3.1,在第一章中解释)。

图 3.1 本章在用数据对抗流失的过程中的位置

这是一个很大的章节,不仅从长度上讲:良好的行为指标是成功流失分析中最重要的一步,本章解释了许多可能阻止你获得最佳结果的陷阱:

  • 在 3.1 节中,我们从一个关于从事件制作行为指标的概念的简要概述开始。

  • 3.2 节介绍了一个典型或最小的事件数据仓库数据库模式,该模式用于本书其余部分的 SQL 程序。

  • 从 3.3 节开始,你将学习最通用的行为指标:在特定时间窗口内测量的计数、平均值和总和。我还教你一些关于测量遵循每周周期和指标测量时间戳的最佳实践。

  • 在 3.4-3.6 节中,你将学习更多关于如何计算 3.3 节中引入的指标的实际细节。

除了学习如何计算指标外,学习通过运行质量保证(QA)测试来检查结果也是合适的。这是必要的,因为数据仓库中的客户事件数据通常不可靠。这种不可靠性可能以不同的方式表现出来;例如,事件在网络传输到数据仓库之前可能会丢失。一般来说,事件数据不会受到很多审查,因此进行流失分析的数据人员可能是第一个检查事件数据字段是否正确的人。

  • 3.7 节介绍了针对指标的时序 QA 测试。QA 揭示了常见的挑战:并非所有事件都同样频繁,因此没有单一的时间框架适用于所有类型的事件。

  • 3.8 节展示了关于事件的一些基本 QA,有助于澄清事件频率的情况。

  • 在 3.9 节中,我向你展示如何使用事件 QA 解决选择指标时间框架的问题。

注意:这次讨论的顺序与实际应用不符。通常,你首先进行事件 QA,然后计算指标,但我想先展示指标的样子,然后再深入 QA 的细节。

在本章的结尾,我们转换了方向:

  • 在第 3.10 节中,你将学习如何对每个客户进行一项重要的测量:他们在当前订阅(或在没有订阅的情况下,当前参与)中的客户时长。这被称为客户或账户任期(而非年龄),以免与客户的实际年龄混淆。

  • 第 3.11 节介绍了一种从订阅中提取数据并将其转换为与其他行为指标可比较的指标的技术。

特征工程与指标设计

接受过数据科学、机器学习或统计学培训的人将我刚才描述的主题称为特征工程。特征工程这个术语的问题在于它很容易与软件产品功能和通过软件工程创建这些功能混淆。相反,我将坚持使用商业用户能够理解的语言,并将这个过程称为指标设计。

警告:术语“特征工程”对于没有接受过数据科学、机器学习或统计学培训的人来说可能会感到困惑。在与您的商业同事交谈时,请避免使用该术语;改用指标设计。这在软件公司尤其如此。

3.1 从事件到指标

在本节中,我首先介绍将事件转换为指标的概念,而不需要任何代码,然后向您展示 SQL。想象一下,你正在收集数据仓库中的登录事件,并希望将其转换为可用的信息。对于每个用户,你有一系列事件,如图 3.2 的上半部分所示。首先,我们只关注单个事件的系列:登录。在典型的在线产品场景中,有许多类型的事件,事件可以随时发生。对于某些类型的事件,甚至可以同时发生多个事件。为了找到订阅者的可比较指标,使用时间段进行测量,如图 3.2 的下半部分所示。

图 3.2

图 3.2 事件转换为时间窗口指标

在这个上下文中,时间段指的是一个时间范围(一个开始时间和一个结束时间),用于测量数据。但这些时间段是相对于每个测量的观测时间来定义的,因此时间段通常用其持续时间或长度来描述。

定义:时间段——在基于事件的指标计算中,用于测量的事件的时间窗口。时间段以其持续时间来描述,因为每个测量(开始和结束时间)的具体窗口是相对于测量日期确定的。

例如,图 3.2 中具有四周周期的指标在四个周长的窗口内进行重复测量。指标计算可能是计算每个结果窗口中的事件数量,或者可以使用更复杂的测量,如本章后面所述。我将所有指标周期定义为偶数周;我在第 3.4.1 节中解释了原因。

还请注意,这些指标是在周期结束后的一天计算的,因此事件观察是完整的。例如,在 1 月 29 日,您可以计算涵盖 1 月 1 日至 28 日的四周内每个订阅者的登录次数。然后,在 2 月 26 日,您可以测量从 1 月 29 日至 2 月 25 日的四周内的登录次数,依此类推。我将在 3.4.2 节中详细讨论这个问题。

3.2 事件数据仓库架构

本章探讨了如何使用代码计算指标;但为了打下基础,我需要解释事件如何在数据仓库中存储。数据仓库有很多种类型,我假设您可以使用 SQL 查询数据仓库。只要数据量不是很大,您可以使用事务 SQL 数据库作为事件数据仓库。本书中的示例是在 Postgres(或 PostgreSQL)中生成的。

表 3.1 展示了典型事件数据架构的关键元素。此架构用于所有与事件相关的 SQL 代码列表。(有关如何使用此架构设置数据库以及如何用模拟数据填充数据库的详细说明,请参阅本书的可下载代码。)以下此类表典型的最小字段集包括:

  • account_id—账户持有人或用户的标识符,用于将事件追踪回创建它们的客户。

  • event_type_id—由于事件通常有多种类型,这是到描述类型的单独表的键外键。

  • event_time—一个时间戳,每个事件都必须有。

表 3.2 展示了一个相关的事件类型表,以便事件的字符串名称不会重复(出于性能原因,这是数据库或数据仓库中的标准做法)。总之,事件是发生在某人(账户 ID)身上(事件类型 ID)的某事(事件时间)。以下附加字段也可以包含在此类表中,但不是必需的:

  • event_id—事件在数据仓库中可能包含或不包含唯一标识符。对于分析来说,这并不相关,因为事件通常没有唯一约束。

  • user_id—用户 ID 可以存在(除了账户 ID 之外),尤其是在有多个个人与单个账户关联的服务中。

  • event_data—事件通常有许多可选数据字段,这些字段提供了关于事件的额外信息。这些字段通常是数字,但也可能包括文本信息。

如果您熟悉数据仓库,您会看到事件架构与任何事实表架构非常典型,只是事件的数据字段是可选的(在某些类型的数据仓库中通常需要这些数字字段)。

表 3.1 典型事件数据架构

类型 备注
account_id integerchar
event_type_id integerchar event_type_name 的键
event_time timestamp
event_id integerchar 可选
user_id integerchar 可选
event_data_1 floatchar 可选
. . .
event_data_n floatchar 可选

表 3.2 典型事件类型数据模式

类型 备注
event_type_name char 唯一
event_type_id integerchar event_type_name 的键

3.3 在一个时间周期内计数事件

图 3.3 是图 3.2 中场景的延续:它显示了基于表 3.1 中事件模式计算一个账户和一个事件的指标细节。表中的每个事件都映射到一个对应的时间段,总计数是该时间段的结果。因为时间段不重叠,每个事件只计算一次。对于每个事件和账户都会重复计算,如果你打算手动或甚至在电子表格中做这项工作,将会很繁琐。幸运的是,SQL 提供了一种简洁的语言来表达和实现此类计算。

在大规模上进行这些计算涉及你在第二章中看到的一个挑战:这些数据可能很大。就像计算流失率一样,我们使用 SQL 在数据仓库中完成所有工作,而不是提取数据然后在 Python 这样的编程语言中处理它。

注意:这又是一个你可能习惯于在过程性语言中执行此类计算的地方,但我要求你保留判断,并学习 SQL 如何成为此类计算的有力工具。

列表 3.1 显示了在单一时间框架内计数事件数量的 SQL(如图 3.2 所示)。查询的主要步骤如下:

  1. 使用公共表表达式(CTE,在第二章中介绍)设置测量日期。

  2. 选择具有正确类型的时间框架内的所有事件。

  3. 按账户聚合计数事件。

图 3.3 从事件模式进行指标计算

如果你创建了模拟的社会网络数据集,它包括用户喜欢帖子的一个事件。列表 3.1 显示了计数这些事件的查询,图 3.4 展示了结果。对于在指标计算率前 28 天内对任何帖子进行过点赞的每个账户,都有一个计数。

图 3.4 展示了在模拟的社会网络数据集上执行事件计数 SQL(列表 3.1)的结果。你的结果将不同,因为数据是随机模拟的。

列表 3.1 在时间窗口内计数事件数量

WITH calc_date AS (                                           ①
    SELECT '2020-05-06'::timestamp  AS the_date   
) 
SELECT account_id, COUNT(*) AS n_like_permonth                ②
FROM event *e* INNER JOIN calc_date d ON
    e.event_time <= d.the_date                                ③
    AND e.event_time > d.the_date - interval '28 day'         ④
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
WHERE t.event_type_name='like'                                ⑤
GROUP BY account_id;                                          ⑥

① 此 CTE 设置了用于计算测量的日期。

② 选择账户、日期和计数

③ 设置测量的周期

④ 使用“大于但不等于”避免重复计数。

⑤ 选择我们进行测量的事件

⑥ GROUP BY 聚合函数为每个账户产生一个测量值。

运行列表 3.1 以查看你自己的数据结果。如果你已经生成了默认的模拟数据集并按照 README 文件中指定的设置配置了环境,你可以使用以下命令运行列表:

fight-churn/listings/run_churn_listing.py —chapter 3 —listing 1

包装程序在连接到数据库、执行查询并打印一些结果之前打印 SQL。如果你不想使用包装程序,列表 3.1 的源代码可以在书籍代码的 fight-churn/listings/chap3 中找到。请注意,列表代码存储为带有绑定变量(以 % 开头)的模板;你可以修改绑定变量,并使用你选择的 SQL 工具运行查询。你的结果将类似于图 3.4,但并不完全相同,因为数据是随机模拟的。

如果你熟悉 SQL,你可能会对那个查询有一些疑问:为什么我在窗口的末尾使用“小于或等于”的日期条件,但在查询的末尾使用“大于且不等于”?为什么不使用“between”语法?这是为了避免在重复测量时在边界上重复计算任何事件。一个相关的问题可能是为什么我不使用 SQL 窗口函数来计算结果。原因是 SQL 窗口函数通常在固定数量的记录上操作,但给定日期范围内的活动数量是不固定的。窗口的边界是由日期条件设置的,而不是窗口中的事件数量。

3.4 指标周期定义的细节

现在你已经知道了如何计算指标,我将讨论选择指标周期。这些细节可能看起来微不足道,但你可能会惊讶它们对分析有效性的影响有多大。

3.4.1 周行为周期

你可能想知道为什么我使用了四周期的指标测量,而不是日历月份。为了理解这一点,你需要意识到人类活动遵循一个周周期,因此你的数据仓库中的事件也可能遵循一个周周期。如果你的产品是人们用于工作的事物,那么大多数事件将在周一至周五发生,而周六和周日将会有较少的事件。另一方面,如果你的产品是人们用于休闲的事物,如观看视频或玩游戏,那么最频繁的使用将在周五至周日,而周一至周四可能相对较慢。

使用定义为偶数周的周期指标的原因是,每个用于计算的测量窗口都有相同数量的高使用日和低使用日。例如,想象一个周末使用量最高的消费者产品。有五个周末的月份看起来比只有四个周末的月份有大约 20% 的更多事件。但认为这代表实际使用量的增加是错误的,因为这是由测量窗口不均匀的事实造成的。

技巧:大多数人类行为遵循每周周期。因此,对于使用一个月或更短周期的指标,最好使用 7 天的倍数时间窗口进行测量。

Klipfolio 上的用户行为是每周行为周期的完美例子。Klipfolio 是一家软件即服务(SaaS)公司,允许企业创建其关键指标的在线仪表板(在第一章中介绍)。图 3.5 展示了 Klipfolio 每日仪表板查看次数的每周周期,它显示了商业对商业(B2B)软件使用的每周周期。很明显,在工作日使用量显著更高,而在周末使用量则慢得多:平均而言,工作日比周末有 40%更多的仪表板查看次数。

图片

图 3.5 Klipfolio 每日总仪表板查看次数

话虽如此,请注意,在偶数周进行测量对于小测量窗口来说很重要:如果测量周期超过大约 12 周,那么增加或减少一个周末不会产生太大影响。例如,对于一年的测量,没有必要选择 52 周,或 364 天,而不是 365 天。

3.4.2 指标测量的时间戳

另一个重要的问题是测量如何进行时间戳。

定义 时间戳——代表指标的单个日期和时间,该指标是在时间窗口内的事件上进行的计算。

这样的测量需要有一个时间戳来表示它们覆盖的时期,因为你会反复进行测量并分析它们随时间的变化。一个看似微不足道但实际上很微妙的问题是以下内容。假设你测量登录次数:

  • 你测量从 1 月 1 日到 1 月 28 日(包括)的所有事件。

  • 你在 1 月 29 日进行测量。

你应该用哪一天的时间戳来标记那个测量?

  1. 1 月 1 日

  2. 1 月 28 日

  3. 1 月 29 日

  4. 一些其他选项

最佳实践是在测量周期结束后立即用日期/时间标记指标:在这种情况下,对于 1 月 1 日至 28 日的测量,午夜是 1 月 29 日。这是一个惯例,它可能看起来是任意的,但错误的选择会导致不希望出现的复杂情况。有几个原因你需要同步测量以在某个时间点创建客户的快照。如果在观察窗口结束时标记时间戳,那么创建同步快照就最容易,因为那时你只需选择相同时间戳的所有指标。

要点:对于行为指标的时间戳,最佳选择是客户测量时的时间日期,假设它是实时计算,尽可能早的时间。即使测量是在稍后的日期计算的,这也适用。

替代方案的问题如下:

  • 如果你在单个分析中使用不同长度的窗口,以窗口的开始日期作为时间戳会导致问题。在这种情况下,你需要从时间戳加上时间周期计算同步日期。

  • 你进行测量的时间不是一个好的时间戳,因为它可能是几天或几个月之后,这引入了不确定性。

  • 使用测量期最后一天(午夜)作为时间戳是微妙地误导性的,因为它暗示你可以在还有一天剩余的情况下观察到整个周期的测量值。如果你在测量完成前 24 小时进行时间戳,然后需要与其他数据源同步测量值,可能会引入微小的错误。

这种在测量后一天的时期使用时间戳的惯例可能一开始会有些令人困惑,因为许多人使用月初作为日历月的测量时间戳,即使测量是在下一个月的第一天进行的。(这可能会使习惯于这种思维方式的其他组织成员感到困惑。)同样,使用四周期而不是日历月也是如此。但是四周“月份”和测量后一天的时间戳有助于分析,我建议将其作为最佳实践。

3.5 在不同时间点进行测量

要理解客户流失,你需要比较他们在生命周期不同点的客户行为测量值。这需要比你刚刚学到的稍微高级一点的指标计算技术。

3.5.1 重叠测量窗口

要比较客户行为测量值随客户生命周期变化的情况,你需要定期在时间上重复计算指标。然而,图 3.2 中显示的简单方法存在问题:如果你真的遵循这种方法,你将每四周只更新一次指标。四周的测量间隔并不非常动态。在四周内可能发生很多事情,你可能需要更频繁地检查客户的行为。图 3.6 说明了解决方案。答案是更频繁地重复四周测量:在这种情况下,每周。

图片

图 3.6 使用重叠时间窗口计算指标

如图 3.6 所示,产生的四周窗口重叠。您还可以看到,测量值逐渐跟踪图 3.2 中显示的月度测量值。对于用户 1,前四周的非重叠测量值是 2,4,2。重叠的测量值包括值为 3 的中间点:2,3,4,3,2,代表过渡期。

图 3.6 中展示的计算细节在图 3.7 中展示了前四个周期。由于计算窗口重叠,第四期的结束日期正好是第一期结束日期后的四周。此外,每个事件都是多个周期计数的一部分。使用每周错开一周的四周窗口,每个事件将在四个周期中被计数,尽管这一点在图 3.7 的简略示例中并不明显。但就像非重叠周期的示例计算(图 3.7)一样,每个事件都是根据周期(的开始和结束日期)映射到时间周期中的,每个周期的结果是总计数。对于非重叠周期来说,这不是你想要手动进行的计算!但是,令人惊讶的是,用于重叠周期指标计算的 SQL 并不比计算非重叠周期更复杂。

图 3.7 使用重叠时间窗口的简单指标计算

列表 3.2 展示了实现此类测量的 SQL,以及模拟流失数据集的示例结果如图 3.8 所示。与单日查询的结果(列表 3.1 和图 3.4)不同,现在每个账户都有多个结果,但只显示了其中几个。此外,每个测量都带有测量周期结束时间的戳记。

图 3.8 使用 SQL(列表 3.2)在模拟社交网络数据集类似事件上的多日期事件计数结果

列表 3.2 中的主要步骤基本上与列表 3.1 相同,但现在 SQL 在日期范围内工作:

  1. 选择用于测量的日期序列。这些日期比预期的周期大小更接近。

  2. 对于每个测量日期,选择与该测量相关的时间段内的事件。

  3. 按账户和测量日期对事件进行聚合计数。

对于步骤 1,列表 3.2 中的 SQL 使用了generate_series函数来创建一个包含计算日期列表的 CTE。通过将这个包含指标日期的列表包含在事件表的连接中,你可以在SELECTGROUP BY语句中都包含测量日期,从而为每个账户和每个测量日期计算计数。因此,查询一次计算整个测量日期序列的指标。

列表 3.2 使用重叠窗口计算指标

WITH date_vals AS (                                         ①
    SELECT i::timestamp AS metric_date 
    FROM generate_series('2020-01-29', '2020-04-16', 
    '7 day'::interval) i                                    ②
)
SELECT account_id, metric_date, COUNT(*) 
    AS n_like_per_month                                      ③
FROM event *e* INNER JOIN date_vals d                          ④
    ON e.event_time < metric_date                            ⑤
    AND e.event_time >= metric_date - interval '28 day'      ⑤
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
WHERE t.event_type_name='like'
GROUP BY account_id, metric_date                             ⑥
ORDER BY account_id, metric_date;                            ⑦

① 指标计算窗口的结束日期的 CTE

② 生成一系列值的 Postgres 函数

③ 选择账户、时间和测量

④ 通过 date_vals CTE 连接来设置日期

⑤ 事件在测量日期前后四周内。

⑥ 账户 ID 和日期都包含在 GROUP BY 子句中。

⑦ 对结果进行排序以提高可读性

运行列表 3.2 以查看结果。如果你使用模拟流失数据和 Python 包装程序,请运行以下命令:

fight-churn/listings/run_churn_listing.py —chapter 3 —listing 2

运行列表 3.2 的结果应该类似于图 3.8,但由于模拟中的随机性,并不完全相同。

generate_series 函数的替代方案

在本书的代码示例中,我使用 Postgres 函数generate_series来创建等间隔的日期序列。然而,其他数据库系统可能不支持此函数。可以通过创建一个常规(永久)表并在一次性加载中填充所需的日期序列来实现相同的目标。此外,如果您在互联网上搜索“XXX 上的 generate_series 替代方案”,其中 XXX 是您选择的数据库或数据仓库,您可能会找到特定于系统的实现。我为非 Postgres 用户缺乏通用性表示歉意,但generate_series函数对于教学很有用,因为它只需要一行简短的代码。

3.5.2 时间度量指标测量

在本书中,我通常使用每周更新的测量值来展示技术,这与第 3.4 节中提到的原因相同:人类行为通常遵循每周周期。但对于典型客户生命周期非常短(少于几个月)的产品,可能需要更频繁地更新测量值。

TIP 对于典型客户生命周期为几个月或更短的产品,您可能需要每天更新指标。但对于客户典型生命周期为几个月或更长的产品,通常每周计算一次指标就足够了。

对于客户生命周期甚至更长的产品,可能只需要每月更新一次测量值(或每四周一次)。这取决于您根据具体情况做出的判断;它与典型客户看到产品价值(或未能看到价值)并决定流失(或不流失)所需的时间有关。无论如何,所有相同的分析技术都适用,只是时间尺度不同。大多数人高估了这些测量值实时(频繁)更新的需求。通常,保留和流失之战是在几周、几个月或几年中进行的;它不太可能归结为快速的信息和干预。

另一个重要的问题是何时进行测量。如前所述,消费者产品通常在周末使用最为频繁,而商业产品则在工作日使用最为频繁。因此,通常最好在周一或周二午夜进行消费者产品的测量,以便捕捉到最新的周末活动。对于商业产品,最好在周末进行测量,无论是周六还是周日,以便测量结果反映最新的完整工作周。

TIP 每周周一或周二午夜测量用于娱乐的产品,以捕捉整个上一个周末的活动。周五或周六午夜测量用于工作的产品,以捕捉整个上一个五天的工作周。

3.5.3 保存度量测量值

查看列表 3.2 和图 3.8 中的查询结果,你可能认为这种测量会产生大量数据。没问题!你有一个数据仓库,对吧?度量计算应该被插入回数据仓库进行存储,以备后续分析。表 3.3 展示了存储度量的典型模式。与度量相关的 SQL 代码列表使用此模式。(请参阅本书的代码,以获取有关如何设置具有此模式的数据库以及如何用模拟数据填充它的详细说明。)以下是一些典型字段:

  • account_id—账户持有者或用户的标识符。账户 ID 是追踪度量回到创建它们的客户所必需的。这是复合主键的第一部分。

  • metric_name_id—度量通常有多种类型,通常有一个外键指向一个描述类型的单独表。这是复合主键的第二部分。

  • metric_time—每个度量都必须有一个时间戳,如 3.5.1 节所述。这是复合主键的第三和最后一部分。

  • value—度量的数值。

  • user_id—除了账户 ID 之外,用户 ID 可能存在于与单个账户关联多个个人的服务中。

此外,还有一个相关的度量名称表(如表 3.4 所示),以确保度量的字符串名称不会重复(出于性能原因,这是数据库或数据仓库中的标准做法)。度量的模式与事件的模式(表 3.1)类似,这是有道理的,因为两者都是数据仓库中的事实表。

度量和事件之间的重要区别之一是,度量应该有一个由 account_idmetric_name_idmetric_time 字段组成的复合主键。这意味着账户、度量以及测量时间的组合必须是唯一的:在任何给定时间,每个账户只有一个测量值。度量与事件模式之间的另一个区别是,虽然事件可以没有数据字段或与每个操作相关联的任意组合的数据字段,但度量始终只有一个数据字段:度量值。

表 3.3 典型度量数据模式

类型 备注
account_id integerchar 复合主键 1
metric_name_id integerchar 复合主键 2;metric_name 的外键
metric_time timestamp 复合主键 3
value float
user_id integerchar 可选

如果您的数据仓库支持直接将SELECT语句的结果插入数据仓库,那么保存度量结果很容易,如列表 3.3 所示。列表 3.3 中的代码与列表 3.2 中的代码相同,只是添加了INSERT关键字,将其转换为 SQL INSERT语句。如果您的数据库不支持INSERT SELECT语句,那么常规做法是将列表 3.2 中的查询结果保存到以分隔符(逗号分隔值)分隔的文本文件中,然后使用数据仓库提供的任何机制将该文件加载回数据仓库。

表 3.4 关联的度量名称数据模式

类型 备注
metric_name char 唯一
metric_name_id integerchar metric_name 的键

列表 3.3 将度量计算插入数据仓库

WITH date_vals AS (                                          ①
    SELECT i::timestamp AS metric_date 
    FROM generate_series('2020-01-29', '2020-04-16', '7 day'::interval) i
)
INSERT INTO metric 
  (account_id,metric_time,metric_name_id,metric_value)       ②
SELECT account_id, metric_date, 0,
    COUNT(*) AS metric_value                                 ③
FROM event *e* INNER JOIN date_vals d                          ④
    ON e.event_time < metric_date 
    AND e.event_time >= metric_date - interval '28 day'
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
WHERE t.event_type_name='post'
GROUP BY account_id, metric_date;

① 此 CTE 包含计算日期。

② 将 SELECT 的结果插入度量表

③ 包含度量值的 ID,我们假设为 0

④ SELECT 语句的其余部分与列表 3.2 相同。

运行列表 3.3 并查看它是否写入您自己的数据库模式。如果您正在使用模拟数据和 Python 包装程序,请使用以下命令

fight-churn/listings/run_churn_listing.py —chapter 3 —listing 3

注意,除了 Python 包装程序打印的行之外,没有其他输出。结果是数据已保存到数据库中。您应该通过针对度量表的查询来选择结果:例如,SELECT * FROM metric limit 10;。此类查询的结果应类似于图 3.8 中的示例。

请记住,您只能运行此列表一次,除非您更改配置或从数据库中清除结果。在为特定日期特定账户插入一个度量值的结果后,您不能为相同的账户和日期重新插入新的结果。如果您为新的度量值插入值,您还需要一次性将度量名称表(表 3.4)中的名称插入。此语句对任何具有基本 SQL 知识的人来说都很短且众所周知,但为了完整性,示例代码在列表 3.4 中给出。

如果您使用列表 3.3 插入了一个度量值,您还必须通过运行列表 3.4 来插入名称,以便运行本章后面的列表。使用 Python 包装程序和参数—listing 4运行列表 3.4,或者通过您选择的 SQL 工具进行等效插入。请注意,您不应两次插入相同的度量名称或 ID。(在关系型数据库中,键约束应防止这种情况。在关系型数据库中的最佳实践是首先插入度量名称,并在度量表上使用外键约束来防止加载没有名称的度量。)

列表 3.4 将度量名称插入数据仓库

INSERT INTO metric_name ('like_permonth',0) ON CONFLICT DO NOTHING;

在继续之前,我想提醒您注意,列表 3.3 中的指标计算没有为没有事件的账户插入零。这是事件内连接的自然产物,您可能没有注意到这一点。定义一个对于没有事件的账户产生零的计数指标并不困难,但当账户数量很大且事件很少时,存储零计数指标的性能会很差。您甚至可能最终存储了大部分零!我采取的方法是在数据仓库中不存储计数中的零;然后,在分析阶段(从下一章开始),我需要分析没有事件的账户时生成零。

3.5.4 保存模拟示例的指标

通过运行列表 3.3 和 3.4,您应该在数据库中插入了一个事件计数指标,用于表示点赞数量,并在 metric_name 表中有一个名称:likes_permonth。模拟社交网络数据集中还有七个其他事件:不喜欢、帖子、新朋友、解除好友、广告查看、消息和回复。这些事件的指标将用于本章和本书的其余部分中的示例,因此您在继续之前应该将它们插入到自己的数据库中。为了便于操作,列表包装程序包括了所需的列表 3.3 和 3.4 的替代版本。要运行它们,请在执行命令中添加 —version 标志和版本号列表。此外,您可以通过在 —listing 标志后列出两个数字来一起运行列表 3.3 和 3.4。要使用包装脚本运行列表 3.3 和 3.4 并插入模拟的下一个七个计数指标,请使用以下命令:

fight-churn/listings/run_churn_listing.py —chap 3 —listing 3 4 
   —version 2 3 4 5 6 7 8

在大多数系统中插入这么多指标至少需要 10 分钟,所以这是一个喝咖啡的好时机。(您可能想先运行一个,看看在您的系统上需要多长时间。)请注意,列表 3.3 和 3.4 的先前运行被认为是版本 1,所以附加指标从版本 2 开始。有关运行列表的更多说明,请参阅 GitHub 仓库根目录下的 README 文件。

3.6 测量事件属性的总额和平均值

到目前为止,我们只看了事件简单计数的指标;但当事件有附加字段中的数据时,您可能希望在该指标中总结这些数据。最典型的情况是事件与一个数值相关联。以下是一些最典型的情况:

  • 事件在时间上有一个持续时间,例如会话长度或某些媒体的播放。

  • 事件有一个货币价值,例如零售购买或超额费用。

在这种情况下,最常见的指标之一如下:

  • 所有事件的总体价值

  • 每个事件的平均值

这些(以及许多其他)都可以使用类似的 SQL 计算,如列表 3.5 所示,假设事件有一个名为time_spent的字段。该指标表示每个用户在四周期间此类会话中花费的总时间。指标计算的步骤如下:

  1. 选择测量日期的序列。

  2. 对于每个测量日期,选择与该测量相关的时间窗口内的活动。

  3. 对按账户和测量日期分组的活动求time_spent字段的和。

SQL 几乎与事件计数指标的列表 3.2 相同。唯一的区别在于SELECT语句,而不是使用COUNT(*)聚合函数来计数事件数量,SQL 使用聚合函数SUM(time_spent)来计算time_spent字段的总量。

Versature(在第一章中介绍)是一家为企业提供统一电信服务提供商。作为统一通信提供商,其最重要的一个事件是存储在每个事件附加字段中的通话持续时间。运行列表 3.5 在 Versature 本地呼叫事件上的几个示例输出显示在图 3.9 中。图 3.9 中的结果看起来与图 3.8 中计数指标的结果相似,但指标值不是时间窗口内事件的数量:它是这些事件中time_spent数量字段的总量。

图片

图 3.9 Versature 本地呼叫事件的 SQL 列表 3.5 的总持续时间结果

在撰写本文时,默认的模拟数据不包括事件属性,因此您无法直接在模拟数据上运行此操作。尽管如此,模拟代码可以被扩展以包括事件属性。我鼓励您这样做,然后提交一个拉取请求来分享您的工作。虽然您无法在模拟上运行列表 3.5,但它被提供作为使用包含事件属性的您自己的数据的示例。

列表 3.5 测量事件属性的总和

WITH date_vals AS (                                         ①
    SELECT i::timestamp AS metric_date 
    FROM generate_series('2020-01-08', '2020-12-31', '7 day'::interval) i
)
SELECT account_id, metric_date::date, 
    SUM(duration) AS local_call_duration                    ②
FROM event *e* INNER JOIN date_vals d                         ③
    ON e.event_time < metric_date 
    AND e.event_time >= metric_date - interval '28 day'
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
WHERE t.event_type_name='call'                              ④
GROUP BY account_id, metric_date
ORDER BY account_id, metric_date;

① 此 CTE 包含计算所需的日期。

② 假设事件有一个持续时间数据字段,并对事件进行求和

③ 日期标准与列表 3.2 相同。

④ 否则,SELECT语句与计数指标相同。

3.7 指标质量保证

现在您已经学会了如何计算一些指标,我需要退一步教您一些基本技巧来检查结果。在前面的章节中,我只展示了输出结果的一些示例,但抽查几行结果并不能充分保证行为指标的质量。如果您没有注意到最后一句话,让我重复一遍。

警告:抽查几行结果并不能充分保证行为指标的质量。

为什么?你可以从几行代码中看到公式是正确的,对吧?但你的担忧不仅仅是代码的正确性,就像在普通编程项目中一样。你的担忧还包括一些账户中缺失或不良数据,而不仅仅是通过抽查几行就能发现。有许多方法可以确保行为度量的质量,我将在下面介绍几种,并就一些常见问题提出建议。

3.7.1 测试度量随时间变化的情况

检查度量问题的一个重要方法是通过查看结果随时间的变化情况。这可以通过一个聚合查询来完成,该查询分别对每个日期选择计数、平均值、最小值和最大值。这并不能告诉你关于度量值的所有信息,但它应该会提醒你任何重大问题,因为此类问题通常会导致这些汇总统计中的一个出现异常变动。图 3.10 展示了这种结果的示例图,该图是为模拟社交网络中的like_per_month度量创建的。随机模拟数据比真实的流失数据集变化更少,所以我在快速查看列表 3.6 和 3.7 中的代码后,将向您展示案例研究中的真实示例。您将看到,一个列表是一个从数据库获取数据的 SQL SELECT语句,另一个是一个简短的 Python 列表,用于生成图表。

列表 3.6 显示了一个查询,用于选择一个度量在整个日期范围内的计数和平均值。请注意,列表 3.6 采取了一种稍微间接的方法:

  1. 创建要检查的日期的 CTE。

  2. 创建要测试的度量的 CTE。

  3. 使用外连接来计算结果。

图 3-10

图 3.10 使用时间序列统计进行度量质量保证

可能看起来有一个更简单的替代方案,那就是在度量表上执行一个单一的聚合SELECT语句,按metric_time列进行分组。间接方法的原因是,即使在没有计算度量值的日子里也能得到结果:对于没有度量的日期,平均值为 null,计数为零。结果中的此类行使得检测没有计算任何度量的日子变得容易。相比之下,如果仅对度量表进行聚合查询,那么在没有度量值的日子里将没有任何结果。当缺失的日子在结果中不产生行时,很容易错过没有计算度量的日子。

提示:当你检查度量结果的质量时,除了检查结果是否产生不良结果外,还要使用使结果未产生时显而易见的方法。这意味着检查日期应独立于被检查的数据。

使用 Python 包装程序和以下参数在模拟数据上运行列表 3.6:

—chapter 3 —listing 6

列表 3.6 测量度量随时间变化的平均、最小、最大和计数

WITH
date_range AS (                                                           ①
    SELECT i::timestamp AS calc_date 
    FROM generate_series('2020-04-01', '2020-05-06', '7 day'::interval) i
), the_metric AS (                                                        ②
    SELECT * FROM metric m
    INNER JOIN metric_name n ON m.metric_name_id = n.metric_name_id
    WHERE n.metric_name = 'like_per_month'
)
SELECT calc_date,  AVG(metric_value), COUNT(the_metric.*) AS n_calc,
       MIN(metric_value), MAX(metric_value)                               ③
FROM date_range LEFT OUTER JOIN the_metric 
    ON calc_date=metric_time                                              ④
GROUP BY calc_date                                                        ⑤
ORDER BY calc_date                                                        ⑥

① 此 CTE 包含计算所需的日期。

② 将指标选择到 CTE 中进行最终 SELECT

③ 选择使用聚合函数计算的平均值和数量

④ 左外连接,以便查询对每一天都有一个结果

⑤ 按 calc_date 分组

⑥ 按 calc_date 排序以使结果更易读

制作图 3.10 中指标 QA 图的代码在列表 3.7 中。首先,列表 3.7 将查询结果(列表 3.6)加载到 Pandas DataFrame 中。之后,列表使用 matplotlib.pyplot 包绘制并保存图表。因为有四个几乎相同的子图,所以使用了一个辅助函数来制作每一个。

列表 3.7 随时间绘制指标 QA 统计图

import pandas as pd
import matplotlib.pyplot as plt
from math import ceil

def metric_qa_plot(qa_data_path, metric_name,**kwargs):     ①
   metric_data_path = qa_data_path + '_' 
      + metric_name + '.csv'                                ②
   qa_data_df=pd.read_csv(metric_data_path)                 ③
   plt.figure(figsize=(6, 6))                               ④
   qa_subplot(qa_data_df,'max',1,None)                      ⑤
   qa_subplot(qa_data_df,'avg',2,'—')
   qa_subplot(qa_data_df,'min',3,'-.')
   qa_subplot(qa_data_df,'n_calc',4,':')
   plt.title(metric_name)                                   ⑥
   plt.gca().figure.autofmt_xdate()

   save_to_path=metric_data_path.replace('.csv','.png')     ⑦
   print('Saving metric qa plot to ' + save_to_path)
   plt.savefig(save_to_path)
   plt.close()

def qa_subplot(qa_data_df, field, number, linestyle):
   plt.subplot(4, 1, number)
   plt.plot('calc_date', field, data=qa_data_df, marker='', 
      linestyle=linestyle, color='black', linewidth=2, label=field)
   plt.ylim(0, ceil(1.1 * qa_data_df[field].dropna().max()))
   plt.legend()

① 从 SQL 列表中获取默认参数的 kwargs

② 这是列表 3.6 保存的文件。

③ 将数据文件加载到 DataFrame 中

④ 打开一个图形

⑤ 使用辅助函数制作子图

⑥ 注释图表

⑦ 保存图表

注意,运行 Python 列表的方式与 SQL 列表相同。要运行列表 3.7,请使用以下命令对 Python 包装程序进行操作:

fight-churn/listings/run_churn_listing.py —chap 3 —listing 7 

程序打印出它保存的图形的位置。例如:

Saving metric qa plot to ../../../fight-churn-output/socialnet7/
socialnet7_metric_stats_over_time_like_per_month.png

它应该等同于图 3.10。如果您想对其他指标运行 QA,还有其他准备好的命令配置供您使用。首先,使用此命令提取所有指标的数据:

run_churn_listing.py —chap 3 —listing 6 —version 2 3 4 5 6 7 8

以下命令为其他指标生成图表:

run_churn_listing.py —chap 3 —listing 7 —version 2 3 4 5 6 7 8

3.7.2 指标质量保证(QA)案例研究

图 3.11 展示了在 Klipfolio 应用程序指标(当用户在 Klip 上应用图层时)上运行列表 3.6 中显示的指标 QA 查询的结果:每月 Klip 叠加事件的数量。图 3.11 显示,对于真实指标,平均数和最大值的结果不如模拟时平滑。

图片

图 3.11 对 Klipfolio 健康指标的时序 QA 结果

图 3.12 展示了当出现问题时 QA 结果的一个示例:通过删除一个事件的一个月数据来模拟缺失数据。(有关常规指标 QA 的解释,请参阅图 3.11。)Broadly(在第一章中介绍)是一个以移动端为先的通讯平台,确保企业在线看起来很棒。当客户撰写正面评论时,就会发生客户推广活动,而每月的客户推广者数量是产品客户成功的重要指标。通常,对于该指标计算的平均值和数量在一年中略有变化,但数据缺失时,这两个 QA 测量值都会下降。它们会继续下降,直到指标计算窗口超过缺失数据的时期。指标的峰值通常变化更大,但在此情况下,由于缺失数据导致的下降甚至更大。

图片

图 3.12 对具有缺失事件数据的指标进行时间序列 QA 的结果

当事件数据缺失时,一个指标的极大值、平均值和计算出的数量都会下降。如果缺失的期间更长,那么在指标测量窗口中没有事件时,这些值会达到零;实际上,用于计算指标的期间比缺失数据的期间长,因此并非所有指标都会降到零。

图 3.13 展示了 Versature 的一个 QA 结果的第二个例子,其中实际上存在错误:极端值被插入到事件属性字段中。如前所述,当客户发起本地通话时,会在数据库中记录一个本地通话事件,而过去三个月内总通话时间是对 Versature 账户的一个重要指标。通常,该指标的年均值和最大值相对稳定,年末会有所增加。但是,当持续时间字段中出现极端值时,QA 测量值都会上升。不良数据对最大值的影响最大,对平均值的影响较小。对平均值的影响几乎可以误认为是正常的变化,除了变化的突然性和不连续性。极端字段值对计算出的数量或最小值没有影响。如果极端值是负数,那么影响将表现为最小值而不是最大值。

图 3.13

图 3.13 Versature 指标具有极端值的时间序列 QA 示例结果

图 3.13 通过展示 QA 结果(在左侧)在将几个极端的本地通话持续时间数据插入事件数据库(在右侧)前后的影响,说明了错误极端值对指标计算的影响。当事件数据字段中存在极端(正值)时,最大的影响是对最大值,对平均值的影响较小。如果极端异常值是负数,那么影响将体现在最小值上,但在此情况下最小值未受到影响。指标值的跳跃持续的时间与异常值在指标计算窗口中的持续时间相同。

3.7.3 检查接收指标的账户数量

QA 的另一个重要问题是每个指标占活跃账户总人口的百分比。列表 3.6 查看随着时间的推移具有该指标的账户数量,这可以显示时间异常,但它不会检测到某些账户从未为指标产生事件的情况。检查收到指标值的客户比例,并查看它是否低于或高于预期,是一个重要的测试。

列表 3.8 中的 SQL 计算了在给定时间范围内所有活跃账户中每个指标的结果的百分比。还计算了平均值、最小值和最大值。模拟社交网络运行列表 3.8 的结果示例如图 3.14 所示。大多数指标覆盖了几乎所有账户(90%+),除了unfriend_permonth,只有 59%的账户收到。这看起来正确吗,还是更可能是数据问题?这可能是因为你预计大多数人每天都会发布和查看广告,但不太可能频繁地删除好友。

图片

图 3.14 模拟数据集中计算具有指标的账户百分比(列表 3.8)的结果

计算具有指标的账户百分比的步骤如下:

  1. 选择一个时间段。查询为该窗口进行一次整体测量(不是像列表 3.6 那样的每日计算)。

  2. 计算在时间窗口内具有活跃订阅的账户数量。

  3. 计算在时间窗口内具有每种类型指标的账户数量。

  4. 通过将步骤 3 的结果除以步骤 2 的结果来计算具有每种类型指标的账户的百分比。

  5. 在时间窗口内测量指标的其他统计数据。

列表 3.8 中的 SQL 使用了两个常见的表表达式(CTE):

  • date_range—设置计算的起始和结束日期

  • account_count—计算在指定时间框架内活跃的账户总数

最终结果通过聚合计算具有事件的账户数量,然后除以account_count CTE 的结果,以获得百分比。

列表 3.8 测量具有指标的账户百分比(指标覆盖率)

WITH date_range AS (                                        ①
   SELECT  '2020-04-01'::timestamp AS start_date, 
      '2020-05-06'::timestamp AS end_date
), account_count AS (                                       ②
   SELECT COUNT(distinct account_id) AS n_account           ③
   FROM subscription s INNER JOIN date_range d ON
   s.start_date <= d.end_date                               ④
   and (s.end_date >= d.start_date 
      or s.end_date is null)                                ④
)
SELECT metric_name, 
   COUNT(distinct m.account_id) AS count_with_metric,       ⑤
   n_account AS n_account,                                  ⑥
   (COUNT(distinct m.account_id))::float/n_account::float 
       AS pcnt_with_metric,                                 ⑦
   AVG(metric_value) AS avg_value,                          ⑧
   MIN(metric_value) AS min_value,                          ⑨
   MAX(metric_value) AS max_value                           ⑩
   MIN(metric_time)  AS earliest_metric,
   MAX(metric_time) AS last_metric
FROM metric m CROSS JOIN account_count                      ⑪
INNER JOIN date_range ON                                    ⑫
   metric_time >= start_date
   and metric_time <= end_date
INNER JOIN metric_name  n ON m.metric_name_id = n.metric_name_id
INNER JOIN subscription s 
    ON s.account_id = m.account_id                          ⑬
    AND s.start_date <= m.metric_time
    AND (s.end_date >= m.metric_time or s.end_date is null)
GROUP BY metric_name,n_account
ORDER BY metric_name;

① 此 CTE 设置 QA 的起始和结束。

② 此 CTE 计算账户数量。

③ 计算具有订阅的账户数量

④ 选择所有活跃的账户

⑤ 计算具有指标值的账户数量

⑥ 来自账户计数 CTE

⑦ 将指标的计数除以订阅者的计数

⑧ 平均值的标准聚合函数

⑨ 最小值的标准聚合函数

⑩ 最大值的标准聚合函数

⑪ 交叉连接,每行重复账户计数

⑫ 限制到由日期范围 CTE 指定的时间段

⑬ 在订阅上设置连接

运行列表 3.8 以确认结果并检查所有指标是否已正确计算。如果您正在使用 Python 包装程序,请使用参数—chapter 3 —listing 8运行它。结果保存在 CSV 文件中,应类似于图 3.14。

3.8 事件 QA

在上一节中,我向您展示了如何查询指标以及具有指标的账户百分比,但事件呢?如果您首先检查事件,您将更好地了解指标将提供什么,甚至可能会改变您决定计算指标的方式。(我将在下一节中展示一些实现这一点的技术。)您已经看到了如何计算一些指标,但我正在以不同于实际操作的方式教授这些内容。

小贴士:在深入计算指标之前,花些时间检查事件数据的质量。本书以混合的顺序教授步骤,以便首先展示最终结果。正确的顺序是(1)事件质量分析(本节),(2)计算指标(第 3.5 节和第 3.6 节),以及(3)指标质量分析(第 3.7 节)。

3.8.1 检查事件随时间的变化情况

图 3.15 演示了对事件进行简单时间序列质量分析的示例:计算所有账户每天的总事件数。您已经看到,大多数真实事件遵循每周周期(图 3.5),模拟也是为了重现这种行为。稍后我将向您展示更多案例研究的成果,但首先让我们看看生成此类图形的代码(列表 3.9)。

图 3.15 每日计数类似事件的结果(列表 3.9)在模拟数据集上

列表 3.9 利用了您在前面章节中看到的技术。使用一个包含生成日期序列(在第 3.4 节中描述)的 CTE 与事件数据进行外连接,以确保每个日期都有质量查询的结果,即使没有事件也是如此。

列表 3.9 事件每日质量检查

WITH
date_range AS (                                               ①
    SELECT i::timestamp AS calc_date 
    FROM generate_series('2020-01-01', '2020-12-31', '1 day'::interval) i
)
SELECT event_time::date AS event_date,                        ②
    COUNT(*) AS n_event                                       ③
      /*, SUM(optional_field) AS total _field */              ④
FROM date_range LEFT OUTER JOIN event *e* 
    ON calc_date=event_time::date                             ⑤
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
WHERE t.event_type_name='like'                                ⑥
GROUP BY event_date                                           ⑦
ORDER BY event_date

① 此 CTE 包含计算日期。

② 将事件时间转换为日期

③ 求事件数的总和

④ 如果事件具有数值属性,则对属性求和

⑤ 对日期序列进行左连接以确保每个日期都有结果。

⑥ 选择正在检查的事件

⑦ 按计算日期分组

在模拟数据集上运行列表 3.9 以确认它是否与您的数据兼容。这样做将创建一个包含每行一个事件计数的 CSV 文件。程序会打印一行说明它所执行的操作:

Saving: ../../../fight-churn-output/socialnet7/socialnet7_events_per_day_like.csv

制作图表的脚本(如图 3.15 所示)在列表 3.10 中展示。此列表使用 Pandas DataFrame加载数据,并使用标准的matplotlib.pyplot进行绘图。为了使 x 轴上的日期可读,创建了一个使用 lambda 的过滤器,该过滤器选择以 01 结尾的日期,即每月的第一天(2020-02-01,2020-03-01 等)。将过滤后的日期列表传递给matplotlib.pyplotxticks函数。

列表 3.10 绘制每日事件数

import pandas as pd
import matplotlib.pyplot as plt
from math import ceil

def event_count_plot(qa_data_path, event_name,**kwargs):
   event_data_path = qa_data_path +
       '_' + event_name + '.csv'                             ①
   qa_data_df=pd.read_csv(event_data_path)                   ②
   plt.figure(figsize=(6, 4))
   plt.plot('event_date', 'n_event', data=qa_data_df, 
       marker='', color='black', linewidth=2)                ③
   plt.ylim(0, 
       ceil(1.1*qa_data_df['n_event'].dropna().max()))       ④
   plt.title('{} event count'.format(event_name))
   plt.gca().figure.autofmt_xdate()                          ⑤
   plt.xticks(list(filter(lambda x:x.endswith(("01")),
       qa_data_df['event_date'].tolist())))                  ⑥
   plt.tight_layout()                                        ⑦
   plt.savefig(event_data_path.replace('.csv',
        '_' + event_name + '_event_qa.png'))                 ⑧
   plt.close()

① 列表 3.9 保存数据的路径

② 将数据读入 DataFrame

③ 绘制事件数与日期的关系图

④ 根据最大值设置 y 轴限制

⑤ 旋转 x 轴日期标签

⑥ 为 x 轴标签创建每月第一天的日期列表

⑦ 确保所有轴标签都可见

⑧ 保存结果

如果你使用 Python 包装程序,你可以一起运行列表 3.9 和 3.10 来创建数据和图表,只需一条命令即可:

fight-churn/listings/run_churn_listing.py —chap 3 —listing 9 10 

程序会打印出它保存的图形位置;它应该等同于图 3.10。如果你想对其他指标进行 QA 测试,还有其他准备好的命令配置供你使用。首先使用以下命令创建所有事件的计数:

run_churn_listing.py —chap 3 —listing 9 —version 2 3 4 5 6 7 8

注意,这可能比指标的 QA 测试花费的时间要长一些,因为它需要每天汇总事件。以下命令将为其他指标创建图表:

run_churn_listing.py —chap 3 —listing 10 —version 2 3 4 5 6 7 8

为了说明如何发现缺失的事件,图 3.16 展示了从列表 3.9 中运行 QA 查询的真实客户推广事件(当客户撰写正面评论时)的输出,以及故意删除了一个月的事件(与图 3.12 中使用的相同删除操作)。如果你花时间检查,通常很容易注意到这样的问题,尽管如果数据缺失时间较短,识别问题可能更具挑战性。

图 3.16 广泛客户推广事件每天事件计数

为了说明如何在事件字段中发现异常值,图 3.17 展示了第二个示例(针对 Versature),展示了在列表 3.9 中运行 QA 查询的输出,其中数据处于真实状态,以及故意添加极端值后的状态。这些是用于制作图 3.13 示例的相同更改。当客户拨打本地电话时,会在数据库中记录一个本地通话事件,数据库有一个存储通话持续时间的字段。事件字段中的极端值不会影响事件计数,但如果你在事件字段的和(总通话时长)或事件字段的某些其他聚合函数上绘制图表,通常很容易发现这些值。

图 3.17 Versature 本地通话每天事件 QA 结果

3.8.2 检查每个账户的事件

对事件的重要检查之一是查看事件的总数以及每个账户的事件数量。你应该这样做来评估事件的总数和类型。这与检查具有指标的账户百分比类似,如前文所述。

本检查的输出示例如图 3.18 所示,用于社交网络模拟。该图显示,平均账户每月有大约 75 个点赞事件,但少于一个取消好友事件。这解释了为什么有较少的账户具有每月取消好友指标(图 3.13)。只有大约三分之一的账户的指标值不为零,这与每月每个账户的事件数一致。

图 3.18 使用列表 3.11 按月计数每个账户的事件结果

列表 3.11 包含计算每个账户的事件数并将其转换为每月计数的 SQL 代码。计算步骤如下:

  1. 选择一个时间窗口。

  2. 计算在时间窗口内拥有活跃订阅的账户数量。

  3. 计算时间窗口内的总事件数。

  4. 将总事件数除以两次:

    1. 将总事件数除以账户数。

    2. 将总事件数除以测量的月份数。

此过程导致每个账户每月的平均事件计数。计算的起点与列表 3.8 相同,该列表计算了每个账户的指标。这里唯一的技巧是在最终的 SELECT 语句中,它也选择了时间框架中的月份数,并将结果除以该数。

列表 3.11 测量每个账户的平均事件数

WITH 
date_range AS (                                              ①
    SELECT  '2020-01-01'::timestamp AS start_date, 
        '2020-12-31'::timestamp AS end_date
), account_count AS (                                        ②
    SELECT COUNT(distinct account_id) AS n_account
    FROM subscription s INNER JOIN date_range d ON
     s.start_date <= d.end_date
    AND (s.end_date >= d.start_date or s.end_date is null)
)
SELECT event_type_name, 
    COUNT(*) AS n_event,                                     ③
    n_account AS n_account,                                  ④
    COUNT(*)::float/n_account::float 
        AS events_per_account,                               ⑤
    extract(days FROM end_date-start_date)::float/28  
        AS n_months,                                         ⑥
     (COUNT(*)::float/n_account::float)/
         (extract(days FROM end_date-start_date)::float/28.0)  
             AS events_per_account_per_month                 ⑦
FROM event *e* cross join account_count                        ⑧
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
INNER JOIN date_range ON                                     ⑨
    event_time >= start_date
    AND event_time <= end_date
GROUP BY e.event_type_id,n_account,end_date, 
    start_date, event_type_name                              ⑩
ORDER BY events_per_account_per_month desc;                  ⑪

① 此 CTE 设置 QA 的起始和结束日期。

② 此 CTE 确定账户数量。

③ 计算总事件数

④ 从 account_count CTE 中获取账户数量

⑤ 将事件数除以账户数以获取每个账户的事件数

⑥ 将天数除以以获取四周月份的计数

⑦ 将每个账户的事件数除以月份数

⑧ 对每行重复账户数量的交叉连接

⑨ 将测量限制在所选日期范围内

⑩ GROUP BY 包括 SELECT 中所有非聚合项

⑪ 对事件进行排序,使最常见的出现在列表顶部

本节是关于 QA,即检查问题;但在这个例子中,考虑到未友好的事件数量,似乎没有问题。也许只是人们不太会频繁地取消好友关系。

当你看到每月每个账户的事件数量较少时,你需要运用你对产品的了解来判断是否是问题。在所有产品中并没有绝对的标准,因为有些产品应该看到几乎恒定的用户互动,而有些产品你可能会期望一年中只与产品互动几次。如果你没有足够的专业知识来判断每个账户的事件数量是低还是高,那么你需要与组织中的其他人交谈,并请他们帮忙。这可能是某些数据人员不想听到的建议,所以让我再重复一遍。

提示:如果你的业务知识不足以判断每月观察到的每个账户的事件数是否合理,那么你必须从组织中的某个人那里获得帮助。在你花费大量时间计算行为指标和进行流失分析之前,请这样做。

图 3.18 中的表格通常足以将此类信息传达给业务部门。如果有大量事件,你应该按频率排序,以便将最频繁的事件放在最上面,因为这些是观众最可能熟悉的事件。即使熟悉他们产品的某人可能也不知道这些罕见事件。如果事件没有直观的类型名称,这种情况在软件和互联网服务中很常见,事件可以是用户点击特定的 URL。

运行列表 3.11 以确认你自己的数据结果。如果你使用 Python 包装程序,可以通过将参数更改为—chapter 3 —listing 11来完成此操作。结果将类似于图 3.18,但并不完全相同,因为模拟中存在随机性。

QA 的自动异常检测

值得指出的是,有方法可以自动检测本节中提到的数据质量问题。自动检测数据问题是一个称为异常检测的领域。与手动生成大量图表并逐一查看相比,我的方法相当低效!但事实是,我从未为典型的流失分析烦恼过自动异常检测。一方面,如果有几十个事件和指标(少于 100 个),查看所有这些事件并不需要很长时间。我编写了生成一组如图 3.18 所示图表的脚本,然后使用图像查看器快速浏览它们。

可视化检测异常很容易——可以说是比几乎所有算法都有效。我推荐手动方法的其他原因是,这是一种了解你数据的好方法。你可能会发现一些有用的模式或关系,如果你依赖算法,可能会错过这些。话虽如此,如果你有超过大约 100 个事件或指标,你可能需要使用完全自动化的方法,但自动异常检测超出了本书的范围。

3.9 选择行为测量的测量周期

假设你了解这个业务,你知道像“取消好友”这样的事件是很少发生的,所以看到每个月每个账户只有少量事件(如图 3.18 中的取消好友事件)是可以接受的。这意味着只有 59%的账户在任何给定月份获得该指标值就一切都好吗?(该结果如图 3.14 所示。)并不完全是这样。数据本身并没有问题,但测量方面可能还有改进的空间。

这里有个想法:如果一个事件很少发生,在指标定义中使用更长的时间周期。这样,你可以在测量中捕捉到更多的客户,并且可以比较更多账户的行为。记住,列表 3.2 中的指标计算只使用了 28 天(四周)的时间周期。目前来看,所有在过去 28 天内没有发生罕见类型事件的账户在指标中显示为 0。另一方面,如果你使用更长的时间周期来衡量指标,可能会有一些账户每隔几个月才发生一次事件,这些事件在 28 天周期中被遗漏,但会在更长的时间周期中被捕捉到。同时,可能会有一些账户每月发生罕见事件,如果你使用更长的时间周期,它们的指标计数将会更高。

TAKEAWAY 在制作罕见事件的指标时,使用更长的测量周期。

选择多长的周期合适?像在用数据对抗流失中做很多事情一样,没有严格的规定;只有一些指导原则。选择测量周期是在行为指标对时间变化的响应性和在捕捉罕见事件账户的敏感性之间进行权衡。行为指标的响应性意味着如果一个账户的事件水平下降或上升,该账户的指标会迅速变化。例如,如果你使用一年(365 天)的时间周期来计算指标,并且每天更新它,那么每天只有 1/365 的数据进入指标发生变化。因此,如果有人在年初事件水平较高,但水平下降到零,那么这个人需要很长时间才能使指标的值变低。总结如下:

  • 短时间周期的行为指标对行为变化的响应较快,但在捕捉事件水平较低的用户账户方面较不敏感。

  • 长时间周期的行为指标对行为变化的响应较慢,但在捕捉事件水平较低的用户账户方面更为敏感。

应该选择哪个周期?根据事件发生的频率,我使用一个经验法则来确定你应该观察事件的最小时间周期以制作行为指标:选择的时间周期至少是平均账户发生一次事件所需时间的两倍。但由于我在 3.4 节中提到的每周行为周期,周期不应少于一周,无论事件发生的频率如何。而且(通常)你不应该进行超过一年的行为测量(更多关于这一点将在后面讨论)。例如,如果每个账户每月发生一次事件,至少使用两个月进行测量。如果每个账户每月发生两次事件,使用一个月也是可以的。遵循这一指南,大多数账户在时间周期内至少发生一次事件的可能性很大。

TIP 指标的最小时间周期应至少是平均账户发生一次事件所需时间的两倍。

规则经验法推荐的最低观察期总结在表 3.5 中。

表 3.5 最小行为测量周期(规则经验法)

每月每账户的事件数 月份:一个事件 最低测量周期
>8 < 0.1 1 week
8 0.125 1 week
4 0.25 2 weeks
2 0.5 1 month
1 1 2 months
0.5 2 4 months
0.333 3 6 months
0.25 4 8 months
0.1666 6 12 months
< 0.1666 > 6 12 months

表 3.5 中的行为测量规则经验法设定了一个最低标准,通常情况下,你不应该用超过一年的周期来测量行为。但在那个范围内,响应性和敏感性之间的良好权衡是什么?

对于像一个月或一年这样的固定期限订阅,行为测量应该在时间尺度上与订阅期限相似。如果你销售一年期的订阅,你可能应该使用一年的时间周期进行行为测量;如果你销售一个月的订阅,你应该使用一个月的时间周期进行行为测量。这样做的原因是为了让你的行为测量反映客户在订阅期限内从服务中获得的经验。话虽如此,服务体验可能在期限结束时最为重要,因为那时它将在客户心中更加直接,当是时候续订时。如果你想用三到六个月的时间来测量一年期订阅的行为,这是有道理的,但不要为一年期订阅使用一个月的指标。

如果产品没有固定期限的订阅呢?那么你应该旨在使用大约是典型订阅者活跃时间四分之一到一半的周期来进行行为测量。如果客户通常会在六个月内保持活跃,使用一或两个月的周期进行行为测量。如果客户通常只停留一个月,那么只需在一周或两周内进行行为测量,但必须遵守表 3.5 中显示的最低标准。这里的理由是,你应该在足够短的时间尺度上进行测量,以确保在平均客户可能考虑流失之前完成测量周期。

你应该根据事件发生的频率来调整测量周期。这是很好的建议,但它有一个问题:如果你为不同的事件选择不同的测量周期,那将会很令人困惑。这尤其在你有很多事件时更为明显。你是否必须将观察周期设置得适合你感兴趣的罕见事件?这种方法的缺点是,如果你使用一个漫长的周期来测量行为,那么在为新账户获得有效测量之前需要很长时间。在第七章中,我将展示你可以拥有两全其美的方法:在长时间内测量一些行为,在较短时间内测量其他行为,并且仍然以平均数的方式呈现它们,以便于解释。

3.10 测量账户期限

到目前为止,我们只考虑了基于事件的行性行为测量。但是,还有一些重要的客户测量是基于订阅而非事件。账户成为客户或活跃用户的时间长度就是这样的一个测量。我把账户成为客户的时间长度称为账户期限,而不是像账户年龄这样的东西,因为年龄可能会与人的实际年龄(或公司的年龄)混淆。

账户期限在分析流失率时很重要,因为它可以以显著的方式与流失率相关:在客户生命周期中可能存在某些点,流失最(或最不)可能发生,或者流失率可能会随着账户期限的延长而普遍降低(或增加)。在第五章中,我将向你展示如何进行这些分析;现在,你将学习如何计算账户期限。

3.10.1 账户期限定义

如果每个账户只有一个订阅(或如果没有正式的订阅,则为活动周期),那么计算账户期限将很容易。这是从订阅开始到期限测量时的时间长度。但在多订阅的背景下(或“混乱”的订阅中),期限计算会更复杂。你不仅想要从当前订阅开始的时间,因为客户可能有一个不间断的早期订阅序列。在这种情况下,将它们都视为一个大的订阅期限测量是有意义的。如果账户在遥远的过去活跃过,然后流失,那么这不会算作“老”客户,因为他们并没有一直活跃。如果一个老客户在长期缺席后重新注册,他们更像是一个新客户,因此期限应该只测量当前订阅。图 3.19 展示了多订阅假设情况下的账户期限定义。

图片

图 3.19 多订阅账户的账户期限

同时,通常允许订阅之间存在小的间隔,同时仍然将其视为连续订阅以进行任职测量是个好主意。例如,假设一个账户未能续订是因为文件中的信用卡已过期;账户随后更新了卡片并在几天后再次注册。订阅中的几天或一周的间隔可能不应该改变任职测量,因为它们并不是真正的新注册客户。另一方面,如果订阅中存在数月(或更长时间)的间隔,那么将新的订阅视为客户和任职计算的全新开始是公平的。

考虑到为任职计算而将账户视为新账户所需的确切间隔量取决于业务。对于月度订阅,通常可以接受一个月的间隔。对于年度订阅,一个月或两个月的间隔,甚至多达四个月的间隔可以被认为是订阅中断的足够短的时间,仍然可以忽略不计,用于任职计算。

定义 账户任职——客户在其当前不间断的订阅序列或当前不间断的活动期间使用产品的时长,可能包括订阅或活动之间的相对较短的时间间隔。

图 3.19 显示,在某个观察时间点,账户正处于其第四个订阅的中间。订阅 3 和订阅 4 之间没有间隔,订阅 2 和订阅 3 之间有一个小的(允许的)间隔,而订阅 1 和订阅 2 之间有一个大的间隔。任职时间是从订阅 2 开始到观察时的时间。

基于图 3.19 中说明的任职计算的更多细节显示在图 3.20 中。任职从第一个订阅的开始为 0,每天增加 1,直到第一个订阅因流失而结束。在流失之后,任职不再定义;甚至不是零。只有当新的订阅开始时,任职才被定义并重新开始积累。任何给定日期的计算会计算自相关订阅开始日期以来的天数。与大多数指标计算一样,对于每个账户,这种计算可能很繁琐或无法手动完成。但是,SQL 再次提供了一个执行任意复杂订阅场景计算的方法。

图片

图 3.20 展示了基于图 3.19 中说明的账户任职计算示例

3.10.2 账户任职的递归表表达式

在多订阅情况下计算账户任职需要 SQL 的一个高级功能,称为递归公用表表达式(recursive CTEs)。正如其名称所暗示的,递归 CTEs 允许在构建结果集时递归地运行SELECT语句。递归 CTEs 与标准 CTEs 类似,但不同之处在于它们有两个部分:

  • 一个主或“锚点”SELECT 语句,定义 CTE 的列并使用初始结果集填充表。

  • 一个递归的 SELECT 语句,通过重复执行直到不再产生更多行,向 CTE 中添加更多行。递归的 SELECT 语句可以引用 CTE 中的当前结果集以及架构中可用的其他表。

这可能听起来很抽象,但计算来自多个订阅的账户服务期的难题将使递归 SQL 计算变得具体。以下是找到任何与当前订阅具有不间断序列且允许一个月间隔的最早订阅起始日期的递归策略:

  1. 通过为每个账户选择具有最小起始日期(如果有多个当前活跃订阅)的当前活跃订阅来创建 CTE。

  2. 选择具有较早起始日期和结束日期的其他相同账户订阅,这些日期在相同账户当前最早起始日期之前最多一个月。

如果步骤 2 重复进行,直到没有更早的订阅起始日期,那么 CTE 中最早起始日期的订阅就是你要找的:与当前订阅形成不间断订阅序列的任何订阅的最早起始日期。找到最早的订阅起始日期很容易适应递归 CTE 的框架。

图 3.21 展示了递归方法在图 3.19 和 3.20 中的假设多订阅场景中的操作示例。以下是通过递归找到最早起始日期的步骤:

  1. 在初始化时,订阅 5 的起始日期(1-Sep)被输入到该账户的 CTE 表中,这是当前的,因为它在当前日期之前开始,并且结束日期在当前日期之后。

  2. 在递归步骤的第一迭代中,订阅 4 的起始日期(1-Aug)被输入到 CTE 中,因为其结束日期在订阅 5 的起始日期之后,但起始日期更早。

  3. 在递归步骤的第二迭代中,订阅 3 的起始日期(1-Jul)被输入到 CTE 中,因为其结束日期在订阅 4 的起始日期之后,但起始日期更早。

  4. 在递归步骤的第三次迭代中,订阅 2 的起始日期(15-Apr)被输入到 CTE 中,因为间隔很小,并且订阅 2 的结束日期仍然足够接近订阅 3 的起始日期。

  5. 在递归步骤的第四次迭代中,由于订阅 1 的结束日期与订阅 2 的起始日期相距太远,因此不再向该账户的表中输入更多行。递归 SELECT 语句可以为其他账户生成额外的结果,但此账户已完成。

  6. 递归完成后,假设账户的 CTE 中的最小起始日期是订阅 2 的起始日期。

从那个最早日期到现在的这段时间是账户任期。这个过程通过递归 SQL 程序识别当前订阅并回溯到相应的最早开始日期,允许对任何数量的账户和订阅进行高效的账户任期计算。

图 3.21 账户任期递归计算示例

3.10.3 账户任期 SQL 程序

列表 3.12 显示了执行账户任期度量的 SQL。现在您已经了解了计算策略,我将根据列表解释一些关于递归 CTE 的更多细节。首先,关于递归 CTE 的一般说明。

注意:对于所有 CTE,RECURSIVE关键字都出现在WITH关键字之后,即使第一个 CTE 可能不是递归的。

接下来是列表 3.12 的细节:

  1. 第一个 CTE,date_range,设置了任期计算的日期。

  2. 第二个 CTE,earlier_starts,是递归的。我选择这个名字是因为它递归地找到同一账户的订阅的较早开始日期。

    1. earlier_starts的前半部分从订阅表中选择了任何当前活跃订阅的最小开始日期。为了选择活跃订阅,查询使用了通常的检查,即如果订阅在计算日期之前开始并在某个未来的日期结束(或者没有定义的结束日期),则该订阅是活跃的。

    2. earlier_starts的第一部分之后是一些重要的 SQL 关键字。

      注意:在递归 CTE 的两个部分之间是UNION关键字。它指定递归查询的结果与 CTE 中已有的结果合并,不重复。未使用的替代方案是UNION ALL,它保留重复项。

    3. 第二个,earlier_starts的递归部分,寻找那些开始日期更早且也满足结束日期条件的订阅。为了做到这一点,它使用订阅表和当前 CTE 结果集之间的内部连接。CTE 的递归部分通过账户 ID 进行连接,因为对较早开始日期的搜索是针对每个账户分别进行的。

  3. 所有 CTE 之后的最终查询是对递归 CTE 中的结果进行聚合,选择最早开始日期以及在当前 CTE 中计算自最早开始以来的天数。

列表 3.12 使用递归 CTE 测量账户任期

WITH RECURSIVE date_range AS (                             ①
    SELECT '2020-07-01'::date AS calc_date                 ②
),  earlier_starts AS (
    SELECT account_id, MIN(start_date) AS start_date       ③
    FROM subscription INNER JOIN date_range  
        ON start_date <= calc_date
        AND (end_date > calc_date or end_date is null)
    GROUP BY account_id
    UNION                                                  ④
    SELECT s.account_id, s.start_date                      ⑤
    FROM subscription s INNER JOIN earlier_starts *e* 
        ON s.account_id=e.account_id                       ⑥
        AND s.start_date < e.start_date                    ⑦
        AND s.end_date >= (e.start_date-31)                ⑧

) SELECT account_id, MIN(start_date) 
    AS earliest_start,                                     ⑨
    calc_date-MIN(start_date) 
        AS subscriber_tenure_days                          ⑩
FROM earlier_starts cross join date_range                  ⑪
GROUP BY account_id, calc_date                             ⑫
ORDER BY account_id;

① RECURSIVE 关键字位于开头。

② 此 CTE 设置了要计算的日期。

③ 使用当前订阅的开始日期初始化

④ 将结果添加到 CTE 中,不重复

⑤ 插入新的账户 ID 和开始日期

⑥ 新记录必须是同一账户的。

⑦ 开始日期早于已输入的订阅的订阅

⑧ 新记录必须有一个在短暂间隔内的结束日期。

⑨ 对于每个账户,选择最早开始日期

⑩ 从最早开始日期到计算日期的时间

⑪ 交叉连接在每一行上重复 calc_date

SELECT 中所有非聚合项的 GROUP BY

图 3.22 展示了运行列表 3.12 的结果示例。输出显示了来自不同账户范围及其任期的 SELECTs。计算是在 2020 年 5 月 6 日进行的,当时最老的订阅者可以追溯到 2020 年 1 月,任期约为 100 天。对于更近的日期,有新账户,其任期仅为几天。

图片

图 3.22 运行账户任期计算的结果

运行列表 3.12 并查看你自己的数据是否得到类似的结果。如果你使用的是模拟数据和 Python 包装程序,那么你需要更新列表 3.12 的参数。结果将类似于图 3.22。

列表 3.12 和图 3.22 展示了最早开始日期以说明任期计算。实际上,目标是像度量一样运行计算并将其插入到度量表中,以便在后续的度量计算和分析(将在后面的章节中讨论)中使用一系列日期的任期计算。要将账户任期计算插入到度量模式中,需要对列表 3.12 进行一些修改,使其更像其他度量计算:

  • 通过在 calc_date CTE 中使用一系列日期,而不是仅使用一个计算日期,来对一系列日期进行计算。

  • 计算日期必须放在递归 CTE SELECT 语句和连接中。否则,递归 CTE 保持不变。

  • 在最终的 SELECT 中不要选择最早开始日期;相反,选择计算日期。这是插入度量表中的测量观察时间戳。

  • 如果你的数据仓库支持 INSERT SELECTs,则需要相应的 INSERT 语句(参见列表 3.3)。

一个经过这些修改的 SQL 程序,它计算度量并将其插入到数据库中,如列表 3.13 所示。运行列表 3.13 以插入度量(我们将在后面的章节中与其他度量一起分析它)。如果你使用的是包装程序来运行列表,则命令是

fight-churn/listings/run_churn_listing.py —chap 3 —listing 13

这将账户任期测量值插入到你的本地数据库中;你需要使用 SQL 查询(或列表 3.6 中的度量 QA 查询)来检查结果。注意,与列表 3.3 一样,除非你更改配置或从数据库中清理结果,否则只能运行此列表一次。最后,将新度量(account_tenure)的名称插入到度量名称表中。为此,你需要重用列表 3.4。为了使这更容易完成,运行列表的代码有另一个版本的列表 3.4 参数,这些参数已经为你设置好了:重新运行列表 3.4,但将参数 —version 11 添加到可执行命令中:

fight-churn/listings/run_churn_listing.py —chap 3 —listing 4 —version 11

列表 3.13 用于计算并保存账户任期作为度量的 INSERT SELECT SQL

WITH RECURSIVE date_vals AS (                                 ①
    SELECT i::timestamp AS metric_date                        ②
    FROM generate_series('2020-02-02', '2020-05-10', '7 day'::interval) i
),
earlier_starts AS                                             ③
(
    SELECT account_id, metric_date, 
        MIN(start_date) AS start_date                         ④
    FROM subscription INNER JOIN date_vals
        ON start_date <= metric_date                          ⑤
        AND (end_date > metric_date or end_date is null)
    GROUP BY account_id, metric_date

    UNION

    SELECT s.account_id, metric_date, s.start_date            ⑥
    FROM subscription s INNER JOIN earlier_starts e
        ON s.account_id=e.account_id
        AND s.start_date < e.start_date
        AND s.end_date >= (e.start_date-31)
)
INSERT INTO metric                                            ⑦
(account_id,metric_time,metric_name_id,metric_value)
SELECT account_id, metric_date, 8 AS metric_name_id,          ⑧
    extract(days FROM metric_date-MIN(start_date)) 
        AS metric_value                                       ⑨
FROM earlier_starts
GROUP BY account_id, metric_date
ORDER BY account_id, metric_date

RECURSIVE 关键字位于开头。

② 此 CTE 包含计算所需的日期。

③ 此 CTE 与列表 3.12 中的基本相同。

④ 为每个日期在递归 CTE 中开始一个条目

⑤ 分别按日期应用订阅活动条件

⑥ 指标日期必须包含在递归 SELECT 中。

⑦ INSERT 语句与之前指标的语句大致相同。

⑧ 将账户使用期限指标作为指标 ID 8 输入

⑨ 使用期限的值是针对每个指标日期单独计算的。

计算非订阅产品和服务的账户使用期限

如果你正在开发一个没有订阅功能的产品(如广告支持、零售、非营利性等),仍然重要的是要分析和理解账户使用期限与流失率之间的关系。唯一的区别是,你并不是从订阅来衡量使用期限,而是从事件来决定某人使用产品的时长。如果你认为这里描述的使用期限计算方法也适用于事件,你是正确的;程序将在下一章(4.3 节)中展示。这里描述的账户使用期限算法是非订阅产品创建流失分析数据集的重要组成部分。

如果你的产品没有订阅功能,这是一个跳到第四章并了解更多信息的好时机,因为下一节是关于特定于订阅产品的指标。但请从第四章的开始部分开始:4.3 节中的技术建立在 4.1 节和 4.2 节的基础上。

3.11 测量 MRR 和其他订阅指标

其他重要的客户测量指标来自他们的订阅,如账户使用期限,但这些指标依赖于订阅的细节,而不仅仅是订阅的开始和结束日期。像账户使用期限测量一样,这些测量在概念上简单直接,如果每个客户只有一个(且仅有一个)订阅,就不需要太多计算。然而,正如通常情况一样,当客户在一段时间内或同一时间拥有多个订阅时,事情会变得复杂。我将展示适用于任何类型“混乱”订阅数据的一般技术,如果你认为你的数据条件允许,你可以选择简化这种方法。

3.11.1 将 MRR 作为指标计算

当客户为订阅付费时,总月度经常性收入(MRR)是一个重要的测量指标。你可能不会将 MRR 视为一个测量指标,而是将其视为一个简单的事实。图 3.23 说明了,每当存在多个可能具有不同价格的订阅的可能性时,MRR 是你需要在不同的时间点测量的,就像常规的行为测量一样。

由于您无法保证账户在任何给定时间拥有哪些订阅,因此您必须单独计算您感兴趣的每个点的 MRR。并且因为多个订阅的成本会累加,所以计算会汇总任何给定日期上所有活跃订阅的总 MRR。图 3.23 中的示例说明了当旧订阅结束或新订阅开始时,MRR 如何发生变化。

图片

图 3.23 每月经常性收入(MRR)指标计算

该图说明了 MRR 随时间的变化:

  1. 客户从基础计划开始(MRR = 19)。

  2. 客户暂时流失(MRR = 0)。

  3. 客户再次使用基础计划注册(MRR = 19)。

  4. 客户购买了附加功能(MRR = 33)。

  5. 在附加功能到期之前,客户升级到高级计划(MRR = 43)。

  6. 附加订阅过期(MRR = 29)。

虽然 MRR 可以随时变化,但如果您每周计算其他行为度量(如第 3.5.1 节中建议的),那么在您进行其他指标计算的那些日期上计算所有账户的 MRR 就足够了。列表 3.14 显示了计算一系列日期上所有账户总 MRR 的 SQL 查询。计算 MRR 的策略如下:

  1. 使用generate_series定义一个固定的测量日期序列(如果使用除 Postgres 以外的数据库,请参阅第 3.4 节侧边栏中关于生成序列函数的替代方案)。

  2. 将订阅与日期连接起来,以找到每个日期上所有活跃的订阅。如果订阅的开始日期在或早于测量日期,并且结束日期在测量日期之后(或者没有结束日期),则该订阅是活跃的。

  3. 使用标准聚合来汇总每个日期上所有订阅的总 MRR。

列表 3.14 计算 MRR 作为指标的 SQL

WITH date_vals AS (                                       ①
    SELECT i::timestamp as metric_date 
    FROM generate_series('2020-04-02', '2020-04-09', '7 day'::interval) i
)
SELECT account_id, metric_date, SUM(mrr)as total_mrr      ②
FROM subscription INNER JOIN date_vals
    ON start_date <= metric_date                          ③
    AND (end_date > metric_date or end_date is null)      ④
GROUP BY account_id, metric_date                          ⑤

① 此 CTE 包含计算日期。

② 对于每个账户和日期,汇总总 MRR

③ 限制到在测量日期之前开始的订阅

④ 限制到在测量日期之后结束的订阅

⑤ 在每个账户和日期上汇总总金额

列表 3.14 没有示例输出。到现在为止,您知道每个账户和日期都会有 MRR 结果。在撰写本文时,默认模拟数据仅包含每个账户的一个订阅和一个 MRR(价格)。因此,您可以运行列表 3.14,但如果您使用默认模拟数据,结果可能不会很有趣。对于真实公司,MRR 通常在不同账户之间变化,并且很少看到单个账户 MRR 的变化。这种变化仅在两个测量日期之间账户的订阅计划发生变化时才会发生。

虽然这种计算月均收入的方法可能看起来过于复杂或计算成本高昂,但它比计算基于事件的指标要快得多,因为客户通常的订阅数量比事件少。此外,将不经常变化的月均收入存储在每周(或每日)更新的表中可能看起来效率低下。这是一个合理的批评,但在实践中,这只是存储在数据仓库中的另一个指标,而数据仓库通常在整个分析过程中包含数十(或更多)个指标。将月均收入指标以与其他行为指标相同的格式存储的好处超过了任何缺点。正如下一章所展示的,这使得将月均收入与其他行为指标集成变得更加容易。

3.11.2 特定金额的订阅

到目前为止,在讨论订阅时,我还没有谈到订阅的具体用途细节。或者更确切地说,订阅被展示为是为了那些有名称但没有其他事实或区分细节的产品。但许多订阅是为了某些具体的东西,从订阅赋予用户一定类型产品使用固定配额的意义上来说。

例如,在 SaaS 中,订阅通常是针对一定数量的座位,这意味着允许的最大用户数。在电信和物联网(IoT)中,通常有一组固定的手机或设备,或者可能是数据或带宽配额。为了泛指订阅的这些属性,通常将订阅所针对的东西称为订阅的单位,而单位的数量是数量。

定义:单位——对于订阅,由订阅提供的特定类型的权利。数量——订阅者有权获得多少单位。

表 3.6 显示了当订阅与关联的单位和数量相关联时可以使用的扩展订阅表模式。这个订阅模式增加了一个文本字段来描述单位,以及一个数字字段来表示数量。请注意,在这个模式中,每个订阅只为单一类型的单位,但在实践中,可以一起销售多种类型的单位。如果一起销售多种类型的单位,通常在模式中以单独的订阅形式输入它们,并具有相同的开始和结束日期。在订阅业务管理软件系统的术语中,对于特定时间段内某些数量单位的单个销售通常被称为收费段、费率计划收费或仅仅是收费。

定义:收费段——对于一定数量某些单位的单一周期性合同。也称为费率计划收费或仅仅是收费。

如果使用收费段术语,则订阅被定义为相关收费段的一组。为了简单和一致性,在这本书中,我将把订阅表中此类条目称为订阅,但理解客户在任何时间点都可以有多个订阅。这主要是一个关于客户销售的各种周期性产品的部分与整体语义问题。

表 3.6 带有单位、数量和计费周期的订阅表架构

类型 备注
subscription_id integerchar 在第二章中引入的标准订阅字段
account_id integerchar
product_id integerchar
start_date date
end_date date
mrr double precision
quantity integerdouble 此订阅的单位数量
units chartext 此订阅的单位
billing_period_(months``) integer 客户被开具发票的月份数间隔

3.11.3 将订阅单位数量作为指标计算

现在引入具有多个单位的订阅的目的在于,客户为每种单位类型订阅的数量是一个重要的指标,就像 MRR 一样。计算单位数量指标依赖于几乎相同的计算。

与 MRR 计算的一个不同之处在于,不是对 MRR 字段求和,而是对数量字段求和。并且当存在多种类型的单位时,必须有一个额外的约束来选择正确的单位:每种单位类型应该有一个度量指标。计算订阅单位数量的策略如下:

  1. 使用 generate_series 定义一个固定的测量日期序列(如果使用除 Postgres 以外的数据库,请参阅 3.5.1 节侧边栏中关于生成序列函数的替代方案)。

  2. 将订阅与日期连接起来,以找到每个日期上所有活跃的订阅。一个订阅是活跃的,如果其开始日期在或早于测量日期,并且结束日期在测量日期之后,或者如果没有结束日期。

  3. 限制订阅为具有正确类型单位的订阅。

  4. 使用标准聚合对每个日期上所有匹配的订阅的总数量进行求和。

计算订阅单位数量的 SQL 在列表 3.15 中显示。正如承诺的那样,它与列表 3.14 中的 MRR 指标计算类似。您可能会想知道,在求和单位数量时,我为什么使用聚合 SQL 来求总和,而不是每种单位类型只有一个订阅?实际上,数量增加是一种非常常见的附加订阅类型。如果客户需要更多单位,并且他们正处于现有订阅的中途,而不是结束原始订阅并开始新的订阅,那么通常更容易为额外单位创建一个带有自己价格和开始和结束日期的第二个订阅。这就是为什么将单位数量作为可以随时间变化的指标来计算,并使用聚合来计算指标的原因。

列表 3.15 计算总单位数量作为指标的 SQL

WITH date_vals AS (                                      ①
    SELECT i::timestamp as metric_date 
    FROM generate_series('2020-04-02', '2020-04-09', '7 day'::interval) i
)
SELECT account_id, metric_date, SUM(quantity) 
    AS total_seats                                       ②
FROM subscription INNER JOIN date_vals
    ON start_date <= metric_date                         ③
    AND (end_date > metric_date or end_date is null)     ④
WHERE units = 'Seat'                                     ⑤
GROUP BY account_id, metric_date                         ⑥

① 此 CTE 包含计算所需的日期。

② 对订阅的总数量进行求和

③ 对测量日期之前开始的订阅进行限制

④ 对测量日期之后结束的订阅的限制

⑤ 对座位单位进行限制

⑥ 对每个账户和日期进行总计

默认的模拟数据不包括订阅单位或数量,因此您无法在模拟数据上运行此操作。这个例子只是为了向您展示当您拥有包含单位数量的自己的数据时如何操作。但模拟代码可以扩展以包括这些细节:我鼓励您作为练习这样做。

3.11.4 将计费周期作为指标计算

表 3.5 显示了扩展的订阅表模式。在上一个部分,我们讨论了如何从订阅单位数量中创建指标。表 3.5 还有一个在查看流失时通常相关的元素:订阅的计费周期。

定义:计费周期—衡量客户被计费的频率。每月计费定义为计费周期为 1;年度计费(每 12 个月计费一次)为计费周期为 12;依此类推。

计费周期可能很重要,因为具有不同支付频率的订阅者可能会以不同的速率流失。通常情况下(但不总是如此),处于较长期限支付周期(例如年度)的人比处于较短期限支付周期(例如每月)的人流失率低。当付款在服务之前进行时,这一点尤其正确,因为客户很难或无法获得退款,因此他们不太可能在中途流失。年度计费通常伴随着折扣,以吸引客户提前支付大额款项:对于订阅业务来说,问题是提供的折扣是否由较低的流失率所证明。当我们在下一章中查看计费周期对流失的影响以及第八章中的客户终身价值时,您将学习如何回答这个问题。

计费周期可以并且应该像基于订阅的任何指标一样处理。它应该从与行为指标相同的间隔对订阅进行聚合计算,以便可以轻松地与其他指标结合进行客户流失分析。计算计费周期作为指标的 SQL 语句显示在列表 3.16 中。它与本节前面(MRR 和单位数量)显示的订阅指标有很多共同之处。关于计费周期指标的一个新颖点是,将不同订阅上的多个计费周期组合的聚合不是求和。这是因为同时有两个 12 个月(年度)计费周期,例如,并不会形成一个 24 个月的计费周期;不同订阅上的计费周期不是累加的。

在不同的计费周期上有多个订阅是罕见的。即使客户有多个订阅产品,大多数客户也会在同一个计费周期内支付单一发票。这绝对是最佳实践,因为客户更喜欢简单的计费方式!但指标计算中需要某种聚合,以确保每个账户在每个测量日期只有一个结果,即使有多个活跃的订阅。聚合的选择是最小计费周期、平均计费周期或最大计费周期。所有这些选择在所有订阅具有相同周期时都返回单个正确的计费周期,并且它们返回一个介于最小和最大计费周期之间的数值。在列表 3.16 中,我选择了最小值,因为如果客户每月收到账单(计费周期 1),他们可能表现得像一个每月收到账单的客户,即使他们还有其他按更长周期计费的产品。

列表 3.16 计算计费周期作为指标的 SQL

WITH date_vals AS (                                               ①
    SELECT i::timestamp as metric_date 
    FROM generate_series('2020-04-02', '2020-04-09', '7 day'::interval) i
)
SELECT account_id, metric_date, MIN(bill_period_months) 
    AS billing_period                                             ②
FROM subscription INNER JOIN date_vals
    ON start_date <= metric_date                                  ③
    AND (end_date > metric_date or end_date is null)              ④
GROUP BY account_id, metric_date                                  ⑤

① 此 CTE 包含计算日期。

② 对于每个账户和日期,总计总量

③ 以测量日期开始的订阅限制

④ 在测量日期之后结束的订阅限制

⑤ 每个账户和日期的总计

您可以通过遵循书中代码的说明来运行列表 3.14,但如果您使用的是默认的模拟数据,那么结果可能不太有趣。在撰写本文时,默认的模拟数据仅包括一个计费周期(每月),但它会向您显示每个账户始终有一个一个月的计费周期。话虽如此,模拟代码可以扩展以包括像变化计费周期这样的细节。我鼓励您作为练习这样做,然后提交一个拉取请求来分享您的工作。

用于自动化指标计算的软件框架

如果还不明显,我必须告诉你一些坏消息:生成和保存大量指标(如客户事件的计数、平均值和总和)会很快变得乏味。如果你有超过几种事件类型,请使用软件尽可能自动化这个过程。如果你还没有从事过这类项目,我应该警告你,这通常比你想象的要多的工作,因为不可避免的是,你不仅仅计算一次指标。你最终会在你和其他组织成员考虑不同的选择和纠正 QA 测试中发现的问题时多次计算它们。对于模拟,你不必处理所有这些,因为它只是一个简单的模拟,我已经为你设置了合理的指标。

警告 在实际案例研究中,你可能需要在测试和修复多个版本时多次计算你的指标。

吸收要点 自动化是成功迭代各种指标以对抗客户流失的关键。

运行列表的 Python 脚本是这种自动化框架的示例,但它不是针对计算指标的任务进行优化的。一个指标计算软件框架至少应包括以下特性:

  • 以通用方式存储指标 SQL 程序,以便可以使用相同的 SQL 通过绑定事件 ID 作为变量和参数化选项(如指标计算中的时间周期)来计算许多不同事件上的指标

  • 处理将生成的指标(包括名称)插入数据仓库的细节

  • 当指标重新计算时删除旧结果

程序包装器被编写来演示各种列表,它实现了第一个目标但没有实现第二个。更高级的指标计算框架可能包括控制指标计算日期范围和自动更新数据仓库中新事件数据到达时的指标等特性。

如何设计和实现更好的指标计算框架超出了本书的范围,因为这取决于特定用例的软件工程练习。本书是关于数据分析和数据科学的。但我已经在 GitHub 仓库中发布了另一个用 Python 编写的指标计算框架的示例:github.com/carl24k/fight-churn/tree/master/metric-framework。在我决定书中的列表之前,我使用了这个框架进行客户案例研究,并且它更专注于指标。

摘要

  • 行为测量,也称为指标,总结每个客户在某个时间点或多个时间点的事件。

  • 指标通过提供行为概要使客户账户可比较。这是必要的,因为不同账户的事件发生速率和不同时间各不相同。

  • 常见度量标准是在从周到一年不等的时间周期内测量的。

  • 对于使用一个月或更短时间周期的测量,最好使用七天的倍数,因为几乎所有的人类活动都遵循每周周期。

  • 常见度量标准包括

    • 事件计数

    • 事件属性(如持续时间、美元价值或大小)的平均值

    • 事件属性(如持续时间、美元价值或大小)的总计(总和)

  • 所有常见度量标准都可以使用聚合 SQL SELECT语句计算。

  • 所有事件和度量标准都需要进行质量保证(QA)测试,因为事件数据并不总是可靠的,度量计算可能包含错误。

  • 计算度量标准的正确程序如下:

    1. 对事件数据进行 QA 测试。

    2. 计算度量标准。

    3. 对度量标准进行 QA 测试。

  • 关于度量标准的一个重要 QA 测试是计算出的度量标准数量以及平均、最小和最大值随时间的变化。

  • 另一个关于度量的重要 QA 测试是,有多少百分比的活动账户的度量值不为零,以及对于所有客户,平均、最小和最大测量值是多少。

  • 关于事件的一个重要 QA 测试是计算每个账户每月的平均事件数量,并确认这与业务专家的意见一致。

  • 当事件很少发生时,进行事件测量的观察时间周期需要更长。

  • 账户任期衡量账户在其当前订阅或活动期间作为客户的时间长度,忽略与当前不连续的较老订阅或活动期间。

  • 当账户可以有多个订阅(或流失和重新注册)时,则账户任期应使用递归公用表表达式(CTE)来计算。

  • 当账户可以有多个不同价格的订阅时,则月度经常性收入应基于与基于事件的指标相同频率的订阅来计算作为度量标准。

  • 订阅可以赋予用户特定数量的产品或功能。这个数量被称为数量,而产品或功能被称为订阅的单位。

  • 当订阅可以有不同数量和/或单位时,每个客户的单位数量应基于与基于事件的指标相同频率的订阅来计算作为度量标准。

  • 订阅可以有不同计费周期,这意味着支付之间的时间长度(每月、每年等)。

  • 当订阅可以有不同计费周期时,则每个客户的计费周期应基于与基于事件的指标相同频率的订阅来计算作为度量标准。

4 观察续订和流失

本章涵盖

  • 在流失前选择观察的提前期

  • 从订阅或活动中选择观察日期

  • 通过展平指标数据创建分析数据集

  • 导出当前客户列表以进行细分

使用数据对抗流失的本质是从每次客户选择继续服务或流失时发生的自然实验中学习。在这个背景下,自然实验意味着一个测试你感兴趣的结果的情况,但你没有像正式实验那样设置它。这些实验是已经发生的流失和续订,结果正等待你在数据仓库中。为什么你不从这些结果中学习呢?实际上,如果你以前从未这样做过,观察这些实验和阅读结果可能会有些棘手。本章教您正确观察已经在你自己的数据中发生的客户实验的方法。

本章的场景假设您已经生成了行为指标(如第三章所述)并计算了一些类型的客户流失率测量(第二章)。本章是流失分析的准备步骤。您将收集在客户流失或继续服务时已知时间点的客户指标观察结果。与第一章中引入的整体书籍场景相关,本章重点介绍图 4.1 中突出的过程。

图片

图 4.1 本章在用数据对抗流失的过程中的位置

本章的组织结构如下:

  • 在 4.1 节中,我介绍了使用数据集从客户那里学习想法。

  • 4.2 节讨论了在概念层面上如何选择观察结果,并介绍了提前期(lead time)的概念。

  • 4.3 节展示了如何简化存在多个重叠订阅或订阅之间有间隔的数据。这极大地简化了选择观察日期的过程。

  • 4.4 节将这些技术应用于没有实际订阅的产品,而是通过合并活动并应用第 4.3 节的技术来使用客户事件数据。

  • 4.5 节教您如何生成一组观察日期,这些日期是在使用第 4.2 节和第 4.3 节的技术准备数据后为顾客生成的。

  • 4.6 节通过教您如何将观察日期与第三章中的指标结合起来,形成分析流失的数据集。

  • 4.7 节增加了一个相关技术:导出当前或最近的客户快照以用于细分。

4.1 数据集简介

在大多数这些场景中,从客户那里学习所面临的挑战部分是由于复杂性,部分是由于后勤考虑。观察许多客户复杂的原因在于他们都在与您的产品的旅程中处于不同的阶段。现在就查看所有客户或任何单一固定时间点是没有意义的。您希望在他们与产品的生命周期中的相同点(或几个点)观察他们,这样他们才是可比较的。如果您这样做不正确,在错误的时间观察,可能会扭曲您的分析,并在对抗流失的斗争中适得其反。本章将向您介绍如何在客户生命周期中挑选合适的观察点。

然后,在所有客户的观察点,您都会捕捉到所有指标在那个时间点的快照。(上一章中的指标计算都是在一系列时间序列中运行的,以便实现这一点。)这个客户快照的组合集合被称为客户观察数据集,或简单地称为数据集。如果您还不熟悉这个术语,数据集在数据科学和统计学中用于特定分析的数据集合。

定义 数据集——对一组您感兴趣分析的情境(事实)和结果的简要总结。通常,数据集是一个单张表格(或文件),每一行都有相同数量的列,并且每一行都包含一个情境和结果的完整信息。

当一组数据被称为数据集时,这意味着数据被组织在一个表格中,每一行都有相同数量的列。每一行都包含一个实例或现象观察的完整信息(意味着不同的行是不同的观察),每一列对应于关于该情况的一种事实(通常是测量或指标)。当您创建数据集时,您确保没有缺失字段或空(空)值。您必须为缺失的测量提供合理的默认值或排除包含缺失数据的观察。

定义 流失分析数据集——每一行代表一个面临流失或留存决策的客户的数据集。结果是他们的行为。这些情境的事实是客户的行为指标测量(以及可能关于他们的其他数据)。

创建此数据集的后勤挑战与第二章和第三章中相同:数据是敏感的,并且可能很大,因此如果您能在数据库或数据仓库中完成所有数据处理,那就更好了。像前几章一样,使这成为可能的方法是编写简短的 SQL 程序并将关键结果保存在数据仓库中。最终,可以有效地提取一个简洁的数据集,其中只包含尽可能少的敏感信息,以便进行进一步分析。

4.2 如何观察客户

为了观察客户流失或继续使用产品时发生的自然实验,你需要首先询问何时进行观察。首先,我们以抽象层面考虑这个问题(代码将在后面的章节中介绍)。

4.2.1 观察提前期

何时观察客户是一个简单的问题,对吧?在客户流失时观察客户,难道不是这个目的吗?并不完全是这样。这样思考一下:当客户流失时,一个媒体分享应用客户的用户行为指标会是什么样子?登录?零。下载?零。点赞?零。因为他们已经流失,他们在产品上的所有行为都应该已经停止。在客户已经流失时观察他们并不很有帮助。此外,在有人流失后,你几乎没有机会让他们再次注册:你更有可能在他们在做出决定之前影响他们。

吸收要点:在客户流失前说服客户留下比在客户流失后重新注册更容易。因此,流失前的时间段是分析的重点。

在客户流失前观察客户。对!我称之为在观察时具有观察提前期的客户,这意味着在真正感兴趣的事情(续订或流失)之前进行观察。在客户流失前应该观察多久:在他们流失前一天?也许吧,但更有可能的是,你应该观察他们在流失前更长时间的行为。这是因为,通常,客户在流失前的行为会发生变化。这如图 4.2 中的假设例子所示。

图片

图 4.2 流失前的时间段和客户行为

如果有人计划流失,在流失前的时期,某些行为可能会减少,而其他行为可能会增加。以一个假设的文件共享服务为例,在流失前,上传可能会完全停止,因为客户不想浪费时间贡献任何其他东西。相反,他们会在服务结束前专注于下载内容。作为另一个例子,登录在流失前的时期可能会增加,然后变为零。

对于某些产品,这类行为的变化可以在客户流失前的一段时间内轻易地识别出可能流失的客户。但是,由即将发生的流失引起的行为仍然不是你想要观察的,因为那并不能告诉你客户最初为什么选择流失。你想要观察的是客户在决定流失之前的状态,因为那时你正在观察客户在做出决定时的样子。这很重要,因为当客户还在做决定时,你最有机会影响他们!我再次强调这个观点。

要点:分析的目标是识别和理解仍在权衡是否流失的客户,因为那时你影响他们的机会最大。

你如何知道客户是否还在权衡是否流失或继续使用产品?除非你有预知能力,否则你无法确切知道。你必须观察客户在合理预期他们正在考虑下一次续订的时间,而不是在最后一次续订后立即,也不是在即将到来的续订前,他们可能会流失。所需的时间取决于服务类型,但一般来说,承诺期限越长,服务越昂贵,提前期就应该越长:

  • 对于月度订阅,在月度续订前一周到两周观察客户,或者在当前月份的一半到四分之三处。

  • 对于消费者或小型企业的年度订阅,在年度续订前大约一个月观察客户。

  • 对于与大型企业的年度订阅,在续订前观察客户的时间从两个月到四个月不等;90 天是典型的时间。

对于非订阅产品,你不需要选择提前期。你选择一系列定期间隔的观察日期,就像订阅产品一样。但由于没有续订,你无法确定何时有人可能考虑取消。

4.2.2 观察续订和流失的序列

当你创建数据集时,你不想只观察流失的客户。你还想观察续订的客户。这样,你可以比较流失和续订,并在分析中看到差异。而且,你不想只选择少数续订:为了分析的目的,你想要选择足够的续订,以便观察数据集中的续订观察与真实的保留率相匹配。

要点:对于流失分析数据集,尽量使数据集中的续订比例与真实的保留率相匹配。流失应在数据集中按真实流失率的比例存在。

例如,如果你有 5%的流失率(churn rate)和 95%的保留率(retention rate),你希望观察到的数据集也大约有 5%的流失和 95%的续订。这可能听起来安排起来很复杂,但实际上很简单:你只需观察每个账户的每次续订以及流失情况。这样,你观察到的续订和流失的比例将与你的真实流失率大致相同。

如果订阅没有固定期限或自动续订,观察应根据每次付款到期的时间进行。付款通常在订阅开始后固定时间段内到期:对于大多数消费者订阅,通常是每月一次。为了与有提前期的流失观察保持一致,你应在每次续订或付款前应用相同的提前期。图 4.3 说明了这种情况。

订阅有定期支付(例如,每月)并持续到取消为止。选择的观察日期是每次付款到期前的提前期。订阅最终在最后一个付费月份结束后结束,流失观察是在该月结束前的提前期进行的;那时客户正在做出最终决定是否流失或续费。这就是为什么本节标题为“观察续费和流失序列”的原因。通常,您会在他们续费时观察每个账户多次,而在他们流失时只观察一次。

图片

图 4.3 支付周期日期、提前期和观察序列

如果您的产品有在不同续费或支付周期上的订阅怎么办?例如,许多产品既有月度计划也有年度计划。有多种处理方式,但我的建议是假设所有客户都在相同的续费或支付周期上,以相同的频率观察所有客户。要选择的观察频率是最常见的支付或续费周期。通常,这是您用来报价流失的周期,所以选择应该是显而易见的:

  • 对于报告每月流失率的消费者订阅,即使有些客户续费或支付年度合同,也要每月观察客户。

  • 对于报告年度流失率的商业订阅,即使有些客户支付或续费按月或季度计划,也要每年观察客户。

记住:当不确定时,如果根据特定时期(每月、每季度或每年)计算您的流失率最有意义,那么这可能就是观察产品整个生命周期中订阅者的正确时期。

如果您这么想,从某种意义上说,在年中观察年度客户是没有意义的,因为他们那时没有机会流失,而且他们可能也没有在考虑这个问题。但如果你每年只观察一次年度客户,这会使得在数据中重现流失和续费率变得复杂,同时也使得解释年度计划和月度计划的影响变得更加困难。(我将在第五章学习如何分析计划的流失影响时进一步解释。)

4.2.3 从订阅创建数据集的概述

现在,我将介绍创建实际订阅数据集的步骤;没有订阅的情况在 4.4 节中介绍。整个过程的概述如图 4.4 所示。过程的起点是第二章中描述的订阅数据以及您在第三章中创建并在数据仓库中保存的指标。

图 4.4 中展示的主要步骤如下:

  1. 识别客户目前订阅的一个或多个正在进行的订阅(没有流失)。这些被称为持续进行的活跃期。

  2. 识别每个客户订阅一个或多个订阅的时间段以及这些时间段何时以流失结束。这些被称为以流失结束的活跃期。

  3. 使用这些活跃期,根据上一节中描述的支付或续订周期和提前期,为每个客户选择观察日期序列。跟踪这些观察中哪些是在实际流失前的提前期内进行的。

  4. 使用观察日期序列来选择数据仓库中保存的指标。指标值,连同流失和观察细节,在一个数据集中选择,每个客户每行数据集为一个观察。

图片

图 4.4 从订阅中创建数据集的四个步骤流程

我刚刚提到了一个将在整个过程中使用的重要新概念。

定义 活跃期 —— 指订阅者至少有一个活跃订阅的时间段。订阅之间可以有小的间隔,而不会打断活跃期。

第 4.3 节提供了此过程步骤 1 和 2 的详细信息:创建活跃期。第 4.4 节偏离主题,解释了对于没有实际订阅的产品,此过程有何不同。第 4.5 节继续并解释此过程的第 3 步:选择观察日期。最后,第 4.6 节讨论了最终步骤:将指标数据与观察日期合并并导出数据集。

4.3 从订阅中识别活跃期

在此过程阶段的目标是在适当的时间对您的订阅者进行定期观察,以了解他们为何流失。第一步是处理由订阅中的冗余或不规则性引起的问题。这些问题与你在第三章学习如何计算账户期限时遇到的问题相同。某些客户可能有多个订阅,这样客户活跃的有效期可能比任何单个订阅都要长,而且个人订阅之间可能有短间隔,这些间隔你可能不想视为流失。此外,当有多个产品或存在基础产品和附加产品时,一些客户可能同时拥有多个订阅。这些额外订阅的日期可能不会与主要订阅对齐。

如果您的订阅产品没有这个步骤旨在处理的任何复杂性,您可以跳到第 4.5 节。为了明确,只有当您产品的订阅已经保证由每个账户的单个、不重叠的周期组成,且没有无意中产生的间隔时,才跳过本节。

4.3.1 活跃期

活跃期间,如图 4.5 所示,是指账户通过一个或多个单独的订阅持续订阅的一段时间。在图 4.5 中,总共有七个单独的订阅,按照它们的开始时间顺序编号。活跃期间与订阅的不同之处在于,活跃期间合并了任何多个订阅并忽略了短暂的间隔。每个账户一次只能处于一个活跃期间,活跃期间之间的任何间隔都代表真正的流失,随后在稍后的日期重新订阅。

注意:如果一个账户不在活跃期间,则最后一个活跃期间的结束即为流失。

在图 4.5 中,第一个活跃期间是一个简单的单一订阅。图中的第 2 期是一个由三个主要订阅(编号 2、3 和 5)组成的复杂活跃期间的例子。在订阅 2 和 3 之间有一个短暂的间隔——短到不应该被视为流失。订阅 3 和 5 对齐,因此没有间隔;还有一个附加订阅(编号 4),它在订阅 3 的中间开始,在订阅 5 的中间结束。所有这些都符合构成一个活跃期间的条件。第 3 期是一个活跃期间正在进行的例子;这是当订阅没有结束日期或在分析时结束日期在未来的情况。

图 4.5

图 4.5 从多个订阅中确定活跃期间

4.3.2 存储活跃期间的架构

活跃期间只是形成数据集过程的一步,将它们存储在数据库中很方便。或者,您可能选择将本章中所有简短程序合并成一个大型程序,该程序生成数据集而不需要任何永久存储。因为这本书通过简短程序教授每个步骤,所以我将结果存储在表中。存储活跃期间的所需模式如图 4.1 所示。它在某些方面与存储订阅的模式(第二章,表 2.1)相似,因为每条记录都有一个账户 ID 和开始日期,这是必需的,还有一个日期是流失日期,它像订阅的结束日期一样可以为空。但是,活跃期间记录与订阅有一些重要的不同:

  • 对于一个活跃期间,account_idstart_date的组合必须是唯一的,因此它们应该作为表上的复合键或索引来实现。

  • 活跃期间没有与订阅相关的任何细节,如产品或 MRR。

(如果您需要关于如何在多个订阅的上下文中计算 MRR 等指标的帮助,请参阅第 3.10 节。)

备注:account_idstart_date 的约束可以作为一个表约束实现,但还有一个隐含的约束必须通过应用程序逻辑实现。对于每个账户,活跃周期的开始日期和流失日期必须定义非重叠的时间段,并且每个账户只能有一个没有流失日期的活跃周期:当前活跃周期。

表 4.1 活跃周期表架构

类型 备注
account_id integerchar 不为空;复合键
start_date date 不为空;复合键
churn_date date 可为空

4.3.3 查找正在进行的活跃周期

我首先向您展示如何找到仍在进行的活跃周期,因为这与找到已经结束的活跃周期更容易。对于正在进行的活跃周期,您不需要找到流失日期,因为您知道它们不会以流失结束:这只是找到开始日期的问题。看看图 4.5,回想一下您在 3.10 节中学到的账户租期计算(见图 3.19-3.21)。找到正在进行的活跃周期的开始日期基本上与找到每个账户的租期相同(即今天为止的每个账户的租期)。唯一的区别是,所需的结果是客户积极订阅的周期开始日期,而不是他们作为积极订阅者的时长。与计算账户租期一样,您需要找到的不仅仅是当前订阅的开始,还要找到最老的、与当前订阅重叠或形成连续序列的订阅。

列表 4.1 展示了一个简短的 SQL 程序,用于计算当前正在进行的活跃周期。再次注意,结果仅仅是一个列表,列出了目前正处于活跃订阅中的所有账户以及它们进入任何当前连续活跃订阅的最早开始日期。这与上一章中描述的,使用递归公用表表达式(CTE)进行的账户租期计算相同,但我会再次讲解,作为快速回顾。这个过程通过以下 SELECT 语句实现:

  1. 一个 CTE 包含控制何时以及如何找到活跃周期的参数。

  2. 一个两部分的递归 CTE 找到活跃订阅的序列。

    • 初始化 SELECT 语句找出所有当前活跃的账户。

    • 递归 SELECT 语句找出与当前找到的订阅重叠或与当前订阅连续但更早的早期订阅。

  3. 一个聚合 SELECT 语句找出每个账户的任何订阅的最早开始日期。

因为这个结果被保存并与其他以流失结束的活跃周期的结果合并,所以最终的 SELECT 语句包括一个 INSERT 语句,将结果保存到名为 active_period 的表中。

列表 4.1 当前正在进行的活跃周期

WITH RECURSIVE active_period_params AS                                     ①
(
    SELECT interval 7  AS allowed_gap,                                     ②
    '2020-05-10'::date AS calc_date                                        ③
),
active AS                                                                  ④
(

    SELECT distinct account_id, min(start_date) 
        AS start_date                                                      ⑤
    FROM subscription INNER JOIN active_period_params 
        ON start_date <= calc_date
        AND (end_date > calc_date or end_date is null)
    GROUP BY account_id

    UNION

    SELECT s.account_id, s.start_date                                      ⑥
    FROM subscription s 
    CROSS JOIN active_period_params 
    INNER JOIN active *e* ON s.account_id=e.account_id                       ⑦
        AND s.start_date < e.start_date                                    ⑧
        AND s.end_date >= (e.start_date-allowed_gap)::date                 ⑨

) 

INSERT INTO active_period (account_id, start_date, churn_date)             ⑩
SELECT account_id, min(start_date) AS start_date, NULL::date AS churn_date ⑪
FROM active
GROUP BY account_id, churn_date                                            ⑫

① 此 CTE 包含常量参数。

② 在流失前的最大无订阅时间

③ 考虑的最新日期

④ 这与计算账户任期(第三章)相同

⑤ 使用每个当前订阅的开始初始化递归 CTE

⑥ 在递归过程中插入新的账户 ID 和开始日期

⑦ 新记录必须属于同一账户。

⑧ 新记录是为开始时间更早的订阅

⑨ 新记录必须有一个在允许的间隔内的结束日期。

⑩ 将结果保存到 active_period 表中

⑪ 选择最早的开始日期和为流失日期选择 null

⑫ 按 account_id 分组;流失日期必须在 GROUP BY 中

按照书中可下载代码的说明运行列表 4.1,代码位于 www.manning.com/books/fighting-churn-with-datagithub.com/carl24k/fight-churn。它已预配置为在默认模拟数据集上运行。(数据模拟的说明在 README 页面中。)设置好你的环境后,使用以下命令运行列表 4.1:

fight-churn/listings/run_churn_listing.py —chapter 4 —listing 1

包装程序 run_churn_listing 打印它正在运行的 SQL。但请注意,列表 4.1 在数据库中执行插入操作,因此不会产生任何输出。要查看结果,请使用你选择的 SQL 查询方法运行查询(参见 README 获取建议);例如:

SELECT * FROM active_period ORDER BY account_id, start_date;

图 4.6 显示了在默认模拟数据集上运行列表 4.1 的结果,然后使用类似于上一个的 SELECT 语句查看结果。如果你有大量数据,请使用 LIMIT 子句,但对于默认模拟,你可能不需要它。结果包含在模拟开始附近开始的账户,以及模拟结束时添加的账户。由于对 active_period 表的约束,你只能运行列表 7.1 一次,而无需删除表中已有的数据。

图 4.6 运行列表 4.1 的活动期结果

4.3.4 寻找以流失结束的活动期

找到以流失结束的活动期可能是本书中最先进的 SQL 程序。但这个程序并不比你所见过的任何程序更难;它只是结合了你已经掌握的其他技术:第二章中用于计算流失的外连接技术,以及第三章中(并在上一节中回顾)用于找到任何订阅最早开始时间并持续到另一个订阅的递归 CTE。

寻找所有流失的算法基于第二章中用于计算流失率的示例外连接方法,但它并不完全相同。它从观察开始,即每次流失都必须对应一个订阅的结束日期。进一步来说,如果一个订阅的结束日期没有通过相同账户的新订阅进行其他扩展,那么这个结束日期就是一个流失。

定义延期——另一个在先前订阅结束之前或允许的间隔期内开始,并且具有未来结束日期的订阅。延期延长了活跃期。这种延期定义仅适用于当前算法的讨论,并不是贸易中一般使用的术语。

图 4.7 从结束日期和延期中查找流失

延期之所以被称为延期,是因为它延长了先前订阅的结束日期,并防止该结束日期发生流失。关键是流失是一个没有延期的结束日期。图 4.7 提供了一个通过考虑结束日期和延期来查找流失的例子。它基于图 4.5 中所示的订阅序列。为了识别一个账户的流失,你使用以下步骤:

  1. 识别该账户所有订阅的结束日期。(没有结束日期的订阅不能是流失,所以忽略它们。)这限于在当前日期结束的时间段内的结束日期,并且可以追溯到你在寻找流失时感兴趣的最早日期。

  2. 识别所有延长这些结束日期的延期。这些是在其他订阅结束日期之前或允许的间隔时间内开始,并在未来较晚结束的订阅。

  3. 选择没有延期的结束日期。这些就是流失。在 SQL 中,使用外连接来处理有延期的结束日期,并选择步骤 1 中的那些订阅,在外连接上使用null,以及步骤 2 中的延期订阅。这些结束日期对应于以流失结束的活跃期。

图 4.8 显示了查找流失及其对应开始日期的完整过程。它包括首先通过考虑结束日期和延期来查找流失,然后查找以流失结束的活跃期的开始日期。这发生在两个额外的步骤(4 和 5)中:

  1. 开始日期的查找方式与持续活跃期的开始日期(以及账户任期计算)相同:使用递归 CTE 来搜索越来越早的开始日期。

  2. 从在流失结束的订阅之前的订阅中取最小开始日期(如果有)。

图 4.8 寻找流失和相应的活跃期开始日期的过程

列表 4.2 是查找以流失结束的活跃期的 SQL 程序。它包括四个 CTE:

  • active_period_params—包含定义程序何时查找流失以及订阅之间允许的最大间隔(不被视为流失)的固定常数。

  • end_dates—包含所有在所需时间段内有结束日期的订阅。为了方便下一步操作,它还计算了可以延长此结束日期的最大日期:结束日期加上参数中定义的允许的间隔。

  • extensions—包含所有具有另一个扩展其订阅(扩展)的订阅结束日期。这是任何在最大扩展日期(在end_dates CTE 中计算)之前开始且具有未来结束日期或null结束日期的匹配账户的订阅。

  • churns—一个递归 CTE,执行算法的关键计算:

    • 初始化SELECT语句是结束日期和扩展的外连接,它只选择没有扩展的结束日期。这些是取消订阅。

    • 递归的SELECT语句找到在相同账户的取消订阅之前的订阅的较早开始日期;这些中的最早的是活跃期的开始。

列表 4.2 中的最后一个SELECT语句以与列表 4.1 中活跃期仍然活跃的方式找到每个结束日期对应的最低开始日期。它还包含INSERT语句,将此结果与active_period表中的持续活跃期一起保存。关键的是,没有扩展的订阅的结束日期是取消订阅日期。

列表 4.2 以取消订阅结束的活跃期

WITH RECURSIVE active_period_params AS                        ①
(

    SELECT INTERVAL '14 day' AS allowed_gap,                  ②
           '2020-05-10'::date AS observe_end,                 ③
           '2020-02-09'::date AS observe_start                ④

),
end_dates AS                                                  ⑤
(

    SELECT distinct account_id, start_date, end_date,         ⑥
        (end_date + allowed_gap)::date AS extension_max       ⑦
    FROM subscription INNER JOIN active_period_params 
        ON end_date between observe_start 
        AND observe_end                                       ⑧

), 
extensions AS                                                 ⑨
(

    SELECT distinct e.account_id, e.end_date                  ⑩
    FROM end_dates *e* INNER JOIN subscription s 
        ON e.account_id = s.account_id
        AND s.start_date <= e.extension_max                   ⑪
        AND (s.end_date > e.end_date 
            OR s.end_date is null)                            ⑫

),
churns AS                                                     ⑬
(

    SELECT e.account_id, e.start_date, 
        e.end_date AS churn_date                              ⑭
    FROM end_dates *e* LEFT OUTER JOIN extensions *x*             ⑮
    ON e.account_id = x.account_id                            ⑯
        AND e.end_date = x.end_date
    WHERE x.end_date is null                                  ⑰

    UNION

    SELECT s.account_id, s.start_date, e.churn_date           ⑱
    FROM subscription s 
    CROSS JOIN active_period_params
    INNER JOIN churns *e* ON s.account_id=e.account_id
        AND s.start_date < e.start_date
        AND s.end_date >= (e.start_date-allowed_gap)::date

) 
INSERT INTO active_period 
    (account_id, start_date, churn_date)                      ⑲
SELECT account_id, min(start_date) AS start_date, 
    churn_date                                                ⑳
FROM churns
GROUP BY account_id, churn_date

① 此 CTE 持有常量参数。

② 在取消订阅前的最大无订阅时间

③ 用于查找取消订阅的最新日期

④ 用于查找取消订阅的最早日期

⑤ 此 CTE 包含每个账户的唯一开始和结束日期。

⑥ 如果多个订阅有相同的结束日期,则使用 DISTINCT

⑦ 账户应重新注册以避免取消订阅的日期

⑧ 限制在检查取消订阅期间的结束日期

⑨ 此 CTE 包含扩展结束日期的订阅。

⑩ 如果多个订阅有相同的日期,则使用 DISTINCT

⑪ 其他订阅必须从扩展期结束的日期开始。

⑫ 其他订阅必须有一个在原始结束日期之后的结束日期。

⑬ 此 CTE 识别取消订阅并找到周期开始日期。

⑭ 订阅的结束日期是取消订阅日期。

⑮ 通过外连接识别取消订阅

⑯ 连接结束日期和扩展

⑰ 识别没有扩展的取消订阅

⑱ 递归 SELECT 找到最早开始日期。

⑲ 将结果插入到 active_periods 表中

⑳ 选择每个取消订阅的最小开始日期

按照本书可下载代码中的说明运行列表 4.2。如果您运行了其他列表,那么现在您知道如何通过更改包装程序的参数为—chapter 4 —listing 2来做这件事。请注意,列表 4.2 执行数据库插入操作,就像列表 4.1 一样。它不会产生任何输出(运行列表的代码打印正在运行的 SQL)。要查看运行列表 4.2 后的结果,运行如下查询:

SELECT * FROM active_period WHERE churn_date is not null ORDER BY account_id, start_date;

图 4.9 展示了在默认模拟数据集上运行列表 4.2 的结果,然后使用类似于之前的SELECT语句查看结果。如果你有大量数据,请使用LIMIT子句,但对于默认模拟,你可能不需要它。活跃期结束时的活跃期开始于模拟时间的所有部分,并且长度各异。由于active_period表的约束,你只能运行此 SQL 一次,而无需删除表中已有的数据。

图片

图 4.9 运行列表 4.2 的活跃期结束时的结果

4.4 为非订阅产品识别活跃期

从第一章开始,我就一直在告诉你,本书中分析流失的技术适用于没有实际订阅的产品,如广告支持的媒体、带有应用内购买的 APP 和零售网站。现在(终于)我将解释如何计算这些账户的活跃期。我等到现在是因为,到这个时候,你已经学到了必要的技巧。

4.4.1 活跃期定义

在第二章中,我提到,理解非订阅产品流失的关键是计算反映个人账户在产品上活跃的时期的活跃期,类似于订阅。在这种情况下,流失被定义为账户在超过一些最大允许时间后变得不活跃。你应该选择这个时间限制,以便大多数变得不活跃的人不会回来,或者如果他们回来了,也应该公平地将其视为一个新的开始。

图 4.10 展示了从事件中派生的活跃期的概念。目的是为每个账户找到一组事件,其中没有事件之间的间隔超过允许的间隔。一旦计算出这样的活跃期,就可以像订阅一样计算流失率。此外,本书中描述的所有分析技术都可以应用,无需任何其他修改。

定义:从事件中派生的活跃期——一个用户至少有一个事件的时间段。事件之间可以有间隔,但不超过限制,不会打断活跃期。从事件中派生的活跃期的定义与从多个订阅中派生的活跃期的定义相似。

图片

图 4.10 从事件中派生的活跃期

基本思路是这样的:在上一个部分,我展示了如何使用 SQL 算法处理账户在一段时间内可能拥有多个订阅的情况。找到这些订阅中任何一个的最早开始日期以及订阅序列何时结束是必要的。所有这些关于订阅的计算还包括一个允许的间隔,以防客户在订阅之间有短暂的空档期。如果你这么想,这正是确定账户活跃期间所需进行的计算。订阅和事件都需要分组,以确保组内没有任何订阅/事件之间的间隔超过允许的间隔。唯一的区别是,订阅有一个持续时间(订阅的结束日期在开始日期之后),但事件是在一个特定的时间点。一个类似于从订阅中查找活跃期间的算法也可以用来从事件中查找活跃期间。

但是,订阅和事件之间有一个重要的区别,这会影响该算法的性能,如果不是逻辑:账户通常一次只有一到几个订阅,但账户可以有非常大量的事件。换句话说,订阅通常是小数据,而事件通常是大数据。因此,直接将上一节中提到的活跃期间算法应用于事件可能不是一个好主意。相反,进行简化:定义一个活跃周为账户在这七天内发生任何事件的七天。

定义:活跃周——一个账户在七天内至少有一个事件的七天周期。

第一步是计算所有账户哪些周是活跃的。这可以通过使用聚合查询并保存结果来完成。这个简单的第一步减少了后续步骤所需的数据量。如果你的用户通常每周有 100 个事件(例如),那么在聚合之后,表示活动数据的大小就减少到原来的百分之一。如果客户每周有 1,000 个事件……(你明白我的意思)。

当然,这样的活跃期间只能精确到聚合步骤中定义的每周间隔,以识别流失日期。这些活跃期间并不能告诉你用户何时变得活跃或流失的确切日期或时间。如果你认为每周的周期对于找到活跃期间的开始和结束不够精确,可以切换到每日聚合。这样你就可以在计算中获得每日的精确度,这是每周精确度的七倍。如果这对你的数据规模和运行过程的系统来说没问题,那就没问题。这是在所需的时间精度和拥有的数据规模以及可用的计算资源之间的一种权衡。我将继续使用假设每周聚合周期的例子,因为这通常是精度和计算成本之间最好的折衷方案。

4.4.2 从事件形成数据集的过程

在计算完活跃周之后,你可以使用持续活跃期算法(列表 4.1)和已流失活跃期算法(列表 4.2)对活跃周进行处理,而不是使用订阅来创建流失分析数据集。因为这些算法旨在合并连续的订阅,它们也可以合并活跃周,你可以选择在形成活跃周时允许没有间隔(或任何数量的周组成的间隔)。(如果你选择使用活跃日,而不是活跃周,你可以运行相同的算法,以天为单位,允许的间隔定义为天数。)运行列表 4.1 和 4.2 的结果就是你要找的:账户在连续时间段内发生事件(每周至少一次)的日期。这些活跃期已知在过去已经结束流失或一直持续到目前。图 4.11 说明了从事件形成流失数据集的完整过程。

图 4.11 基于事件的活动周期查找过程

如果你将形成事件数据集的过程(图 4.11)与上一节中形成订阅数据集的过程进行比较,唯一的区别是在开始时多了一步:将事件分组到活动周。因为其余的过程是相同的,所以每周分组是唯一需要的新代码。

4.4.3 计算活跃周的 SQL

因为存储了有活动的周,你需要创建一个数据库表来保存它们,使用表 4.2 中显示的架构。这与你之前看到的订阅和活跃期架构类似。对于活跃周,结束日期与开始日期是冗余的,因为开始日期唯一地标识了固定长度周期的结束日期。但包括结束日期允许上一节中用于订阅的 SQL 程序与活跃周一起工作,而不改变查询的逻辑。

表 4.2 活跃周表架构

类型 备注
account_id integerchar 不为空;复合键
start_date date 不为空;复合键
end_date date 不为空

列表 4.3 提供了将事件分组到周活动的 SQL 语句。这个技术对于任何了解聚合GROUP BY查询的人来说都是众所周知的,因为查询的主要逻辑是按照一周的时间段定义事件组。列表 4.3 中唯一值得注意的技术是使用生成序列函数来选择日期。

列表 4.3 将事件分组到周活动的代码

WITH periods AS 
(                                                           ①
    SELECT i::timestamp AS period_start, 
        i::timestamp + '7 day'::interval AS period_end 
    FROM generate_series('2020-02-09', '2020-05-10', '7 day'::interval) i
)
INSERT INTO active_week 
    (account_id, start_date, end_date)                      ②
SELECT account_id, 
period_start::date,                                         ③
period_end::date                                            ③
FROM event INNER JOIN periods 
    ON event_time>=period_start                             ④
    AND event_time < period_end                             ⑤
GROUP BY account_id, period_start, period_end               ⑥

① 此 CTE 包含一系列周间隔。

② 将结果插入到active_periods

③ 序列中每个周期的开始和结束

④ 事件时间大于或等于周期开始。

⑤ 事件时间必须严格小于周期结束。

⑥ 按account ID和周期日期分组

按照书中可下载代码的说明运行列表 4.3。使用包装程序,将参数更改为 —chapter 4 —listing 3。请注意,列表 4.3 在数据库中执行插入操作,就像列表 4.1 和 4.2 一样,因此不会产生任何输出。(运行列表的代码会打印正在运行的 SQL 语句。)要查看运行列表 4.3 后的结果,请运行类似以下的查询:

SELECT * FROM active_week ORDER BY account_id, start_date;

图 4.12 显示了在默认模拟数据集上运行列表 4.3 的结果,然后使用类似于之前的 SELECT 语句查看结果。如果你有大量数据,请使用 LIMIT 子句,但对于默认模拟通常不需要。请注意,GitHub 仓库中保存的代码没有设置为从活跃周计算活跃期。我鼓励你自己进行这些修改。

图 4.12

图 4.12 运行列表 4.3 的示例输出

在计算活跃周并将它们保存到表中之后,你可以使用列表 4.1 和 4.2 中的相同程序来查找活跃期。将这些程序修改为使用 active_week 表(表 4.2)而不是订阅表(表 2.1)。一旦从活跃周计算出活跃期,这些就可以在第二章的标准流失计算(列表 2.2)中替代订阅使用。

注意:要使用列表 4.1 和 4.2 中的程序,并使用从活动而非订阅中得出的活跃周,请通过将连接中的 subscription 表替换为 active_period 表来修改代码;不需要进行其他更改。

注意:要使用列表 2.2 中的程序计算从活动而非订阅中的流失率,请通过将连接中的订阅表替换为 active_period 表来修改代码;此外,active_period 中的 churn_date 列替换了 subscription 表中的 end_date 列。

4.5 选择观察日期

你的目标是定期在适当的时间观察你的订阅者,以了解他们为何会流失。一旦订阅(或事件)被划分为活跃期(无论来自订阅还是活动),下一步就是为每个账户选择实际的观察日期。

4.5.1 平衡流失和非流失观察

正如我在第 4.2 节中描述的,这个想法不仅是在账户流失时观察账户,还要在账户没有流失时对账户进行快照。当数据集中的流失和非流失观察与流失率和续订率成比例时,流失分析效果最佳。实现这一目标的方法是观察每个账户在计算流失率时使用的相同周期周期。此外,请记住,观察是提前进行的。正如第 4.2 节中所述,提前期被设计得如此之短,以至于在客户可能已经决定是否流失或留下之前进行观察。这个过程之前在图 4.3 中已经说明,并在图 4.13 中重现。

图 4.13

图 4.13 重述:支付周期日期、提前期和观察序列

如所示,除了在客户流失前观察客户外,在整个订阅期间应定期进行观察。这些观察应以与组织衡量其流失频率相同的频率进行,这通常与客户续订(对于定期订阅)或支付账单(对于常青订阅)的频率相同:B2B(商业)产品的年度频率和 B2C(消费者)或 SMB(小型和中型企业)产品的月度频率。为了与流失观察保持一致,这些观察在每次支付或续订前都使用提前期。

4.5.2 观察日期选择算法

考虑图 4.13,选择观察日期的详细算法如下:

  1. 从每个账户每个活跃期间的起始日期开始。

  2. 对于首次观察:

    1. 将你将进行观察的周期时间间隔(例如,一个月)分别加到所有账户和活跃期间的起始日期上。对于订阅,这通常是下一次支付或续订日期。

    2. 减去提前期以找到每个活跃期间的首次观察日期。

    3. 如果此观察日期之后发生流失,将其标记为流失观察,这意味着如果活跃期间在这次观察和下一次观察期间结束流失,那么这是流失前的最后一次观察。

  3. 对于每个账户的第二次观察:

    1. 将观察期的时间加倍加到每个账户的起始日期上。

    2. 减去提前期。

    3. 如果此观察日期之后发生流失,将其标记为流失观察。

    4. 对每个账户重复步骤 3,将添加到起始日期的周期数递增(并且始终减去提前期),直到下一个观察日期超出活跃期。

图 4.14 展示了运行算法选择每月观察日期的示例,其中有两个账户,提前期为七天。它表明在初始计算之后,每个账户都会在每月的同一天重复观察。如果有月度付款或订阅续订,日期会被安排在每月付款前一周。如果这是一个年度订阅的产品,观察日期将每年在同一天。再次强调,观察会被安排在年度续订(或付款)之前有提前期,提前期会更长——一到三个月,如第 4.2 节所述,但原理是相同的。

图 4.14 观察日期选择算法示意图

要为每个账户选择观察日期,从每个活跃期的起始日期开始,加上观察周期(在这个例子中是一个月),再减去提前期(一周),以得到第一个观察日期。要得到第二个观察日期,将观察周期加两次,再减去提前期,加到起始日期上。要得到第三个观察日期,将观察周期加三次,再减去提前期,依此类推。

4.5.3 观察日期 SQL 程序

因为观察日期被存储,至少暂时如此,所以你需要为它们创建另一个表。表 4.3 展示了用于存储观察日期的架构。此表包含账户 ID 和观察日期,这两者共同定义了表上的复合主键,以及一个额外的列:一个逻辑值,用于跟踪观察是否是导致流失的活跃期结束时的最后一个观察。

观察日期表架构

类型 备注
account_id integerchar Not null; compound key
observation_date date Not null; compound key
is_churn logical Not null

列表 4.4 提供了生成观察日期的 SQL 程序。它假设存在一个 active_period 表,其中包含从订阅中定义的周期。生成观察日期的 SQL 程序使用递归 CTE,创建观察日期的递归策略如下:

  1. 初始化递归 CTE,为每个 active_period 设置一个观察:

    1. 选择第一个观察日期为起始日期后的一个观察间隔,减去提前期。

    2. 在观察上设置计数器为 1。这用于计算后续观察的时间。

    3. 设置一个布尔值,指示流失日期是否在观察日期和下一个观察日期之间,这将是在此观察日期之后的观察间隔。

  2. 递归地插入每个活跃期的额外观察日期到 CTE 中:

    1. 计数器加一。

    2. 新的观察日期是从起始日期加上新的计数器值乘以观察周期,再减去提前期计算得出的。

    3. 在每个观测上设置一个布尔指示符,以便紧接活跃期结束(流失)的观测被设置为 true。

    4. 当满足以下条件之一时退出递归:

      • 下一个观测日期是在活跃期结束之后。

      • 下一个观测日期是在考虑的整体周期结束之后。

观测日期的 SQL 程序仅使用两个 CTE:一个用于设置常数参数,另一个用于递归。在此递归过程之后,结果被插入到观测表中。列表 4.4 中的 SQL 程序使用一个月的观测间隔和一周的提前时间。

列表 4.4 观测日期的 SQL

WITH RECURSIVE observation_params AS                                    ①
(                                                                       ②

    SELECT interval '1 month' AS obs_interval,                          ③
           interval '1 week'  AS lead_time,                             ④
           '2020-02-09'::date AS obs_start,                             ⑤
           '2020-05-10'::date AS obs_end                                ⑤

),
observations AS                                                         ⑥
(

    SELECT account_id,                                                  ⑦
    start_date,
    1 AS obs_count,                                                     ⑧
    (start_date+obs_interval-lead_time)::date 
        AS obs_date,                                                    ⑨
    CASE 
        WHEN churn_date >= (start_date +   obs_interval-lead_time)::date 
           AND churn_date <  (start_date + 2*obs_interval-lead_time)::date 
        THEN true 
        ELSE false 
    END AS is_churn                                                     ⑩
    FROM active_period INNER JOIN observation_params
    ON (churn_date > (obs_start+obs_interval-lead_time)::date 
        OR churn_date is null)                                          ⑪

  UNION                                                                 ⑫

  SELECT o.account_id, 
      o.start_date,
      obs_count+1 AS obs_count,                                         ⑬
      (o.start_date+(obs_count+1)*obs_interval-lead_time)::date 
          AS obs_date,                                                  ⑭
      CASE 
          WHEN churn_date >= (o.start_date + 
                              (obs_count+1)*obs_interval-lead_time)::date
          AND churn_date  <  (o.start_date + 
                              (obs_count+2)*obs_interval-lead_time)::date
          THEN true 
          ELSE false 
      END AS is_churn                                                   ⑮
  FROM observations o INNER JOIN observation_params
  ON  ( o.start_date+(obs_count+1)*obs_interval-lead_time)::date 
      <= obs_end                                                        ⑯
  INNER JOIN active_period s 
      ON s.account_id=o.account_id                                      ⑰
      AND ( o.start_date+(obs_count+1)* obs_interval-lead_time)::date 
          >= s.start_date                                               ⑱
      AND ((o.start_date+(obs_count+1)*obs_interval-lead_time)::date  
          <  s.churn_date or churn_date is null)
) 
INSERT INTO observation (account_id, observation_date, is_churn)
SELECT distinct account_id, obs_date, is_churn
FROM observations
INNER JOIN observation_params ON obs_date BETWEEN obs_start AND obs_end

① RECURSIVE 关键字位于开头。

② 此 CTE 保持常数参数。

③ 观测之间相隔一个月的日历月。

④ 观测是在提前七天进行。

⑤ 观测是在这些日期之间进行的。

⑥ 此递归 CTE 包含观测日期的序列。

⑦ 初始化每个账户的第一个观测记录

⑧ 计数器用于确定后续的观测日期

⑨ 观测是在开始后一个周期,减去提前时间。

⑩ 如果流失发生在观测和下一个周期之间,则将 is_churn 设置为 true。

⑪ 跳过流失发生在第一次观测之前的案例。

⑫ 递归的 SELECT 添加了额外的观测。

⑬ 每次新的观测增加计数器一次

⑭ 开始时间加上计数器乘以间隔,减去提前时间

⑮ 如果流失发生在观测和下一个周期之间,则将 is_churn 设置为 true。

⑯ 如果观测会超过日期限制,则不要添加观测。

⑰ 通过账户与上一个结果进行连接

⑱ 为继续的订阅周期添加新的观测。

按照书中可下载代码的说明运行列表 4.4。使用包装程序,将参数更改为 —chapter 4 —listing 4。请注意,与列表 4.1-4.3 一样,列表 4.4 在数据库中执行插入操作,因此不会产生任何输出。(运行列表的代码会打印正在运行的 SQL。)要查看运行列表 4.4 后的结果,请运行如下查询:

SELECT * FROM observation ORDER BY account_id, observation_date;

图 4.15 显示了在默认模拟数据集上运行列表 4.4 的结果,然后使用类似于前面的 SELECT 语句查看结果。如果你有大量数据,请使用 LIMIT 子句,但对于默认模拟通常不需要。is_churn = TRUE 的观测是在流失日期之前立即发生的。对于每个账户,观测日期按照观测间隔均匀分布,如指定。

图片

图 4.15 运行列表 4.4 的示例输出

你可能会想知道为什么列表 4.4 中的 SQL 程序保持一个计数器并将其乘以观测期持续时间来计算每个观测日期。这导致代码中反复出现以下表达式:

  (o.start_date+(obs_count+1)*obs_interval-lead_time)::date

另一种选择是将观察期添加到最后一次观察日期。虽然将观察期按顺序添加到日期会更简单,但这样做会导致月末日期处理不当:例如,如果起始日期是 31 日。那么当二月到来时,月份的天数会变成 28 日。这就是数据库在将月份加到 1 月 31 日时定义的结果。但是当下一个月到来时,如果算法简单地添加观察期,它不会将日期回移到 31 日;它会使用 3 月 28 日,并在随后的月份继续使用 28 日。在非闰年,所有月末续订都会在二月之后移至 28 日。

通过将观察期乘以并添加到每个观察的起始日期,31 日的续订被视为每个月有 31 天的 31 日。尽管在天数较少的月份中会相应地调整,但它不会改变后续月份的结果。

4.6 导出流失数据集

创建数据集的最终步骤是从观察日期选择所有账户的指标。原则上,这很简单,但像往常一样,还有一些复杂的情况。

提取数据集的一个重要部分是将数据从数据库表的传统格式转换为分析数据集的格式,这在图 4.16 中有说明。在分析数据集中,你必须安排数据,使得每一行对应一个账户的单次观察(无论是流失了还是未流失),每一列对应一个行为指标。然而,在数据库表中,数据是归一化的,因此所有指标的值都在一个单独的列中,另一个列标识了给定行中的哪些指标。在数据库中,单个账户在单个日期的行为快照分布在许多行中。这通常被称为宽数据与长数据。分析数据集是宽的,因为它有多个列用于所有不同的变量;数据库表是长的,因为数据都堆叠在一个列中。将数据从长转换为宽通常被称为数据展平,这就是创建流失数据集必须做的事情。(对于那些已经看到在电子表格中使用 Pivot 函数进行数据旋转的人来说,这也被称为数据旋转。)

4.6.1 数据集创建 SQL 程序

在标准 SQL 中进行数据旋转有一个技巧;如果你知道它,可以随意跳过。如果你不知道,准备好要么喜欢它要么讨厌它,但我保证你会再次使用这个技巧!

你可以使用 GROUP BY 聚合将如图 4.16 所示的度量表这样的高维表展平为宽数据集,其中将每个账户的单独度量行分组到每个日期的一行中。你可能已经看到了使用聚合函数(如求和或平均值)将多行合并为一行的用法。聚合函数也可以用于从多行中选择特定值并将每个值放入特定的列中,这是展平所需进行的转换。

图 4-16

![图 4.16 展平高维数据为宽数据]

列表 4.5 展示了将数据展平的技巧。它使用了多个 SUM 聚合函数,每个函数对应于你想要从高维表展平到宽表的每个度量指标。为了从列中获取所需的值(而不是向其添加任何内容),你需要在每个 SUM 函数内放置一个 CASE 语句,以从高维表中仅选择一种类型的度量值。一系列这样的 SELECT 语句在 GROUP BY 聚合内执行,有效地将高维数据展平为宽表。这样做可能不太美观,但它确实有效!

另一个复杂的问题是,你可能并不是每天都为你的账户计算度量指标。我建议你每周只计算一次度量指标。如果你遵循我的建议并且不每天计算度量指标,那么在创建数据集时,你需要选择那些不一定在观察当天存在的度量指标。这导致在 SELECT 中使用日期范围来选择度量指标。

列表 4.5 中的最后一个技巧是处理在观察日期附近没有为账户计算度量指标的情况。回想一下,在第三章中,计数和平均值度量指标被定义为当账户没有事件时,不存储任何度量值。这意味着对于这类度量指标,列表 4.5 中的 JOIN 语句可能没有任何值。但有一个度量指标始终有值,无论账户的事件如何:账户使用期限。

账户使用期限必须始终有一个值,对于有活跃订阅或处于活跃使用期间的用户来说更是如此。账户使用期限始终有值的事实意味着列表 4.5 中的 JOIN 操作始终为每个账户在观察日期周围找到至少一个度量指标。那么其他度量指标呢?CASE 语句的逻辑意味着任何其他被展平的度量指标都将填充为零,这有效地处理了这些类型度量指标的缺失值问题。

警告:如果你在每次计算时都不使用账户使用期限度量指标或至少一个保证每个账户每次计算都有值的度量指标,那么列表 4.5 中的查询可能会丢弃没有度量指标的账户观察结果。这个问题可以通过度量指标和观察结果之间的外连接来解决,但我的建议是在你的分析中包含账户使用期限。

列表 4.5 使用 CTE 来保存常量参数,包括一个指标计算间隔。INNER JOIN 语句使用指标间隔来选择在观测日期前七天内计算的最后指标。

注意:列表 4.5 的输出还包括账户 ID 和观测日期。尽管这些信息对于分析不是必需的,但这种描述性数据对于数据质量检查通常很有用。

在下一章中,你将花费更多时间了解如何检查以这种方式提取的数据集的质量。

列表 4.5 数据集创建的 SQL

WITH observation_params AS                                                 ①
(
    SELECT interval  interval '7 day'  
        AS metric_period,                                                  ②
    '2020-02-09'::timestamp AS obs_start,                                  ③
    '2020-05-10'::timestamp AS obs_end                                     ③
)
SELECT m.account_id, o.observation_date, is_churn,                         ④
SUM(CASE WHEN metric_name_id=0 THEN metric_value ELSE 0 END) 
    AS like_per_month,                                                     ⑤
SUM(CASE WHEN metric_name_id=1 THEN metric_value ELSE 0 END) 
    AS newfriend_per_month,
SUM(CASE WHEN metric_name_id=2 THEN metric_value ELSE 0 END) 
    AS post_per_month,
SUM(CASE WHEN metric_name_id=3 THEN metric_value ELSE 0 END) 
    AS adview_feed_per_month,
SUM(CASE WHEN metric_name_id=4 THEN metric_value ELSE 0 END) 
    AS dislike_per_month,
SUM(CASE WHEN metric_name_id=5 THEN metric_value ELSE 0 END) 
    AS unfriend_per_month,
SUM(CASE WHEN metric_name_id=6 THEN metric_value ELSE 0 END) 
    AS message_per_month,
SUM(CASE WHEN metric_name_id=7 THEN metric_value ELSE 0 END) 
    AS reply_per_month,
SUM(CASE WHEN metric_name_id=8 THEN metric_value ELSE 0 END) 
    AS account_tenure,
FROM metric m INNER JOIN observation_params
    ON metric_time between obs_start and obs_end                           ⑥
INNER JOIN observation o ON m.account_id = o.account_id
    AND m.metric_time > (o.observation_date - metric_period)::timestamp    ⑦
    AND m.metric_time <= o.observation_date::timestamp
GROUP BY m.account_id, metric_time, 
    observation_date, is_churn                                             ⑧
ORDER BY observation_date,m.account_id 

① 此 CTE 保存常量参数。

② 指标计算的频率:每七天一次

③ 在此日期范围内形成观测值

④ 每个账户每天一个观测值;包括流失标志

⑤ 将 CASE 语句的结果求和以简化数据

⑥ 通过观测参数连接以限制整体日期范围

⑦ 在观测值上连接,选择最新的指标

⑧ 使用 SUM/CASE 聚合模式的 GROUP BY

按照书中可下载代码的说明运行列表 4.5。使用包装程序,将参数更改为 —chapter 4 —listing 5。列表 4.5 以一个包含结果的 SELECT 语句结束。如果你使用 GitHub 仓库中的代码运行它,它会为你保存结果到一个 CSV 文件中。包装程序会打印出文件的路径;例如:

Saving: ../../../fight-churn-output/socialnet/socialnet_dataset.csv 

图 4.17 展示了运行列表 4.5 的结果示例。它跳过一些行以跟踪几个账户通过数据集;数据集观测值按日期排列,因此单个账户的记录分散在整个数据集中。

图 4.17 运行列表 4.5 的示例输出

注意,列表 4.5 是硬编码到固定数量的指标,具有预定义的名称,因此列表 4.5 只适用于默认的模拟数据集。如果你想在你的数据上运行列表 4.5,你需要修改它,使其反映你创建的具体指标。然而,更好的选择是使用一个脚本来自动化这一步骤,该脚本根据数据库中的任何指标生成正确的 SQL。书中代码中也包含了一个执行此操作的函数,但不是在列表中:数据集导出文件夹中包含一个脚本,用于清理结果表,并使用自定义生成的指标简化脚本作为本章所有列表的结束。README 文档中提供了有关如何配置和运行该程序的更多信息。

账户键对齐问题

本节中的代码假设你可以简单地将用于创建活跃期间的订阅中的账户 ID 与用于创建指标的活动中使用的账户 ID 相链接。不幸的是,订阅数据库和事件数据仓库通常是不同的系统。为了简化问题,本书将它们表示为位于一个共同的数据库中,并使用一个共同的账户 ID 集。为了让你为现实世界可能遇到的情况做好准备,我将解释一些你可能遇到的实际问题,如果你的订阅(观察)和事件(指标)数据位于两个不同的系统中。

你不能在不同的系统上运行像列表 4.5 那样的程序。你必须要么让包含指标的系统的观察日期可用,要么让包含订阅和观察日期的系统的指标可用。通常,从包含订阅的系统生成活跃期间数据并将其加载到包含指标的系统中,然后从包含指标的系统的活跃期间生成观察日期是最简单的。这是容易的部分。

当两个系统中的账户 ID 不匹配且需要映射时,事情会变得更加困难。必须创建一个查找表,其中每一行都有两个系统中账户的唯一匹配标识符。这种映射可以用作列表 4.5 中的额外内部连接。这并不太难,但真正的问题出现在无法创建完美映射的情况下,导致账户被删除、重复或两者兼而有之。例如,这可能会发生在事件数据通过电子邮件跟踪,而订阅通过某种数据库主键跟踪,并且有包含订阅的电子邮件,但这些电子邮件与事件数据不可靠地匹配的情况下。在业务环境中,如果部门级别有订阅,而事件数据通过用户跟踪,但某些用户被分配到多个部门或没有部门,也可能出现问题的另一种版本。除非你参与过大量的提取-转换-加载(ETL)管道,否则你可能不会想到这种映射问题可能会发生。我向你保证,它们非常常见。如果你发现自己处于这种情况,至少你并不孤单!

无论如何,我无法教给你一个总是能解决这类问题的技巧。我的方法是放弃不完整的记录,而不是试图修复它们,只要最终你拥有足够的数据进行分析即可。好消息是,你的数据不必完美才能使用!在我看来,如果数据有 90%是好的,那么它就是干净的;即使有些许杂乱,仍然可以使用。

对于受影响的数据来说,重要的是它在大体上是随机的,就受影响的客户类型而言。例如,如果所有流失的客户都使用一个特定的产品功能或来自一个销售渠道,那么你在这些群体上的结果将会不准确。但如果只有少数账户随机流失,或者随机地分布在所有产品功能和销售渠道中,那么你就不必担心。我们将在后面的章节中回顾一些从数据集中删除问题记录的额外方法,但你的最佳选择是在一开始就尽可能准确地获取映射。只是不要期望它完美无缺。

4.7 导出当前客户进行分段

本章教了你如何准备一个数据集来分析你的客户。但最终,你也会想要采取一些行动来尝试减少流失。正如我在第一章中解释的,有各种可能的策略来减少流失。从数据的角度来看,所有减少流失的方法都有一个共同点:你必须使用数据来挑选最合适的客户进行目标定位。这通常被称为分段。

定义 分段——根据一组标准选择一组客户。

现在,你将学习一种技术,通过制作当前客户的快照来为分段打下基础。更多关于分段的内容将在后面的章节中介绍。

4.7.1 选择活跃账户和度量

分段和针对干预措施定位客户的第一步是制作一个快照,显示你的客户当前的表现。这与当前客户集及其度量值的数据集相同,但没有历史记录。这比构建历史数据集简单。

要点 仅包含当前客户的数据库用于客户分段。

列表 4.6 展示了一个 SQL SELECT语句,用于创建一个适合分段的当前客户数据集。它使用了两个技巧:首先,一个 CTE 使用MAX聚合选择带有度量值的最新日期。你也可以简单地硬编码一个日期,但可能最带有任何度量值的最新日期就是你更新数据的最后一天。然后列表 4.6 使用了你在上一节中学到的扁平化聚合技巧。这就是全部内容!运行 SQL 并检查结果是否与列表 4.5 相似,但所有账户都在一个日期上观察:最新的。

列表 4.6 选择当前活跃账户

with metric_date AS                                               ①
(
    SELECT  max(metric_time) AS last_metric_time FROM metric
)
SELECT m.account_id, metric_time,
SUM(CASE WHEN metric_name_id=0 THEN metric_value ELSE 0 END)      ②
    AS like_per_month,
SUM(CASE WHEN metric_name_id=1 THEN metric_value ELSE 0 END) 
    AS newfriend_per_month,
SUM(CASE WHEN metric_name_id=2 THEN metric_value ELSE 0 END) 
    AS post_per_month,
SUM(CASE WHEN metric_name_id=3 THEN metric_value ELSE 0 END) 
    AS adview_feed_per_month,
SUM(CASE WHEN metric_name_id=4 THEN metric_value ELSE 0 END) 
    AS dislike_per_month,
SUM(CASE WHEN metric_name_id=5 THEN metric_value ELSE 0 END) 
    AS unfriend_per_month,
SUM(CASE WHEN metric_name_id=6 THEN metric_value ELSE 0 END) 
    AS message_per_month,
SUM(CASE WHEN metric_name_id=7 THEN metric_value ELSE 0 END) 
    AS reply_per_month, 
SUM(CASE WHEN metric_name_id=8 THEN metric_value ELSE 0 END) 
    AS account_tenure,
FROM metric m INNER JOIN metric_date 
    ON metric_time =last_metric_time                              ③
INNER JOIN subscription s 
    ON m.account_id=s.account_id                                  ④
WHERE s.start_date <= last_metric_time
AND (s.end_date >=last_metric_time or s.end_date is null)
GROUP BY m.account_id, metric_time
ORDER BY m.account_id

① 这个 CTE 选择了带有度量值的最新日期。

② 这就是与列表 4.5 一起教授的扁平化聚合。

③ 仅选择单日度量

④ 通过连接订阅来确保客户当前活跃

列出 4.6 还使用订阅表上的连接来确保这些客户仍然处于活跃订阅状态。在第三章中计算指标时,代码从未检查过客户是否活跃,因此可能会有已经淘汰但仍在指标时间段内有事件的客户条目。从指标计算的角度来看,结果只是额外的一些记录,所以我没有麻烦展示如何防止这些指标被保存。但在历史数据集中,我确保每个观察结果都对应一个活跃客户。但对于当前客户列表,你需要确保你只获取活跃订阅者的数据,订阅表上的连接处理了这个细节。

注意:在实际操作中,列出 4.6 时必须包括账户名称或电子邮件,或任何必要的标识符,以将账户与其他用于与客户进行干预的系统(如电子邮件营销和客户关系管理系统)联系起来。我在这个模拟示例中省略了这些细节。

4.7.2 按指标细分客户

现在你已经有一个当前客户列表以及他们所有的指标,细分行为是直接的。如果你将列出 4.6 的输出给业务同事,他们可能会知道该怎么做:在电子表格中打开它,并使用过滤功能探索和选择基于指标的不同的行为轮廓的客户。你还可以通过在列出 4.6 的SELECT语句中放入标准来定义细分。困难的部分是知道应该选择什么值来定义细分,以及首先应该关注哪些行为。为了以数据驱动的方式进行,你需要真正理解这些行为如何与淘汰和参与度相关联,这就是下一章的主题。

摘要

  • 淘汰分析数据集是客户行为快照的表格,包括已经淘汰和未淘汰的客户。

  • 淘汰领先指标是在客户尚未做出决定时表明淘汰可能性很高的行为。淘汰领先指标通常是抗淘汰工作的重点。

  • 淘汰滞后指标是客户在已经决定淘汰后通常会采取的行为。淘汰滞后行为通常不是淘汰的根本原因。

  • 为了将分析集中在淘汰领先指标上,在实际淘汰发生之前,对淘汰客户的客户行为快照进行了提前的领先时间处理。

  • 观察淘汰所需的时间通常在消费者产品淘汰前几周,对于商业产品则是一到三个月。

  • 确保数据集中的淘汰和非淘汰客户样本大致按实际淘汰率和续订率成比例,这一点很重要。

  • 对于续订的客户,行为快照的生成频率与流失测量频率相同:通常对于消费产品是每月一次,对于商业产品是每年一次。这确保了数据集中流失的比例大致与流失率相匹配。

  • 对于订阅产品,在续订或付款日期之前,会对未流失的客户进行行为快照,同时也有一个提前量。

  • 创建流失分析数据集的第一步是确定每个客户的活动期,即客户在短时间内至少有一个订阅(对于订阅产品)或至少有一个事件(对于非订阅产品)。

  • 对于非订阅产品,事件首先被聚合到周,作为一个单一指标来表示每个账户在该周是否有任何事件。通过活跃周找到活跃期。

  • 找到活跃期后,根据活跃期的开始,为每个账户按顺序选择观测日期。

  • 数据集是通过将观测日期与先前计算的指标合并来创建的。

  • 在创建数据集时,指标会被展平,这意味着它们从所有指标都在一列的格式转换为每个指标都在自己一列的格式。

第二部分:发起战争

现在您已经打下了基础,可以开始认真对抗客户流失了。对于数据人员来说,这场战斗主要意味着向组织中的客户流失斗士提供可操作的客户指标:产品经理、市场营销人员、客户成功和支持代表等等。优秀的、可操作的客户指标与客户流失和留存有明确的关系,可以用来细分客户以针对减少客户流失的干预措施。一套优秀的客户指标还必须简洁,这样客户流失斗士才能保持目标明确,不会因为信息过载而感到困惑。

第五章介绍了我用来理解客户流失与行为之间关系的方法。这一章将向您展示哪些指标可以推动客户健康度和参与度,并帮助您了解一个健康的客户是什么样的。

第六章向您展示当您拥有太多相关事件和指标时应该做什么。这个问题很重要,因为如今大多数在线服务拥有如此多的数据,结果就是信息过载。然而,当您掌握了本章中的技术后,就不会存在数据过多的问题了。

第七章教您如何制作高级客户指标,将所有内容整合在一起。这些指标比第三章中介绍的简单指标能提供更细致的理解和客户定位。您在第五章和第六章中学到的知识使得这种理解成为可能。

5 通过指标理解客户流失和行为

本章涵盖

  • 使用群体分析展示客户流失与指标之间的关系

  • 使用数据集统计来总结客户行为的范围

  • 将指标从其正常尺度转换为分数

  • 从群体分析中移除无效观察

  • 根据指标和客户流失定义客户细分

如果你需要使用统计学来理解你的实验,那么你应该已经做了更好的实验。

—欧内斯特·卢瑟福,1908 年诺贝尔化学奖获得者,以其发现放射性衰变而被称为“核物理之父”

是时候做你来的目的了:理解为什么你的客户会流失以及是什么让他们保持活跃。尽管花了些时间,但你在第三章和第四章学到的数据集是接下来工作的基础。你可能期望我现在会深入一些严肃的统计学或机器学习来进行分析。然而,我想将你的注意力引到页面顶部的引言,这是我最喜欢的科学家的名言。

这句话暗示,如果你使用统计学来分析实验,那么可能存在某些问题。在大数据和数据科学的时代,这可能会听起来像是异端。但我邀请你保持你的判断,并考虑卢瑟福想要表达的意思。首先,他是在一个科学家组装电学实验室设备的时代当物理学家。如果你的实验装置中有很多噪声,你可以通过在许多实验中平均结果来使用统计学来处理它,但也许你应该花更多的时间设置一个使用更少噪声设备的实验。对于一个 21 世纪的数据分析师来说,这意味着你应该花很多时间清理数据以获得更好的结果,这是完全正确的。但这能证明诋毁统计学的合理性吗?

也许我过分解读了卢瑟福的建议,但我想这其中有更深层次的意义。这也可能意味着,如果一个实验的结果并不是明显地证实了假设,那么你不必费心用统计学来查看是否可以使结果看起来更好。相反,提出一个更好的假设——一个真正能解释你所试图理解的事情的假设。为了达到这个目标,你应该设计实验来寻找强烈驱动结果的解释,而不是二级影响。如果这样的假设是正确的,实验的定性结果可以从结果图的一瞥中读取。

结果表明,对于客户流失分析和用户参与度来说,最重要的结果通常是这样的:当你看到它时,你就会知道,而且你不需要统计。此外,这个客户流失的特性将使得与组织中非技术性的商业人士沟通变得容易。在客户流失分析和行为度量指标的情况下,寻找与客户流失有强烈关系的度量,如果你找不到,就继续寻找。

要点你正在寻找与客户流失有强烈关系的行为度量。当你找到它时,你会看到结果,而不需要使用统计。

话虽如此,本书的第三部分,我将教授用于客户流失的统计和机器学习方法。这并不是我认为统计和机器学习没有用武之地;问题在于侧重点。此外,我目前不会使用统计和机器学习,但我没有说不会用到数学;我在本章和随后的章节中会教授一些必要的数学知识。

从本书的整体主题来看,本章的主题在图 5.1 中被突出显示。本章假设你已经使用第二章的技术测量了客户流失率,使用第三章的技术创建了行为度量,并在第四章中创建了一个数据集。本章是所有内容开始整合的地方!

图片

图 5.1 本章在用数据对抗客户流失的过程中的位置

下面是如何组织本章的:

  • 第 5.1 节介绍了我认为的度量群体技术,它允许你调查可能与客户流失相关的行为的影响。我通过案例研究中的例子来演示这一技术,以展示典型的结果是什么样的。

  • 在第 5.2 节中,我向你展示如何通过总结数据集中的所有行为来看到客户行为的整体图景。你在数据集摘要中找到的内容对于完善你的群体分析是有用的。

  • 第 5.3 节教授另一种辅助技术,称为评分,这是一种将客户度量转换为提高分析质量的方法。这里没有统计,但该节包含了一些方程式。

  • 在第 5.4 节中,我讨论了何时以及如何删除使群体分析难以解释的无效或不希望的数据。

  • 第 5.5 节介绍了如何使用群体分析来定义客户细分。

5.1 度量群体分析

群体分析是一种分析客户流失(以及其他行为)如何依赖于行为和订阅度量值的方法,就像第三章所教授的那样。

定义以下定义适用于本章:

  • 群体是一组具有相似性(在特定意义上,所有这些个体在相对较小的范围内都有一个特定的度量)的个人。

  • 度量群体是由在某个度量上具有相似值的客户群体定义的。

  • 群体分析是对不同群体在其他测量标准(不是用于定义群体的那个)上的比较——可能是另一个指标。

  • 流失率群体分析是对不同指标群体流失率的比较。

我通常使用“群体分析”这个术语来简化,但除非明确说明,否则这些分析都是基于流失的指标群体分析。你将在本书的其余部分看到很多群体分析,因此我将在查看用于计算和绘制结果的 Python 代码之前,花时间介绍这个概念。在你学习了概念和代码之后,我将通过一些实际案例研究来展示结果。

5.1.1 群体分析背后的理念

可能任何流失调查的最基本假设是,使用产品较多的用户比使用产品较少或根本不使用的用户更不可能流失。使用常见的产品行为来形成群体的流失群体分析是对该假设的测试。图 5.2 展示了这个想法。如果活跃客户比不活跃客户流失率低,那么活跃客户群体应该比不活跃客户群体有更低的流失率。你可以通过根据客户的活跃程度将客户分为群体,然后测量每个群体的流失率来检验这个假设。如果一个活动与较低的流失率相关,你应该发现最活跃群体的流失率最低,较不活跃群体的流失率较高,而最不活跃群体的流失率最高。图 5.2 展示了这个理想场景的三组。

要点 如果使用产品较少的客户流失率较低,那么相对不活跃的客户群体应该比相对活跃的客户群体有更高的流失率。

需要注意的一个重要观点是,在较不活跃和较活跃的群体中找到相对较高的和较低的流失率比期望所有活跃客户都不流失,所有不活跃客户都流失更现实。流失涉及很多明显的随机性:有时,你的最佳客户会离开,而最差的客户会留下,原因只有他们自己知道。比较群体的流失率(平均值)是有意义的,但你不能期望所有客户都表现出完全相同的流失行为。

图 5.2 指标群体的概念

接下来,考虑在实际情况中,如何进行群体分析,考虑到你在第四章中创建的数据集。当你完成那一章时,你创建了一个数据集,这是一个包含大量数据的大表格,每一行代表一个特定日期上客户的观察结果,包括多个指标以及客户在该日期是否流失或续订。在单个指标上进行的指标群体分析过程,如图 5.3 所示,如下进行:

  1. 从一个包含客户观察数据的完整数据集开始,这些数据包括感兴趣的指标以及客户是否流失。如果客户续订,某些客户可能被考虑多次。假设数据是通过第四章中描述的过程创建的,数据最有可能按日期和账户 ID 排序。

  2. 使用指标和表示流失或非流失的变量,按指标对这些观察结果进行排序。在队列分析的其余部分,账户的身份和观察日期被忽略。

  3. 将观察结果按预选的等大小组分组到队列中。在实际的队列分析中,通常使用 10 个队列,因此每个队列包含 10%的数据。(在图 5.1 和图 5.2 所示的简单示例中,只使用了三个队列。)请注意,您事先并不决定队列的边界在哪里;队列之间的边界是分析的结果。

  4. 对于每个队列,进行两个计算:

    • 队列中所有观察结果的指标平均值

    • 队列观察中流失的百分比

  5. 在 x 轴上用平均指标值,在 y 轴上用流失率绘制平均指标值和流失率图。

图片

图 5.3 指标队列分析示例

可能会令您惊讶的是,客户身份和观察日期在队列分析中并不重要。因为我们的数据集通常包含大多数客户的多个观察结果,所以同一个客户通常会在您的队列中出现多次。有时,一个客户在一个队列中会出现多次;在其他时候,同一个客户出现在不同的队列中。尽管这种情况可能令人困惑,但这是有道理的:您正在研究这样一个假设,即指标所代表的行为与流失有关,而不是客户身份或观察时间与流失有关。

话虽如此,您可能不想向您的业务同事解释这个细节,因为它可能会导致混淆。如侧边栏“分析队列随时间的变化”中更详细地讨论的那样(第 5.1.4 节),您可以测试观察的行为和时间是否都重要,但到目前为止,我们继续探索行为本身是否相关。

要点 指标队列是客户指标和流失的观察结果组;它们不同于客户组,因为一个客户可以被观察多次。

警告 理解一个客户可以在队列分析中出现多次是很重要的,但您可能不应该向您的业务同事解释这个事实,因为他们可能会觉得困惑。

5.1.2 使用 Python 进行队列分析

图 5.4 显示了使用 Python 执行并绘制的模拟数据集的队列分析。度量是每月帖子数,绘制在 x 轴上:该度量值的队列平均值范围从接近 0 到超过 175。流失率绘制在 y 轴上,范围从大约 0.02 到 0.12。流失率在队列中急剧下降,因此行为与模拟社交网络的流失率有预期的关系。

图 5.4 中模拟数据所显示的图案在真实的流失案例研究中极为常见。随着向上移动到底层队列,流失率迅速下降,但流失率的下降速度减慢,甚至可能在顶层队列中趋于平稳。这种模式使得很容易识别度量的健康水平。对于模拟中的每月帖子数,超过 25 是健康的,因为在此点之后,进一步的增加对流失率没有可观察的影响。

我将在 5.1.3 节中展示真实的案例研究,但首先,让我们看看执行队列分析的代码。列表 5.1 显示了一个使用 Pandas DataFrame 执行单个度量队列分析的 Python 函数。该函数具有以下输入:

  • data_set_path — 一个字符串变量给出的数据集文件路径

  • metric_to_plot — 要创建队列图的度量名称,由一个字符串变量给出

  • ncohort — 要使用的队列数量,由一个整型变量给出

图 5-04

图 5.4 每月帖子数模拟客户数据集的队列分析

给定这些输入,您创建队列图的主要步骤如下:

  1. 将数据集加载到 Pandas DataFrame 对象中,并设置 DataFrame 索引。

  2. 使用 DataFrame 成员函数 qcut 将观测值划分为队列。此函数返回一个序列。序列长度与观测值的数量相同,序列值是表示分组分配的整数。

  3. 使用 DataFrame 函数 groupby 和传递 qcut 结果(组标识符的序列)作为参数,计算平均度量值和平均流失率。

  4. 从平均值和流失率中创建一个新的 DataFrame

  5. 使用 matplotlib.pyplot 绘制结果,并在保存之前添加适当的标签。

注意,此过程与 5.1.1 节中示例问题的解决方案有一个重要区别:而不是通过排序数据、形成队列和使用您的逻辑计算平均值,代码依赖于 Pandas DataFrame.qcutDataFrame.groupby 函数。qcut 是基于分位数离散化的缩写,这是我们所形成的队列组的术语,明确地基于分位数的概念。

定义:分位数是当数据被分成相等的组时,作为分割点出现的一个值,每个组包含相同比例的总观测数。十分位数是当数据被分成 10 组时,每个组包含 10% 的数据。百分位数是当数据被分成 100 组时,每个组包含 1% 的数据。

第一个十分位数是将数据按指标组织时,将前 10% 的数据与后 10% 的数据分开的指标值。第二个十分位数是将第二个 10% 的数据与第三个 10% 的数据分开的指标值,依此类推。在数学语境中,离散意味着分开(或不连续)。这些组是离散的,因为它们的成员资格要么全部要么没有(不是指秘密或隐藏的离散)。qcut 函数被称为基于分位数离散化,因为数据是根据分位数的值被分成离散组的。

列表 5.1 Python 中的指标队列

import pandas as pd
import matplotlib.pyplot as plt
import os

def cohort_plot(data_set_path, metric_to_plot, ncohort=10):
   assert os.path.isfile(data_set_path),
      '"{}" is not a valid path'.format(data_set_path)                 ①
   churn_data = pd.read_csv(data_set_path,
      index_col=[0,1])                                                 ②
   groups = pd.qcut(churn_data[metric_to_plot], ncohort, 
      duplicates='drop')                                               ③
   cohort_means = 
      churn_data.groupby(groups)[metric_to_plot].mean()                ④
   cohort_churns = 
      churn_data.groupby(groups)['is_churn'].mean()                    ⑤
   plot_frame = pd.DataFrame({metric_to_plot: cohort_means.values, 
      'churn_rate': cohort_})                                          ⑥
   plt.figure(figsize=(6, 4))                                          ⑦
   plt.plot(metric_to_plot, 'churn_rate', data=plot_frame,marker='o', 
             linewidth=2,label=metric_to_plot)                         ⑧
   plt.xlabel('Cohort Average of  "%s"' % metric_to_plot)              ⑨
   plt.ylabel('Cohort Churn Rate (%)'
   plt.grid()
   plt.gca().set_ylim(bottom=0)
   save_path = data_set_path.replace('.csv', '_' + 
      metric_to_plot + '_churn_corhort.png')
   plt.savefig(save_path)                                              ⑩
   print('Saving plot to %s' % save_path)
   plt.close()

① 检查数据集路径

② 将数据集加载到 Pandas DataFrame 对象中并设置索引

③ 将数据分组并返回一系列的组编号

④ 计算队列指标的均值

⑤ 计算队列的流失率

⑥ 打开一个新的图形

⑦ 从队列中创建一个新的 DataFrame

⑧ 绘制队列流失率与队列指标平均值的对比图

⑨ 添加坐标轴标签

⑩ 保存并关闭图形

编写自己的算法与使用现成的模块函数

如果您还在计算机科学或编程课程中,您可能会认为使用像 DataFrame.qcut 这样的函数来实现算法是作弊,因为通常,计算机科学教育是关于编写自己的算法。或者,您可能认为在这类书中使用这样的函数是作弊,因为书籍应该教会您编写算法。然而,在这种情况下,使用 Pandas 算法是最佳实践。

如果您还没有注意到,在分析流失率时,有很多工作要做,而且无需重新发明轮子,即通过编写将数据分组到组中的算法。同样,使用 DataFrame.groupby 函数计算平均值和流失率也是如此;当标准模块函数正好满足您的需求时,就没有必要编写自己的逻辑。我在整本书中都采用这种方法,总是试图通过使用标准模块中的算法来实现目标。

由于 DataFrame.qcutDataFrame.groupby 函数在算法中执行主要步骤,列表 5.1 的一半内容都关注于结果的绘制。因为这是本书中的第一个绘图代码,我想简要提及在分析中清楚地标注所有生成的图表和图形的重要性。

警告:清楚地标注您分析产生的所有图表。与您分享分析的商业人士可能不熟悉细节,如果您没有清楚地标注结果,他们可能难以理解这些图表。

标注结果也有助于您以后回顾分析时记住结果的意义,尤其是如果您有很多事件和指标。您可能需要筛选数十个甚至数百个群体图,如果您没有在过程中构建清晰的注释,这将是不可能的。

如果您还没有这样做,请运行列表 5.1 以使用您自己的数据测试它。假设您已经设置了您的环境(说明在本书 GitHub 仓库中的 README 文件中,网址为github.com/carl24k/fight-churn),并且正在使用 Python 包装程序,请使用以下命令运行列表 5.1:

fight-churn/listings/run_churn_listing.py —chapter 5 —listing 1

结果应该是一个看起来像图 5.4 的群体图(cohort plot)的.png 文件。

5.1.3 产品使用群体

图 5.5 展示了从真实案例研究中得出的第一个群体分析示例,显示了 Broadly 客户的指标群体中的流失情况。对于 Broadly 的客户来说,一个重要的事件是更新的在线评论数量,因此计算了每月更新的评论数量作为指标。

图 5.5 广泛的指标(每月更新的在线评论)的群体流失分析

因为这个图展示了本书中第一个真正的指标群体流失案例研究,所以我需要提出一个重要的观点,这个观点适用于本书中所有基于真实公司(而非模拟)的其他研究:图 5.5 并没有在 y 轴上显示实际的流失率百分比。相反,y 轴没有标签,流失率被描述为相对的。实际的流失率被省略,以保护案例研究中公司的隐私和商业利益,但您仍然可以看到群体之间流失差异的重要性,因为群体图的底部始终固定在零流失。因此,点与图表底部之间的距离显示了群体的相对流失率。

对于 Broadly 的指标,流失率在第一个群体中最高,并在前五个群体中下降;顶部三个群体(图表右侧,具有最高指标值)的流失率大约是底部群体的半数。你可以通过注意到它大约是图表底部距离的一半,使用等间距的网格线,来判断顶部群体的流失率大约是底部群体的一半。(为了精确起见,图 5.5 顶部群体的流失率略高于底部群体的半数。)图 5.5 中另一个值得注意的点是,流失率的下降发生在每月大约零次评论更新和每月四次评论更新之间的群体之间;每月四次评论更新后,流失率没有进一步下降。

图片

图 5.6 Klipfolio 指标(每月仪表盘编辑次数)的流失率群体分析

图 5.6 展示了 Klipfolio 的另一个指标流失率案例研究。此图显示了使用 Klipfolio 客户计算的指标仪表盘每月编辑次数的指标群体分析案例。与图 5.5 类似,流失率以相对尺度显示,图表底部固定为零流失率。在这种情况下,顶部群体的流失率是底部群体的几分之一(大约 10%)。

图 5.7 展示了另一个使用 Versature 每月总本地通话时间指标的指标流失率示例。此图描绘了另一个重要行为指标与流失率之间相当典型的关系。每月总本地通话时间超过 2,500 次的群体,其流失率大约是底部群体的三分之一,而底部群体几乎不打电话。在零和 2,500 之间,流失率有所下降,之后流失率似乎略有上升但并不显著。

图片

图 5.7 Versature 每月本地通话时间指标的流失率群体分析

观察图 5.7 中本地通话(而非流失率)的分布,请注意,大多数群体在图表左侧的低范围内被压缩。实际上,七个群体占据了图的一部分(0 到 5,000 次通话之间的区域)。此图显示,通话次数最多的群体比其他群体通话次数多得多,因此即使是第二高的群体,平均通话次数也少于三分之一。这个例子展示了一个偏斜的行为指标。

定义:一个偏斜的指标是指顶部群体包含的值比下一个最接近的群体高几倍。通常,大多数较低群体的平均值在一个相对较小的范围内。

偏斜在度量指标中是一个重要的概念,我将在 5.2.1 节中解释。偏斜可能会在分析中引起问题,首先就是 5.6 图有点难以阅读。大部分空间都被前两个队列占据了;其他的则挤在一起。这种安排是度量值分布偏斜的自然结果,因为顶级队列的数值远高于其他队列。当你尝试理解指标之间的关系时,偏斜也会引起另一个问题,正如第六章所述。因此,我在 5.3 节中介绍了称为评分指标的技术。

我想提醒你注意 5.5、5.6 和 5.7 图中队列换率分析的另一个特点:它们都具有相同的整体形状,前几个队列的队列换率迅速下降;对于更高队列,换率大致保持恒定。这种结果是常见的,这也是为什么图 5.4 中显示的模拟被设计成那样的原因。

换率随着行为减少然后趋于平稳,这既有用又是一个问题。它有用,因为很容易识别出该指标的健康发展水平:换率停止下降的水平。它是一个问题,因为到了某个点,该指标就不再帮助你理解换率或根据换率风险细分客户。在干预以减少换率方面,让用户采取更多特定行动在某个点之后就没有区别了。如果你想解释那些指标值较高的客户之间的换率差异并减少这些客户的换率,你需要做些其他事情。在第六章和第七章中,你将学习创建与换率保持强关系的指标的技术,即使是在顶级队列中也是如此。

5.1.4 账户使用期限的队列

另一种常见的队列分析类型是查看基于客户成为客户的时间长度的换率——我称之为账户使用期限。这种形式的队列分析是最常见的,所以如果你看到过基于队列的换率分析,它很可能是基于使用期限的。

这种类型的队列分析与度量指标队列分析相同,只是它查看的是账户使用期限而不是行为。此外,你通常预期会发现,长期客户不太可能流失,而新客户更有可能流失。使用账户使用期限进行的队列分析是对该假设的检验。因为账户使用期限是作为账户指标计算的,所以这种类型的队列分析使用与 5.1.3 节中行为度量指标队列分析中使用的完全相同的代码进行。

图 5.8 显示了 Klipfolio 账户使用期限队列分析的结果。结果相当典型:

  • 新客户(平均使用期限约为一个月)的换率低于长期客户。

  • 在年度前半段,churn 率上升,在第三队列中大约高出三分之一。

  • 在半年至一年之间,客户的 churn 率下降。但在第一年结束时,客户的 churn 率上升。

  • 在第一年(平均任期超过 365 天的队列)之后,churn 率较低,并且呈下降趋势,以至于任期最长的队列(大约四年)的 churn 率比最新队列低约三分之一。

虽然基于任期的队列分析在 churn 文献中是最常见的形式,但我图 5.8 中展示的分析略有不同。定义账户任期队列和执行此分析的最常见方式是按客户注册的时间分组:月份、季度或年份。在最初队列分配之后,每个队列中剩余客户的数量会随着时间的推移而跟踪,并用于推导出不同时间点的每个队列的 churn 率。相比之下,我展示的方法是独立观察客户,观察的频率由他们自己的续订周期决定,并从所有在任期测量上具有相似值的观察中形成队列。

图 5.8 Klipfolio 账户任期 churn 队列分析

这种方法的一个优点是,对于更成熟的(任期较长的)队列,churn 率是在更大的客户池中估计的,因为较老的队列结合了在不同时间注册的客户。一个潜在的缺点是,这种队列分析没有显示给定任期的队列 churn 率如何随时间变化,因为任期队列分析方法是混合了在不同时间注册的客户的观察结果。

分析队列随时间的变化

通常,对于在几个月内注册的相同任期的客户,churn 率没有显著差异。任何差异可能是由随机变化或季节性驱动的。但如果你等待一年或更长时间,churn 模式可能会发生相当大的变化。

要检查 churn 与某个指标之间的关系是否发生了变化,我建议使用第四章中的方法为不同时期创建不同的数据集,然后比较不同队列分析的结果。例如,您可以创建一个只包含过去一年观察结果的数据集,以及另一个来自前一年观察结果的数据集。(使用一年期的数据集应该可以控制季节性。)如果您在不同数据集的队列 churn 率之间看到显著差异,那么 churn 与任期之间的关系在这两年之间发生了变化。当您的产品或营销策略的变化导致不同时间段内客户行为不同时,可以使用这种方法。为每个时间段创建一个数据集,并比较来自不同数据集的队列分析。(有关所需观察数的最小数量的讨论,请参阅第 5.1.6 节。)

5.1.5 账单周期队列分析

基于客户订阅的指标可以通过群体分析进行评估。图 5.9 展示了 Broadly 客户在每月或年度计费周期内的流失和订阅计费分析。该图展示了两个群体,因为计费周期只有两个不同的值,在图表中以 1 和 12 的点出现,因为计费周期是以月份来衡量的。Broadly 的客户显示出典型的模式:年度计费的客户流失率显著低于每月计费的客户。

图 5.9

图 5.9 Broadly 客户按月度和年度计费周期进行的流失群体分析

图 5.9 中的结果基于月度流失率,因为进行了月度观察。以这种方式分析年度客户可能看起来有些奇怪,然而,因为年度客户每年只有一次流失的机会。另一种可能的方法是对年度和月度客户进行单独的指标群体分析,然后每年观察一次年度客户。如果你有很多年度客户,这种技术可以工作,但通常是个问题,因为你对年度客户的观察远少于对月度客户的观察。因此,你可能没有足够的年度客户观察数据来进行单独的行为分析。

要理解年度客户行为,最好是将年度和月度客户结合起来。你可能认为每年观察一次年度客户并将他们与月度客户合并到一个数据集中(通过改变数据集构建的逻辑)是个好主意。但这样年度客户似乎会有更高的流失率;通过每年观察一次,你隐含地计算了他们群体的年度流失率。想象一下图 5.9 显示年度客户的流失率高于月度客户。如果你每年观察一次年度计费客户并想与他们进行比较,你必须将月度客户的流失率转换为年度,从而显示真实的关系:月度计费客户的年度流失率更高。最佳选择是观察每月和年度客户的混合群体,并每月进行观察。

5.1.6 最小群体大小

在使用群体分析流失问题时,每个群体中观察到的数量是一个重要的问题。你需要每个群体中有足够的观察数据,这样当你估计群体的流失率时,估计结果更有可能是准确的。记得从第一章的讨论中,流失受到许多你不知道和影响的随机因素的影响。当你根据指标估计群体的流失率时,结果的一部分是由于指标的影响,另一部分是由于所有其他因素。想法是,你需要大量的观察数据来使所有那些“其他因素”相互抵消并显示出指标的影响。多少是“大量”取决于具体情况,但一个简单的经验法则是,你应该在每个群体中有 200 到 300 个观察数据,最好是几千个。

要点:每个群体至少应有 200 到 300 个观察数据,最好是几千个。

注意,我在谈论每个群体中应该有多少观察数据,而不是有多少客户。如果你有 500 个客户,每月续订,并且你观察他们的行为(指标和流失)六个月,你将有大约 3,000 个观察数据。如果你形成 10 个群体,每个群体将有 300 个观察数据,那么你应该没问题。问题是,最小群体大小出现在年度续订时:如果你有 500 个客户,年度续订,一年后,你只有 500 个观察数据。

如果你每个群体中的观察数据太少,你应该做的第一件事是使用更少的群体。以 500 个观察数据的例子来说,你可以形成三个群体,167 个、167 个和 166 个。至少你每个群体中会有超过 100 个观察数据,如果不是你想要的几百个。然后你的群体将代表你正在分析的指标的低、中、高三个水平。

警告:在群体分析中,有足够的观察数据在少数几个群体中比有大量群体更重要。相应地减少群体的数量。

如果你每个群体中的观察数据仍然少于 200 个,你将进入一个危险区域,其中随机事件的噪声可能会压倒你在基于指标的群体中寻找的信号。如果你的流失率相对较高——两位数以上(大于 20%),大约 100 个观察数据可能仍然有效。这是因为如果流失率相当高,随机因素对流失率的影响可能相对较小。但是,当流失率低(低于 10%)时,与指标的影响相比,随机外部因素更有可能变得重要。所以如果你的流失率低,你需要更多的观察数据来抵消噪声并揭示趋势。如果你的流失率低,你不应该在每个群体至少有 200 个观察数据之前尝试进行分析,最好是更多。

另一条经验法则是分析中至少要有 100 个客户流失。例如,如果你的客户流失率是 5%,你应该有大约 2,000 个观察值(因为要从 5%的客户流失率中得到 100 个客户流失观察值,你需要有 100 / 0.05 = 2,000 个观察值)。通常,观察值和客户流失的下限不是问题;大多数公司最初专注于客户获取,只有在运营一段时间后才会考虑客户流失。但是,各种问题可能会限制观察值的数量,例如数据仓库中事件的历史较短,你应该意识到这些限制。

吸收要点:如果你的数据中客户流失少于 100 例,你应该专注于获取新客户,并通过调查和焦点小组了解他们的观点。在如此少的例子中,从数据中理解客户流失将很困难。

这些粗略的指南更多地基于实践经验,而不是严格的统计。第八章和第十章讨论了一些你可以用来更好地回答关于样本大小问题的统计方法。

5.1.7 显著和不显著的群体差异

到目前为止,我只展示了客户指标与客户流失明显相关的案例。然而,不可避免的是,你将测试一些指标,并发现没有显著的结果。图 5.10 显示了云通信服务提供商 Versature 的一个例子。在这种情况下,指标是客户购买的扩展单元数量。客户流失率没有明显的趋势:底部群体与顶部群体的客户流失率大致相同,中间群体的客户流失率上下波动,但从未显著偏离平均水平。这种行为与客户流失无关。

图片

图 5.10 当客户流失差异不显著时的群体分析

其他情况不太明确,但并非明显无关。在第八章中,我将讨论如何更严格地使用统计方法来回答这些问题,但你通常可以在没有统计的情况下做出合理的判断。如果你试图决定一个指标是否与客户流失相关,首先要问的问题是是否有其他指标与客户流失有更明显的关系。

吸收要点:如果你不确定一个指标是否与客户流失相关,首先询问是否有其他明显的指标与客户流失有很强的关系。如果有,关注这些指标,尝试在保留策略中使用这些知识,稍后再处理有疑问的案例。

唯一需要担心的是边缘案例:

  • 当你只有少数与客户流失强相关的指标时

  • 当一个特定的指标预期与客户流失相关,并且基于这种预期已经制定了保留或参与策略时

在这种情况下,你应该查看流失率的变化是否在队列之间一致,以及最低和最高流失率队列之间的差异是否显著。如果流失率通常从高到低(或从低到高)变化,并且最高和最低之间的差异至少是 1.5 倍,那么可以合理地认为你发现了一个可能对流失率有意义的关联。

最后,你应该考虑与流失率相关的业务原因:

  • 该度量衡是否衡量与产品的有用性或客户满意度密切相关的因素?

  • 或者,这些度量衡是否与产品的核心特性无关?

如果你坚信这个度量衡应该很重要,你可以给你的分析留有疑问,并在你拥有更多数据时重新检查结果。另一方面,如果数据不支持一个宠爱的理论,不要尝试太久;这是一个数据驱动的例子。

最后,请注意,显著性问题与样本大小有关,这在第 5.1.6 节中讨论过:如果你有很多样本,你更有可能看到有意义的关系。更高级的方法在第八章和第十章中用于研究这些问题。

5.1.8 大多数客户度量衡为零的度量衡队列

在队列分析中,你可能会遇到的一个问题是,即使你有很多观察值,你仍然可能对感兴趣的度量衡没有结果。这种情况可能发生在事件很少发生时,即使你在很长的时间内进行了测量(如第三章所述)。图 5.11 显示了 Klipfolio 的一个示例度量衡队列分析。大多数账户在所讨论的度量衡上为零:用户查看仪表板时每月进行的导向切换次数。在这种情况下,只有三个队列是由 Python 函数DataFrame.qcut形成的,尽管队列的参数被设置为 10。

图 5.11 中的队列图仍然显示出与流失率的关系,但图表看起来并不完全符合预期。如果你知道原因,你会知道没有问题,但你将不得不向你的业务同事解释为什么只有三个队列。在第 5.4 节中,我向你展示了如何通过去除不想要的观察值来改进这类分析。

图 5.11 队列分析中大多数观察值为零

图 5.11 大多数观察值为零的队列分析

如果一个事件很少发生,并且对于大多数客户来说该度量衡的值为零,那么这个度量衡与流失率的关系可能不如更多账户参与的行为那样显著。在某种程度上,你应该忽略罕见事件,专注于常见事件。当然,如果所讨论的行为与少数账户的流失率有重要关系,可能会有例外情况。处理罕见行为度量衡的另一种方法是,如第六章所述,将它们组合在行为组中。

5.1.9 因果关系:这些度量衡是否导致了流失率?

现在你已经知道如何发现行为指标与客户流失之间的关系,并且你对这些关系何时重要有了很好的感觉。但你可能想知道因果关系(如果你还没有,现在是开始的好时机)。因果关系提出了以下问题:

  • 如果你看到当指标值高时客户流失率降低,那么所讨论的事件和行为是否导致了客户留存?

  • 指标(事件发生的低频次)的值低是否会导致客户流失?

不幸的是,这些问题没有简单的答案。高级统计学可以用来分析因果关系问题,但这些技术超出了本书的范围。就这一点而言,我不建议使用高级方法来理解客户流失的因果关系,因为这需要一种简约、敏捷的分析(第一章)。

我对因果关系的处理方法如下:客户流失或不流失是因为他们从使用产品或服务中获得效用或享受。在这里,我指的是经济意义上的效用和主观愉悦。如果指标的背后的行为是向客户提供效用,那么可以说这个行为既导致了留存也导致了流失。如果事件不是提供效用的行为,而是在过程中发生的事情,那么可以说这个事件和指标与客户参与和留存相关,但不是原因。

你如何知道哪个事件为顾客提供了效用?你应该依靠你对产品的了解和常识(如果你不知道,可以尝试与一些客户交谈)。话虽如此,如果你认为某个事件为顾客提供了效用,但发现它与流失和留存只有微弱的关联,你可能需要重新考虑你的信念——这是数据驱动的另一个例子。

TAKEAWAY 您需要依靠对商业知识的了解来判断与客户流失相关的指标是导致留存和流失,还是仅与留存和流失相关联。要导致客户流失,事件必须与客户从产品中获得效用或享受紧密相关。

在分析客户流失相关数据时,区分导致留存或流失的指标和事件与相关联的指标和事件之间的区别并不太大,但在制定留存策略时,这种区别却很大。如果你认为某个事件或指标与留存相关,但不是导致留存的原因,那么尝试鼓励客户采取该特定行动是没有意义的。

WARNING 如果你不认为某个事件是导致客户留存和流失的原因,不要尝试鼓励客户采取该行动,即使它与流失有很强的关联。

5.2 总结客户行为

到现在为止,您已经了解了如何根据您的指标执行流失的指标群体分析。您也看到了一些可能导致结果难以解释的问题。一个问题就是指标可能存在偏斜,这可能会使群体分析难以阅读和比较。另一个可能出现的问题是,大多数账户指标的结果为零的罕见事件。这些问题相当典型,并不一定错误,但如果它们非常极端,您可以采取一些措施。首先,为了帮助诊断这些问题并确保当它们发生时您不会感到惊讶,我将向您展示如何通过制作数据集的摘要来检查这类问题。

数据集摘要有助于您检查问题,也是了解您的客户展现出的整体行为范围的好方法。这种理解将帮助您规划客户细分和干预措施,以增加参与度。

5.2.1 理解指标的分布

数据集的摘要是一组对数据集内容的测量——一组对您指标的指标。数据集摘要中的结果可以很好地说明您客户行为的范围和多样性。它还可以帮助您在花费时间进行分析之前发现数据中的许多问题。

小贴士:在开始对流失进行群体分析之前,您应该计算一组摘要统计量并解决任何数据问题。我首先教您群体分析,是为了向您展示为什么您需要一个初步的摘要。

指标的分布是统计学家和分析人员用来描述这类属性的工具——诸如最小值、最大值和典型值等。

定义:一个指标的分布是指关于一个指标对客户取值的整体事实集合。理解分布意味着在适当的细节水平上了解诸如有多少客户具有该指标、典型值是什么以及最小值和最大值是什么等事实。

图 5.12 展示了模拟社会网络数据集的此类摘要。本节末尾的表 5.1 简要解释了摘要统计量,这些统计量对于已经学习过统计学课程的人来说应该是熟悉的。如果您需要更多信息,许多文本和在线资源提供了对这些措施的深入解释。

图片

图 5.12 社会网络模拟数据集的摘要统计示例

下面是关于图 5.12 中数据集摘要的一些关键点:

  1. 大多数指标在几乎所有账户中都有非零值,除了“每月取消好友”指标,它只显示 25% 的账户的值。

  2. 指标事件计数通常在数百,最大事件计数在数千。例外的是“取消好友”和“新好友”,两者都较为罕见。

  3. 大多数指标在程度上有偏斜;计数较高的事件有更高的偏斜。

  4. 账户留存期的统计数据显示,所有账户都有一个测量值,因为非零测量值(第 2 列)为 100%。平均值为 61 天,账户留存期的偏斜为 0.2,表明它在其平均值周围分布得或多或少是均匀的。

  5. 该表格还显示了数据集中的流失统计信息:4.6%的观测值是流失。

真实的流失案例研究数据在指标的分布和偏斜方面通常相似,但真实公司的数据通常有更多的指标,其中只有一小部分账户具有非零值。(我故意在模拟中放入一个,以便你在保持模拟简洁的同时学习。)

表 5.1 指标分布汇总统计 (续)

汇总统计 说明
非零百分比 数据集中指标非零的观测值百分比——这是检查行为多么罕见的重要检查。
平均值 指标典型值的度量(也称为平均值)。它是通过将所有观测值上的指标相加,然后除以观测值数量来计算的。
标准差 一个度量指标值如何变化的量度,即所有值是否相对接近典型值。当许多值远离典型值时,会出现高标准差。有时,通过值与平均值的标准差来引用指标值是方便的。例如,如果平均值是 20,标准差是 5,那么一个指标观测值为 25 就被说成是高于平均值 1 个标准差。在这种情况下,30 就被称为高于平均值 2 个标准差,依此类推。这种术语是有用的,因为它传达了指标观测值与典型值相比的感觉,而不需要你记住每个指标的典型值。
偏斜 一个统计量度,用于衡量指标分布的对称性或倾斜程度。这种倾斜性在本章前面的群体分析中出现过。如果偏斜为零,则低值和高值对称地分布在平均值周围。如果偏斜为正,则高于平均值的指标观测值多于低于平均值的观测值。如果偏斜为负,则情况相反:低于平均值的观测值多于高于平均值的观测值。(在典型的行为指标中,你不太可能看到这种结果。)一般来说,偏斜低于 3 或 4 并不显著,但偏斜为 5 或更大的指标显著倾斜。
分位数(1%,5%,等等) 分位数是找到低于该值的固定百分比的观测值所需的度量值。例如,1% 分位数是观测值中有 1%的值小于该度量值的值;其他 99%的观测值具有大于 1%分位数的值。5% 分位数是观测值中有 5%小于该值,有 95%大于该值的度量值。这种模式适用于所有更高的分位数。查看摘要中的分位数序列是了解客户值在什么范围内的百分比的好方法。如果你看到登录的 25 分位数是 20,登录的 75 分位数是 100,那么 50%的客户(75 - 25 = 50)的登录次数在 20 到 100 之间。
中位数(50% 分位数) 一个度量值的典型值的另一种度量,如平均值。中位数是观测值中一半大于一半小于的值(等同于 50% 分位数)。当数据包含极端异常值时——当度量值有高偏斜时,中位数比平均值更能衡量度量的典型值。极端异常值会提高平均值但不会提高中位数,因此中位数总是反映中间的客户。
最小值和最大值 观测到的任何客户的最低和最高值。

正态和厚尾分布

在著名的正态(钟形曲线)分布中,大约三分之二的所有值都在平均值的一个标准差范围内,几乎所有值都在平均值的三个标准差范围内。如果平均值是 20,标准差是 5,那么三个标准差就是 3 * x * 5 = 15。在这种情况下,大多数观测值都在 5 到 35 之间(因为 20 - 15 = 5,20 + 15 = 35)。对于正态分布,具有比平均值 5 个或更多标准差值的值极为罕见。

但行为度量通常比正态分布有更多的异常值,所以这些关系可能不会在你的数据中成立。比正态分布有更多极端异常值的分布通常被称为厚尾分布。分布的尾部是极端值,如果尾部很厚,那么就会有大量的极端观测值。对于你自己的大多数度量值,不到三分之二的观测值将位于平均值的一个标准差范围内,而更多的观测值将更远;可能只有三分之一的观测值将位于平均值的一个标准差范围内。对于大多数产品,行为度量值在平均值 5 到 10 个标准差(或更多)之外是很常见的。

5.2.2 在 Python 中计算数据集摘要统计量

在数据集上执行一系列摘要测量是数据分析中的常见任务,因此 Python 的 Pandas 模块已经提供了一个函数来完成这项任务——DataFrame.describe。此函数为数据集中的每一列计算一组测量值。回想一下,数据集的每一列都包含一个指标的观测值。调用 describe 函数会产生每个指标的摘要统计量集合。

列表 5.2 展示了一个完整的程序,它使用 Pandas 函数并添加了一些字段到摘要中,我认为这对于理解客户行为很有用。列表 5.2 中的主要步骤如下:

  1. 在给定路径的情况下,将数据集加载到 Pandas DataFrame 中。

  2. 调用 Pandas 函数 DataFrame.describe 以获取基本摘要。基本摘要包括平均值、标准差、最小值和最大值,以及第 25、50 和 75 个百分位数。摘要数据作为另一个 Pandas DataFrame 返回。

  3. 计算一些额外的统计量,并将它们添加到摘要结果中。这些额外的统计量包括

    • 使用 Pandas 函数 DataFrame.skew 计算偏度

    • 使用 Pandas 函数 DataFrame.quantile 计算第 1 和第 99 个百分位数

    • 每个指标的零观测值的百分比,这是通过一个小技巧计算的:将列转换为布尔类型并求和得到非零值的计数;然后除以行数将其转换为百分比

  4. 最终结果的列按更合理的顺序重新排序。

  5. 结果被保存。

列表 5.2 将在本章的后面再次使用,因为这些摘要统计量对于进一步分析你的数据集是必需的。

列表 5.2 展示了客户流失分析数据集的统计信息

import pandas as pd
import os

def dataset_stats(data_set_path):
   assert os.path.isfile(data_set_path),
      '"{}" is not a valid path'.format(data_set_path)                  ①
   churn_data = 
      pd.read_csv(data_set_path,index_col=[0,1])                        ②

   if 'is_churn' in churn_data:
      churn_data['is_churn']=
         churn_data['is_churn'].astype(float)                           ③

   summary = churn_data.describe()                                      ④
   summary = summary.transpose()                                        ⑤

   summary['skew'] = churn_data.skew()                                  ⑥
   summary['1%'] = churn_data.quantile(q=0.01)                          ⑦
   summary['99%'] = churn_data.quantile(q=0.99)                         ⑧
   summary['nonzero'] = churn_data.astype(bool).sum(axis=0) / 
                        churn_data.shape[0]                             ⑨

   summary = summary[ ['count','nonzero','mean','std','skew','min','1%',
                       '25%','50%','75%','99%','max']]                  ⑩
   summary.columns = summary.columns.str.replace("%", "pct")

   save_path = data_set_path.replace('.csv', '_summarystats.csv')
   summary.to_csv(save_path,header=True)
   print('Saving results to %s' % save_path)

① 检查数据集路径

② 将数据加载到 Pandas DataFrame 对象中并设置索引

③ 将流失指标转换为浮点数

④ 提供一组标准摘要统计量

⑤ 将指标放在行中,结果更容易阅读。

⑥ 使用标准数据集函数测量偏度

⑦ 使用分位数函数测量第 1 个百分位数

⑧ 使用分位数函数测量第 99 个百分位数

⑨ 计算非零值的比例

⑩ 重新排序列

你应该自己运行列表 5.2 上的模拟数据集。假设你正在使用 Python 包装程序,将参数更改为

fight-churn/listings/run_churn_listing.py —chapter 5 —listing 2

运行列表 5.2 的典型结果如图 5.12 所示;你的结果可能有所不同,因为数据是随机模拟的。

5.2.3 筛选稀有指标

在创建数据集摘要统计信息后,你应该检查有多少账户在所有指标上都有非零值。在这个时候,你应该已经为描述在第三章中的罕见指标选择了更长的观察窗口。如果你仍然有一些指标,其中只有一小部分账户有非零值,那么这可能就是你能做到的最好了。如果是这样,我建议你从数据集和分析中删除那些少于大约 5%非零值的指标。确切的截止点取决于你有多少“好的”指标。如果你有很多指标,其中大多数账户都有非零值,你应该使用更高的阈值——可能是 10%。另一方面,如果你有很多罕见事件和零值的指标,你可能想为这种筛选使用较低的阈值,例如 1%。

如果你发现这些罕见指标中的一个与流失有很强的相关性,或者你知道它对业务特别感兴趣,你可以对此方法做出例外。指导这一方法的原则是简约:如果一个指标只适用于一小部分账户,那么它可能不太有用,即使它与流失和保留有很强的关系。

5.2.4 将业务纳入数据质量保证

在生成数据集摘要统计信息后,是让组织中的业务人员参与质量保证的好时机。我建议你与代表你业务不同部分的人进行一到多次会议,并与他们一起审查摘要统计信息。你应该在他们看到你的队列分析结果之前这样做。我首先教你如何进行队列分析,这样你就可以在花费时间进行更多数据质量保证之前做一些实际的流失分析,但你应该首先进行质量保证,然后进行流失分析。

与业务利益相关者的这种审查是一个机会,可以确认数据质量良好的人知道这一点。特别是,你想要询问业务代表,指标值的分布是否符合他们的预期。指标低于他们预期的账户百分比是否更高,或者最大值是否高于他们预期的?在将关于流失的发现与业务分享之前获取这些信息很重要,因为如果你的数据集需要额外的纠正或修改,你的发现可能会改变。

警告:在分享队列分析结果之前,完成对数据的质量保证检查,包括与业务审查摘要统计信息。如果你分享的队列分析后来因数据质量问题需要撤回,你可能会失去业务的信任。

5.3 评分指标

当我们查看 5.1 节中的队列分析时,我们发现一个问题,即对于某些指标,大多数队列仅占据指标总范围的很小一部分。在 5.2 节中,你了解到可以通过查看数据集摘要中的指标偏斜统计来识别这种类型的分布。在本节中,你将学习评分指标的技术,以便在您的指标高度偏斜时提高队列分析的可解释性。在第六章中,你将了解到指标分数还有其他重要的用途,因此本节是一个介绍。

评分与数据归一化

如果你受过统计学或数据科学的训练,你可能知道我所说的评分就是数据的归一化或标准化。我发现商界人士觉得这些术语令人畏惧且令人困惑。我发现将整个过程称为评分,将转换后的数据称为分数而不是归一化数据,效果更好。商界人士似乎觉得这些术语更容易与之相关联。因此,我使用这些术语而不是传统的统计语言来描述这个过程。

5.3.1 指标分数背后的理念

指标分数背后的理念是,它是你最初使用的指标的缩放版本。在这种情况下,缩放意味着分数将位于与原始指标不同的数字范围内。

定义 指标分数(或简称分数)是指标的缩放版本。

重新缩放还意味着较大的指标观测值总是转换为较大的分数,并且具有相同指标的两位客户最终会得到相同的分数。因此,如果按指标值从大到小对客户进行排序,然后按指标分数对相同的客户进行排序,顺序将完全相同。从您的指标创建的队列也将与基于分数的队列完全相同。图 5.13 说明了这个过程。

图 5.13

图 5.13 将指标映射到分数

分数具有一些特性,使它们对进一步分析您的指标和流失率很有用。以下是一些这些特性的例子:

  • 而指标可以取任何值,但分数总是小数,可以是正数或负数,但接近于零。典型的分数是-1、0 或 1。一个分数的极端值可能是 3 或 5,因此分数的典型范围大约在-5 到 5 之间,无论原始指标的取值范围如何。

  • 如果指标是偏斜的,分数就不会那么偏斜。等价地,指标可以有许多接近零的观测值和少量很大的值,但分数值将在分数占据的整个范围内(大约-5 到 5)更加均匀地分布。

  • 平均分数始终为零,无论原始指标的均值是多少。通过查看分数,你可以一眼看出客户在指标上的表现是否平均,即使你不知道指标的均值。

  • 指标得分的标准差始终为 1。这个特性很有用,因为你还可以知道给定得分与平均值的距离。得分为 1 表示客户的原始指标比平均值高一个标准差,得分为-1 表示客户的原始指标比平均值低一个标准差。

这些特性使得得分对于查看客户指标和流失很有用。你将在本书的后面部分了解指标得分的更多有用特性。

5.3.2 指标得分算法

现在你将查看用于从指标计算得分的公式。你可以使用算法根据数据确定一个得分公式,而不是一个单一的公式。图 5.14 说明了这个步骤,总结如下:

  1. 通过检查偏斜统计量(第 5.2 节)来确定指标是否显著偏斜。一个典型的阈值认为当偏斜超过 4 时是显著的,尽管你可以根据你的偏好调整阈值。此外,你必须确认指标的最小值(在任何转换之前)为零。如果指标没有显著偏斜或具有负值,则跳到步骤 4。

  2. 将偏斜的客户指标加 1,这样原来事件计数为零的客户现在有 1,原来有 1 的客户现在有 2,依此类推。

  3. 对所有偏斜的指标取对数。通常使用自然对数(以 e 为底),但对数底数并不重要。

  4. 计算在此过程此点的指标值的平均值和标准差。如果指标没有偏斜,这些值就是原始指标。如果指标有偏斜,使用 1 加上原始客户指标的对数。

  5. 从所有值中减去平均值。

  6. 将所有值除以标准差。结果是得分。

图 5.14

图 5.14 示例得分计算

取对数是使数据不那么偏斜的关键步骤。两个数字之间的数量级差异在它们的结果对数中变成了小的差异。但只能对正数取对数,这就是为什么你需要检查最小值是否为零,然后加 1。(关于负指标的解决方案在第七章中介绍。)

从平均值中减去并除以标准差可以使最终得分的平均值变为 0,标准差变为 1。这个推导在统计学教科书中有详细说明,但你可以通过注意以下内容来理解它:

  • 平均值是通过将所有指标相加然后除以观测数来计算的。如果你从每个观测值中减去一个特定的数,新的平均值将减少你减去的量。如果你从每个观测值中减去原始平均值,新的平均值必须是零。

  • 假设平均值为零,标准差为任何数;然后除以标准差。原本等于标准差的观测值现在值为 1,因为它就是标准差;然后它被除以相同的数。实际上,在除以原始标准差之后,每个观测值都被转换为一个值,这个值是观测值与平均值的距离的标准差数。这个结果与指标具有标准差为 1 是相同的。

从单个指标创建分数与计算公式 5.1 中的公式相同:

图片

其中

图片 公式 5.1

在公式 5.1 中,μm¢ 表示 m ¢ 分布的均值,σm¢m ¢ 分布的标准差,ln 是自然对数函数。如果指标不是偏斜的,则使用原始指标代替 m ¢

5.3.3 在 Python 中计算指标分数

列表 5.3 展示了一个实现分数计算的 Python 函数。请注意,此函数计算数据集中所有指标的分数,而不仅仅是单个指标。列表 5.3 中的 metric_scores 函数有以下输入:

  • data_set_path—一个字符串变量,指向保存文件中的数据集路径

  • skew_thresh—确定一个指标是否应被视为偏斜的阈值

给定这些输入,计算分数的主要步骤如下:

  1. 将数据集加载到 Pandas DataFrame 对象中,设置 DataFrame 索引,并创建一个副本。分数将写入副本。

  2. 删除换行指示列,因为它不会被转换为分数;在分数计算后,它将按原样重新附加。

  3. 加载由列表 5.2 保存的数据集统计文件。

  4. 使用汇总统计,通过比较偏斜统计量与阈值来确定哪些列显著偏斜。

  5. 对于每个显著偏斜的列,加 1,然后取对数。

  6. 对于所有列,减去平均值,然后除以标准差。

  7. 重新附加换行指示列。生成的 DataFrame 将被保存。

关于列表 5.3 需要注意的一点是,它没有遵循第 5.1 节中关于使用标准 Python 函数的建议。此列表没有使用标准的 Python Pandas 函数来计算分数;虽然有一个,但列表 5.3 没有使用它,因为标准的 Pandas 函数不包括对偏斜变量取对数的选项。

列表 5.3 计算指标分数的 Python

import pandas as pd
import numpy as np

def metric_scores(data_set_path,skew_thresh=4.0):

   assert os.path.isfile(data_set_path),
      '"{}" is not a valid path'.format(data_set_path)            ①
   churn_data = 
      pd.read_csv(data_set_path,index_col=[0,1])                  ②
   data_scores = churn_data.copy()                                ③
   data_scores.drop('is_churn',axis=1)                            ④

   stat_path = data_set_path.replace('.csv', 
      '_summarystats.csv'))                                       ⑤
   assert os.path.isfile(stat_path),
      'You must running listing 5.2 first to generate stats'
   stats = pd.read_csv(stat_path,index_col=0)                     ⑥
   stats=stats.drop('is_churn')                                   ⑦
   skewed_columns=(stats['skew']>skew_thresh) & 
                  (stats['min'] >= 0)                             ⑧
   skewed_columns=skewed_columns[skewed_columns]                  ⑨

   for col in skewed_columns.keys():                              ⑩
       data_scores[col]=np.log(1.0+data_scores[col])              ⑪
       stats.at[col,'mean']=data_scores[col].mean()               ⑫
       stats.at[col,'std']=data_scores[col].std()

   data_scores=(data_scores-stats['mean'])/stats['std']           ⑬
   data_scores['is_churn']=churn_data['is_churn']                 ⑭

   score_save_path=
       data_set_path.replace('.csv','_scores.csv')                ⑮
   print('Saving results to %s' % score_save_path)
   data_scores.to_csv(score_save_path,header=True)

① 检查数据集路径

② 将数据集加载到 Pandas DataFrame 对象中

③ 在数据的副本上工作以计算分数

④ 换行列不应转换为分数。

⑤ 检查汇总统计文件

⑥ 加载汇总统计

从汇总统计中删除掉换行行

⑧ 为偏斜的列创建一个布尔序列

⑨ 移除低于阈值的列的条目

⑩ 遍历偏斜的列

⑪ 将偏斜的列转换为原始值的对数

⑫ 记录新的均值和标准差

⑬ 减去均值并除以标准差

⑭ 将原始数据集中的 is_churn 列添加回来

⑮ 保存指标的分数版本

如果你正在遵循本章中的示例,你应该自己运行列表 5.3 在模拟数据集上。假设你正在使用 Python 包装程序,将参数设置为—chapter 5 —listing 3。这个操作将在输出目录中创建文件 socialnet_dataset_scores.csv。运行列表的包装程序会打印输出目录。

如果你想要检查分数数据集与原始数据集的不同之处,一个选项是将其在文本编辑器或电子表格中打开。你应该能够看到所有指标都是小数(接近零),并且既有正数也有负数。更好的选项是重新运行数据集摘要程序(列表 5.2)在新数据集上。你可以通过在 Python 包装程序中添加参数—version 2来运行数据集摘要列表的第二版,因此使用以下参数运行包装程序:

—chapter 5 —listing 2 —version 2 

包装程序将结果保存到名为 socialnet_dataset_scores_summary.csv 的文件中。如果你检查结果文件,所有指标都应该像图 5.15 中显示的那样:

  • 均值是一个接近零的小舍入误差。在图 5.15 中,均值为-2.65E-16,这意味着 10 的负 16 次方(一个非常小的数)。

  • 标准差为 1。

  • 最小值和最大值分别约为-4 和 4。

  • 尽管原始指标严重偏斜(图 5.12 中的 15.5),但评分实际上没有偏斜。

图片

图 5.15 模拟数据集分数数据集中一个指标的摘要统计示例

5.3.4 使用评分指标进行队列分析

当你从原始数据集中创建了分数数据集后,你可以按照 5.1 节中学到的方法进行队列分析,使用相同的代码。如果你回顾一下 5.1.2 节中的列表 5.1,你会发现队列分析函数中根本不在乎数据是原始指标(在其自然尺度上)还是转换尺度上的评分指标。还要记住,由于将指标转换为分数保留了按指标排序的客户顺序,因此使用分数进行队列分析只会改变数据在队列图水平轴上的分布方式。

你应该使用 Python 包装程序对社交网络模拟的评分指标运行自己的队列分析。你可以使用以下参数运行队列图列表的第二版(列表 5.1):

—chapter 5 —listing 1 —version 2  

结果看起来像图 5.16,其中水平轴被重新标记为从-1.5 到 1.5(群组平均值)的分数。每个群组的流失率与图 5.4 中显示的相同。但是,在转换为分数后,群组的定位在图中分布得更均匀。同时,分数指标群组中每个点的流失率与原始群组中的一个的流失率相匹配。

图 5.16 从模拟中得到的分数指标群组分析

图 5.17 展示了比较 Broadly(一个帮助企业管理其在线存在的服务)一个指标在其自然尺度上及其作为分数的群组流失率图的示例。对于 Broadly 的客户来说,一个重要的事件是添加新的交易,并计算了每月新增交易量的指标。每月新增交易量的指标高度倾斜,统计值为 23。因此,当使用该指标的自然尺度制作群组图时,除了一个群组外,所有群组都位于整个图大约八分之一宽的水平范围内。群组平均值介于 0 到 250 之间,最高群组的平均值是 2,300。相比之下,基于指标分数的群组在大约-1.5 到 2.0 的分数之间分布得更加均匀。此外,现在很清楚群组与平均值的比较,这个平均值是 0。前三组低于平均值,大部分的流失率降低都是从最低群组到接近平均值的群组。

图 5.17 Broadly 每月新增交易量分数指标和流失率示例

要点:使用分数指标群组的群组分析使得很容易看到群组与平均指标值的关系,这个值总是等于零的分数。

5.3.5 每月经常性收入(MRR)的群组分析

在第三章中,你学习了每月经常性收入(MRR)应该作为一个指标来计算,但到目前为止,我们还没有查看过任何关于它的群组分析结果。现在我将演示 MRR 群组分析的典型结果,使用的是在第 5.3.4 节中介绍的指标分数技术。我演示 MRR 和流失率的群组分析使用分数有两个原因:

  • MRR 对于企业对企业(B2B)产品通常高度倾斜,因为最大的客户,可能是大型企业,通常支付的价格比最小的客户,可能是单一业主企业,高出许多倍。

  • 以分数形式呈现这一群组分析,可以保持案例研究中定价信息的保密性。

图 5.18 显示了分数 MRR 流失率分析的结果。但在你查看结果之前,停下来思考一下你期望的结果是什么。(好吧,你们中的一些人可能还记得第一章中的答案。)支付更多费用的客户群组的流失率会是

  • 高于支付较少的客户流失率吗?

  • 低于支付较少的客户流失率吗?

  • 大约与支付较少的客户的流失率相同吗?

图 5.18 显示答案是 B:平均支付更多的客户群体流失率较低,尽管趋势有点嘈杂。高支付客户群体的流失率大约比最低支付群体低三分之一到一半。

图片

图 5.18 Versature 的 MRR 得分和客户流失率示例

这个结果可能会让你感到惊讶。通常的想法是高价格会让客户不太满意,因此他们流失率更高。然而,情况通常相反。平均而言,支付更多的客户通常流失率较低,原因有很多。支付更多客户流失率较低的第一个原因是被动流失。被动流失(也称为非自愿流失)发生在客户没有表明流失意愿的情况下(因此得名被动)。被动流失最常见的情况是支付卡过期或可用余额不足。因为支付更多的客户通常在更高的计划级别注册,他们通常有更多的钱,因此不太可能遇到这些问题。(为了减少被动流失,大多数公司会多次尝试信用卡直到交易成功,但这种减少流失的方法超出了本书的范围。)

第二个通常更重要的原因是,MRR 较高的客户流失率较低,尤其是在商业产品方面:商业产品以更高的价格卖给大客户,而大客户由于与规模相关的多个原因流失较少。大公司有更多的员工,所以在产品使用方面,如打电话或使用软件,大客户通常使用更多的可用服务。此外,大企业客户在建立系统时投入更多,因此不太可能放弃投资。你可能惊讶地发现,同样的模式通常也适用于消费产品。注册更昂贵计划的客户往往投入更多并更频繁地使用产品,因此他们的流失率比注册低成本计划的客户低。

MRR 与较低的客户流失率相关,但并非原因。如果你看到这种关系,不要试图提高 MRR 来降低客户流失率。这种理解在直觉上是不令人满意的,因为为某物支付过多应该是导致客户流失的原因,而得到一个好的交易应该是导致客户保留的原因。更好地理解客户支付与客户流失之间关系的方法是使用不同的指标——该指标反映客户获得的价值而不是他们支付的价值。这些主题是第六章和第七章的主要主题之一。

5.4 移除不需要或不正确的观察值

另一种应该成为您群体分析工具包中的有用技术是从群体分析中移除不想要的观察结果。尽管您已经尽力进行质量保证和清理数据,但某些观察结果可能无效(坏数据),或者它们可能不是无效的,但您不希望它们存在,因为它们会使您的群体分析更难理解。我将向您展示两个激励性的案例研究,其中一些观察结果被从群体分析中移除,以及一个执行移除操作的 Python 函数。

5.4.1 从流失分析中移除非付费客户

您可能需要从分析中移除一些观察结果的一个常见场景是,您既有付费客户又有非付费客户。不需要付费的客户可能处于临时免费试用阶段,或者他们可能属于某些特殊客户类别,例如永久免费使用产品的合作伙伴。您可能也有类似的情况,即一些客户支付的费用远低于普通客户支付的费用。非付费(或低付费)客户的问题在于,由于产品对他们来说不花钱,他们往往不会流失,但他们不一定大量使用产品。因此,非付费客户与行为和流失之间的正常关系不存在,如果您有超过少量非付费客户,他们可能会破坏您分析的结果。

图 5.19 通过向 Versature 的 MRR 和本地通话观察结果中添加模拟的非付费客户来展示免费客户的影响,这些观察结果在 5.3.5 节中进行分析。这些非付费客户不是真实的;它们是随机生成的观察结果,具有 $0 MRR。模拟的非付费客户被分配了一个本地通话指标,这个指标也是随机生成的,位于真实客户的底部两个十分位。添加了足够的模拟非付费客户,以占总数据的 15%。当群体图重新生成时,如图 5.19 所示,它们由于非付费客户的存在而严重扭曲,指标与流失之间的关系似乎不太显著。

图 5.19

图 5.19 添加到 Versature 订阅数据中的 $0 MRR (免费) 测试的影响

如果你有一些客户付款,而有些客户不付款,在尝试进行群体分析或后续章节中描述的其他分析之前,你应该移除不付款的客户。理想情况下,你可能在生成观测值时(如第四章所述)通过使用基于客户使用的计划的某种 SQL 逻辑来移除此类客户。但是,多个订阅的存在可能会使这种方法变得复杂。一个客户可能有某些$0 MRR 订阅,而其他订阅则附加了费用,因此为了确保客户没有支付任何费用,你必须使用第三章中计算的 MRR 指标。这个例子说明了为什么在生成数据集之后可能需要移除此类客户。5.4.2 节中的 Python 程序展示了如何做到这一点。

5.4.2 基于 Python 中的指标阈值移除观测值

从客户流失分析中移除观测值的一种方法是为一个指标定义一个最小值,为一个指标定义一个最大值,或者两者都定义。任何指标值低于最小值或高于最大值的观测值都可以被移除,从而生成一个新的数据集,它是原始数据集的子集。

列表 5.4 展示了执行这些操作的 Python 函数。函数remove_invalid具有以下输入:

  • data_set_path—一个字符串变量,指定保存数据集的文件路径。

  • min_valid—一个包含任何要筛选的指标的最低有效值的字典。条目假定是键值对,其中键是指标的名称(一个字符串),值是应用于筛选该指标的最低值。可以通过这种方式指定任意数量的标准。

  • max_valid—一个包含任何要筛选的指标的最高有效值的字典。条目假定是键值对,其中键是指标的名称(一个字符串),值是应用于筛选该指标的最高值。可以通过这种方式指定任意数量的标准。

  • save_path—一个文件路径,用于保存分数。

给定这些输入,以下是要创建一个新数据集的主要步骤,其中无效观测值已被移除:

  1. 将数据集加载到 Pandas DataFrame对象中,设置DataFrame索引,并创建一个副本。清洗后的数据被写入DataFrame的副本中。

  2. 对于在min_valid字典参数中指定的每个指标,从DataFrame中移除那些值低于最小值的观测值。

  3. 对于在max_valid字典参数中指定的每个指标,从DataFrame中移除那些值高于最大值的观测值。

  4. 将结果DataFrame保存到文件中。

在本章前面提到的图 5.19 中,展示了使用此算法的一个示例。图的左侧是由列表 5.4 清洗的数据集生成的;图 5.19 的右侧是由原始未清洗数据生成的。

列表 5.4 移除无效观测值

import pandas as pd
import os

def remove_invalid(data_set_path,min_valid=None,max_valid=None):
   assert os.path.isfile(data_set_path),
      '"{}" is not a valid path'.format(data_set_path)              ①
   churn_data = 
      pd.read_csv(data_set_path,index_col=[0,1])                    ②
   clean_data = churn_data.copy()                                   ③

   if min_valid and isinstance(min_valid,dict):
      for metric in min_valid.keys():                               ④
          if metric in clean_data.columns.values:                   ⑤
              clean_data=clean_data[clean_data[metric] > 
                         min_valid[metric]]                         ⑥
          else:
              print('metric %s not in dataset %s' % (metric,data_set_path))

   if max_valid and isinstance(max_valid,dict):
      for metric in max_valid.keys():                               ⑦
          if metric in clean_data.columns.values:                   ⑤
              clean_data=clean_data[clean_data[metric] < 
                         max_valid[metric]]                         ⑧
          else:
              print('metric %s not in dataset %s' % (metric,data_set_path))
   score_save_path=
      data_set_path.replace('.csv','_cleaned.csv')                  ⑨
   print('Saving results to %s' % score_save_path)
   clean_data.to_csv(score_save_path,header=True)

① 检查数据集路径

② 将数据加载到 Pandas DataFrame 中,设置索引

③ 创建原始 DataFrame 的副本

④ 对用于清理的变量进行迭代,以最小值结束

⑤ 确认该指标是否在 DataFrame 中

⑥ 移除指标小于最小值的行

⑦ 对用于清理的变量进行迭代,以最大值结束

⑧ 移除指标大于最大值的行

⑨ 将生成的 DataFrame 保存到文件中

本书所用的模拟数据集不包含任何免费试用用户或其他需要移除的不必要数据,因此没有可以运行列表 5.4 的示例。

5.4.3 从罕见指标分析中移除零测量值

在另一种情况下,你可能想要从群体分析中移除观测值,当指标衡量的是一个罕见事件时,结果大多数客户在该指标上为零。这种情况在第 5.1.7 节中有所说明。列表 5.4 中的函数提供了一个简单的方法来查看当你只考虑有该事件(以及在指标上有非零值)的客户时,群体看起来像什么。

图 5.20 显示了 Klipfolio 的方向切换事件的分析,包括有和没有零指标计数客户的情况。

图 5.20 Klipfolio 稀有行为的群体

没有零指标客户的版本是通过在数据集上运行列表 5.4 来创建的,以移除在指标上为零的客户,然后保存。之后,由于只有大约 25% 的观测值剩余,因此使用了五个区间进行群体分析。

5.4.4 解离行为:与增加流失率相关的指标

到目前为止,我已经向你展示了与减少流失率相关的行为的群体分析。你可能想知道与增加流失率相关的行为。我把这些行为称为解离行为。

定义:解离行为是一种客户行为,这种行为越频繁地发生,导致客户流失的风险就越高。

我没有避免解离行为来对案例研究进行积极解读。事实是,在流失案例研究中,解离行为很少见,并且通常无法用你迄今为止学到的简单计数和平均指标来检测。一方面,产品创造者的工作是创建吸引人的功能,因此如果创造者正在做他们的工作,解离行为应该是罕见的。通常,甚至没有使用产品的客户比执行解离行为的客户有更高的流失率。因此,如果存在解离行为,它们与流失率的关系可能很弱,并且很容易被忽视。

吸收:解离行为通常与增加的流失率显示出较弱的关系——通常小于使用产品即使是一小部分所带来的流失率减少。

图 5.21 展示了 Klipfolio(一个用于商业仪表板的 SaaS 产品)的一个不活跃功能的例子。案例研究显示了两种版本的群体分析。一种版本包括所有客户,包括那些不使用产品或功能的人,另一种版本只包括使用该功能的客户。如果你包括所有客户,你可能会错过该功能是不活跃的。群体分析最显著的特点是,不使用该功能的客户具有最高的流失率。很容易忽略使用该功能的客户流失率会随着使用频率的增加而略有上升。当不使用该功能的客户从分析中去除后,使用该功能的用户中流失率的上升变得更加明显。

图片

图 5.21 Klipfolio 不活跃行为的群体

考虑到所有因素,图 5.21 中显示的不活跃功能用户的流失增加与第 5.1 节中展示的使用主要产品功能相关的流失减少相比是微不足道的。这种结果对于用简单指标测量的不活跃行为来说是典型的。在第七章中,你将学习创建检测更显著不活跃行为的先进技术。

你的直觉可能认为不活跃行为一定是坏事,即客户不喜欢的那种体验。但我见过一些情况,其中不活跃行为是好的,例如当这种行为有助于完成产品目的,而用户正好得到了他们想要的东西时。当产品对某些用户只有一个目的并且该目的可以完成时,也可能发生不活跃行为。一个常见的例子是观看热门视频系列。如果只有一个热门系列,人们在看完了每一集后可能会流失。在这种情况下,观看最佳内容会导致流失。创作者必须制作更多同样受欢迎的内容来改变这种模式。为了得出这样的结论,你需要运用你的商业知识。通常,人们知道一个功能或内容是否好,无需流失分析(但如果你不确定,可以调查用户)。

5.5 通过群体分析对客户进行分段

现在你已经知道了如何通过群体分析来理解客户行为和流失。在对抗流失的下一步,是利用你所学的知识来对客户进行分段并规划干预措施。在第六章和第七章中,你将更深入地了解你的客户,但你现在无需再等待就可以开始行动。正如我在第一章中提到的,我不会详细介绍不同类型的客户干预措施,因为它们针对的是每个公司的产品和环境。但是,基于流失数据创建客户分段的基于数据的程序几乎是通用的。

5.5.1 分段过程

我合作过的多数公司发现客户群体划分相当直接,因此这个解释比较简短。主要步骤如下:

  1. 使用当前客户数据集(你可以在第四章末尾学习如何创建)来创建群体。不要使用你进行群体分析的原始数据集。

  2. 客户群体划分通常由将要进行客户干预的业务人员通过电子表格进行。在一个大型公司中,可能会使用商业智能系统。

  3. 通过根据你的群体分析结果选择指标水平,定义一个处于流失风险的客户群体。假设该指标是一个与较低流失率相关的较高值,那么处于风险中的客户是那些指标低于你选择的水平的客户。

  4. 如果需要,可以将生成的客户名单加载到另一个系统中,例如电子邮件营销工具或客户关系管理系统。

这个程序很简单,但在设置群体标准时有一些细微差别。

5.5.2 选择群体标准

你可以使用一些策略来设定一个指标水平,以定义一个客户群体。一种常见的方法是根据流失风险设定指标水平,例如,选择所有流失风险高于一定水平的客户,如根据群体分析结果所建议的。假设你的群体分析显示,不使用产品的客户流失率为 20%,而在某个指标水平上,风险降低到 5%。为了定义一个处于风险中的客户群体,你选择一个流失风险显著高于最低流失率的指标水平。例如,你可能选择流失率为 10%的指标值。另一种策略是选择一个指标水平,在这个水平上,通过增加使用量所实现的流失减少量最大(假设存在这样的水平,详见第 5.1 节)。

许多公司也对特定干预措施中将要处理的客户数量有一定的资源预算或其他限制。定义群体的另一种方法是选择在某种行为上度量最低的 500 名客户。当处于风险中的客户数量大于你的资源时,你需要对努力进行分级,这种方法是有意义的。为了创建这样的群体,根据感兴趣的指标对当前客户数据集进行排序,并从列表底部(或顶部)选择预定的客户数量。

使用干预措施(如电子邮件、电话或培训)来针对处于某些中间风险水平的客户也是常见的。

总结:通常情况下,你不会对最不活跃的客户进行干预以减少客户流失。

这种推理是,最高风险(最低使用)的客户可能会如此疏离,以至于干预措施将没有效果,将是浪费的。当干预措施与成本相关联或您认为不想要的沟通可能会进一步疏离客户时,这种考虑尤为重要。

汇总

  • 队列分析比较基于单个指标测量的客户观察组的流失率。

  • 指标队列分析通常显示,使用产品更多的客户流失较少。

  • 每个队列至少应有 200 到 300 个观察值,最好有数千个。如果您没有很多客户观察值,请使用较少的队列。

  • 队列分析可以应用于订阅指标,如服务期限、MRR 和账单周期。

  • 数据集的统计摘要包括对数据集中每个指标所采取的一系列度量(如平均值、最小值和最大值)。

  • 数据集的统计摘要是对您数据质量保证的良好检查,并且可以提醒您需要调整队列分析的某些条件。在进行队列分析之前,您应该检查一组汇总统计量。

  • 在执行队列分析之前,您应该与业务人员讨论数据集的汇总统计量,并纠正任何数据问题。

  • 当大多数观察值都在一个较小的范围内,但相对较少的观察值却非常大时,指标就会偏斜。

  • 从指标创建的分数是对每个指标观察值的重新缩放,使得重新缩放后的值位于接近零的小范围内。但根据分数排序的观察值顺序与根据指标排序的顺序相同。(较大的指标值总是映射到较高的分数。)

  • 当一个指标偏斜时,使用该指标分数的队列分析比使用未转换的指标更容易阅读。

  • 如果非付费用户与付费客户混合在一起,您应该在执行队列分析之前移除非付费用户。非付费用户往往不会流失,无论他们使用产品的程度如何,因此会扭曲队列分析中的关系。

  • 对于基于罕见事件的指标,您可能希望移除具有零指标值的客户,以便队列反映具有事件的客户之间的差异。

  • 疏离行为是那些执行该行为越多的客户流失率越高的行为。

  • 疏离行为很少在简单的行为计数指标中显现;通常,您必须从队列分析中移除非用户,才能看到这些行为的队列分析趋势。

  • 通过根据队列分析的结果选择最小指标水平,您可以找到有风险的客户细分,以进行干预。

6 客户行为之间的关系

本章涵盖了

  • 分析成对度量的关系

  • 计算相关系数矩阵

  • 计算相关度量分数的平均值

  • 使用度量平均值细分客户

  • 通过聚类发现度量群组

对于大多数产品和服务的分析,分析单个度量与流失率的相关性只是使用数据减少流失的开始,但不是结束。本章教你如何解决一个常见问题:拥有大量可用于对抗流失的数据。在大数据时代,一些公司收集了大量关于他们的客户信息。这应该使得使用数据对抗流失变得更容易,对吧?并不完全是这样。

许多客户行为密切相关,因此基于这些行为的度量具有相似的相关性。在典型公司的数据库事件和度量中进行的客户群体流失分析可能不会只给你几个客户群体流失图:你可能会有几十个或更多。这实际上可能比好的情况造成更多的困惑。当度量的行为不是给用户带来愉悦或效用具体行为时,那么与流失率的关系只是关联而不是因果关系。当你有很多与流失相关但不是因果关系的度量时,你没有一个很好的方法来理解它们是如何共同作用的。

为了有效地使用数据对抗流失,你需要做的不仅仅是理解单个客户行为与流失率的关系。你需要理解客户行为之间的关系。当你这样做而不是仅仅看单个行为与流失率的关系时,你可以看看行为群体与流失率的关系。这样,你将拥有过多数据的问题转变为一种资产,因为行为群体往往比单个行为更能清晰地显示出与流失率的关系。

本章的组织结构如下:

  • 在第 6.1 节中,本章从一些案例研究开始,展示行为相关性的含义,然后教你如何使用 Python 以及称为相关矩阵的东西来计算你自己的数据中的相关性,这是观察大量度量之间相关性的一种重要方式。

  • 在第 6.2 节中,你将学习一种形成相关行为度量分数平均值的技术,然后使用平均分数来分析流失率。这是你将使用的关键技术,用于减少与流失相关的大量度量所引起的信息过载。

  • 最后,在第 6.3 节中,你将学习一种使用称为聚类算法的算法在大型数据集中自动找到相关度量组的技术。通过掌握这些技术,你将准备好处理具有大量相关度量和行为的庞大数据集,以便在对抗流失的斗争中更加有效。

行为分组与维度缩减

如果你接受过数据科学或统计学的正规培训,你可能会认识到这一章节涵盖了降维的概念和实践,并且是线性代数的一个快速课程。但是,由于需要将概念传达给商业人士,我将其称为行为分组,它用简单的英语描述了关键结果。如果你接受过正规培训,你也会发现我坚持使用一种基本且直观的降维方法,但我想要提醒你,不要将这种方法视为“简化”的方法。采取的方法是有意为之的简单。虽然它从通常的统计意义上来说不是最优的,但它优化了可解释性。它对于面对混乱的数据和不断变化的问题(流失是非平稳的)的鲁棒性和样本外预测性能也非常出色。根据我的经验,使用更复杂的方法从降维中提取最大信息并不会导致在流失预测中的性能更好。对于感兴趣的读者,这一章节以一个侧边栏结束,比较了本章中使用的方法与使用主成分分析(PCA)进行标准降维得到的结果。

6.1 行为之间的相关性

如果你认为有两种客户行为是相关的,首先从客观上测量它们是如何相关的。最实际的方法是通过测量相关性来完成,这是本节的主题。你将了解客户行为之间的相关性意味着什么,并将在案例研究中看到客户行为数据的演示。然后,你将学习如何计算和可视化成对指标之间的相关性,以及数据集中所有指标之间的相关性。

6.1.1 指标对之间的相关性

当一个在第一个指标上有高值的客户也在第二个指标上有高值,而一个在第一个指标上有低值的客户也在第二个指标上有低值时,我们说这两个指标或行为是相关的。你也可以通过说第一个指标的增加与第二个指标的增加相关联来描述相关性,在这种意义上,如果一个客户增加了一种行为,他们也有可能增加另一种行为(以及相关的指标)。

定义:一对指标或行为之间的相关性是衡量一个指标或行为增加(或减少)与另一个指标或行为增加(或减少)之间一致性的度量。

这就是相关性的概念。还有一种称为相关系数的相关性度量,通常也简单地称为相关性。

定义:相关系数是衡量相关性的一个度量,其范围可以从-1.0 到 1.0:

  • 1.0 的相关性意味着一个指标的增加总是与另一个指标相同的增加相关联。

  • 负相关意味着当一个指标增加时,另一个指标会减少。

当增加是 1:1(第一个指标增加 1 对应第二个指标增加 1)时,最容易想象 1.0 的相关性,但相关性相同的情况下,也可以是任何比例(1:2、2:1 等等)。这就是为什么相关性取决于两个指标之间关联的一致性,而不是比例的精确大小。相关性也可以存在于任何测量单位或刻度的两个指标之间(登录、下载、查看等)。

注意:关系的一致性意味着一个指标的一定程度的增加会导致另一个指标按特定比例成比例增加。

图 6.1 展示了不同相关程度的指标对示例,这些示例来自前面章节中介绍的公司案例研究。散点图通过在每个轴上绘制每个观察值作为一个点来显示两个不同指标的价值。图 6.1 中的每个点代表数据集中单个观察值中的两个指标;这些点的完整集合是这两个指标的所有成对值。

图 6.1 Klipfolio、Broadly 和 Versature 的案例研究,展示了不同程度的正相关

图 6.1A 显示了相关性超过 0.95(确切地说为 0.98)的高度相关指标。在实践中,你永远不会看到两个指标之间有 1.0 的相关性(除非你意外地计算了两次相同的指标),但你可能会看到像图 6.1A 中那样高度相关的指标。这些是 Klipfolio 仪表板编辑器中的紧密相关指标。(Klipfolio 在第一章中介绍,是一种用于企业仪表板的 SaaS 产品。)

当你绘制一组高度相关的观察值时,它们会排列成几乎对角线的线。在 1.0 的相关性下,点将精确地位于对角线上。

超过 0.7 的相关性测量被认为是高度相关。图 6.1B 显示了 Broadly 中客户增加数和展示请求数这对指标,它们的相关性为 0.88。(Broadly 在第一章中介绍,帮助企业管理其在线存在。)图 6.1C 显示了 Versature 的本地通话和国内通话指标分数,这显示了中等程度的高度相关性(0.75)。(Versature 在第一章中介绍,提供基于云的企业通信解决方案。)对于相对高度相关的指标,这些散点图中的点往往位于某种斜向的椭圆或椭圆形中。

大约在 0.3 到 0.7 范围内的相关性测量被认为是适度相关的,并在图 6.1D 和图 6.1E 中展示。图 6.1D 是 0.57 的适度相关性,展示了 Versature 的本地和国内通话的相同两个指标。在这种情况下,指标是在其自然尺度上显示的,而不是作为分数。请注意,指标分数与基础指标的相关性显著更高。正如第五章所述,当指标有偏斜时,这种情况经常发生。

吸收要点 指标分数通常比其自然尺度上的基础指标具有更高的相关性,尤其是在指标严重偏斜时。这是在分析中使用指标分数的另一个重要原因。

图 6.1E 展示了 Klipfolio 的两个具有较弱但仍然适中的相关性(0.31)的指标。这些是数据源数量和编辑的 Klip 数量指标。在适度相关的指标的散点图中,点倾向于靠近对角线,但结构较少。图 6.1F 展示了 Broadly 的两个具有更弱相关性(0.18)的指标。这些是添加交易数量和客户推广者(给出正面评价的客户)数量指标。交易数量更多的企业往往有更多的推广者,但关系较弱,并且有许多异常值:一些观察值在一个指标上很高,而在另一个指标上很低,导致点靠近两个轴。

图 6.2 Klipfolio、Broadly 和 Versature 的零相关性案例研究

图 6.2 展示了具有零相关性或接近零相关性的指标示例;这些通常被称为不相关。尽管具有高相关性的指标的散点图通常看起来很相似,但在低相关性和接近零相关性的指标中存在更多多样性。图 6.2A 展示了 Broadly 的两个具有精确 0.0 相关性的指标。这些指标用于查看客户列表和发送跟进邮件。在这种情况下,两者之间没有关系,点倾向于靠近原点(在两个轴上均匀分布)。图 6.2B 展示了 Versature 的本地通话和账户期限指标。账户期限在 x 轴上均匀分布到最大值,但与本地通话次数没有关系。图 6.2C 展示了 Klipfolio 的一个示例,其中添加模板和切换方向的指标之间存在接近零的相关性。添加模板的指标很少见,因此大多数观察值在该轴上的值接近零,并且与另一个指标(方向切换)没有关系。

图 6.3 展示了你在数据中不太可能看到的相关性模式。案例研究中没有可用的示例,因此这些是通过在本书网站上提供的代码(www.manning.com/books/fighting-churn-with-data)和本书的 GitHub 仓库(github.com/carl24k/fight-churn/tree/master/data-generation)中使用的代码进行模拟数据生成的。图 6.3A 展示了一个示例,其中两个行为指标都位于非零范围内,几乎没有异常值和偏斜,但仍然没有相关性。此类指标的散点图中的点倾向于位于球体中,这是统计学教科书中不相关指标的典型例子。但令人惊讶的是,在客户行为中很少看到这种情况。通常,当两个客户指标几乎没有零值和极端值时,它们之间会有某种程度的相关性。

图片

图 6.3 展示了从模拟数据中得出的罕见行为相关性

在图中,6.3B 和 6.3C 分别展示了低度和中度的负相关性示例。与正相关类似,散点图中的点倾向于位于一个椭圆中,但在这个例子中,椭圆是斜向右下方倾斜的,而不是向左上方倾斜。这表明一种行为的增加与另一种行为的减少相关联。

在基于客户事件的计数指标之间观察到负相关性是很少见的,因为通常拥有更多事件的客户会做更多的事情。话虽如此,还有其他类型的更高级的行为指标(参见下一章),这些指标可以与数据集中的其他指标有负相关性。

6.1.2 使用 Python 调查相关性

列表 6.1 展示了一个简短的 Python 程序,用于创建散点图和相关性测量,类似于上一节中展示的内容。该程序假设数据集是使用第四章中的代码(特别是列表 4.1、4.2、4.4 和 4.5)创建并保存的。列表 6.1 的大部分内容处理了加载数据集和带有注释的散点图的细节。

相关性是通过 Pandas 函数 Series.corr 的单个调用计算的。如果你想知道相关性系数是如何计算的,网上和统计学教科书中有许多资源(例如,搜索“皮尔逊相关性系数”)。散点图是通过调用 Matplotlib 函数 pyplot.scatter 创建的。与之前的绘图示例一样,提供详细的标签和注释对于图表非常重要,这样你的商业同事就能知道他们在看什么(并且当你稍后再次查看时,你可以记住你绘制了什么)。

图片

图 6.4 展示了在模拟指标上运行列表 6.1 的结果,显示了每月点赞数和每月发帖数的得分

图 6.4 显示了在默认模拟数据集上运行列表 6.1 的结果。你应该亲自尝试一对度量。假设你已经设置了你的环境(GitHub 仓库中书籍的 README 中的说明,github.com/carl24k/fight-churn),并且你正在使用 Python 包装程序,使用以下命令运行列表 6.1:

fight-churn/listings/run_churn_listing.py —chapter 6 —listing 1 —version 1 2

这应该会给你一个包含度量 post_per_month 和 like_per_month 之间散点图的 .png 文件,其外观类似于图 6.4。你也可以通过运行带有版本参数的替代版本(最多到 16)来检查不同度量对的结果:

—version 3 4 5 6 7 8 9 10 11 12 13 14 15 16 

该命令生成了每月帖子数作为得分和自然尺度度量的配对图。这只是从数据集中所有可能的配对图中的一小部分,但它将展示可能的关联模式的一些多样性,以及当度量转换为得分时产生的差异。

列表 6.1 分析度量对的关联性

import pandas as pd
import matplotlib.pyplot as plt
import os

def metric_pair_plot(data_set_path):
   assert os.path.isfile(data_set_path),
      '"{}" is not a valid path'.format(data_set_path)                ①
   churn_data = 
      pd.read_csv(data_set_path,index_col=[0,1])                      ②

   met1_series = churn_data[metric1]                                  ③
   met2_series = churn_data[metric2]

   corr = met1_series.corr(met2_series)                               ④

   plt.scatter(met1_series, met2_series, marker='.')                  ⑤

   plt.xlabel(metric1)                                                ⑥
   plt.ylabel(metric2)
   plt.title('Correlation = %.2f' % corr)                             ⑦

   plt.tight_layout()                                                 ⑧
   plt.grid()
   save_name = 
      data_set_path.replace('.csv','_'+metric1+'_vs_'+metric2+'.png')
   plt.savefig(save_name)                                             ⑨
   print('Saving plot to %s' % save_name)
   plt.close()

① 检查数据集路径

② 将数据集加载到 DataFrame 中

③ 选择将要分析的度量

④ 计算两个序列之间的相关性

⑤ 从两个序列制作散点图

⑥ 添加轴标签

⑦ 在标题中打印相关测量值

⑧ 调整布局以适应标签和标题

⑨ 将图形保存为 .png 格式

6.1.3 理解通过相关矩阵度量集之间的相关性

散点图对于理解你感兴趣的度量对之间的关系很有用,但它们是调查大量度量对之间相关性的低效方式。这是因为如果你有相当数量的度量,组合的数量会大得多(对于数学爱好者,对于 N 个度量,有 N × (N - 1) / 2 种组合,如后面所示)。你将在下一节学习一种更有效的方法来查看数据集中的大量相关性;这被称为相关矩阵。矩阵是一个表格(数据),其中所有条目都是数字,相关矩阵是数据集中所有相关性的表格。也就是说,相关矩阵中的每个条目都是两个度量之间的相关系数。

定义:相关矩阵是数据集中所有度量之间的成对相关系数的表格。

图 6.5 展示了从简单数据集中创建相关矩阵的示例。该数据集模拟了五个度量,即每月事件(点赞、阅读、回复、发送和撰写)的计数,在一个消息应用中。这些度量被转换为分数,因为这样可以显示更多的相关性。每一对度量都有自己的关系,可以通过散点图和单独的相关计算来研究。为了在一个单一矩阵中显示所有相关性,度量被放置在表格的行和列中(相同的顺序)。每一对度量之间的相关性被输入到表格中该对度量的交叉点上。这样,每个相关性都可以在单个表格中查找。

图 6.5 一个相关矩阵(底部)总结了数据集中所有度量之间的成对相关性(顶部)。

注意:相关矩阵通常按值着色,因为这样可以更容易地通过视觉识别高和低相关性;着色相关矩阵通常被称为热图。

本书中的相关矩阵是灰度图,因此可以打印,但我建议在其他所有情况下(无论是自己的分析还是向同事展示)都使用全色热图。

因为度量位于相关矩阵的行和列中,所以对于每一对度量,矩阵中都有两个交叉点。这些交叉点在从左上角到右下角穿过矩阵的对角线两侧是对称的。有两种处理这种冗余的方法:显示每个条目两次,或者省略一半的矩阵。最常见的方法是显示每个条目两次;因此,相关矩阵在对角线上是对称的。这可以更容易地找到你想要的关联,因为无论你从行还是从列开始,你都能以相同速度找到条目。另一种方法是省略对角线以上或以下的一半矩阵。这会导致更干净的外观,更适合演示。此外,每个度量在相关矩阵中都有一个与自身的交叉点,并且这个交叉点位于矩阵的对角线上,因为度量在行和列中的顺序是相同的。

根据定义,每个度量与其自身的相关性为 1.0。尽管这个信息没有用,但对于涉及相关矩阵的算法来说在数学上是必要的。但在演示中显示矩阵中的对角线 1.0 可能会分散注意力,因此可以省略。

6.1.4 案例研究相关矩阵

图 6.6 展示了 Klipfolio 案例研究中的相关矩阵,以热图的形式呈现。大约有 70 个指标,它们被排序以显示不同类型指标之间高度相关的关联。有六个高度相关的指标组。最大的单个组包括使用产品最常见方式的指标。还有五个其他较小的指标组,它们与产品的其他方面相关,有些指标与任何其他指标的相关性不强。这种结构相当典型,尽管并不总是有这么多定义良好的组。产生这种顺序的技术将在列表 6.4 中展示。

图 6.6 Klipfolio 的指标相关性,按顺序显示高度相关的关联

图 6.7 展示了 Klipfolio 案例研究中的相关矩阵,指标按字母顺序排列。这些指标与图 6.6 中的指标相同;只是顺序不同。图 6.6 中组织良好的矩阵比图 6.7 中按字母顺序排列的矩阵结构更明显。但像图 6.7 这样的结构是你第一次查看热图(列表 6.2)时更有可能看到的。指标的字母顺序揭示了一个结构,其中以相同单词开头的指标通常相关。尽管如此,还有很多例外,因此相关组不像图 6.6 中那样明显。

图 6.7 Klipfolio 的相关矩阵,显示按字母顺序排列的指标

6.1.5 在 Python 中计算相关矩阵

列表 6.2 是一个简短的 Python 程序,用于创建如图 6.6 和 6.7 所示的相关矩阵。在模拟数据集上运行列表 6.2 的结果如图 6.8 所示。该程序假设使用第四章中的代码创建并保存了一个数据集。回想一下,这个数据集是一个表格,每行有一个客户的观察结果,每列有一个指标。列表 6.1 的大部分内容处理加载数据集和保存结果的细节。格式化将在后面描述的免费电子表格中进行。

图 6.8 在模拟数据集上运行列表 6.2 的结果

在列表 6.2 中,通过调用 Pandas Dataframe.corr函数计算相关矩阵。请注意,列表 6.2 并不试图创建如图 6.6 和 6.7 中的示例那样的热图图像;该函数在将相关矩阵数据保存到逗号分隔的(.csv)文件后停止。这样做的原因是在 Python 中为大量指标制作热图并不实用。如果有超过 15 到 20 个指标,热图图像必须非常大,或者指标名称和相关性值太小而无法阅读(参见图 6.6 和 6.7 中的示例)。

TIP 在静态图像中探索大型相关性热图并不实用。你绝对应该仔细检查相关性热图,但通常在电子表格应用程序中查看它更容易。固定指标名称的行和列,使矩阵可滚动,并使用条件格式化添加热图颜色。对于演示,你可以导出各种格式版本。

列表 6.2 在 Python 中计算数据集的相关矩阵

import pandas as pd
import os

def dataset_correlation_matrix(data_set_path):

   assert os.path.isfile(data_set_path),
      '"{}" is not a valid path'.format(data_set_path)                   ①
   churn_data = 
      pd.read_csv(data_set_path,index_col=[0,1])                         ②

   churn_data = 
      churn_data.reindex(sorted(churn_data.columns), axis=1)             ③

   corr_df = churn_data.corr()                                           ④
   save_name = data_set_path.replace('.csv', '_correlation_matrix.csv') 
   corr_df.to_csv(save_name)                                             ⑤
   print('Saved correlation matrix to' + save_name)

① 检查路径

② 将数据集加载到 DataFrame 中并设置索引

③ 按字母顺序排序列

④ 使用 Dataframe.corr 函数计算相关矩阵

⑤ 以 .csv 格式保存相关矩阵

你应该运行列表 6.2 并确认它在你自己的数据集上给出相似的结果。如果你正在使用包装程序来运行列表,到现在你应该知道这意味着将命令行参数更改为 —chapter 6 —listing 2。程序将数据保存为 .csv 文件(其位置将由包装程序打印出来)。

6.2 平均行为指标组

假设你有 5 或 10 个客户行为,其中指标中度到高度相关。你该怎么办?处理高度相关指标的基础技术是将相关指标的得分平均在一起。

6.2.1 为什么你要平均相关指标得分

在客户流失分析和客户细分中单独处理多个相关指标存在两个相互关联的问题:

  • 你在两个不同的群体分析中观察到的客户流失关系在整合意义上是没有的,因为无法理解不同群体在不同指标上的客户是如何相互关联的。如果一个特定客户在一个指标上位于第三个群体,而在另一个相关活动中位于第六个群体,这意味着什么?将它们平均在一起是一种处理方法,这将在下面解释。

  • 信息过载来自于查看过多的指标。记住,行为指标通常不衡量直接导致客户流失或保留的东西。更常见的是,你的行为指标仅与客户流失相关。给定与客户流失相关的大量指标(但不是因果),无法知道哪些指标和事件最重要。

在将相关指标得分平均后,它们在客户细分中的客户流失分析中通常更容易使用。正如上一章所解释的,将许多客户合并成群体以形成群体,可以通过平均掉影响行为的个体情况来显示指标对客户流失的影响。同样,将一组指标的平均值进一步减少随机变化,使客户流失与一系列行为之间的基本关系更加清晰。

将不同的指标分数平均在一起意味着什么?记住,不同的指标通常意味着完全不同的事物,比如登录和编辑文档或观看视频并点赞。将登录和编辑平均在一起有意义吗?因为可能编辑的次数会比登录的次数多,这会导致不平衡。将内容的观看和点赞平均在一起有意义吗?观看的次数可能会比点赞的次数多,所以这样的平均可能没有意义。如果不同的指标有货币价值或时间这样的单位,问题会更严重。在电信的背景下,总通话时长和超额费用的平均值意味着什么?实际上,这根本不是问题;这是将指标转换为分数的另一个优势。

总结:因为每个指标分数衡量的是客户相对于平均水平的地位,所以将不同类型指标的分数平均在一起是可以的。

当使用原始单位时,如果这些指标指的是不同的事物,将不同类型的指标平均在一起是没有意义的。但使用分数时则没有问题:平均分数描述了这些不同指标所关联的整体活动区域。如果某人在使用 SaaS 产品的登录和编辑方面都高于平均水平,那么称他们为整体上高于平均水平的用户是公平的。如果某人在流媒体视频产品的观看和点赞方面低于平均水平,那么将他们视为整体上低于平均水平的用户是有意义的。如果某人在电信产品中的通话次数低于平均水平,而在超额费用方面高于平均水平,如果你平均这些分数,他们只是一个普通用户。

事实上,一组指标的平均分数通常比单独的分数更有用。这是因为不同的指标提供了不同的方式来观察同一活动区域,它们可以相互替代。如果一个客户没有使用某个特定的产品功能,而是使用了一个相关的功能,平均分数会捕捉到这两种情况。如果你只依赖单一指标,你可能会错过某些客户的某些活动。如果一个客户在一个指标上得分高而在另一个指标上得分低,在一个客户流失群体中他们可能属于低风险群体,而在另一个群体中则属于高风险群体。通过将这两个指标的平均值结合起来,你可以得到一个更全面的总体活动图景。

6.2.2 使用权重矩阵(加载矩阵)平均分数

将相关度量指标分组求平均值是一个简单的概念,但实现起来有点棘手,因为你可能要对许多指标和观测值进行此类操作。你将使用一种技术,在权重矩阵中编码组,以跟踪哪些指标属于哪些组以及形成平均值所需的权重。回想一下,矩阵只是一个所有条目都是数字的表格。在这个上下文中,权重意味着乘法因子,1/n,需要将总和转换为平均值。这个权重矩阵被称为加载矩阵。

定义:加载矩阵是一个表格,用于应用权重到度量指标上,以便形成平均值。

加载矩阵不仅跟踪每个组中的度量指标,还提供了平均计算的高效实现(下一节将详细介绍)。我将通过一个只有少量度量指标的示例来引导你。对于像示例这样的玩具问题,这项技术可能看起来过于复杂,但它可以很好地扩展到数十个甚至数百个度量指标和大型数据集。

图片

图 6.9 使用权重矩阵将相关度量指标分组求平均的过程

图 6.9 演示了对于具有 10 个观测值和 6 个度量指标的小数据集的平均值技术,继续图 6.5 的示例。以下是它是如何工作的:

  1. 登录、阅读和回复事件的度量指标被合并到一个组中,因为它们高度相关。发送和写入的度量指标被合并到另一个组中,而点赞的度量指标则保持不变。这些决策是由检查小的相关矩阵驱动的。(在本章的后面部分,你将学习如何自动在具有大量度量指标的数据集中发现组。)

  2. 加载矩阵的定义形状是度量指标的数量乘以组的数量(例如,三乘以五)。在示例中,权重矩阵显示组按行排列,度量指标按列排列;在实践中,通常以另一种方式存储(度量指标按行排列,组按列排列),但下一节将详细介绍。

  3. 每个组的行都包含用于从适当的度量指标形成平均值的权重,以及其他组的零值。形成平均值的权重是组中度量指标数量的倒数:

    • 对于对应于第 1 组的行,登录、阅读和回复三个列中的权重均为 1/3(0.33),其他列中的权重为零。为了形成第 1 组,将 0.33 的权重应用于登录、阅读和回复事件的得分。对于其他度量指标,其位置显示为零,表示这些指标在第 1 组中未使用。

    • 对于对应于第 2 组的行,写入和发送两个列中的权重为 1/2(0.5),其他列中的权重为零。

    • 对于对应于第 3 组(点赞)的行,权重列中有一个 1。

  4. 为了计算组平均值,每个账户的指标乘以每个组的权重,然后将结果相加。

  5. 结果之和是每个组的平均值。

6.2.3 加载矩阵案例研究

图 6.10 显示了为模拟社交网络数据创建的加载矩阵。创建了两个组:

  • 广告查看、点赞和帖子的指标组:最常见的三个指标

  • 消息和回复的指标组

如果你查看图 6.8 中的相关矩阵,你可以很容易地让自己相信这些组中的指标彼此之间以及与其他指标的相关性很高,而与其他指标的相关性较低。(自动发现组的正式方法将在本章后面介绍。)

在这一点上,我必须提醒你注意图 6.10 的一个你预料不到的特征:实际加载矩阵中的权重略高于 1/N,其中 N 是该组中的指标数量。

图片

图 6.10 模拟案例研究的加载矩阵

加载矩阵仍然从相关分数中形成平均值,但权重略高于 1/N。我之前没有提到这个细节,因为其含义相同,并且当用 1/N权重解释时,概念更清晰。对于三个指标组,权重是 0.41 而不是 0.33;对于两个指标组,权重是 0.62 而不是 0.5。推理的细节在 6.3.3 节中解释(这与调整组合分数的标准差有关)。

图 6.11 显示了 Klipfolio 案例研究中创建的实际加载矩阵。现在矩阵以指标为行,组为列显示;这是图 6.9 中视图的转置。(图 6.9 展示了转置的加载矩阵,以便权重在视觉上与数据列对齐,用于说明目的。)将指标排列在行中,将组排列在列中的原因在图 6.11 中很清楚:通常指标比组多得多,因此这样阅读更容易。将指标沿行排列也是平均计算的正确方向,下一节将展示这一点。

图片

图 6.11 Klipfolio 案例研究的加载矩阵

在图中,你可以看到平均权重也不完全是 1/N:第一个指标组包含 28 个指标,因此每个指标分配的权重为 0.041,但 1/28 = 0.0357。还有五个其他组,每个组包含不到 10 个指标,它们在矩阵中的加权条目略高于 1/N。还有一些几十个其他指标,它们的相关性不足以被分组;这些在图 6.11 中只部分显示。

6.2.4 在 Python 中应用加载矩阵

列表 6.3 展示了将加载矩阵应用于数据集以计算平均分数的代码。列表的大部分内容是常规的数据集读取和结果保存,这次还读取了一个加载矩阵(由列表 6.4 创建)。列表的核心是以下这一行:

grouped_ndarray = np.matmul(ndarray_2group, load_mat_ndarray)

那一行执行了数据与加载矩阵的矩阵乘法,这完成了上一节中描述的平均计算。

定义 矩阵乘法是对两个矩阵进行操作以创建结果矩阵的过程。结果矩阵的第一行中的每个元素是通过将第一个矩阵的第一行与第二个矩阵的每一列相乘,然后对每一列的结果进行求和得到的;结果矩阵的第二行是通过将第一个矩阵的第二行与第二个矩阵的所有列依次相乘并求和得到的,依此类推。

注意,为了使矩阵乘法生效,第一个矩阵的列数必须等于第二个矩阵的行数;当加载矩阵的行包含指标时,这个条件得到满足。

列表 6.3 在 Python 中将加载矩阵应用于数据集

import pandas as pd
import numpy as np
import os

def apply_metric_groups(data_set_path):
   score_save_path=
      data_set_path.replace('.csv','_scores.csv')                 ①
   assert os.path.isfile(score_save_path),
      'Run listing 5.3 to save metric scores first'
   score_data = 
     pd.read_csv(score_save_path,index_col=[0,1])                 ②

   data_2group = score_data.drop('is_churn',axis=1)               ③

   load_mat_path = data_set_path.replace('.csv', '_load_mat.csv')
   assert os.path.isfile(load_mat_path),
      'Run listing 6.4 to save a loading matrix first'
   load_mat_df = pd.read_csv(load_mat_path, index_col=0)          ④

   load_mat_ndarray = load_mat_df.to_numpy()                      ⑤

   ndarray_2group = 
      data_2group[load_mat_df.index.values].to_numpy()            ⑥
   grouped_ndarray = 
      np.matmul(ndarray_2group, load_mat_ndarray)                 ⑦
   churn_data_grouped
      = pd.DataFrame(grouped_ndarray,
                     columns=load_mat_df.columns.values, 
                     index=score_data.index)                      ⑧

   churn_data_grouped['is_churn'] = 
      score_data['is_churn']                                      ⑨
   save_path = data_set_path.replace('.csv', '_groupscore.csv')
   churn_data_grouped.to_csv(save_path,header=True)               ⑩
   print('Saved grouped data to ' + save_path)

① 列表 5.3 保存了这些分数数据。

② 将文件重新加载到 DataFrame 中并设置索引

③ 现在移除了流失指示器;这返回了一个副本。

④ 从文件中读取加载矩阵

⑤ 将加载矩阵转换为 NumPy 数组

⑥ 将数据列重新排列为加载矩阵行的顺序

⑦ 使用 ndarray 上的矩阵乘法进行分组

⑧ 从 ndarray 结果创建 DataFrame

⑨ 添加回流失状态列

⑩ 保存结果

图 6.12 矩阵乘法是通过加载矩阵实现分数平均的操作。

图 6.12 阐述了矩阵乘法定义的平均分数。这个定义可能听起来很复杂,但它正是你在上一节中学到的:

  • 账户 1 的第一个平均分数是通过将数据中的账户 1 行与第一列的第一组加载权重相乘并求和得到的。

  • 账户 1 的第二个平均分数是通过将数据中的账户 1 行与第二列的第二组加载权重相乘得到的,依此类推。

矩阵乘法是将加载权重应用于计算大型数据集中任何数量指标和组的平均值的简洁且高效的方法。

到目前为止,你可能想要在数据上运行列表 6.3 并查看结果,但你可能想知道加载矩阵是从哪里得到的。你确实应该这样想,因为我首先在教你如何使用加载矩阵,以便你理解其目的。下一节将向你展示如何从头创建一个加载矩阵。请耐心等待:你将看到一些案例研究,以进一步证明使用加载矩阵分组指标的有用性,然后在第 6.3 节中,你将学习如何运行代码来创建一个加载矩阵。然后你可以回来,使用你创建的加载矩阵运行列表 6.3。

6.2.5 指标组平均分数的客户流失群体分析

一旦将相关指标分组为相关行为的平均分数,就可以对平均组进行客户流失分析。为此不需要编写新的代码。步骤如下:

  1. 使用列表 6.3 并将分组分数保存到新的数据集文件中。它将具有与原始数据集相同的名称,但现在以group_scores结尾。

  2. 使用列表 5.1 通过替换新文件名和变量名metric_group_1(对于第一组)以及依此类推(详情请见列表 6.3)从分组数据集中创建一个客户群体图。

图 6.13 展示了 Klipfolio 主要指标组查看和编辑仪表板的结果。这是在图 6.6 的相关矩阵和图 6.11 的加载矩阵中首次展示的组。在第五章中介绍了客户流失群体分析,因此我将简要总结其主要特点。每个点代表一个由分数的十分之一定义的客户群体。垂直轴显示群体中的客户流失率,以相对比例表示,图表底部固定为零。如果一个群体距离图表底部的距离是另一个群体的两倍,那么它的流失率也是另一个群体的两倍。

图片

图 6.13 Klipfolio 主要指标组流失率客户群体分析

图 6.13 显示,Klipfolio 主要指标组的平均分数与客户流失之间存在强大的关联:平均分数最高的群体流失率低于较低群体流失率的十分之一。这种关系的另一个优点是流失率持续下降,直到最高群体。这个分数组的平均数与流失率的关系比上一章图 5.6 中展示的个别行为与流失率的关系更强。

图 6.14 展示了针对 Broadly 主要指标组分数的平均值的流失客户分析示例。所有相关指标组都与向系统中添加客户和交易、请求客户进行评论和推荐以及这些请求的结果相关。基于此分数组的客户分析是另一个与流失有强烈关系的例子。在这种情况下,顶级客户组的流失率大约是底层客户组流失率的七分之一。

图片

图 6.14 广泛的指标组分数的流失客户分析

图 6.13 和 6.14 都表明,在实际案例研究中,指标分数组的平均值往往比单个指标更能有效地显示与流失的关系。你自己的结果可能不会显示如此强烈的结果,但仍然最好是分析相关度量的组。这是因为它避免了过多指标的信息过载。

要点 对于相关度量,最好使用平均分数而不是单个指标来分析流失客户。

6.3 发现相关度量的组

你现在知道如何计算指标组的平均值,但还有最后一件事:我没有解释如何在大型数据集中找到这些指标组。对于只有几个指标的简单情况,你可能可以通过查看相关矩阵来识别指标组。这就是图 6.5 和 6.9 例子中使用的小数据集的情况。但是,如果你有一个包含数十个指标(或更多)的相关矩阵,如案例研究(图 6.7)中的那样,那就不会那么简单了。幸运的是,有一个标准算法可以为你完成这项工作。

6.3.1 按聚类相关性分组度量

你用来寻找相关度量组的方法被称为聚类算法。

定义 聚类算法是基于数据自动将相似项目分组在一起的过程。

技术上,聚类算法的流程与度量项目之间相似性的测量方法是不同的。为了将度量分组在一起,你会使用相关系数;相关系数越高,度量越相似。你将使用的聚类过程称为层次聚类。

定义 层次聚类是一种贪婪的、聚合的聚类算法:

  • 聚合意味着算法通过自下而上的方式组合相似的项目。从仅两个相似元素开始形成组,随着算法的进行,更多元素被添加到形成更大的相似项目组。

  • 贪婪意味着算法通过选择最相似的两个元素来工作,在这两个元素被分组后,每个阶段都会将下一个最相似的项目分组。

  • 在这个上下文中,“分层”指的是贪婪聚合意味着项目之间存在结构或层次。有两个最相似的项目,然后是下一个最相似的项目,依此类推。

图 6.15 展示了分层聚类,继续展示图 6.5、6.9 和 6.12 中所示的小数据集示例。算法从图 6.5 中的相关系数矩阵开始,找到任何两个指标之间的最高相关系数(图 6.15.1)。两个最相关的指标形成一个分组:这是阅读和回复消息指标之间的 0.93 相关系数。

分层聚类算法的第二步(图 6.15.2)是创建一个加载矩阵,将原始数据集转换为一个新的数据集,其中两个最相关的指标被分组,但所有其他指标仍然保持独立。这个加载矩阵比指标少一个列,因为只有一个指标分组。算法的第三步(图 6.15 中没有展示)是使用新的加载矩阵创建数据集的新版本,按照上一节中展示的程序进行。分层聚类算法的第四步(图 6.15.3)是在前两个指标分组后计算数据的新相关系数矩阵。

拥有一个新的相关系数矩阵后,算法开始新的迭代:寻找下一个最高的相关系数。在示例中,下一个最高的相关系数是登录指标与上一步创建的阅读和回复分组指标的 0.77 相关系数(图 6.15.3)。在加载矩阵的新迭代中,将登录指标添加到第一个分组(图 6.15.4),从而得到新的数据集和相关系数矩阵版本(图 6.15.5),依此类推。

图 6.15

图 6.15 通过聚类相关系数发现指标分组

当足够多的指标被分组,以至于没有剩下任何中等或高度相关的指标时,算法停止。算法应该停止尝试分组的精确相关系数水平是一个控制分组水平的参数。通常,你将阈值设置在中等的相关系数水平。我在进行的分析中通常将其设置为 0.5 或 0.6。我将在第 6.3.3 节中提供更多关于如何设置此参数的细节。现在,让我们看看图 6.15 中的示例是如何结束的。登录、阅读和回复在一个分组中,而写作和发送在另一个分组中(图 6.15.6),剩余的相关系数(图 6.15.7)都在-0.28 和 0.27 之间;这些只是弱相关,因此算法停止。算法的结果是产生图 6.9 中首次展示的加载矩阵。(图 6.15.6 是图 6.9 中加载矩阵的转置和稍微重新排序的版本。)

回顾一下,以下是层次聚类算法在每一步的工作方式:

  1. 识别最高的相关性。

  2. 更新加载矩阵,将两个最相关的元素分组在一起。

  3. 使用原始分数数据集的加载矩阵创建一个新的分组数据集。

  4. 计算一个新的相关性矩阵。

  5. 重复步骤 1 至 4,直到所有剩余的相关性都低于一个预定的阈值。

大数据集的层次聚类和相关性计算的效率

你可能会在其他参考资料中读到层次聚类效率低下且不适合大数据。但这里有一个关键的区别:即使你的数据很大,相关性矩阵也不是大数据!影响层次聚类运行时间的大小是数据集中度量指标的数量,而不是客户(观察)的数量。每一步都会减少一个度量指标的数量,所以最大迭代次数是度量指标的数量。对于更大的数据集使用层次聚类没有问题。

如果你确实有很多客户(很多观察),你会发现计算相关性矩阵实际上是成本最高的步骤。如果你的数据真的很大,你应该考虑优化或近似相关性矩阵的计算,而不用担心层次聚类。正如你将在下一节中看到的,你实际上只计算一次相关性矩阵。我对算法的解释将其呈现为在每一步重新计算相关性矩阵,但在实践中,每一步的相关性矩阵可以通过加载矩阵推导出来(这个细节超出了本书的范围)。

6.3.2 Python 中的聚类相关性

现在你已经知道了基于相关性矩阵的层次聚类是如何工作的,你就可以学习实现它的 Python 代码了。列表 6.4 展示了程序。剧透:代码使用了预写的开源包函数来实现聚类。列表 6.4 主要关注准备输入使其为包函数做好准备,并接收包函数的输出,将这些输出转换为所需的加载矩阵。整个过程被分解为三个步骤,这些步骤在列表 6.4 中是独立的函数。我将逐一解释它们。

列表 6.4 中的实际聚类是在函数find_correlation_clusters中进行的。SciPy 在scipy.cluster.hierarchy包中提供了一个层次聚类的实现,包含两个函数:linkagefclusterlinkage函数是真正执行工作的函数。它可以在原始数据集上工作,也可以在数据集中点之间距离的预计算测量上工作,这就是列表 6.4 中发生的情况。但linkage函数的结果实际上不是聚类;相反,linkage函数返回数据点之间距离关系结构的描述,这就是算法名称中提到的距离层次。

我不会解释层次表示的细节,因为还有一个函数可以将结果传递进去以获取你想要的聚类:那就是fclusterfcluster函数接受来自linkage的层次描述和一个截止阈值来形成聚类。在我们的情况下,这个阈值是我们认为高度相关的相关截止值。fcluster的结果是以numpy Series形式为原始项目分配的聚类。

列表 6.4:在 Python 中查找指标分组和创建加载矩阵

import pandas as pd
import numpy as np
import os
from collections import Counter
from scipy.cluster.hierarchy import linkage, fcluster              ①
from scipy.spatial.distance import squareform                      ①

def find_correlation_clusters(corr,corr_thresh):
   dissimilarity = 1.0 − *c*orr                                      ②

   diss_thresh = 1.0 − *c*orr_thresh                                 ③

   hierarchy = linkage(squareform(dissimilarity), 
                       method='single')                            ④

   labels = fcluster(hierarchy, diss_thresh, 
                     criterion='distance')                         ⑤
   return labels

def relabel_clusters(labels,metric_columns):
   cluster_count = Counter(labels)                                 ⑥

   cluster_order = {cluster[0]: idx for idx, cluster in            ⑦
                    enumerate(cluster_count.most_common())}

   relabeled_clusters = [cluster_order[l] 
                           for l in labels]                        ⑧

   relabeled_count = Counter(relabeled_clusters)                   ⑨

   labeled_column_df = pd.DataFrame({'group': relabeled_clusters, 
      'column': metric_columns}).sort_values( 
      ['group', 'column'], ascending=[True, True])                 ⑩
   return labeled_column_df, relabeled_count

def make_load_matrix(labeled_column_df,metric_columns,relabeled_count, corr):
   load_mat = np.zeros((len(metric_columns), 
      len(relabeled_count)))                                       ⑪

   for row in labeled_column_df.iterrows():                        ⑫
      orig_col = metric_columns.index(row[1][1])
       if relabeled_count[row[1][0]]>1:                            ⑬
            load_mat[orig_col, row[1][0]] = 1.0/(np.sqrt(corr) * 
               float(relabeled_count[row[1][0]])  )                ⑭
       else:
            load_mat[orig_col, row[1][0]] = 1.0                    ⑮

    is_group = load_mat.astype(bool).sum(axis=0) > 1               ⑯
    column_names=
       ['metric_group_{}'.format(d + 1) 
          if is_group[d]                                           ⑰
          else 
             labeled_column_df.loc[                                ⑱
                labeled_column_df['group']==d,'column'].item()
                                    for d in range(0, load_mat.shape[1])]
   loadmat_df = pd.DataFrame(load_mat, 
      index=metric_columns, columns=column_names)                  ⑲

   loadmat_df['name'] = loadmat_df.index                           ⑳

   sort_cols = list(loadmat_df.columns.values)                     ㉑

   sort_order = [False] * loadmat_df.shape[1]                      ㉒

   sort_order[-1] = True                                           ㉓

   loadmat_df = loadmat_df.sort_values(sort_cols, 
      ascending=sort_order)                                        ㉔

   loadmat_df = loadmat_df.drop('name', axis=1)                    ㉕
   return loadmat_df

def find_metric_groups(data_set_path,group_corr_thresh=0.5):
   score_save_path=
      data_set_path.replace('.csv','_scores.csv')                  ㉖
   assert os.path.isfile(score_save_path),
      'You must run listing 5.3 to save metric scores first'
   score_data = pd.read_csv(score_save_path,index_col=[0,1])
   score_data.drop('is_churn',axis=1,inplace=True)
   metric_columns = list(score_data.columns.values)                ㉗

   labels =                                                        ㉘
      find_correlation_clusters(score_data.corr(), group_corr_thresh)
   labeled_column_df, relabeled_count = 
      relabel_clusters(labels,metric_columns)
   loadmat_df = make_load_matrix(labeled_column_df, metric_columns, 
      relabeled_count,group_corr_thresh)
   save_path = data_set_path.replace('.csv', '_load_mat.csv')
   print('saving loadings to ' + save_path)
   loadmat_df.to_csv(save_path)

   group_lists=                                                    ㉙
      ['|'.join(labeled_column_df[labeled_column_df['group']==g]['column'])
                    for g in set(labeled_column_df['group'])]
   save_path = data_set_path.replace('.csv', '_groupmets.csv')
   print('saving metric groups to ' + save_path)
   pd.DataFrame(group_lists,                                       ㉚
               index=loadmat_df.columns.values,
                columns=['metrics']).to_csv(save_path)

① 导入执行层次聚类的 SciPy 函数

② 聚类使用不相似性,因此反转相关矩阵

③ 阈值参数也被反转。

④ 计算指标之间相对距离的顺序

⑤ 根据层次和阈值确定分组

⑥ 计算每个聚类中元素的数量

⑦ 找到聚类成员数的顺序

⑧ 按顺序创建聚类标签的新序列

⑨ 从重新标记的聚类中创建新的计数

⑩ 创建一个 DataFrame,列出每个指标的分组

⑪ 创建一个空的(零)矩阵来存储平均权重

⑫ 在加载矩阵中输入每个指标的权重

⑬ 选择加载矩阵中是分组的那些列

⑭ 使用方程 6.3(第 6.3.3 节)获取权重

⑮ 对于未分组的指标,权重简单地是 1.0。

⑯ 创建一个布尔序列,显示哪些列是分组

⑰ 为分组创建名称metric_group_n

⑱ 否则,将原始指标名称输入列表中。

⑲ 从加权矩阵创建 DataFrame

⑳ 从 DataFrame 索引列创建名称列

㉑ 创建一个列的列表,该列表按行排序

㉒ 按降序排序大部分列

㉓ 按升序排序名称列

㉔ 按顺序排序加载矩阵以进行可解释性

㉕ 删除名称列,因为它用于排序

㉖ 重新加载列表 5.3 创建的分数

㉗ 创建原始指标列的列表

㉘ 计算分组分配

㉙ 创建一个列出每个组中指标的列

㉚ 保存加载矩阵

将数据输入到聚类算法中并不那么困难。最重要的细节是linkage函数是编写来处理基于差异性的数据的,但到目前为止,我们考虑的是相关性,这是一种相似性的度量。解决方案如下:你从 1.0 减去相关性,那么原本是相似性度量的指标现在变成了差异性度量。这意味着什么?考虑:最高的相关性(最大的相似性)是 1.0,减去 1.0 后变成了 0.0。现在这是两个项目之间最不相似的情况。从相关性角度来说最不相似的是-1.0,但减去 1.0 后变成了 2.0(1 - -1 = 1 + 1 = 2);现在这是最不相似的情况。在用于 SciPy 函数linkagefcluster之前,相关矩阵和相关阈值都通过逐元素减去 1.0 进行转换。这就是运行聚类算法所需的所有准备工作。

很遗憾,聚类算法的结果并不完全符合你的期望。你想要的是一个加载矩阵,并且按照特定的顺序排列。当最大的组别排在最前面,并且按照大小顺序降序排列时,这最容易理解。fcluster函数返回了分配给各个组的聚类,但它们在大小上并没有任何特定的顺序。在调用linkagefcluster之后,后处理有两个主要部分:首先是排序和重新标记聚类,然后是创建加载矩阵。

列表 6.4 中的第二个函数relabel_clusters是后处理的第一步。为了排序和重新标记聚类,使用 Python 的set来找到唯一的聚类,并使用 Python 的Counter来统计fcluster结果中每个标签的出现次数。Counter对象还有一个实用函数,可以按最常见到最不常见的顺序遍历元素:这就是函数Counter.most_common。在找到重新标记的聚类名称后,结果被保存在一个新的标签Series中。创建了两个对象来表示聚类以供后续使用:一个两列的DataFrame,列出了原始指标及其所在的组,以及一个新创建的Counter对象,用于统计新的标签。

列表 6.4 中的第三个函数make_load_matrix是最后一步。加载矩阵初始化为正确大小的零ndarray:行数是指标的数目,列数是组的数目。relabel_clusters函数创建了一个DataFrame,列出了每个指标及其组。这被用来遍历指标,并在加载矩阵中相应的组下填充适当的条目。这个ndarray被转换成一个DataFrame,使用指标名称作为索引。

加载矩阵中每个条目的权重是通过将 1.0 除以组内元素的数量来计算的:代码中是relabeled_count[row[1][0]]中的组内元素数量。relabeled_count是一个计数器对象,row[1][0]选择适当的元素。但在权重计算的分子中还有一个额外的项,即用于聚类的相关阈值平方根:np.sqrt(corr)。正如我在 6.2.3 节首次向您展示加载矩阵时提到的,这个额外项使得权重略高于 1/N。我将在解释完算法后,在下节解释这个选择的原因。

函数make_load_matrix的其余部分按照使读取最简单的顺序对加载矩阵进行排序:最大的组首先,然后是第二大的,依此类推。在每个组内,指标按名称的字母顺序排序。这是通过使用 Pandas 的DataFrame.sort_values和适当的参数来实现的。sort_values函数接受一个要排序的列的列表和一个布尔值列表,表示每一列是升序还是降序。指标的名称被添加为一个列(之前是索引),并且所有列都被用于排序。组权重的列首先,并按降序排序,而名称的列最后,并按升序排序。因为表示组成员的列是从大到小排序的,这实现了加载矩阵的期望排序:从大到小分组,并在每个组内按字母顺序排序。此外,列被标记为一个标签(metric_goup_x,其中x是组的编号,或者当组只是一个单一指标时,就是指标名称)。

执行所有步骤的主要函数位于列表 6.4 的末尾:find_metric_groups。此函数加载一个数据集,然后调用算法中的其他步骤。find_metric_groups返回加载矩阵作为结果,默认选项是将它保存到.csv 文件中。请注意,程序只输出一个简单的确认信息,表明它正在运行以及结果保存的位置。

图片

图 6.16 运行列表 6.4 在默认模拟数据集上的结果(图 6.10 的再现)

如果你使用模拟数据,那么打开文件在电子表格或文本编辑器中时,得到的加载矩阵应该看起来像图 6.16 中的那样。存在两组度量指标:一组是相互关联的最常见行为(包括发帖、查看广告和点赞),另一组是阅读和回复消息的小组。账户时长、不喜欢和取消好友关系的度量指标相关性不足,因此它们没有进入任何一组。请注意,权重不是标准的平均值的 1/N,而是使用方程 6.3(第 6.3.2 节)进行修改,以使平均值本身作为分数。

图 6.17 模拟数据的有序相关矩阵

现在你有了加载矩阵,你可以生成一个有序相关矩阵,如图 6.6 所示。对于模拟数据集,这个结果在图 6.17 中显示。从有序相关热图中,你可以看到两组之间的高相关性以及其他度量指标之间的低相关性。图 6.17 是通过运行列表 6.5 并在电子表格中对结果数据进行格式化创建的。

列表 6.5 显示,创建有序矩阵的代码几乎与创建常规相关矩阵的代码完全相同。唯一的区别是,在计算相关矩阵之前,你读取加载矩阵并按加载矩阵中度量指标的顺序重新排序数据集列。重新排序是一行代码,因为你已经费心将加载矩阵正确地按组排序:只需重用那个顺序即可。

列表 6.5 创建有序相关矩阵

import pandas as pd
import os

def ordered_correlation_matrix(data_set_path):

   churn_data = pd.read_csv(                                           ①
      data_set_path.replace('.csv','_scores.csv'),index_col=[0,1])

   load_mat_df = pd.read_csv(                                          
      data_set_path.replace(‘.csv’, ‘_load_mat.csv’), index_col=0)
   churn_data=churn_data[load_mat_df.index.values]                     ②

   corr = churn_data.corr()                                            ③

   save_name =                                                         ④
      data_set_path.replace('.csv', '_ordered_correlation_matrix.csv')
   corr.to_csv(save_name)
   print('Saved correlation matrix to ' + save_name)

① 加载保存的分数

② 将数据集列重新排序为加载矩阵行的顺序

③ 计算相关矩阵

④ 保存结果

6.3.3 加载使分数平均值为分数的矩阵权重

关于加载矩阵的一个技术细节我还没有解释,这与加载矩阵中应该使用的确切权重有关。上一节提到的要点是,你不需要在加载矩阵中使用精确的 1/N 权重。我第一次教你加载矩阵的概念时说 1/N,是为了让你更容易地理解这个概念,而这个概念并没有改变:加载矩阵的转换仍然代表对度量指标分数取平均值。但权重需要稍作调整。

1/N 是当平均中的所有数字具有相同的尺度或单位时,制作等权重的平均值的正确权重。但与分数不同,因为分数没有自然单位。(注意,如果你不喜欢方程,这将是跳到下一节的好时机,在你阅读了要点之后。)

要点:加载矩阵中的权重将略高于 1/N,但意义仍然是相同的。

使用 1/N权重来平均分数的问题在于,这样得到的指标分数的平均值就不再是分数了。这意味着什么?分数被定义为指标的缩放版本,并且具有一些特定的属性:平均(均值)分数是 0,分数的标准差是 1。这些事实使得分数具有可比性。

好消息是,如果你用任何等权重的平均分,这些分数的平均值(平均数)仍然会是零。但坏消息是,这些分数平均的标准差不会是 1,而是小于 1.0。具体小多少取决于你平均了多少个指标以及它们的相关性如何。但我会教你们如何修改加载矩阵中的权重,使得平均分数具有(几乎)应有的 1.0 标准差。这种调整使得平均数仍然是一个分数,无论你平均了多少个指标。

首先,我需要提醒你们什么是方差:方差是标准差的平方。当标准差是 1 时,方差也是 1(因为 1 的平方是 1)。在接下来的内容中,我用σ表示标准差,用σ²表示方差;这是希腊字母 sigma,在数学书中,它是表示标准差和方差的常用字母。关于标准差的一个特点是,当你对指标或其他变量求和时,每个指标都有自己的标准差,求和的标准差不会保持不变;它们会根据权重进行缩放求和。这个关系用方差来理解更容易,这就是为什么我要提醒你们什么是方差。我会展示如何得到具有各自方差的指标求和的方差。假设你通过乘以每个指标的权重来形成一个平均数,加权指标和的方差由方程 6.1 给出:

σ²(wx[1] + wx[2] + ... + wx[N]) = Σ[ij] w²σ[i] σ[j] c[ij] 方程 6.1

在方程 6.1 中,符号Σ[ij] ... 是所有不同元素索引的求和的简写(在代码中,这就像在双层循环中添加求和一样)。方程 6.1 所说的就是,指标求和的方差是所有标准差成对乘积的求和,乘以成对的相关系数。这有点复杂,但希望这能让你明白为什么指标求和的标准差只有在某些条件下才会是 1。现在,我将展示这些条件是什么。然而,首先,我将做一些简化:

  • 在我们的情况下,所有标准差都是 1,因为它们都是分数,所以项σ[i/j]消失。

  • 你不知道所有指标之间的相关性 c[ij] 究竟是什么,但你确实知道这一点:如果你将它们分组在一起取平均值,那么它们高度相关。它们可能具有至少与你的相关性阈值一样高的个体相关性。因此,而不是使用 c[ij] ,我用 c[thresh] 来近似它,这是形成聚类的阈值。

通过这些简化,方差方程 6.1 大约在方程 6.2 中给出:

σ²(wx[1] + wx[2] + ... + wx[N]) ≈ N² w² 方程 6.2

在成对相关性的和中存在 N² 项;这就是方程 6.2 中的 N² 的来源。方程 6.2 是一个近似值,因为相关性实际上并不是 c[thresh](最明显的是每个指标的自我相关性都是 1),但它足够接近。下一步是解方程以找到使方差(以及标准差)等于 1 的权重 w。结果在方程 6.3 中给出:

图片 6-17_E03.png 方程 6.3

在所有方程之后,有一个相当直接的变化:在加载矩阵中使平均值时,不再使用 1/N 作为权重,而是将 1/N 乘以一个额外的因子,这个因子是 1/ c[thresh] 的平方根,这是聚类算法中使用的相关性阈值。因为相关性阈值小于 1(通常在 0.5 或 0.6 左右),所以它的倒数大于 1(通常在 1 和 2 之间),平方根不会改变这一点。因此,你用来平均分数的权重将比标准平均中的 1/N 略大。这是一个技术细节,但通过这种调整保持你的平均分数作为分数,会使你的分析更容易解释,因为你的指标分数的标准差仍然为 1。

6.3.4 运行指标分组和分组队列分析列表

现在你有了加载矩阵(通过运行列表 6.4 生成),你可以返回并运行列表 6.3。列表 6.3 将加载矩阵应用于分数数据集以创建分组平均分数。请注意,运行列表 6.3 只会产生一行输出,显示它在运行,而实际结果将是一个新的 .csv 数据集(结果打印出保存的位置)。图 6.18 显示了运行列表 6.3 保存的数据集的小样本。列标题显示的是组号,而不是指标名称。

图片 6-18.png

图 6.18 运行列表 6.3 的默认模拟数据集的结果

当你有一个分组指标的数据集时,也是尝试使用分组指标进行队列分析(列表 5.1)的时候了。这是在第 6.2.4 节中演示的技术。为此,你可以通过将—chapter 5 —listing 1 —version 3参数传递给 Python 包装程序来运行列表 5.1 的另一个版本。图 6.19 显示了在模拟数据中的主要相关指标组上运行队列分析的结果。分组指标显示出与客户流失的强烈关系。(因为数据集是随机模拟的,你的结果可能不会完全相同。)

图 6.19

图 6.19 运行列表 5.1 在默认模拟数据集生成的第一组指标上的结果

6.3.5 选择聚类相关性阈值

在解释聚类算法时,我提到了聚类相关性的阈值;回想一下,这个阈值决定了何时将指标分组在一起或分开。我没有详细解释如何设置这个参数,因为我想让你在学习技术细节之前先了解分组是如何工作的。但聚类阈值参数对于行为分组成功至关重要。如果你将此参数设置得太低,那么你可能会得到一个包含所有指标的大组,即使它们之间并不都相关。而且,如果你将相关性阈值参数设置得太高,那么强相关的指标仍然不会分组,你最终会得到(几乎)与最初指标数量一样多的组数。

不幸的是,没有一种适用于所有情况的最佳值,所以你可能需要做一些实验。我也不建议任何分组度量来评估你的选择。相反,我建议你了解业务(或者从了解业务的人那里了解)以及相关性矩阵告诉你关于业务的信息。然后问问自己:分组在一起的指标是否合理?如果将更多指标分组在一起或分开,分组是否更有意义和/或更有用?

例如,假设你知道一些指标通常与一个产品特性或内容区域相关联。在这种情况下,合理地调整参数(如果需要的话)将它们分成自己的组或防止它们被分开是有道理的。(这是一个使用你的先验知识来指导分析的一个例子。)另一方面,如果相关性矩阵告诉你某些活动高度相关,但你的某些商业同事希望你将它们分开以形成更多的组,这可能只是他们的一厢情愿的想法或办公室政治在影响决策。使用你的先验知识来帮助决定关键时刻,但不要忽视你分析的结果!

在设置我自己的分析中的相关性水平时,以下是一些我常用的经验法则:

  • 你通常应该将相关阈值参数设置在中等或较高相关性的水平,范围在 0.4 到 0.7 之间——永远不要设置在非常高的或很低的相关性,也就是说,不要超过 0.8 或低于 0.3。

  • 通常最好从较低的水平开始,阈值值为 0.5 或更低,并将所有(或大多数)度量指标分组到一个大组中:

    如果每个度量指标都与其他至少几个度量指标有高相关性(> 0.7),那么你可能应该将它们全部分组到一个组中。这可能适用于没有广泛特征或内容的较小产品,或者如果你跟踪的事件变化不大。

  • 在 0.5 到 0.7 的范围内使用简单的二分搜索:如果 0.5 看起来太低,尝试 0.6(0.5 和 0.7 之间的一半)。如果 0.6 仍然看起来太低,尝试 0.65(0.6 和 0.7 之间的一半);如果 0.6 太高,尝试 0.55,以此类推。你很快就会耗尽可能的范围,并感觉到最佳值所在的位置。

    使用手动搜索,而不是算法——我从未找到一种始终有效的停止标准。无论如何,搜索通常不会花费很长时间。

  • 使用彩色编码的相关热图和美学标准(真诚地):对角线上的正方形图案看起来井然有序(见图 6.7),但如果你在任何方向上走得太远(相关性过多或过少),就会破坏对称性。一旦你做了几次,这就会变得相当直观。

  • 最大的挑战是,有时相关性的微小变化会对分组产生不成比例的影响。这意味着阈值在 0.01 或 0.02 的小幅度变化偶尔会对组数产生重大影响,可能从只有 1 或 2 组变为 5 或 10 组。

    在我的研究中,我编写了一个分组算法的替代版本,如列表 6.4 所示,其中参数是生成的块数。它使用算法搜索来返回具有所需组数的分组。这是一个很好的编程练习,我留给你们去尝试;如果你因为对微小变化的反应不规则而难以找到相关阈值,这将很有帮助。但只有在你已经足够实验,对选择什么有很好的想法时,你才能使用这种方法(选择块数)。

在第八章中,当我们查看统计数据时,你会了解更多关于这个主题的内容。当你使用统计分析时,如果你使用过高的相关性阈值错误地分组行为,可能会出现一些真正的问题。但,到目前为止,你已经知道足够的信息来处理你自己的数据。

6.4 向商业人士解释相关度量组

本章展示了以下内容:

  • 理解你度量指标之间相关性的重要性

  • 如何发现相关度量指标组

  • 如何对度量组平均分数进行客户流失分析

本章相当技术性,你可能学到了一些新术语:相关性矩阵、加载矩阵和聚类(更不用说真正的怪物——层次聚类了!)。现在让我们深呼吸,思考如何向你的业务同事解释所有这些。如果你只是出于教育目的阅读这本书,这不是问题,但如果你试图在商业环境中应用这些技术,这是一个非常大的问题。

本章中的概念并不难理解,但有很多技术细节和术语。我建议从简单开始,并确保用你自己的公司实际数据解释每个概念。你可以省略完成所有事情的具体细节,只传达最终结果。

吸收要点:你的任务是尽可能让业务同事远离术语。所以不要试图用术语让他们印象深刻!相反,尝试将事情简化为共同语言。

这是我通常在第一次向商业听众展示案例研究结果时的处理方式:

  1. 在开始之前,询问业务人士他们了解多少统计学知识。你应该根据他们的知识水平调整你的解释。以下内容,我将假设一个平均的业务用户群体,他们没有接受过任何统计学培训,但也不害怕统计学。

  2. 通过展示你从业务数据中创建的散点图(如图 6.1 所示)来教(或提醒)每个人什么是相关性。使用“相关性”这个词与业务人士交流是可以的(也是必要的),但你可能应该放弃“系数”这个词。即使是非数学人士,当他们知道哪些产品行为是一起发生的时,也容易理解相关性。向他们展示散点图和相关性数字,是在给他们提供一种全新的方式来看待他们已经知道的东西。

  3. 展示热图,组织(在你形成组之后)并且格式上与图 6.5 中的类似(除了使用全色)。我尽量避免在商人面前使用“矩阵”这个词,所以我通常只是将其描述为相关性热图,而不是相关性矩阵。在他们了解了单个相关性之后,他们通常也能理解热图所展示的整体模式。同样,这是展示他们已经直觉上知道的东西,所以他们喜欢(而且热图看起来很酷!)。

  4. 向他们展示形成这些组的指标,既可以通过在热图中概述它们(见图 6.5),也可以通过给他们列出每个组中包含哪些指标。你需要解释这些组是自动形成的,并且基于数据(他们必须理解你并没有选择这些组)。不要试图解释算法的细节。但如果组里有一些相对技术性的人,你可以提到它是一个聚类算法:

    他们可能会对此进行辩论,这是有益的。如果他们以合理的方式挑战分组,你可能想尝试调整第 6.3.4 节中描述的阈值。但确保他们意识到你不能(或者至少不应该)手动选择组。

  5. 向他们展示对分组指标进行的群体行为分析,以及它与对单个指标(如果有很多的话,则仅选择单个指标)的群体分析如何比较。

就这样!你已经完成了。特别是,你不需要与商业人士讨论以下术语或算法:

  • 矩阵(无论是相关矩阵还是加载矩阵)

  • 加载

  • 矩阵乘法

  • 聚类(甚至更糟,层次聚类)

层次聚类与主成分分析比较

如果你学习过统计学或数据科学,你很可能学过一种称为主成分分析(PCA)的技术。PCA 与层次聚类(HC)相似,因为它通过乘以加载矩阵来减少数据集中指标的数量。但 PCA 的加载矩阵是通过与 HC 不同的技术推导出来的。PCA 具有统计学家喜欢的某些良好属性,但由于它对客户流失不太有用,并且它产生的加载对于大多数人来说太难解释,所以它超出了本书的范围。然而,HC 和 PCA 产生的加载有很多共同之处,如图所示。

图片

层次聚类(HC)和主成分分析(PCA)的加载矩阵比较

此图是通过计算 PCA 加载矩阵并按与 HC 组相同的顺序排列指标创建的。当在相同的数据上运行时,HC 发现的块通常与 PCA 加载矩阵中高权重的聚类相似。你可以看到,这两个算法正在捕捉数据的一些相同的基本属性。但是,PCA 加载矩阵既有正权数也有负权数,加载矩阵中的每个条目都不为零。如何解释 PCA 加载矩阵的细节超出了本书的范围。

(继续)

一个需要注意的重要点是,由于 PCA 矩阵既有负权重也有正权重,因此得到的分组度量不仅仅是平均值,还包括(评分)度量之间的差异。两个订阅度量之间的差异意味着一个派生度量,当其中一个度量值高而另一个低时,该度量值高,而平均或总度量值在两个度量值都高时才高。评分订阅度量之间的差异不太直观,但可能很重要,因为它们衡量一个行为超过另一个行为多少。例如,如果你对一个电信服务中的本地通话和国际通话进行了评分度量,差异将显示订阅者是更多还是更少地进行国际或本地通话。这样的差异对于理解参与度可能很重要,但由具有负条目的载荷矩阵产生的评分度量之间的差异很难解释。下一章将教授捕捉行为之间差异信息的技术,以便商业人员和数据人员都能容易理解。

摘要

  • 正相关是指当一个度量值高时,总是与另一个度量值高相关联,或者当一个度量值增加时,总是与另一个度量值增加相关联。

  • 负相关是指当一个度量值增加时,与另一个度量值减少相关联。在事件客户行为度量中,负相关很少见。

  • 相关系数是介于-1 和 1 之间的相关性的统计度量,其中 1 表示完美的正相关,而-1 表示完美的负相关。

  • 相关系数衡量两个度量值之间关系的一致性,但对关系所暗示的比率不敏感。等价地,度量的单位或刻度对相关性无关紧要。

  • 成对度量散点图是可视化数据中单个相关性的好方法,但可能存在太多对需要查看。

  • 相关系数矩阵是一个包含数据集中所有成对相关系数的表格,并且是探索大量相关性的有效方式。

  • 当度量值高度相关时,你可以通过平均相关度量的分数来提高客户流失分析。

  • 载荷矩阵是一个用于平均度量的权重的表格。它在平均分数的计算中使用。

  • 载荷矩阵与数据集的矩阵乘法是执行分组度量平均的有效操作。

  • 在使用载荷矩阵平均度量分数后,你可以使用平均分数进行客户流失的行为群体分析。这通常比单个度量给出更强的结果。

  • 聚类是指根据数据中相关项之间的相似度度量将它们分组在一起。

  • 层次聚类是一种可以将相关度量分组在一起的算法。该算法在相关性的阈值处停止,以便所有高度相关的度量都被分组在一起。

  • 运行层次聚类后,您使用结果创建一个负载矩阵。

7 使用高级度量标准细分客户

本章涵盖

  • 由其他度量标准的比率构成的度量标准

  • 将行为作为总量的百分比来衡量的度量标准

  • 显示行为随时间变化的度量标准

  • 从长周期到短周期的公制转换,反之亦然

  • 多用户账户的度量标准

  • 选择要使用的比率

你已经通过事件和订阅衍生出的度量标准了解了大量关于理解流失的知识。你已经看到,简单的行为测量对于细分可能面临流失风险且参与度水平不同的客户非常有用。但你同时也看到了简单行为度量标准的一些局限性。

许多简单的度量标准是相关的,相关性产生的原因是拥有大量产品相关事件的客户往往也有许多其他事件。相关性使得难以判断哪些类型的行为最重要。这个问题比缺乏细化更深。在本章中,你将了解到度量标准之间的相关性可能会让你误解行为的影响。一个具有负面性质(在从客户那里夺走效用和乐趣的意义上)的行为,当它与提供效用和乐趣的其他行为相关联时,可能会看起来增强了参与度。

此外,你可能对行为之间的关系有所好奇。许多关于流失的常见假设都询问行为组合是否具有大于其部分之和的效果。例如,你可能想知道,对于文档编辑和文件共享应用程序的用户来说,即使他们不分享,创建大量文档是否更好,或者是否更好的做法是用户分享他们所创建的一切,即使他们没有创建很多。另一个尚未解决的问题是在时间上行为的变化是否告诉你任何信息。例如,使用激增是否表明客户正在变得更加投入,或者在他们流失之前进行最后一次狂欢的迹象?如果你认为你在第三章中学到的简单行为度量标准无法回答这些问题,你是正确的。

从本书开头概述的主题来看,本章与图 7.1 中显示的所有领域相关。行为度量标准和流失分析一起呈现,这可以产生关于为减少流失策略对客户进行细分的思想。

在本章中,你将创建一种度量标准,它允许你理解复杂的行为组合,并了解它们对你客户流失和保留情况所传达的信息。我称这种度量标准为比率度量标准。

定义 比率度量标准是指通过将一个度量标准与另一个度量标准的比率计算出的任何客户度量标准;等价地,一个度量标准被另一个度量标准除。

本章的组织结构如下:

  • 7.1 节教你主要的比率度量标准技术,并包括几个案例研究来激发其使用并展示典型结果。

  • 第 7.2 节教您如何制作从部分到整体的比率指标,这使得比率成为总体的百分比。

  • 第 7.3 节涵盖了两个不同时间点的单一指标的比率,这衡量了行为的变化,通常以百分比表示。本节还涵盖了一个衡量客户不活跃时间量的指标。

  • 第 7.4 节转向非比率高级指标:本节涵盖了像流失率一样扩展指标测量时间周期。这允许您快速估计指标,使用较短的测量窗口为新客户进行估计,但仍为经验丰富的用户提供更好的估计。

  • 第 7.5 节教您如何对多用户系统进行测量(当多个个人共享一个产品的一个订阅或账户时)。

图片

图 7.1 第七章的主题包括行为指标、流失分析以及订阅者细分。

7.1 比率指标

您即将学习本书中最重要的单一技术(如果我只选一个的话):使用其他行为测量的比率指标。这些指标提供了强大且易于理解的客户行为解释。

7.1.1 何时使用比率指标及其原因

图 7.2 提供了一个案例研究,说明了何时需要更仔细地观察两个指标之间的关系。该图重现了第五章中 Versature(一家提供集成云通信服务的提供商)的一些群体分析。在图中,支付更高月费的客户流失率相对较低。这个图可能违背了您认为支付更多对客户参与度是坏事的感觉。同时,图 7.2 显示,支付更多与通话次数的增加高度相关——这是您在第六章中学到的分析方法。当然,通话次数多的客户流失率低于通话次数少的客户,这与月度经常性收入(MRR)相比,与流失率的关系更紧密。

图片

图 7.2 比率指标的案例研究

支付更多(更高的 MRR)看起来像是在减少流失,原因是,通常情况下,支付更多的客户也会进行更多的通话——足以证明支付更高 MRR 的合理性。但是,对于支付更多但通话不足以证明更高 MRR 合理性的客户怎么办?这些客户可能面临的最大流失风险,但在单独查看 MRR 和通话的指标中并未显现。为了找到支付更多但通话不多(并查看他们是否以更高的比率流失)的客户,您需要创建一个第三项指标,以捕捉前两个指标之间的关系:前两个指标的比率。

定义:比率指标是通过取两个其他指标值的比率而制作的指标。新指标中的每个值都是一项指标的值除以另一项指标的值。

在接下来的章节中,我将解释如何计算此类度量以及为什么,并试图说服你这是最好的方法。现在,让我们从结果开始。

如果你取一个客户支付的金额(MRR)并将其除以他们打的电话数量,结果是平均通话成本——这是衡量客户支付的单位成本的一种度量,类似于汽油或牛奶的每加仑或每升价格。不同之处在于,MRR 与通话的比率是一个有效的重复单元成本,而不是合同成本,因为产品不是按通话计费和包装给客户的。

图片

图 7.3 Versature 的按通话成本流失群体分析案例研究

图 7.3 显示了为 Versature 客户执行的按通话成本流失群体分析,使用 MRR 每通话作为度量。这个度量显示了随着度量值的增加,流失率与度量值之间有很强的关系。确实,支付更多的客户流失得更多,但你需要用比率度量来衡量这个结果。图 7.3 还显示了 MRR 每通话度量与创建它的 MRR 和通话度量之间的相关性。MRR 每通话与通话的相关性较弱(负相关),与 MRR 实际上没有相关性。所有这些事实使 MRR 每通话成为理解客户参与和流失的一个很好的度量。在第 7.1.2 节中,你将学习如何计算这样的比率度量,并使用你在整本书中使用的流失模拟数据来练习这样做。

要点:从 MRR 与客户实现的一些成果的比率中创建了一个有效的重复单元成本度量。重复单元成本度量通常显示随着单元成本的提高而增加的流失率。相比之下,简单的重复成本度量(MRR)通常显示随着成本的提高而减少的流失率,这是由于与使用产品获得的效用或享受的相关性。

图 7.4 显示了社交网络模拟案例研究中的一个情况,类似于图 7.2 和图 7.3 中的情况。你预计一个度量——每月查看的广告数量——是糟糕的,因为大多数人不喜欢看广告。但是当你运行流失群体分析时,你会发现人们看的广告越多,流失得越少。同时,你会发现查看广告与发帖相关,发帖多的客户流失率低。

图片

图 7.4 模拟案例研究场景激励比率度量

在进入下一节之前,你应该使用模拟的流失数据以及本书 GitHub 仓库中的代码(GitHub.com/carl24k/fight-churn)重现图 7.4 中的结果。这项任务是本章代码练习的第一步。通过重现图 7.4 中的图表,你可以确认你的数据和度量已经准备好迎接接下来的内容。

你应该在第三章中计算出每月查看的广告和每月发布的帖子度量。如果你当时没有计算这些度量,在按照存储库的 README 文件中解释的设置好环境之后,你可以使用以下命令通过 Python 包装程序计算所有度量:

fight-churn/listings/run_churn_listing.py —chapter 3 —listing 3 4 —version 1 2 3 4 5 6 7 8

如果你运行了第五章中的列表代码,你应该已经创建了一个类似于图 7.4 所示的每月帖子群体分析。如果没有,你现在可以通过使用参数—chapter 5 —listing 1运行 Python 包装程序来完成。要创建一个针对每月查看的广告的新群体分析,你可以通过添加参数—version 3运行列表 5.1 的另一个版本。总的来说,需要添加的参数是—chapter 5 —listing 1 —version 3

与第六章(列表 6.1)中重新创建度量对散点图相同。你可以使用参数—chapter 6 —listing 1 —version 2重新运行 Python 包装程序,以重新创建类似于图 7.4 中的散点图。

7.1.2 如何计算比率度量

现在你已经知道了比率度量的定义,是时候深入了解如何计算它了。比率度量的计算通过一小部分示例数据(图 7.5)进行说明。当我提到你将一个度量除以另一个度量时,我是字面意思。你从已经保存在数据库中的两个度量开始。为了计算比率度量,你需要匹配这两个度量,按账户和日期进行匹配;假设你已经在所有相同的日期计算并保存了其他两个度量,如第三章所示。对于每个账户和日期,比率是那个日期和账户的度量值除以相同日期和账户的另一个度量值。这个过程并不复杂,尽管你需要注意以下两个“陷阱”:

  • 分母度量(你正在除以的度量)必须大于零。

  • 分子度量可以是零,但不应该是负数。到目前为止,我们还没有查看可以取负值的度量,但在第 7.3 节中你会看到一些。如果你从自己的数据中创建度量,比如可以同时为正或负的货币金额之和,你可能已经有一些可以取负值的度量。

图片

图 7.5 比率度量计算机制

如果比率分母中的度量对一个账户为零,则比率未定义。如果你在任何程序语言中尝试除以零,将会得到错误。而且如果任一度量有时可能是负数,比率在数学上是可行的,但它缺乏两个其他测量值比率的通常意义:一个单位成本或一个事件相对于另一个事件的比率。否则,计算比率相当直接。

列表 7.1 提供了一个简短的 SQL 程序,用于计算比率指标,图 7.6 展示了输出的一小部分。列表 7.1 中显示的简短 SQL 程序还返回了指标的分子和分母,以供说明。否则,列表 7.1 中的计算策略与图 7.5 非常相似:两个常见的表表达式(CTEs)选择将要形成比率的两个指标的值。最后的 SELECT 语句是一个左外连接(LEFT OUTER JOIN),其中分母位于左侧。因此,当分母可用时,将计算任何账户的比率指标。让我们先看看图。

图 7.6 运行列表 7.1 的输出

SQL 使用 CASE 语句作为计算的一部分,以防止分母中的零指标。如果您使用第三章中提到的计数指标,您不会存储零,但防止除以零是最佳实践,以防您处理从其他系统导入的指标。当您使用 CASE 语句时,零分母指标会产生零比率。该结果将与零分子产生的结果相同,这在数学上是正确的,并产生零。话虽如此,如果您没有存储分子指标的零,您将不会产生比率,但您仍然会在您的流失分析数据集中为任何未获得比率的账户填充零(如第四章所述)。

注意:缺失值或比率的任一指标为零的账户在流失分析数据集中将获得零指标。

您应该运行列表 7.1 并确认您的结果与图 7.6 类似。如果您使用包装程序运行列表,请使用参数 —chapter 7 —listing 1 如此操作:

fight-churn/listings/run_churn_listing.py —chapter 7 —listing 1 

列表 7.1 SQL 比率指标计算

WITH num_metric AS (
    SELECT account_id, metric_time, metric_value AS num_value
    FROM metric m INNER JOIN metric_name n ON n.metric_name_id=m.metric_name_id
    AND n.metric_name = 'adview_per_month'                    ①
    AND metric_time BETWEEN '2020-04-01' AND '2020-05-10'
), den_metric AS (
    SELECT account_id, metric_time, metric_value AS den_value
    FROM metric m INNER JOIN metric_name n ON n.metric_name_id=m.metric_name_id
    AND n.metric_name = 'post_per_month'                      ②
    AND metric_time BETWEEN '2020-04-01' 
        AND '2020-05-10'                                      ③
)
SELECT d.account_id, d.metric_time, 
    num_value, den_value,                                     ④
    CASE WHEN den_value > 0                                   ⑤
        THEN COALESCE(num_value,0.0)/den_value                ⑥
        ELSE 0                                                ⑦
    END AS metric_value
FROM den_metric d  LEFT OUTER JOIN num_metric n               ⑧
    ON n.account_id=d.account_id
    AND n.metric_time=d.metric_time;

① 选择分子的指标

② 选择分母的指标

③ 匹配分子和分母的日期范围

④ 选择用于说明的分子和分母值

⑤ 当分母不为正数时,比率是未定义的。

⑥ 计算比率;对于零分子进行合并

⑦ 当分母缺失时填充零

⑧ 左外连接(LEFT OUTER JOIN)在分母可用时总是产生结果。

您还需要将结果保存到数据库中以便继续示例,但由于列表 7.1 选择指标的分母和分子以供说明,因此它不适合将指标插入到数据库中。列表框架中的预写 SQL 语句执行此操作:insert_7_1_ratio_metric.sql(与 listing_7_1_ratio_metric.sql 相同,只是在 SQL 文件名中使用 insert 而不是 listing)。要运行为插入结果设计的列表 7.1 版本,请向运行列表的脚本的参数中添加 —insert 标志。如果您使用 Python 包装程序运行列表,则命令如下:

fight-churn/listings/run_churn_listing.py —chapter 7 —listing 1 —insert

该插入显示了它使用的 SQL,但它不会打印任何结果。您应该创建自己的 SELECT 语句来验证结果,或者更好的是,运行第三章中的度量标准质量保证查询(列表 3.6)以查看结果的时间总结。这种学习和然后将度量标准插入数据库的模式将在本章中重复:

  • 书中展示的列表包括额外的列以供说明之用。

  • 如果需要将度量标准保存到数据库中以遵循示例,则存储库中的列表的第二个版本执行插入操作。您可以通过在包装脚本命令中添加 —insert 标志来运行它。

  • 列表的示例版本路径类似于 listings/chap7/listing_7_,而写入数据库的列表版本路径类似于 listings/chap7/insert_7_。

将新的每篇帖子广告度量标准保存到数据库后,您应该分析其相关性及其与流失率的关系。记住,要运行队列分析,您首先需要使用 SQL 重新导出您的流失率数据集。导出包含您新度量标准(以及您在本章中创建的所有其他度量标准)的数据集的代码显示在列表 7.2 中,这是列表 4.5 的更新版本。

您应该运行列表 7.2 以保存一个新的数据集,该数据集允许您在新指标每篇帖子广告上运行队列分析。如果您不记得列表 7.2 的工作方式,请回顾第四章,特别是列表 4.5;它们是完全相同的。不用担心您还没有创建列表 7.2 中的所有度量标准;您将在本章中创建它们。目前,您未创建的任何度量标准将在数据集中用零填充。

列表 7.2 导出包含第七章度量的数据集

WITH observation_params AS                                       ①
(
    SELECT  interval '7' AS metric_period,
    '2020-03-01'::timestamp AS obs_start,
    '2020-05-10'::timestamp AS obs_end
)
SELECT m.account_id, o.observation_date, is_churn,
SUM(CASE WHEN metric_name_id=0 THEN metric_value ELSE 0 END)
    AS like_per_month,
SUM(CASE WHEN metric_name_id=1 THEN metric_value ELSE 0 END)
    AS newfriend_per_month,
SUM(CASE WHEN metric_name_id=2 THEN metric_value ELSE 0 END)
    AS post_per_month,
SUM(CASE WHEN metric_name_id=3 THEN metric_value ELSE 0 END)
    AS adview_per_month,
SUM(CASE WHEN metric_name_id=4 THEN metric_value ELSE 0 END)
    AS dislike_per_month,
SUM(CASE WHEN metric_name_id=5 THEN metric_value ELSE 0 END)
    AS unfriend_per_month,
SUM(CASE WHEN metric_name_id=6 THEN metric_value ELSE 0 END)
    AS message_per_month,
SUM(CASE WHEN metric_name_id=7 THEN metric_value ELSE 0 END)
    AS reply_per_month,
SUM(CASE WHEN metric_name_id=8 THEN metric_value ELSE 0 END)
    AS account_tenure,
SUM(CASE WHEN metric_name_id=21 THEN metric_value ELSE 0 END) 
    AS adview_per_post,                                          ②
SUM(CASE WHEN metric_name_id=22 THEN metric_value ELSE 0 END)
    AS reply_per_message,
SUM(CASE WHEN metric_name_id=23 THEN metric_value ELSE 0 END)
    AS like_per_post,
SUM(CASE WHEN metric_name_id=24 THEN metric_value ELSE 0 END)
    AS post_per_message,
SUM(CASE WHEN metric_name_id=25 THEN metric_value ELSE 0 END)
    AS unfriend_per_newfriend,
SUM(CASE WHEN metric_name_id=27 THEN metric_value ELSE 0 END)
    AS dislike_pcnt, 
SUM(CASE WHEN metric_name_id=28 THEN metric_value ELSE 0 END)
    AS unfriend_per_newfriend_scaled,
SUM(CASE WHEN metric_name_id=30 THEN metric_value ELSE 0 END)
    AS newfriend_pcnt_chng,
SUM(CASE WHEN metric_name_id=31 THEN metric_value ELSE 0 END)
    AS days_since_newfriend,
SUM(CASE WHEN metric_name_id=33 THEN metric_value ELSE 0 END)
    AS unfriend_28day_avg_84day_obs,
SUM(CASE WHEN metric_name_id=34 THEN metric_value ELSE 0 END)
    AS unfriend_28day_avg_84day_obs_scaled
FROM metric m INNER JOIN observation_params
ON metric_time BETWEEN obs_start AND obs_end    
INNER JOIN observation o ON m.account_id = o.account_id
    AND m.metric_time > (o.observation_date - metric_period)::timestamp    
    AND m.metric_time <= o.observation_date::timestamp
GROUP BY m.account_id, metric_time, observation_date, is_churn    
ORDER BY observation_date, m.account_id

① 此列表与列表 4.5 的起始相同。

② 新度量标准从这里开始。

创建新版本的数据集后,您可以运行队列分析以查看其与流失率的关系。列表版本是 —chapter 5 —listing 1 —version 5。使用这些参数运行 Python 包装程序以创建队列图。

图 7.7 显示了这些分析典型的结果:每篇帖子广告的比例越高,与流失率相关,而不是与保留率相关。这一结果证实了直觉,即看到更多的广告在客户满意度方面付出代价(或者更确切地说,证实了模拟被设计得更多广告观看会降低满意度)。但要看到看到更多广告的负面影响,您必须将度量标准从与其他导致客户满意度的行为的相关性中分离出来。图 7.7 还显示了新的 adview_per_post 度量标准与原始的每月广告数和每月帖子数之间的相关性。每篇帖子的广告数与观看广告有弱正相关,与观看帖子有中度的负相关。(您可以通过在您的新数据集上重新运行列表 6.2 来检查。)

图 7.7

图 7.7 关于指标 adview_per_post、流失和相关性分析的模拟案例研究

图 7.7 中的相关性对于许多比率指标来说是典型的。比率与分子指标呈正相关是自然的,因为当分子较大时,比率往往较大。比率与分母指标呈负相关也是正常的,因为所有其他条件相同,较大的分母会导致较低的比率。然而,精确的结果取决于两个指标之间关系的本质。如果您查看真实的案例研究,您会发现结果与图 7.3 和图 7.7 中显示的典型场景有很大的不同。

7.1.3 比率指标案例研究示例

比率指标不仅在您认为会导致脱节的指标存在时有用,而且在您有两个行为交互与客户参与和流失相关时也很有用。通过交互,我指的是两个指标之间的关系很重要的情况——不是两个指标的大小,而是哪一个比另一个大或小。本节包含一些来自案例研究的更多示例,以展示这种交互。

两个指标之比在某个场景中很重要,这个场景涉及效率。许多行为之间存在一种关系,其中一个事件在过程中导致另一个事件,并且过程结束时发生的事件越多,效果越好。图 7.8 展示了 SaaS 服务 Broadly 的一个例子,该服务帮助企业管理其在线存在。Broadly 跟踪客户和交易,交易总是跟随客户注册,因此客户数量和交易数量之间的相关性(0.93)是自然的。每个客户的交易比率高通常对业务有利;它也可能与 Broadly 平台上的参与和成功相关。图 7.8 显示,在 Broadly 上,每个客户的交易比率高于平均水平的业务(得分大于 0.0)的流失率显著低于交易比率低于平均水平的业务。这样的业务可能更成功,因此它们不太可能与 Broadly 有更多的互动,导致更高的客户参与和更低的流失率。

图片

图 7.8 案例研究:展示 Broadly 的每个客户的交易比率分析

要点:如果一个事件在过程中的另一个事件之后,那么下游事件上的指标与先导事件上的指标之比可以被视为该过程的效率度量。

图 7.9 展示了 Broadly 使用另一个比率进行的流失率案例研究:评论请求接受率。向客户请求评论是 Broadly 最重要用途之一,客户接受此类请求的比率是使用产品的效率度量。这个比率也可以被视为成功率,因为每次尝试该活动要么成功要么失败。

图 7.9 展示了 Broadly 评论请求接受率群体分析的案例研究

图 7.9 可能会让你感到惊讶,因为它显示,除了拥有最高流失率的底层群体外,成功率增加与流失率增加之间显示出适度相关。这种模式对于一种不参与的行为(在第三章中介绍)是典型的。底层群体是那些没有一种或两种行为的群体,因为他们没有指标。但对于使用产品的客户来说,更高的评论请求接受率与更高的流失概率相关联,这可能令人惊讶,因为你可能会预期更成功的客户流失更少。但这种情况可能发生在具有特定目的的产品上。如果客户更快地取得成功,这可能会加速他们的流失。在这种情况下,大多数使用 Broadly 的企业需要一定数量的评论来让自己看起来更好。当它有足够的评论时,企业可能面临更高的流失风险,因为它们已经实现了目标,不再认为 Broadly 有用。对于 Broadly 的客户来说,流失风险最高的情况是客户很少使用产品(底层群体)。

吸收要点:两个事件指标的比率,其中一个事件代表第一个事件的成功结果,定义了一个成功率指标。

Klipfolio 是一家 SaaS 公司,它允许企业创建其关键指标的在线仪表板。图 7.10 展示了 Klipfolio 另一种效率率的案例研究:每月仪表板编辑次数除以保存次数。在这种情况下,这个比率可能更准确地描述为不效率率,因为大量编辑而没有大量保存可能表明用户需要付出更多努力才能达到他们想要保存的可以接受的结果。图 7.10 显示,这类不效率(比率分数高)的客户实际上比在这方面更有效率(更低的比率分数)的 Klipfolio 用户面临更高的流失风险。

图 7.10 展示了 Klipfolio 编辑/保存比率群体分析的案例研究

我希望这些例子能给你一些关于你可以使用比率指标的广泛情况的启示。它们是强大的工具!接下来的几节将教你一些不同、更专业的比率。在本章末尾,我将回到这样一个问题:何时所有这些类型的比率都是合适的,以及如何找到对你特定情况有用的比率指标。

7.1.4 模拟社交网络的其他比率指标

在继续之前,尝试在模拟社交网络上使用更多比率指标。关于当你有很多指标时如何选择比率,我将在第 7.6 节中详细说明。现在,我只想说,以下这一组指标测试了一些有趣的关系:

  • 每条消息的回复数

  • 每篇帖子的点赞数

  • 每条消息的帖子数

  • 每个新好友的取消好友数

所有这些指标都有 7.1 列表的准备版本,您可以在 Python 包装程序中使用以下命令运行:

run_churn_listing.py —chapter 7 —listing 1 —insert —version  2 3 4 5

重要提示:您必须已经根据第 7.1.2 节中的说明计算了比率的基础指标。

然后,作为一个练习,检查这些新的比率指标与额外的指标群体图如何与以下 Python 包装程序中的命令相关联:

run_churn_listing.py —chapter 5 —listing 1 —version 11 12 13 14 15 16

你可以在第八章中了解更多关于这些额外指标如何帮助你分析和预测流失率的信息。

7.2 总指标百分比

百分比是比率的特殊案例:分子是某一部分的测量,分母是分母中该事物的整体数量。当只有两种可能的结果时,百分比指标可以用来制作一个更可解释的比率版本。一组百分比指标可以用来分析一组相关指标之间的关系,其中每个指标测量一种特定类型的一般行为。

7.2.1 计算总指标百分比

图 7.11 说明了虚构的流媒体服务和您可能想要制作测量总指标百分比的典型情况。该服务有四种内容类型:动作、喜剧、戏剧和浪漫。对于具有一个主要活动和子类别的任何产品,每个活动区域的周期性计数指标通常与其他活动区域高度相关;使用服务较多的客户倾向于在所有区域都使用得更多。理解活动的相对数量是否与客户参与度和流失率相关的方法是为每个类别计算一个额外的比率指标:该类别中的活动除以所有类别中的总活动。如图 7.11 所示,所有类别的总数正好是所有类别的所有事件数,百分比是每个类别中活动的相对比例。

图 7.11 一个虚构的内容流服务的总指标百分比

总指标百分比是一种特殊的比率指标,因为它像其他任何类型的比率指标一样计算(参见表 7.1)。使百分比指标与众不同的地方在于,比率中的分母是代表类别的其他指标的总量。为了制作这些比率,您需要的只是(除了列表 7.1 之外)一个合适的总量作为分母。

TAKEAWAY 总指标百分比揭示了与一组密切相关、高度相关的活动的相对平衡。

您已经接触到了从其他度量中计算比率的度量值的概念,这里还有一个领域,这个技巧非常有用。如果您需要计算一个作为比率分母的不同类别的总和的度量值,您可以求和您打算在分子中使用的所有度量值的值。这种计算策略,总结在图 7.12 中,与比率度量值的计算相似,因为其他度量值的账户和计算日期必须匹配。一个重要的区别是,其他度量值的总和可以作用于多个其他度量值。图 7.11 显示了四个内容区域的四个度量值,在真实的流媒体服务中可能有更多。其他例子可以是不同类别的总购买量和不同地区的呼叫量;参见 7.2.2 节中的示例。

图 7.12 总度量值计算百分比

列出 7.3 的计算其他度量值总和的 SQL 语句,它显示了 GitHub 仓库中 churn 模拟的账户的总点赞和不喜欢。图 7.13 显示了典型的输出。请注意,列出 7.3 为了说明目的选择了度量值的字符串聚合。总度量值的 SQL 语句比图 7.12 中的计算简单:当度量值以列表形式提供时,SQL 语句使用按日期和账户分组的SUM聚合。这种方法比使用 CTEs 进行比率度量值(由图 7.12 暗示)更容易。更简单的方法是可能的,因为度量值的顺序并不重要,SQL 提供了标准的SUM聚合。

图 7.13 在 GitHub 仓库的默认模拟数据集上运行列表 7.3 的输出

您可能会想知道是否可以直接通过将进入其他度量值的事件总和起来来计算其他度量值的总和——通过SELECT来计数多个事件类型而不是分别计数不同的事件类型。答案是您可以这样做并得到相同的结果。添加预先计算的类别度量值的优势在于,如果您的数据集很大,它可能比重新计数基础事件的总数要快得多。但话虽如此,添加预先计算的度量值会在您度量值计算顺序中引入一个依赖。您将不得不决定在您的特定情况下什么最有意义。

列表 7.3 度量值总和

SELECT account_id, metric_time,
    STRING_AGG(metric_value::text,'+') AS metric_sum,     ①
SUM(metric_value) AS metric_total                         ②
FROM metric m INNER JOIN metric_name n 
    ON n.metric_name_id=m.metric_name_id                  ③
AND n.metric_name in 
    ('like_per_month', 'dislike_per_month)                ④
WHERE metric_time BETWEEN '2020-01-01' AND '2020-02-01'
GROUP BY metric_time, account_id                          ⑤

① STRING 用于说明正在求和的度量值。

② 使用 GROUP BY 聚合计算总和

③ 使用 INNER JOIN;可用的度量值将包含在总和内。

④ 列出对总和有贡献的度量值

⑤ 按日期和账户汇总结果

您应该在本书 GitHub 仓库中提供的代码创建的模拟数据集上运行列表 7.3。如果您正在使用 Python 包装程序,请使用以下参数运行它:

—chapter 7 —listing 3

您的输出不会完全相同,因为模拟数据是随机生成的。请注意,列表 7.3 为了说明目的选择了被求和的指标的字符串聚合。为了计算此类指标并将其保存到数据库中,您必须删除SELECT中的该部分,并在insert语句中选择指标的 ID。

列表代码文件夹中的预写 SQL 版本列表 7.3 执行插入操作。(插入版本的路径相同,但文件名以insert_7_3开头而不是listing_7_3。)通过在运行列表的参数中添加—insert标志来运行插入 SQL 语句;您需要保存的结果来继续示例。该列表显示了它使用的 SQL,但不会打印结果。您应该创建自己的SELECT语句来验证结果或运行第三章中的metric_qa代码(列表 3.6)。

在保存总体指标后,您仍然需要创建将作为总体百分比的比率指标。为此,再次使用列表 7.1(比率指标)。已经准备了一个带有参数的列表 7.1 的额外版本:分子指标是名为dislikes_per_month的指标,分母是使用列表 7.3 的插入版本创建的新指标(命名为total_opinions)。在运行列表 7.1 时,通过添加参数—version 2 —insert来运行该插入语句。所有参数如下:

fight-churn/listings/run_churn_listing.py —chapter 7 —listing 1 —version 2 —insert

7.2.2 具有两个指标的总体指标案例研究百分比

在模拟中创建完总点赞和点踩的指标以及模拟客户点踩百分比的新的指标后,你应该为该指标完成一个新的客户流失群体分析。为了进行点踩百分比的群体分析,请按照以下步骤操作:

  1. 重新运行列表 7.2 以导出包含您新指标的版本的数据集到.csv 文件,使用参数—chapter 7 —listing 2

  2. 重新运行列表 5.1 以使用版本参数 6、7 和 8 创建新的群体分析:—chapter 5 listing 1 —version 6 7 8。该代码将运行三个指标的群体分析:每月点赞数、每月点踩数和点踩百分比。所有参数如下

—chapter 5 —listing 1 —version 6 7 8.

图 7.14 显示了此分析的典型结果。每月点赞数和每月点踩数与降低的流失率相关,并且相互关联,但点踩百分比显示较高百分比点踩的流失风险增加。此分析的结果在定性上类似于第 7.1.1 节中介绍的广告查看分析。在那个分析中,减少参与度的行为与增加参与度的行为相关联,比率指标使这一事实变得明显。不同之处在于,这次您使用的是百分比而不是简单的比率。

图 7.14 百分比点踩模拟案例研究,说明了使用总体指标的百分比

如果你认为使用简单的比例(不喜欢和喜欢的比例)结果不会有太大差异,你是正确的。那么为什么还要费心使用百分比呢?我之所以推荐在这种情况下使用百分比而不是简单的比例,是因为可解释性。因为喜欢和不喜欢构成一个类别,用百分比来描述这种关系更直观。我的建议是,当两个指标是整体的一部分(例如喜欢和不喜欢)时,使用百分比比例;当两个指标不是整体的一部分时(例如广告观看次数和帖子),使用简单比例。

要点:当两个指标是整体的一部分时,百分比比例比简单比例更易解释。

对于 Broadly 也有类似的案例研究。如图 7.15 所示,这个案例研究展示了客户每月推广者、客户批评者和批评者百分比的指标结果。当客户留下正面评论时,就会发生客户推广者事件,因此这个事件预计会给使用 Broadly 的企业带来价值,并使客户不太可能流失,如图 7.15 所示。当客户留下负面评论时,就会发生批评者事件,这预计会令使用 Broadly 的企业感到不悦,但它似乎也与降低流失率有关。图 7.15 还显示了批评者百分比的流失群体结果,这是通过将批评者数量除以推广者和批评者总数得到的比率。批评者百分比越高,与流失率的相关性就越强,而在研究期间,批评者百分比最低的企业——大约 2%——几乎没有流失。

图片

图 7.15 消费者批评者百分比案例研究

7.2.3 多指标情况下的总指标百分比案例研究

总指标百分比对于使两个类别的比例更易解释是有用的。但是,当有多个子类别共同构成一个整体时,总指标百分比的优势就更加明显。图 7.16 展示了 Versature 这个综合电信服务提供商的例子。Versature 在四个地理区域提供服务,如图 7.16 中标记为 1 至 4。图 7.16 中的相关矩阵显示,所有四个地区的每月通话次数中等至高度相关。

图片

图 7.16 Versature 的区域百分比案例研究

由于四个区域电话数量的相关性,试图通过使用简单的计数指标来分析流失率只能告诉你一件事:客户打更多的电话会减少流失率。但总指标百分比(如图 7.16 右侧所示)可以提供更多信息。在区域 1,当百分比较高但不是最高时,发生最低的流失率。区域 1 拥有最多的电话,但当客户只在区域 1 打电话时,这些电话似乎导致较低的参与度。区域 2 和 3 显示出与流失率的关系,其中区域内的适度电话百分比是最佳的;要么太少要么太多都会导致更高的流失率。

如图 7.16 所示,所有四个区域中总指标百分比的相互关联性较弱。由于区域 1 在整体上拥有最多的电话,区域 1 的百分比与其他百分比指标之间显示出较弱到中等的负相关性:区域 1 的高百分比通常意味着其他区域留下的电话很少。其他三个区域的总指标百分比相互之间以及与电话量指标的相关性较弱,这表明这些指标为理解客户参与度和流失率提供了新的信息(与电话量中的信息不同)。

7.3 测量变化指标

到目前为止,你已经查看了一些在测量时客户行为的测量结果。但行为的变化可以为你提供关于参与度的额外线索。为了理解客户行为的变化如何与参与度和流失率相关联,你需要创建一些专门为此目的而设计的更多指标,然后使用你已经学到的相同技术来分析变化测量结果。

7.3.1 测量活动水平的变化

由于你已经计算了基于日期序列的指标,通过查看客户的指标并比较当前值与之前值,很容易看出客户的行为是否在变化。如果指标上升,你知道行为在增加;如果指标下降,行为在减少。这就是为什么你需要在不同的时间点计算指标的原因之一。但如果你想了解变化与流失率和参与度之间的关系,你需要将变化视为另一个自然实验,并比较行为增加的客户与行为减少的客户。为此,你需要代表变化的指标;然后你可以应用你学到的群组分析技术。

表示变化的度量是一个派生度量,表示你感兴趣的主要度量的变化。因为你已经学习了如何从其他度量中制作度量,所以这个想法可能不会像你在拿起这本书之前那样让你觉得陌生。图 7.17 说明了测量两个账户登录次数变化的假设场景,并引入了百分比变化作为度量的概念。

图片

图 7.17 使用百分比变化度量对两个假设账户的分析

定义:百分比变化是一个比率,它将度量随时间的变化除以该时期开始时度量的值。

假设有两个账户的登录次数不同;一个账户的登录次数远多于另一个账户。还假设这两个账户每月的登录次数都在增加。如果你查看登录次数随时间的变化,你可能会发现变化的大小对于一开始登录次数较多的账户更大。也就是说,度量变化的大小与度量的水平相关。因为变化的大小通常与度量的起始水平相关,所以简单的差值不是表示变化的最佳度量。如果你查看每个账户都有自己 y 轴刻度的图表中的登录次数变化,你可以看到这种结果。如果你允许每个账户有自己的刻度,相对变化可以看作是相同的。因为你已经了解到使用比率是一种重新缩放度量以强调相对数量的方法,你可能已经看到了这个例子的发展方向。

这种比率计算使得变化测量与度量在开始时的水平高低相关性较低。图 7.17 显示,对于这两个账户,百分比变化是相同的,这表明尽管起始点有很大的不同,它们的增长相似。方程 7.1 展示了百分比变化的数学公式定义。

图片 方程 7.1

在方程 7.1 中,度量[@start]表示测量窗口开始时的度量,而度量[@end]表示结束时的度量。方程 7.1 还展示了一个简化:百分比变化实际上是开始时度量与结束时度量的比值,减去 1.0,因为分数可以分解为结束时度量与开始时度量的比值,减去开始时度量与自身的比值。这种简化基于任何数除以自身等于 1.0 的事实。

要点:使用百分比变化度量来查看行为是否发生变化。在分析中直接不包括(旧的)度量起始值或绝对变化值;这些值与结束度量值所捕获的整体活动水平相关。

为了使这个例子更具体,图 7.18 显示了百分比变化作为指标的详细计算。此图继续了图 7.17 中的登录的假设例子,但有一个重要区别:像其他所有指标一样,这个指标是在一系列日期上重复计算的。这种计算有时被称为滚动百分比变化计算。

在图 7.18 中,使用了四周的时间段。对于每一周,计算都应用方程 7.1 于该周的指标(作为最终指标)和四周前的指标(作为起始指标)。如图 7.18 的右侧所示,得到的滚动百分比变化测量值不是恒定的;它们根据基础指标的精确波动而波动。在两个指标相当一致的示例期间,四周百分比变化增加的范围从大约 5%到大约 25%。

图片

图 7.18 滚动百分比变化指标的计算

图 7.19 显示了计算百分比变化(列表 7.4)的 SQL 语句的示例输出。该图显示,这个指标对本书来说有新的内容:负值。如果你在阅读这本书之前就熟悉百分比变化,这个事实就不会感到惊讶。如果你对这个术语是新的,那么负百分比变化意味着在测量期间所讨论的指标已经下降。回想一下方程 7.1,百分比变化是

图片

当最终指标小于起始指标且分数小于 1.0 时,因此在减去 1 之后,百分比变化变为负值。

图片

图 7.19 运行列表 7.4 的输出,显示负结果

因为百分比变化是比率指标减去 1.0,所以列表 7.4 与计算常规比率指标相似。列表 7.4 与常规比率计算之间的区别是

  • 分子和分母来自同一个指标,而不是两个不同的指标。

  • 分母指标(起始值)是从早于结束值的时间(例如四周)选择的。(这个选择必须在JOINSELECT语句中考虑到。)

  • 在列表 7.4(百分比变化)中,最终计算中从比率中减去了数字 1。

列表 7.4 指标百分比变化

WITH end_metric AS (                                             ①
    SELECT account_id, metric_time, metric_value AS end_value
    FROM metric m INNER JOIN metric_name n 
        ON n.metric_name_id=m.metric_name_id
    AND n.metric_name = 'new_friend_per_month'
    AND metric_time BETWEEN '2020-04-01' AND '2020-05-10'
), start_metric AS (                                             ②
    SELECT account_id, metric_time, metric_value AS start_value
    FROM metric m INNER JOIN metric_name n 
        ON n.metric_name_id=m.metric_name_id
    AND n.metric_name = 'new_friend_per_month'                   ③
    AND metric_time BETWEEN                                      ④
        ('2020-04-01'::timestamp -interval '4 week')
        AND ('2020-05-10'::timestamp -interval '4 week')
)
SELECT s.account_id, s.metric_time + interval '4 week',
    start_value, end_value,                                      ⑤
    COALESCE(end_value,0.0)/start_value - 1.0 
        AS percent_change                                        ⑥
FROM start_metric s LEFT OUTER JOIN end_metric *e*                 ⑦
    ON s.account_id=e.account_id
    AND e.metric_time
        =(s.metric_time + interval '4 week')                     ⑧ 
WHERE start_value > 0                                            ⑨

① 此 CTE 选择所有分子指标。

② 此 CTE 选择所有分母指标。

③ 分子和分母使用相同的指标

④ 对分母的日期进行变化周期的偏移

⑤ 使用最近指标的时期作为比率

⑥ 根据方程 7.1 的百分比变化

⑦ 左外连接;如果最终指标为 NULL,变化为-100%。

⑧ JOIN 调整起始和结束之间的偏移

⑨ 防止除以零错误

还要注意,列表 7.4 为了说明目的选择了计算中使用的起始值和结束值。如果你将百分比变化作为要保存到数据库的指标来计算,你必须省略说明列,并包含一个带有指标 ID 的INSERT语句。你应该以通常的方式使用 Python 包装程序运行列表 7.4 以查看类似于图 7.19 的输出,然后重新运行带有—insert标志的列表 7.4 以将其保存到数据库,如下所示:

fight-churn/listings/run_churn_listing.py —chapter 7 —listing 4 —insert

你可能想知道是否在列表 7.4 中使用start_value作为COALESCE的第二个参数会更合理,以便当end_value为空时,计算结果返回0。但请注意,由于WHERE子句,start_value必须大于0。然后,如果没有end_value,变化被定义为-100%,因为你从某个东西(非零的start_value)变成了没有东西,因此end_value周围的COALESCE给出了正确的结果。

你应该对你新朋友的百分比变化指标进行群体分析。步骤与你在 7.1 节中看到的基本相同:

  1. 重新运行列表 7.2 以重新导出数据集,包括新指标。

  2. 使用参数—version 9运行列表 5.1 以绘制新朋友百分比变化的分析图。所有参数如下:

 —chapter 5 —listing 1 —version 9

图 7.20 显示了这种分析的典型结果。发现每月新朋友数量显著下降的群体,其流失风险增加。

图片

图 7.20 显示了流失案例研究中每月朋友百分比变化的模拟结果

7.3.2 具有极端异常值(厚尾)的指标得分

百分比变化指标的问题在于这些指标可能具有极端值。由于模拟数据通常比真实人类行为不那么极端,因此这个问题并不明显。实际上,当分母较小时(小于 1),任何比率都可能具有极端值,因为此时比率的值变得相当大。但对于大多数常见的比率,这个问题并不存在,因为像单位成本和交易每客户这样的效率度量受到业务性质的限制,并且百分比必须按设计在 0%到 100%之间。当客户在某个指标上的值较低,而他们的下一次测量值较大时,百分比变化可以非常大。但如果客户从一个高指标值下降到零(或接近零),指标会变得极端负吗?并不完全是这样:最低可能的百分比变化测量值是-100%,因为这是任何非零指标变为零时的百分比变化。

图 7.21 显示了来自社交网络模拟和 Versature 案例研究的百分比变化指标的分布测量。要重现案例研究的统计数据,请使用以下 Python 包装程序:

  1. 重新运行列表 7.2,使用参数—chapter 7 —listing 2导出包含您新指标的 CSV 文件版本。

  2. 重新运行列表 5.2,使用参数—chapter 5 —listing 2 —version 2保存数据集摘要统计表的表格。

您的结果将包括图 7.21 第一行显示的模拟指标百分比变化在新朋友中的统计数据。模拟的值并不太极端,因为百分比变化是针对四周期的,模拟客户的变化并不大。尽管如此,最大值是 1,200%,这意味着至少有一个客户从每月一个新朋友增加到每月 12 个新朋友;最小值如承诺的那样是-100%。

图片

图 7.21 Versature 通话百分比变化统计

Versature 案例研究指标更接近你从真实客户数据中期望看到的情况:最小值也是-100%,最大值是 117,150%。这是 11,000 倍的增长!在数据集中某个地方有一个客户,三个月前每月只有一个通话,12 周后变成了 11,715 个通话——这是一个大客户在百分比变化测量开始时开始服务的极端案例。变化的第 99 百分位数是 423%,这意味着 99%的客户增加次数小于 4.23 倍。

另一个需要注意的重要点是,中位数变化为 0%(如图 7.21 中 50%列所示的第 50 百分位数)。增加通话次数的客户数量与减少通话次数的客户数量大致相同。这种情况通常出现在百分比变化指标中。如果你只看平均值,可能不会意识到这一点,因为平均值通常大于 0%。这在图 7.20 中 Versature 的百分比变化指标和模拟百分比变化指标中都是如此。当正负变化指标的客户数量相等时,平均数大于 0%的原因是正百分比变化测量值可能远大于负百分比变化测量值。

在图 7.21 中还有其他需要注意的事情是模拟数据和实际数据在 5 和 90 附近的偏斜。你可能还记得第五章,偏斜意味着大多数指标值都聚集在一起,但有几个值远远超出。5 的偏斜被认为是中等偏斜;90 的偏斜是高度偏斜。当一个指标高度偏斜时,你应该将其转换为分数,以便更容易理解你的群体分析(参见第五章)。

但是存在问题。如果一个度量是零或负数,你不能使用偏斜度量版本的得分变换,但百分比变化通常是零和负数。为了解决这个问题,你可以使用另一个得分公式,我称之为肥尾公式,因为它在正负两个方向都有极端异常值时将度量转换为得分。具有正负极端值条件被称为肥尾,因为尾巴指的是分布的极端。当一个分布是正态分布或具有瘦尾巴时,最极端的值相对于分布的中间部分并不是太极端。如果一个度量的分布具有肥尾,极端值离中间范围更远,并且有更多的极端值。

方程 7.2 展示了肥尾得分公式:

图片

其中

图片 方程 7.2

方程 7.2 的格式与第五章中的得分公式(方程 5.1)相同:你将变换度量,然后减去平均值,并除以变换度量的标准差。在方程 7.2 中,μm ¢代表变换度量 m ¢的分布的平均值,σm ¢代表变换度量的标准差。

在肥尾得分公式中的度量变换与常规得分公式只有一点点的不同:方程 7.2 的第二部分说明变换后的度量 m ¢是通过取原始度量 m 的对数加上原始度量平方的平方根再加 1 来创建的。回想一下,在第五章的得分公式中,你使用了原始度量对数加 1(没有平方或取平方根)。

肥尾得分公式对负值也适用,因为当原始度量 m 为负时,平方根中的项总是正的,并且绝对值略大于负项。负值最终成为接近零的小数,而正值则被推远。在应用对数函数后,减去平均值并除以变换变量的标准差。

方程 7.2 的后半部分带有对数,也被称为科学家和数学家所知的双曲正弦逆变换,这是一个很长的名字,你不必记住。这种变换用于某些类型的工程和几何计算。无论你叫它什么,肥尾得分变换是处理具有极端值的度量的一个很好的技巧。

要点:使用肥尾变换从具有正负极端值的度量中创建得分。

列表 7.5 更新列表 5.3,包括肥尾分数转换。 (我在第五章中跳过了这个附加的复杂性,因为我们没有查看需要它的指标。) 扩展的列表执行所有相同的事情,并且它还额外检查了具有负值的偏斜列。如果有这样的列,除了对偏斜和非偏斜列的常规分数转换外,还会应用由公式 7.2 定义的分数公式。

注意:为了简化,省略了对肥尾指标进行的另一个测试,即检查统计量 kurtosis 的高值,这是一个旨在检测肥尾分布的测量。在流失案例中,肥尾指标也是偏斜的。

列表 7.5 使用肥尾分数进行评分

import pandas as pd
import numpy as np
import os

def transform_skew_columns(data,skew_col_names):                  ①
    for col in skew_col_names:
        data[col] = np.log(1.0+data[col])                         ②

def transform_fattail_columns(data,fattail_col_names):            ③
    for col in fattail_col_names:                                 ④
        data[col] = np.log(data[col] + 
                    np.sqrt(np.power(data[col],2) + 1.0))         ⑤

def fat_tail_scores(data_set_path, 
                    skew_thresh=4.0,**kwargs):                    ⑥

   churn_data = 
       pd.read_csv(data_set_path,index_col=[0,1])                 ⑦
   data_scores = churn_data.copy()
   data_scores.drop('is_churn',inplace=True, axis=1)

   stat_path = data_set_path.replace('.csv', '_summarystats.csv')
   assert os.path.isfile(stat_path),'You must running listing 5.2 first to generate stats'
   stats = pd.read_csv(stat_path,index_col=0)
   stats.drop('is_churn',inplace=True)

   skewed_columns=(stats['skew']>skew_thresh) & (stats['min'] >= 0)
   transform_skew_columns(data_scores,skewed_columns[skewed_columns].keys())

   fattail_columns=(stats['skew']>skew_thresh) 
       & (stats['min'] < 0)                                       ⑧

   transform_fattail_columns(data_scores,
                             fattail_columns[fattail_columns].keys())

   mean_vals = data_scores.mean()                                 ⑨
   std_vals = data_scores.std()
   data_scores=(data_scores-mean_vals)/std_vals
   data_scores['is_churn']=churn_data['is_churn']

   score_save_path=data_set_path.replace('.csv','_scores.csv')
   data_scores.to_csv(score_save_path,header=True)

    print('Saving results to %s' % score_save_path)
    param_df = pd.DataFrame(
        {'skew_score': skewed_columns,                            ⑩
         'fattail_score': fattail_columns,
         'mean': mean_vals,
         'std': std_vals}
    )
    param_save_path=data_set_path.replace('.csv','_score_params.csv')
    param_df.to_csv(param_save_path,header=True)
    print('Saving params to %s' % param_save_path)

① 包含了列表 5.3 中的偏斜数据转换

② 偏斜分数的转换

③ 对肥尾数据的新的转换

④ 遍历所有具有肥尾的列

⑤ 应用肥尾分数公式(公式 7.2)

⑥ 使用 kwargs 忽略默认的列表参数

⑦ 这段代码的大部分与列表 5.3 相同。

⑧ 当偏斜高且存在负值时,肥尾分数

⑨ 这个缩放与列表 5.3 相同。

⑩ 保存转换的列和参数

你可以通过对 Python 包装程序进行以下调用来在自己的数据上尝试列表 7.5:

  1. 如果还没有这样做,使用—insert标志运行列表 7.4 以保存新的指标。

  2. 重新运行列表 7.2,使用参数—chapter 7 —listing 2来重新创建你的保存的数据集。这个数据集包括你的新的百分比变化率指标。

  3. 运行列表 7.5,使用参数—chapter 7 —listing 5来创建分数数据集。

  4. 重新运行列表 5.2,版本 2,使用参数—chapter 5 —listing 2 —version 2来检查分数的统计数据。

图 7.22 展示了使用 Versature 数据的通话百分比变化率分析。对于这个案例研究,指标是每月通话数量的百分比变化,测量过去 12 周的数据。通话数量减少最多的群体显示出较高的流失风险。因为这个指标是在比观察流失前的时间更长的时间段内测量的,所以这个指标可能不是流失的领先指标,正如第四章所讨论的。也就是说,如果客户在续约前两周,你看到他们的使用量比三个月前大幅下降,那么他们很可能已经决定流失。另一方面,在续约前还有两周的时间,如果存在问题,可能还有时间解决。

图 7.22 Versature 通话百分比变化率流失群体案例研究

7.3.3 测量自上次活动以来的时间

百分比变化是了解用户行为是否下降或增加的好方法。一个有用的相关测量是自上次活动以来时间。自上次活动以来时间不是行为变化量的测量,而是将客户的当前行为与过去行为进行比较。特别是,自上次活动以来时间区分了新不活跃的客户和可能已经长时间不活跃的客户。

图片 7-23

图 7.23 测量自上次活动以来时间:一个事件

图 7.23 通过一个账户的事件序列的简单示例说明了该概念。每次计算指标时,自上次活动以来时间是指测量日期之前最近的事件与测量日期之间的时间差。如果客户在测量当天有事件,则指标为零。如果客户在一段时间内没有事件,则指标每天增加一,直到发生另一个事件。

与我们使用的所有指标一样,自上次活动以来时间手动计算会非常繁琐。幸运的是,使用 SQL CTE 和聚合进行计算并不困难。图 7.24 显示了 SQL 计算自最后事件以来天数典型输出的一个示例。请注意,为了说明目的,此输出包括SELECT语句中的最后事件日期。

图片 7-24

图 7.24 运行列表 7.4 的输出。对于每个账户和日期,选择最后事件的日期进行说明;指标值是从最后事件到测量日期的天数。

列表 7.6 提供了计算自最后事件以来时间的 SQL 程序。基本策略是在事件时间上使用MAX聚合(限制在测量日期之前的事件)以找到所有最近的事件。最近的事件日期存储在一个公用表表达式(CTE)中。之后,该指标是事件日期与测量日期之间的差异。使这个计算变得复杂的是,与所有指标一样,计算是在一系列测量日期上同时进行的。查询不仅为每个账户找到一个最后事件日期,而是为每个账户找到一系列最后事件日期。

您应该在模拟数据集上运行列表 7.6,按照 Python 包装程序的习惯模式进行,并确认输出类似于图 7.24。按照惯例,要将指标插入数据库,您需要删除说明(额外)列,并在插入语句中包含指标名称 ID。GitLab 存储库有一个可以运行的列表版本,您可以通过将—insert标志作为参数传递给运行列表的程序来运行,如下所示:

fight-churn/listings/run_churn_listing.py —chapter 7 —listing 6 —insert

列表 7.6 用于测量自事件以来时间的 SQL

WITH date_vals AS (                                                       ①
  SELECT i::date AS metric_date
    FROM generate_series('2020-05-03', '2020-05-10', '7 day'::interval) i
),
last_event AS (                                                           ②
    SELECT account_id, metric_date, 
        MAX(event_time)::date AS last_date                                ③
    FROM event *e* INNER JOIN date_vals d
    ON e.event_time::date <= metric_date                                  ④
    INNER JOIN event_type t 
        ON t.event_type_id=e.event_type_id
    WHERE t.event_type_name='like'                                        ⑤
    GROUP BY account_id, metric_date                                      ⑥
)
SELECT account_id, metric_date, 
last_date,                                                                ⑦
metric_date - last_date AS days_since_event                               ⑧
FROM last_event

① 用于计算指标的日期序列的 CTE

② 用于临时结果的 CTE:最后事件的日期

③ 使用 MAX 聚合选择最后一个日期

④ 使用每个测量日期的上一个事件的日期。

⑤ 选择要测量的事件

⑥ 对每个账户和日期进行汇总

⑦ 选择最后事件的日期进行说明。

⑧ 结果是自上次事件以来的天数。

在运行插入指标的 7.6 版本后,您可以重新生成数据集并在其上运行群体流失分析。总的来说,Python 包装程序的运行次数

  1. 使用插入标志运行 7.6 列表以保存新指标:—chapter 7 —listing 6 —insert

  2. 重新运行 7.2 列表以重新导出数据集:—chapter 7 —listing 2

  3. 使用 5.1 列表的第 10 版运行新的群体分析:—chapter 5 —listing 1 —version 10

图 7.25 显示了群体分析的结果。自上次新朋友事件以来超过五天的差距与流失风险的增加相关。风险的增加是渐进的,但对于自事件以来时间最长的群体来说,这种增加变得相当显著。

图 7.25 流失和自上次新朋友事件以来的天数案例研究

图 7.26 显示了 Klipfolio 自上次仪表板编辑以来的天数流失群体分析。在实际情况研究中,自上次编辑以来的天数是流失风险的显著预测因素:风险在第一个月大幅增加。与模拟数据不同,在活动时间最长的群体中,风险仅适度增加。

图 7.26 Klipfolio 的流失和自上次仪表板编辑以来的天数案例研究

当客户长时间不活跃时,他们可能会忘记订阅。在这种情况下,一些从业者认为,最好的减少流失策略是不采取任何行动,让沉睡的狗继续沉睡,不提醒客户他们有订阅。虽然这是一个合理的假设,但也是一种有些可疑的商业策略,依赖于人们忘记你以提高你的保留率。无论如何,图 7.26 并没有表明,自上次行动以来时间越长,流失风险就越低。对于考虑这种方法的任何公司来说,是否对那些已经走得很远的人进行干预会有正的投资回报,这是一个必须通过实证回答的问题。

7.4 指标时间段的缩放

在第三章中,当您学习了如何计算行为指标时,我建议您根据事件的频率对简单指标的指标测量窗口进行缩放,对于更罕见的事件使用更长的测量窗口。这个建议是好的,但它引入了一些问题:

  • 为不同事件选择不同的测量窗口将会令人困惑。

  • 如果您为罕见事件使用较长的测量周期,您必须等待很长时间才能正确观察您的客户。如果您还想制作像第 7.3 节中介绍的那样百分比变化的指标,这个问题会变得更加严重。

本节介绍了通过将一个时间框架的测量扩展到另一个时间框架来解决这两个问题的缩放技术。这些技术与你在第二章中学到的客户流失率缩放类似,但它们在度量指标方面的工作方式略有不同。

7.4.1 将较长的指标缩放到较短的引用周期

使用不同的测量周期来测量不同的指标可能会让人困惑,尤其是如果你有很多事件。当所有测量都在同一尺度上时,比较行为更容易。

警告 报告大量在不同时间尺度上测量的指标会让人困惑。

你如何将这些建议与第三章中建议使用长时间框架来测量罕见事件的建议相协调?很简单:你可以使用不同于你进行测量的窗口的时间尺度来描述你的行为测量。图 7.27 说明了这个概念。本质上,你将描述这个指标作为一个每月的平均计数,而不是多个月份的总计数。

图 7.27 引用长期计数指标作为短期平均数

你可以测量一年期内的行为,例如,但通过将一年内测量的数量除以 12 来将其转换为月度值,因为每月的平均事件数是每年事件数除以 12。这个想法与缩放客户流失度量(第二章)相同,但数学更简单。你不需要任何关于生存率的复杂推理来缩放标准行为指标。这次,技术是直接的乘法和除法。

总结 不要混淆你进行平均行为测量的时间周期与你选择描述行为的时间周期。这两个时间周期不必相同。你可以在一个时间尺度上描述所有你的指标,即使这些指标是在不同长度的时窗内测量的。

公式 7.3 展示了将测量期间内任何时间周期的事件计数(TMeasure)转换为描述行为时任何其他时间周期的平均数所需的乘法和除法。

公式 7.3

将公式应用到简单的年度测量与月度描述的例子中,比率是四周描述周期除以 52 周测量周期,或者说是十三分之一。或者如果你喜欢用天数来计算,那就是 30.4(平均每月天数)除以一年中的 365 天,或者说是十三分之一。记住,无论这些单位是天数、月份还是年,你都必须使用相同的单位来描述描述和计数的时间周期。

更好的是,你可以在测量时即时计算缩放吗?列表 7.7 展示了一个按周期行为测量的事件(如列表 3.2),该事件自动将测量扩展到月度描述周期的平均值。

公式 7.3 也适用于事件属性总和的指标,例如在某个活动上花费的总时间。但缩放不是必要的;实际上,如果指标是事件属性的平均值,那么缩放是不正确的。例如,如果事件是在应用内购买,并且你想制作平均购买金额的指标,这个指标不依赖于你测量时间的长度,因为事件属性的平均值被定义为每个事件的测量,即使你可以测量更长的时间框架。

警告:不要对事件属性值的平均值进行时间缩放。只有计数或总和的指标应该进行缩放。

您应像往常一样使用 Python 包装程序运行列表 7.7(使用参数 —chapter 7 —listing 7)。图 7.28 显示了典型结果。

图 7.28 列表 7.7 的示例输出

列表 7.7 缩放每个账户的事件数量指标

WITH date_vals AS (                                         ①
     SELECT i::timestamp AS metric_date 
     FROM generate_series('2017-12-31', '2017-12-31', '7 day'::interval) i
)
SELECT account_id, metric_date,  COUNT(*) AS total_count,
(28)::float/(84)::float * COUNT(*) AS n                     ②
FROM event *e* INNER JOIN date_vals d
ON e.event_time <= metric_date 
AND e.event_time > metric_date - interval '84 day'          ③
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
WHERE t.event_type_name='unfriend'
GROUP BY account_id, metric_date
GROUP BY account_id, metric_date;

① 此 SQL 与列表 3.2 大致相同。

② 计数已从 84 天缩放到 28 天周期(公式 3.1)。

③ 84 天内的计数

注意,列表 7.7 包含一个用于说明目的的总计数列。要将此类指标插入数据库,您需要删除该列,用指标名称 ID 替换它,并添加一个 INSERT 语句。像往常一样,存储库还包含一个可插入的指标版本,您可以通过在执行命令中添加 —insert 标志来运行它,如下所示:

fight-churn/listings/run_churn_listing.py —chapter 7 —listing 7 —insert

在将列表 7.7 中的 unfriend_per_month 指标插入数据库后,执行以下步骤以检查您的结果:

  1. 通过重新运行列表 7.2 并使用以下参数来重新生成数据集:—chapter 7 —listing 2

  2. 通过重新运行列表 5.2 并使用以下参数来重新运行数据集摘要统计:—chapter 5 —listing 2 —version 1

图 7.29 展示了原始 unfriend_ per_month 指标和新的平均每月 unfriend 指标(在 84 天内测量,unfriend_28day_avg_84day_obs)的典型结果。平均覆盖了具有非零测量的 50% 的账户,但原始计数仅覆盖了 26% 的账户。同时,平均值和百分位数相似,不是三倍之大,因为指标是在三倍长的时间框架内测量的。实际上,新的指标略低:不仅平均值较低,而且分布的分位数也较低。想想这是为什么。(你将在第 7.4.2 节中找到答案。)

图 7.29 比较在短时间和长时间期间测量的罕见模拟事件的统计数据

使用比描述期更长的观察期来衡量指标的好处之一是,这种方式估计的指标在客户活动出现暂时性变化时具有鲁棒性。例如,如果你用一个月的时间来衡量某些行为,一个客户休假两周可能会显得活动水平较低。同样,一些客户可能会经历一段短暂而强烈的活跃期。在两种情况下,如果你在三个月的时间里衡量平均行为,这些暂时性的变化就不会产生太大的影响。

但使用长观察期作为指标的一个缺点是,指标不再反映行为的最新变化。使用长观察期时,当行为发生变化时,这种变化在指标中登记的时间会更长。处理这种情况的最佳方式是,在大多数行为中使用长观察窗口指标,并结合一些衡量行为百分比变化的指标。这样,你既有对行为平均水平的稳定估计,也有几个可以迅速揭示最近变化的指标。

在长时间框架内衡量行为指标的一个其他重要问题是衡量新账户。这个问题在任何测量窗口中都存在,但当测量窗口较长时,问题会加剧。如果一个账户只使用了很短的服务时间(低于测量期的任期),那么这个测量是不有效的。假设一个老客户每月只登录一次,因此他们是产品的轻度用户,有流失的风险。一个昨天加入的新客户在过去一个月内也可能只登录过一次,但这并不等同于老客户每月只登录一次。

注意:对新客户进行的事件计数测量与在整个测量期间都存在的正常账户不可比。

这种情况通常适用于在首次续订时进行测量的客户。对于月度续订订阅,你的数据集中客户的首次测量是在续订前两周到三周进行的,这是由于在续订前观察客户所使用的提前期(如第四章所述)。如果你使用四周的指标测量周期,新客户只有整个周期的半到四分之三,他们的指标可能被低估。如果你使用超过一个月的指标观察期,这个问题会加剧。首次续订至关重要,因此你不希望犯这类错误。

7.4.2 为新账户估计指标

如前所述,任何在几周或几个月内测量的指标对于没有使用产品那么长时间的新账户都是无效的。幸运的是,你有一个简单直接的方法来处理这个问题,这个方法与你在 7.4.1 节中学到的平均技术一致:你可以使用比描述平均时间更短的时间段来估计平均数。这种技术与在长时间段计算平均值并将其描述为较短时间段的方法类似,但方向相反。图 7.30 说明了这个概念。

图片

图 7.30 使用短期历史估计长期指标

理念如下:假设一个账户已经使用产品两周,每月有 10 次登录。你不知道它在四周后会有多少次登录,但你可以做出一个合理的猜测——四周后,登录次数应该是两周后的两倍。这个理念可以扩展到将任何较短时间段的测量扩展到估计较长时间段的平均值。

一个重要的注意事项是针对新客户:如果一个账户在第一天登录一次,那么在 28 天后估计它会有 28 次登录是否合理?表面上看起来是合理的,但实际上并不合理。问题在于,如果你从一个很短的时间段开始估计,比如一天,那么估计将是不稳定的,并且会波动。假设一个客户在第一天登录一次后,你估计他每月有 28 次登录。但在第二天,客户没有登录,所以你的估计变成了 14 次。(两天登录一次意味着 28 天会有 14 次登录。)这种活动在一天之内对 logins_per_month 指标的估计产生了很大的波动。这种波动性在仅基于几天数据的估计中是正常的;通常至少需要 5 到 10 天的数据,这种估计才会稳定下来。在第三章中,你了解到大多数客户行为遵循每周周期,所以一般来说,你应该使用以下规则:

  • 在观察到至少一周的行为,最好是两周的行为之前,不要对一个月的平均数进行估计。

  • 同样,如果你正在制作平均数来估计季度或年度的计数和总计,你应该在观察到感兴趣的行为至少一个月之后才估计指标。

公式 7.4 提供了为新账户估计计数指标所需的数学和逻辑,这还包括你在 7.4.1 节中学到的平均计数缩放。公式 7.4 中的 Count Tmeasure 指的是实际的事件跟踪计数,公式 7.3 中有三个时间周期参数:

  • Tmin 是账户获得此指标估计的最短期限(对于月度指标是一到两周,对于年度指标是两到三个月)。

  • Tdescribe 是用来描述平均时间周期(四周)的。

  • Tmeasure 是用于测量的时间周期(对于老客户)。

在公式 7.4 中,根据订阅者任期有三个情况:

  • 如果任期小于 Tmin,则不计算任何指标值。

  • 如果任期大于 Tmin 但小于描述指标的时期,计数将按描述期与账户任期的比率进行缩放。这个计数是估计的平均值。

  • 如果任期大于描述期,计数将按描述期与测量期的比率进行缩放。这个计数是在更长的时间内计算的平均值。

如果任期 < Tmin

图片

Else If Tmin <= tenure <= Tdescribe

图片

Else

图片 公式 7.4

公式 7.4 的第三个情况与公式 7.3 相同。此公式在第二个情况下添加了逻辑,使用账户任期。

列表 7.8 提供了实现公式 7.4 作为指标的 SQL 代码。这个指标与之前看到的任何指标都略有不同:它使用账户任期指标(假设已保存在数据库中),同时,它还对事件进行计数。保存的账户任期指标定义了新事件计数指标将计算日期序列,任期值进入逻辑和缩放。

你可能预期在列表 7.8 中会有一个 IFCASE 语句来实现公式 7.4 中的逻辑。相反,这种逻辑在两个不同的地方实现:

  • 对于任期低于最小值的账户,应该没有结果的情况是通过 WHERE 子句约束实现的,即任期指标值必须高于最小值。

  • 通过在缩放公式的分母中使用 LEAST 函数来实现任期低于描述期和高于描述期的情况之间的差异:

    • 当任期低于描述期时,它是 LEAST 函数的结果,任期是缩放的分母(第二种情况)。

    • 当任期高于描述期时,描述期是 LEAST 函数的结果,描述期是缩放项的分母。

只要描述期长于最小任期,这种逻辑就会起作用,这在使用此类指标时应是这种情况。

列表 7.8:使用新的账户估计的缩放计数指标

SELECT m.account_id, metric_time, 
    m.metric_value AS tenure_metric,                              ①
    COUNT(*) AS count_unscaled,                                   ②
    (28/ LEAST(84,m.metric_value))  AS scaling,                   ③
    (28/ LEAST(84,m.metric_value))  * COUNT(*) 
        AS message_permonth_84day_avg                             ④
FROM event *e* INNER JOIN metric m                                  ⑤
    ON m.account_id = e.account_id                                ⑥
    AND event_time <= metric_time
    AND event_time >  metric_time-interval '84 days'              ⑦
INNER JOIN event_type t ON t.event_type_id=e.event_type_id
INNER JOIN metric_name  n ON m.metric_name_id = n.metric_name_id
WHERE t.event_type_name='unfriend_per_month'                      ⑧
    AND n.metric_name='account_tenure'                            ⑨
    AND metric_value >= 14                                        ⑩
GROUP BY m.account_id, metric_time, metric_value                  ⑪
GROUP BY m.account_id, metric_time, metric_value

① 选择先前计算的任期指标

② 计算事件数量

③ 计算缩放乘数(公式 3.2)

④ 缩放乘以原始计数

⑤ 内连接仅计算有任期的账户的指标。

⑥ 在账户 ID 上进行连接

⑦ 限制事件到适当的时间范围

⑧ 计算指标的相应事件

⑨ 账户任期的指标 ID

⑩ 设置计算账户最低任期的最小值

⑪ 包含了 SELECT 语句的非聚合部分(必需)

您应使用 GitHub 仓库中 Python 包装程序的代码,通过参数 —chapter 7 —listing 8 运行列表 7.8 来计算 unfriend_per_month 指标的新缩放版本。图 7.31 展示了在默认模拟数据集上列表 7.8 的典型输出。请注意,列表 7.8 除了最终的指标值外,还输出计数和缩放因子以供说明。图 7.31 还说明了落入方程 7.4 不同情况的账户:

  • 一个较老的账户(ID 21)以 58 天的任期开始,这超过了描述期,但低于测量期。缩放因子始终低于 1.0,并且在任期大于 84 天的测量期后达到最小值 0.33。

  • 当账户 12371 达到 14 天的任期时,它出现在结果中;此时,缩放因子为 2.0,以从 14 天的数据中估计 28 天的平均值。随着任期的增加,缩放因子下降。在 28 天的任期时,缩放因子为 1.0;此时,指标相当于精确的 28 天计数。在任期超过 28 天后,缩放因子低于 1.0。

图片

图 7.31 默认模拟数据集上列表 7.8 的样本输出

要将指标保存到数据库中,您需要删除未缩放的计数和缩放列,然后在 INSERT 语句中提供指标名称 ID。具有这些更改的列表版本在 GitHub 仓库中,可以通过在脚本可执行语句中添加 —insert 参数来运行。您应采取以下步骤:

  1. 使用参数 —chapter 7 —listing 8 —insert 将列表 7.8 的结果保存到数据库中。

  2. 通过重新运行列表 7.2 并使用参数 —chapter 7 —listing 2 来重新生成数据集。

  3. 通过重新运行列表 5.2 并使用参数 —chapter 5 —listing 2 —version 1 来重新运行数据集汇总统计。

图 7.32 展示了汇总统计的典型结果。汇总统计显示,新的指标(标记为 unfriend_28day_avg_84day_obs_scaled)与第 7.4.1 节中教授的 12 周期指标(unfriend_28day_avg_84day_obs)具有相同的账户覆盖率,但指标值略高,总体上与简单的计数指标更匹配。原因是未缩放的指标通过使用较长的观察期来增加覆盖率,但没有纠正并非所有账户都有足够的任期来覆盖该观察期的事实。新的指标通过对新账户进行缩放来纠正这种情况。

图片

图 7.32 比较在短时间和长时间期间测量的罕见模拟事件的统计

由于你有了最终的每月取消好友数指标,你也应该重新计算它所使用的比率指标——每新增好友取消好友数——并重新检查群体分析。以下是要使用的程序参数的附加版本:

  1. 计算一个新的每新增好友取消好友数指标 (—第七章 —列表 1 —版本 7 —插入)。

  2. 通过重新运行列表 7.2 (—第七章 —列表 2) 来重新生成数据集。

  3. 通过重新运行列表 5.1,版本 14 和 16 (—第五章 —列表 1 —版本 14 16) 来运行每月取消好友数和每新增好友取消好友数的群体分析。

现在你已经了解了基于账户期限的缩放指标,你可能期待一个案例研究来展示这种新技术在公司中的应用。我很抱歉让你失望,但我没有新的公司案例研究可以添加——因为书中所有的公司案例研究都使用了这种类型的缩放指标。我直到现在才提到这个事实,因为在你学习所有其他技术之前,这会是一个过多的信息量。

我总是使用类似于列表 7.8 的指标来对我的案例研究,因为这些指标具有许多优点,包括对账户的最高可能覆盖率以及稳健的指标估计,而不会牺牲对新账户的最佳可能估计。唯一的适度缺点是指标计算稍微复杂一些,这意味着你告诉你的业务人员这是一个平均值(而不深入细节)。

总结:要理解客户流失,你应该使用比描述期更长的观察期和缩放来对新账户的平均值进行可比估计的平均指标。简单的计数指标仅应用于衡量合同数量的使用,在这种情况下,合同期间的准确计数很重要。

我根据产品主要使用月度或年度订阅,使用以下标准指标进行客户流失研究:

  • 对于月度订阅,使用以下参数的缩放计数指标:

    • Tmin = 14 天(2 周)

    • Tdescribe = 28 天(4 周)

    • Tmeasure = 84 天(12 周)

  • 对于年度订阅,使用以下参数的缩放计数指标:

    • Tmin = 28 天(1 个月)

    • Tdescribe = 28 天至 84 天(4-12 周,1 个月至 1 季度)

    • Tmeasure = 365 天(1 年)

7.5 用户指标

你应该了解的最后一个行为度量领域是如何处理具有多个用户的产品。这些产品包括企业软件的多座位许可证和消费者产品的家庭计划。

7.5.1 测量活跃用户

关于多用户产品,首先要知道的是,仍然最好在订阅或账户级别理解客户流失,因为所有用户共享一个订阅;如果订阅未续订,所有用户将一起流失。

注意:在多用户产品中,当个别用户变得不活跃时不会发生客户流失。

如果你对分析用户健康感兴趣,你可以通过修改第四章中基于活动流失分析的技巧来执行用户活动和不活动的分析。目标仍然是理解账户层面的流失,而不是用户层面的流失,并利用有关单个用户行为的信息。

要了解单个用户行为如何影响流失,首先要回答的一个重要问题是活跃用户有多少。这个问题可以通过基于事件的指标来回答,如图 7.33 所示。这与在时间段内计数事件以创建指标类似,但不同之处在于,你计数的是产生事件的唯一用户数量。要程序化地计数活跃用户,你必须将用户标识存储在数据库或数据仓库中的事件中,这比标准事件表模式(表 7.1)需要额外的一个字段。你可能还记得第三章中提到,通常事件可以包含带有附加事件信息的可选字段,所以这种情况并不太不同。

表 7.1 带有用户 ID 的事件表

类型
account_id 整数或字符
event_type_id 整数或字符
event_time 时间戳
user_id 整数或字符

图 7.33 从事件计算活跃用户数量

列表 7.9 显示了一个简短的 SQL 程序,它将活跃用户数量作为一个指标进行计数。这个列表实际上与第三章中的简单事件计数指标几乎相同,但有一个关键的区别:不是计数事件的数量,而是对唯一用户 ID 的数量进行聚合。这个指标与标准事件计数指标之间的另一个区别是,这个指标没有指定事件类型:任何事件都表示用户活动。(当然,这个选项是可用的,如果你只想从某些事件中确定用户活动,这种改变很容易实现。)

通过对用户 ID 进行DISTINCT聚合,计算活跃用户数量是很容易做到的。

活跃用户计数指标与事件计数指标之间的一个微妙区别是,活跃用户的计数不应该按任期或与任期或测量周期有关的内容进行缩放。唯一活跃用户的数量是一个不按这种方式缩放的聚合指标示例。如果一个账户在四周期的前两周有两个活跃用户,并不意味着四周内会有四个活跃用户。从数学上讲,DISTINCT聚合不是可加的,就像COUNT聚合一样。

列表 7.9 计算活跃用户数量

WITH date_vals AS (                                       ①
     SELECT i::timestamp AS metric_date
     FROM generate_series('2018-12-01', '2018-12-31', '7 day'::interval) i
)
SELECT account_id, metric_date, 
    COUNT(DISTINCT user_id) AS n_distinct_users           ②
FROM event *e* INNER JOIN date_vals d
ON e.event_time <= metric_date                            ③
AND e.event_time > metric_date - interval '84 days'
GROUP BY account_id, metric_date                          ④
GROUP BY metric_date, account_id;

① 此 CTE 定义了用户将被计数的日期。

② 使用COUNT DISTINCT聚合计算用户数量

③ 使用SELECT限制查询为任何 12 周内的活动。

④ 使用GROUP BY来按账户级别测量用户数量。

GitHub 上的默认流失模拟不包括用户,但可以将模拟框架扩展以包括用户。如果你对这个主题感兴趣,可以考虑将框架以这种方式扩展作为练习。

7.5.2 活跃用户度量

图 7.34 展示了测量 Klipfolio 活跃用户数量的案例研究。该产品以多座位许可证的形式销售,因此有一个活跃用户数量的度量,如列表 7.9 所示,还有一个销售座位数的度量,使用第三章中描述的单位数量度量模式。图 7.34 显示,活跃用户数量与流失之间存在强烈的关联——这种模式现在应该很熟悉:在 1 到 4 个活跃用户之间,流失率迅速下降,但随后风险下降的速度放缓,对于拥有数十个或更多用户的客户,流失率几乎没有差异。同时,许可证用户数量似乎与流失没有强烈的关联。

许可利用是一个定义为用户数量与允许的最大用户数量之比的度量。有时,用户数量是通过创建用户账户来测量的,但对我来说,为了衡量流失,我更喜欢通过将活跃用户数量除以许可证用户数量来形成一个比率来衡量实际或活跃的许可证使用情况。图 7.34 还展示了 Klipfolio 按这种方式定义的许可证利用率的流失 cohort 分析。许可证利用与流失之间存在强烈的关联——比单独的活跃用户更强烈。随着许可证利用率的增加,流失风险的降低对于每个 cohort 来说相当连续。显然,许可证利用率是一个衡量客户参与度的有用度量。

总结:许可利用是活跃用户数量与允许用户数量的比率,通常是对用户或座位销售的产品的参与度的重要衡量指标。

图 7-34

图 7.34 Klipfolio 每月活跃用户度量指标的客户流失 cohort 分析

图 7.34 展示了另一种用户度量类型,通过 Klipfolio 案例研究中的另一个例子来说明:每月每个用户的仪表板查看次数。这个比率是由每月仪表板查看次数和活跃用户数量测量的。这种类型的许多度量都是可能的。几乎任何你在账户级别测量的行为都可以通过活跃用户数量来除,从而形成一个每个用户的比率。因为大多数行为的总量与活跃用户总数相关,这种类型的比率可以产生与活跃用户数量、用户整体行为或两者都较少相关的有用度量。

7.6 使用哪些比率

你现在对客户流失和行为中的客户度量以及案例研究的设计和解释了解了很多。在本节中,我将结合几个主题并回答一些常见问题。

7.6.1 为什么使用比率,还有其他什么?

我在本章中花费了大量时间讲解比率指标,而对其他方面的内容投入的时间并不多。我教过你们,比率指标是理解客户行为之间关系的一种很好的方式,但还有其他选择吗?确实有其他选择,但根据我的经验,没有哪一种像比率指标那样有用。如果你有统计学或数据科学背景,你可能听说过交互测量。这个概念与比率类似,但不是通过将一个指标除以另一个指标,而是将两个指标相乘。

图 7.35

图 7.35 Klipfolio 仪表板每个用户查看的流失率分析

定义 交互指标是两个其他指标的乘积(乘法)。

交互测量,就像比率一样,是理解行为之间关系的一种方式。实际上,在经典统计学中,这种方法是理解测量之间关系的主要方法。例如,要有一个高交互项的测量值,你必须在这两个基础指标上都有高值。就像比率一样,任一指标为零意味着交互项为零。

交互项在计算机科学中有点像“与”操作。如果你有计算机科学背景,你可能正在想,你可以使用布尔运算,比如“当两个指标都高于某个特定水平时分配 1”。你可以将乘法交互视为一种更细腻的替代方案。当两个指标都高时,交互项不仅仅是01;它测量两个指标的实际高度。当应用于可以取负值的分数或指标时,交互项也有有趣的统计特性,因为当任一指标或另一个指标为负时,交互测量值将变为负值。

如果交互测量在统计学中如此有趣且被广泛使用,你可能想知道为什么我不推荐它们用于客户流失分析。简单的答案是,商业世界中没有人理解交互项,而比率则容易理解。也就是说,由两个其他指标相乘得到的指标,与比率相比通常具有不直观的单位。支付金额与通话次数(或观看视频数量等)的比率是每次通话(或视频等)的支付金额,但支付金额与通话次数(或任何行为)的乘积并没有一个常规的意义。

如果你上过物理课,你可能还记得不同类型数量的乘法,例如质量(千克)和距离(米),会产生组合单位,如千克米。通常,这些单位并不太容易理解。(什么是美元的称呼?)证明规则例外的情况是当一个度量是时间,另一个度量是强度度量时,比如电力销售中的千瓦时。我从未找到过解释为什么比率单位容易理解而乘法单位不容易理解的认知研究,但这个事实从每个人的经验中都很明显。我的建议是,只有在你已经有了一个明显意义的业务案例时,才使用乘法交互度量。

另一个数据科学家和统计学家熟悉的选择是从得分(减法)而不是比率(自然尺度度量)中创建度量。在第六章关于主成分分析(PCA)的侧边栏中,我指出 PCA 隐式地进行了这样的减法。想法是,如果你想了解两个度量之间的关系,你可以通过从其中一个减去另一个来查看差异,就像取比率一样。如果度量针对的是不同的事物,这种方法并不真正合理。通过从某人使用的电信产品通话次数中减去 MRR 支付的度量,你不会得到一个有意义的度量,但在将度量转换为无单位得分后这样做是可以的。你在第六章中看到了这个技巧被用来取平均组:得分显示某人是否高于平均水平(大于零)、平均水平(接近零)或低于平均水平(负数)。

通话得分与 MRR 得分的差异是衡量与支付金额相关的通话倾向的度量。因此,度量得分之间的差异可以像比率一样工作,但问题再次在于解释。你可以告诉你的业务人员你为每通电话的美元制定了度量标准,他们认为那很好;但解释从每月通话得分减去 MRR 得分的度量标准并不那么容易。当涉及到能够捕捉两种行为之间关系的可理解度量时,比率是唯一的选择。唯一的例外是如果你有理解不那么直观方法的业务技术人员。

7.6.2 应使用哪些比率?

我希望到这一点,你已经相信比率度量对于理解不同行为之间的关系以及它们与客户流失和客户参与度之间的关系是有用的。现在是时候更广泛地考虑要调查哪些比率了。

首先,要注意并非所有指标都能形成有用的比率。一个要求是,对于大多数客户来说,这两个指标都应该不为零(它们应该没有负值,这不太成问题)。一个简单的标准是,是否有很多客户在这两个指标上都有非零值。即使你发现与活跃度和参与度有强烈的关系,适用于少数客户的指标也不如适用于大量客户的指标有用。

在典型的数据集中,有很多事件类型可以定义比率。如果你熟悉概率和组合数学,你可能还记得,从 N 个项目中选择的可能配对数量是 N × (N - 1)。如果你有 N 个可能的指标,你可以用 N 个不同的指标选择比率的分子,剩下 N - 1 个用于分母。这样就有很多组合,这又提出了一个额外的问题:哪个指标应该放在分子,哪个应该放在分母?首先重要的是要意识到,你不应该尝试每一个可能的组合。

警告:不要为可能的比率创建每一个可能的指标对来检查与活跃度的关系。

原因在于,通常情况下,组合方式太多,其中大多数都没有意义。即便如此,通过检查大量指标,你仍然可能会发现一些虚假的关系,从而影响活跃度和参与度。

定义:一个指标与结果之间的虚假关系是指由于随机机会发生的关系,而不是由于可重复的因果关系。因此,这种关系不太可能再次发生。

如果你刚开始接触数据分析,可能会觉得奇怪,因为你可能会发现数据中存在某种并非真实的关系,但这个问题在数据科学中是众所周知的。如果你检查足够的指标,最终你可能会发现一些看似相关但实际上并不相关的指标。可以通过使用严格的准则来判断关系是强还是弱来解决这个问题,这是第八章的主题。但最好的做法是,一开始就不考虑那些看起来并不直观的关系。

要点:你主要考虑那些对业务人员来说直观的比率指标。

正如你所看到的,没有一条规则总是适用;你需要运用你对情况的知识。对于哪个指标应该放在分子和分母的问题,答案也是一样的:哪个更有意义。你可以尝试以下两种方法:

  • 有时候,当两个指标位于一组相关活动的不同部分时,可能会存在有趣的关系(和良好的比率指标)。尝试查看相关组中最常见指标的比率(如第六章所述);忽略那些不太常见的组员。

  • 有时,不同活动领域之间存在有趣的关系。尝试测试一个相关组中最常见指标与另一个相关组中最常见指标的比率。再次,不必担心任何不太常见的指标。

表 7.2 总结了本章中涵盖的参与度和流失最常见的情况。

表 7.2 客户参与度比率指标摘要

名称 比率 相关性 信息
单位成本 MRR/使用 在更昂贵计划上的客户通常使用产品更多。 单位成本显示与其他客户相比,每单位价格是高还是低。
单位价值 使用/MRR 在更昂贵计划上的客户通常使用产品更多。 单位价值显示使用相对于支付的价格是高还是低。
利用率 使用/允许 在允许大量使用的计划上的客户通常使用得更多。 利用率显示使用是否接近限制。
成功率(或效率) 成功次数/总尝试次数 尝试很多事情的客户通过纯粹的坚持而更成功。 成功率显示客户在活动中的相对成功或效率。
总计百分比 部分/整体 假设某些活动属于相互排斥的类别:使用产品较多的客户在所有类别中都使用得较多。 总计百分比显示客户在类别中的相对高低,除了整体使用水平之外。
百分比变化 (当前指标/过去指标) -1.0 如果客户现在使用产品很多,他们可能过去也使用得很多。 百分比变化显示使用相对于客户自己的历史是高还是低。

许多有趣的比率指标没有列在表 7.2 中,所以请将此表视为说明性的,而不是详尽的。但这个表应该足以让你开始。

摘要

  • 从其他指标的比率中创建的指标可以揭示行为平衡与流失和参与度之间的关系。

  • 比率指标通常与分子和分母指标的相关性低于这些指标相互之间的相关性。

  • 重复单位成本指标是使用产品的一些成本(如支付 MRR 或观看广告)与使用产品的结果(如打电话或查看内容)之间的比率。

  • 流失率通常随着重复单位成本指标值的增加而增加,即使非单位重复成本指标本身(纯 MRR 或广告数量)没有显示出增加的流失率。

  • 在某个过程中的下游事件与上游事件的比例可以被视为一个效率指标。例如,包括每客户的交易次数或每份文档编辑的节省量。

  • 过程完成或成功结果与尝试次数的比例是成功率。接受请求率是此类指标的例子。

  • 流失率会随着效率和成功率指标值的增加而增加或减少,具体取决于业务的特征。

  • 总比率中的百分比是一个特殊的比率情况,其中分子是分母表示的总体总量的部分。例如,包括对不同地区拨打电话的百分比和在不同类别(动作、喜剧、戏剧等)中观看的节目百分比。

  • 总比率中的百分比指标可以用来理解不同类别中行为平衡与所有类别水平相关时,与流失率和参与度之间的关系。

  • 百分比变化指标是某个时间段内指标值变化的比率与该时间段开始时指标值的比率。例如,从一个月到下一个月登录次数的百分比变化。

  • 百分比变化指标可以用来分析任何行为的增加或减少是否预示着未来的流失率和参与度。

  • 自上次事件以来经过的时间是理解不活跃期与未来流失率之间关系的指标。

  • 当产品跟踪多个用户 ID 时,可以测量活跃用户。

  • 活跃用户可以用来形成各种比率指标,例如许可证利用率,这是活跃用户数与允许用户数的比率。

  • 任何在时间段内测量的计数指标都可以描述为较短时间段的平均值或较长时间段的估计值。

  • 单个缩放指标可以结合新账户的估计值和成熟账户的平均值。这种方法是计算用于分析流失率和参与度的计数和总指标的最佳方式。

第三部分. 特殊武器和策略

我将这一部分的技术称为“特殊”武器和策略,因为并非所有公司都需要使用它们。然而,在我看来,所有与客户流失作斗争的公司都需要一套优秀的客户指标。对于受过数据科学训练的人来说,这可能会感到惊讶,因为这一部分的主题包括大多数人认为的数据科学核心:预测!但在第一章中,我已经解释过,客户流失是不同的:预测客户流失只有少数用例,而优秀的客户指标有更多的用例。尽管如此,预测可以成为您武器库中的重要武器,它具有针对客户流失的独特之处。

如果您之前从未从事过任何预测分析工作,您可能会发现第八章和第九章的学习曲线很陡峭。尽管如此,这些章节确实涵盖了所有基础知识,我认为任何学习过第一部分和第二部分技术的人都可以掌握第三部分的技术。但是,如果您在预测分析方面没有经验,您可能需要投入更多的时间,并使用一些推荐的在线资源。

第八章教您如何使用逻辑回归预测客户流失概率。使用这项技术,您可以看到所有影响流失的因素的综合影响,并按重要性对它们进行排序。回归分析还能为您提供预测结果,您可以用它来计算客户终身价值。

第九章介绍了如何衡量客户流失预测的准确性;对于客户流失,常规规则不适用。这一章节还介绍了机器学习,您可以使用它来获得最准确的预测。但您也会看到,在第二部分中您辛苦工作的优秀客户指标开始对预测准确性产生回报。

第十章是关于在对抗客户流失中使用人口统计和企业统计的独立章节。您无法改变客户成为他们不是的人,但您可能能够找到更多与您最好的客户相似的客户。这一章节将向您展示如何做到这一点。

8 预测流失

本章涵盖

  • 使用逻辑回归预测客户流失的概率

  • 理解不同行为对流失的相对影响

  • 检查你预测的校准情况

  • 使用客户流失预测来估算客户生命周期和生命周期价值

到目前为止,你已经知道了分析流失和设计优秀客户指标所需的所有步骤。这些指标将允许商业人士进行有针对性的干预,以减少他们产品的流失。对于大多数产品来说,这些事情是最重要的,这就是为什么从本章(本书的第三部分)开始的技术可以被认为是特殊或额外策略:如果你需要,你可以使用它们,但它们并不总是必要的。在对抗流失的最重要的事情是,在细分客户和进行有针对性的干预时,业务应该基于数据驱动决策。

本章致力于预测客户流失的可能性,这是基于所有行为的组合。到目前为止,我已向你展示了如何通过查看队列图中的流失率来一次评估一个客户行为,以评估客户健康。但如何整合这些多个客户视角?如果一个客户在一个行为低流失风险的高队列中,但在另一个行为高流失风险的低队列中,该怎么办?考虑到客户行为之间的通常相关性,我所描述的是一个边缘情况,但了解如何处理它仍然是有益的。

一个值得了解答案的相关问题是哪种行为对客户健康和流失影响最大。不同行为的相对重要性在决定追求哪些减少流失的干预措施或产品修改时可能是一个重要的信息点。到目前为止,你只知道如何通过比较队列图中的关系来定性分析这些信息。这种方法适用于少数指标(或指标组),但如果你有很多指标,你需要一个更系统的方法。本章将教你如何使用被称为逻辑回归的统计模型来回答这些问题。

定义 逻辑回归是一个统计模型,它根据可能影响结果的多因素预测事件发生的概率。

逻辑回归经常用于评估医疗数据以发现疾病的原因。它适用于流失,因为你正在发现良好和不良客户健康的原因。因为逻辑回归是本书中唯一涵盖的回归模型,所以我有时会简称为回归。本章的设计如下:

  • 第一部分(第 8.1 节)展示了逻辑回归背后的概念。

  • 第 8.2 节回顾了你在整本书中使用的所有数据准备步骤,以确保你准备好运行逻辑回归算法。

  • 第 8.3 节向您展示如何运行回归算法并解释结果。

  • 第 8.4 节教您如何使用您创建的模型进行预测。

  • 第 8.5 节解释了您在过程中可能遇到的一些陷阱和问题。

  • 最后一个部分(第 8.6 节)致力于客户生命周期估计和衡量客户生命周期价值,这些都是从客户流失概率预测中得出的。

8.1 使用模型进行客户流失预测

对于不熟悉逻辑回归理论的读者,我将从逻辑回归理论概述开始。这些解释是针对预测流失和保留的具体案例。

8.1.1 使用模型进行概率预测

当我提到客户流失或保留概率预测时,我指的是为每个客户单独做出的估计,就像一个指标。但与指标不同,流失概率预测不是对已经发生的事情的测量:它是对未来可能发生的事情的概率估计,即流失。

定义 对于客户的流失概率预测是指,如果您有一个具有相同预测的客户群体,您期望根据概率给出的百分比流失。流失概率预测永远不会告诉您单个客户是否一定会流失或不会流失。

由于预测是针对特定客户的,就像一个指标一样,预测概率的定义并不是关于单个客户是否会保留或流失,这有点反直觉。理解对于单个客户来说,事情总是可能向两个方向发展。尽管我大多数时候不会提到这个事实,但预测中隐含了一个时间范围。更准确地说,预测是客户在下次续订前(在第四章中定义)的领先时间内流失的概率。这种时间范围是隐含的,因为历史数据集就是这样设计的,而这个数据集将被用来确定预测。

预测是对一组相似客户行为的陈述。即使预测一个客户有 99%的流失概率,这并不意味着他们一定会流失。这意味着在 100 个这样的客户群体中,您预计有 99 个在下次续订前的领先时间内会流失。重点是,如果您关注的是单个客户,这个客户可能最终会留下来。(如果您预测 99%的流失概率,您的数据可能存在问题;参见第 8.5 节。)同样的情况也适用于保留:如果您预测保留概率,并且您有一个假设的客户群体,其保留概率为 90%,您预计该群体中有 90%的客户会保留。

我将用预测保留概率的术语来教您所有内容,因为这种背景更容易理解。客户流失概率将从 100%减去保留概率得出。我将在第 8.1.2 节中指出为什么预测保留率更容易。

你将学习如何使用数学预测模型进行预测:逻辑回归。在这个背景下,模型意味着像真实事物一样工作,但我想要警告你不要认为模型是真实的。模型假设客户参与度和留存率以某种方式工作,但它这样做是因为这些假设适用于预测的目的,而不是因为世界就是那样的。不要怀疑。记住,模型是一个与现实紧密匹配的构造,但它的每一部分并不都是真实的,而且不是每一部分都需要完美,整个模型才有用。

8.1.2 参与度和留存率概率

逻辑回归在预测留存率中的第一个概念是,增加客户参与度会导致留存概率的增加。我所说的参与度是一种无法衡量的主观状态。不同的人有不同的想法,但以下是我对参与度的操作定义。

定义:参与度是一种参与和承诺的状态。

在这次讨论中,参与度意味着产品的使用,承诺意味着续订的可能性。更多的参与度应该导致更高的留存率是有道理的。现在,不要担心参与度本身是一个主观状态。

预测流失和留存的关键特征是,参与度和留存率之间的关系受到参与度效应递减的影响。即使是最活跃的客户也有可能流失。一个后果(或要求)是,客户越活跃,额外的参与度对进一步增加留存概率的影响就越小。相反也应该是真的:客户越不活跃,他们被留存的概率就越低。但即使是最不活跃的客户仍然有被留存的可能。此外,客户越不活跃,进一步减少参与度对留存概率的影响就越小。图 8.1 说明了这个概念。

在参与度和留存率的高端和低端之间的递减关系形成了一个类似于图 8.1 所示的 S 曲线。参与度中间范围的概率变化率必须达到峰值,而在极端情况下则趋于平坦。

图 8.1

图 8.1 使用 S 曲线映射参与度到留存率

8.1.3 参与度和客户行为

在我解释参与度和留存率之间的关系时,我把客户的参与度比作其他任何行为指标一样,是可以衡量的。这个定义对于模型来说很好,因为按照我的框架,参与度是留存和流失的潜在驱动因素。但参与度仍然不可衡量。逻辑回归就是这样解决这个问题的:你相信参与度存在,即使你不能衡量它,所以你假设参与度可以从行为测量和流失观察中估计出来。

TAKEAWAY 客户参与度无法直接测量,但你可以通过匹配观察到的流失与 S 曲线来预测流失概率,从客户指标中估算它。

如果这个解释看起来像是循环推理,你必须接受它是一个模型;你很快就会看到它是有效的。估算客户流失和保留概率的过程包括估算每个客户参与度的中间步骤。模型假设参与度测量采取类似于指标得分的形式。参与度估算将是数字,大多数在-4 到 4 的范围内,并且客户的平均参与度将被设定为零。就像指标得分一样,正数表示高于平均的参与度,而负数表示低于平均的参与度。这个定义是任意的,因为你无法测量参与度,但它对于做出预测是方便的。

图 8.2 说明了从行为估算参与度并将其转换为保留概率估算的模型。关键概念是每个行为指标得分乘以一个参与度强度,我称之为权重,它捕捉了行为对参与度的贡献程度。权重也可以是负数,表示该行为与脱钩和增加的流失而不是保留相关。整体参与度是每个行为的贡献之和。

TAKEAWAY 客户参与度是通过一个模型估算的,该模型将参与度权重乘以每个指标的得分,然后将它们加在一起。

图 8.2 中的预测模型包括以下步骤:

  1. 从所有指标的得分开始。

  2. 假设每个指标都有一个权重,表示指标得分值对参与度的贡献强度。

    • 每个指标得分的参与度贡献是该指标参与度权重乘以得分值。

    • 总参与度估算是从每个指标的所有贡献中求和。

  3. 在计算出总参与度后,通过应用 S 曲线到参与度估算来获取保留概率。

图片

图 8.2 从指标估算参与度和流失概率

这些权重也被称为系数,因为系数是一个乘以另一个数的数字。Python 包使用术语系数,但我更喜欢权重,因为它是一个更功能性的描述(与与商人用普通英语交流的目标保持一致)。

这种从行为估算参与度的方法绕过了参与度无法测量的问题,假设参与度遵循一个简单的模型。但新的问题是,你不知道你的各种指标的参与度权重应该是多少。我建议你用一个你无法测量的东西来替换另一个你无法测量的东西——行为的参与度权重。

如果这种方法看起来像是作弊,我想再次提醒你(再次)这种情况并不是真实的;这是一个将要像真实情况一样工作的模型。寻找参与度权重的问题是由逻辑回归算法本身解决的(见第 8.3 节)。但在你学习如何运行算法并找到权重之前,你需要更多地了解如何将预测模型与你的数据细节相匹配。第 8.1.4 节展示了回归模型如何精确地匹配客户的流失率。

8.1.4 偏移量匹配观察到的流失率到 S 曲线

关于模型的一个重要细节是了解 S 曲线如何匹配具有特定流失率的产品。首先,回忆一下,每个指标的平均值是 0,因为这就是指标得分是如何定义的。因此,一个完全平均的用户在模型中具有零参与度。因为参与度是通过将权重乘以得分得到的,而所有得分都是 0,所以参与度也必须是零。图 8.3 说明了这个概念。

如图中所示,S 曲线的默认版本将与 0 参与度匹配 50%的保留概率(以及 50%的流失概率),因为它是对称定义的。但这个结果只有在产品的平均用户真的有 50%的保留概率时才是正确的。必须有一种方法来调整模型,以便平均用户有一个现实的保留和流失概率预测。

这个问题的解决方案来自模型的一个另一个特性。参与度和保留概率之间的 S 关系可以包括一个偏移量,以便平均用户映射到平均保留概率。偏移量意味着 S 曲线相对于默认值向左或向右移动。偏移量也被称为截距,因为偏移量决定了 S 曲线与零参与度线的交点。(截距意味着交点的值。Python 包使用截距这个术语,但我使用偏移量,因为它描述了这个数量做什么,而不仅仅是它是什么。)

在图 8.3 中,假设保留概率约为 90%。在这种情况下,S 曲线必须偏移约 2,以便具有 0(平均)参与度的用户最终具有约 90%的保留概率预测。

TAKEAWAY 逻辑回归模型包含一个偏移量,允许标准的 S 曲线匹配任何平均保留概率。

图 8.3 偏移量匹配 S 曲线到平均保留概率

再次提醒,你可能想知道如何得出这个新变量的正确值。这个问题也由逻辑回归算法解决。因此,参与度权重和 S 曲线偏移量是算法的主要输出,这就是你为客户做出现实的流失和保留概率预测所需的所有内容。

8.1.5 逻辑回归概率计算

现在我已经解释了所有概念,我将向你展示定义数学模型的方程组。像这本书中的其他数学一样,这个数学是为了帮助有数学倾向的人理解而设计的。如果你不是这样的人,不要担心。如果你已经理解了到目前为止解释的概念,无论你是否研究本节中的方程,你都将准备好进行预测。

这些方程使用向量(即数字列表)来表示指标和参与度权重。变量上方的横线表示它是一个向量,因此我将使用 图片 来表示一个账户所有指标分数的向量,以及 图片 来表示所有参与度权重的向量。在这种情况下,账户的参与度 (E) 由方程式 8.1 给出:

图片 方程式 8.1

点(·)表示点积运算,它是两个向量元素逐个相乘后的结果求和。点积是我在解释如何将指标分数与参与度权重结合以获得账户的总参与度时描述的过程。给定参与度,保留概率 P 的模型其余部分为

图片 方程式 8.2

其中 E 代表参与度,off 代表偏移量,S(...) 代表 S 曲线函数。方程式 8.2 中引用的 S 曲线函数由方程式 8.3 给出:

图片 方程式 8.3

注意,方程式 8.3 中的小 e 代表自然对数的底数,即 e ≈ 2.72。但你不需要学习自然对数是什么就能理解方程式 8.3。要理解它,首先记住以下关于指数化(或取数的幂)的事实:

  • 对于任何大于 1.0 的正数,如果你用正数来指数化它,它会变得更大。(关于 e 你需要知道的是,它是一个大于 1.0 的正数。)

  • 如果你用负数来指数化这样一个数,它会变得更小,因为用负数指数化等于用正数指数化的数的倒数:x^(−y) = 1.0/x^(y).

在方程式 8.3 中,x 代表参与度加上偏移量,x 在 e 的指数化变量上有负号。负号反转了指数化的通常效果,使得当 x 变大时,e 项变小,而当 x 为负时,e 项变大。因此:

  • x(参与度)为正且 e 项较小时,分母接近 1,分数也趋向于 1,这对应于当参与度高时保留率达到 100%。

  • x(参与度)为负且e项较大时,分母会变得很大,分数趋向于 0,这对应于参与度低时保留率趋向于 0%。

理解方程 8.3 与使用e和自然对数无关。方程 8.3 使用自然对数的底而不是其他数字的原因,与逻辑回归算法中的技术细节有关,而不是因为它有必要产生 S 曲线。

本节完成了从指标分数到预测保留概率的模型解释,从指标分数到预测保留概率。接下来你需要知道的是如何为指标制定参与度权重以及用于 S 曲线的偏移量。当你对你的数据运行逻辑回归算法时,这项任务由逻辑回归算法处理。

8.2 数据准备回顾

在我向你展示运行逻辑回归算法的细节之前,让我们回顾一下你为生成数据所采取的所有步骤。这次回顾将确保你的数据为接下来的步骤做好准备,并且这些步骤在你的脑海中保持清晰将有助于你在进行概率预测时。

准备的第一步是导出数据集的一个略微修改的版本。在第七章中,你尝试了几种针对罕见事件每月取消好友数的指标版本。为了避免混淆,你现在将导出一个只包含每月取消好友数最终缩放版本的指标数据集版本。在一个真实公司的案例研究中,你也会尝试不同的指标版本,然后为你的数据集选择一个子集。这个最终的数据集还省略了账户期限的测量。在真实服务中,你应该在分析中包含账户期限,但在模拟中它没有意义。因为你已经多次看到数据集的导出(在第 4.5、4.6 和 7.2 的列表中),所以我不会在书中展示这个 SQL。代码在第八章的列表文件夹中,如果你想查看的话。为了匹配本章中展示的结果,你应该使用以下命令行提取数据集的新版本,用于 Python 包装程序:

fight-churn/listings/run_churn_listing.py —chapter 8 —listing 0

图 8.4 显示了从数据库导出后的数据准备所使用的所有函数的总结。步骤如下:

  1. 计算关于数据的一组汇总统计。这些汇总统计保存在一个表中(列表 5.2)。

  2. 使用汇总统计将指标从它们的自然尺度转换为分数。这一步保存了数据集的第二版本,以及用于创建分数的均值和标准差表,以及用于偏斜和厚尾的转换指标(列表 7.5)。

  3. 找到组合在一起的关联指标,并保存一个解释分组的加载矩阵。这个矩阵用于实现分数的平均(列表 6.4)。

  4. 使用加载矩阵创建第三个也是最终的版本的数据集,其中相关的指标分数被平均(列表 6.3;我在列表 6.4 之前讲解了这一点,以便你在创建加载矩阵之前理解它)。

图 8.4 展示了流失率分析和预测的数据准备步骤

列表 8.1 显示了数据准备的所有步骤,如果你还没有完成所有这些步骤,你可以使用列表 8.1 来准备自己的数据。第七章没有明确告诉你重新运行你的统计数据、分数或分组。如果你按照给出的指示执行,这个列表就是为你准备的。运行列表 8.1 会在你的输出目录中创建几个项目:数据集的两个额外版本,以及三个在过程中使用的统计数据和导出参数的表格。

列表 8.1 数据准备列表合并

from listing_5_2_dataset_stats import dataset_stats
from listing_7_5_fat_tail_scores import fat_tail_scores
from listing_6_4_find_metric_groups import find_metric_groups
from listing_6_3_apply_metric_groups import apply_metric_groups
from listing_6_5_ordered_correlation_matrix 
   import ordered_correlation_matrix

def prepare_data(data_set_path='',group_corr_thresh=0.55):
   dataset_stats(data_set_path)                             ①
   fat_tail_scores(data_set_path)                           ②
   find_metric_groups(data_set_path,group_corr_thresh)      ③
   apply_metric_groups(data_set_path)                       ④
   ordered_correlation_matrix(data_set_path)

① 找出分布的平均值、偏度和分位数

② 将数据从指标转换为分数

③ 找出哪些指标是相关的,并决定哪些要分组

④ 创建一个将分组指标平均在一起的数据集

如果你还没有执行所有这些步骤,请使用 Python 包装程序和以下参数运行列表 8.1:

fight-churn/listings/run_churn_listing.py —chapter 8 —listing 1 

当你在第七章学习高级指标时,重点是新的指标动机和代码以及测试与流失率的关系。我从未使用第七章的技术展示过所有第七章指标的相关性和指标组的最终结果。图 8.5 显示了加载矩阵的结果,它总结了分组。

图 8.5 展示了在第七章创建的附加指标上运行指标分组算法(列表 6.4)的模拟数据集的结果

如果你使用默认参数在第七章的数据集上运行指标分组算法,你应该找到两个多指标组,其中一些指标保持独立。第一个指标组包括三个代表最常见行为的指标:发帖、点赞和查看广告。第二个组平均了包括回复在内的消息指标。那些相关性不足以进入组别的指标是取消好友关系的指标、广告点击数/帖子的高级指标、新朋友天数和新增朋友事件率的变化百分比。

如果运行你的代码返回的结果看起来像图 8.5,那么你就可以运行第 8.3.1 节中的逻辑回归示例了。如果你得到的是其他结果,最可能的原因是你没有在第七章中创建所有新的指标。为了确保你拥有所有指标,你可以使用以下两组参数来运行 Python 包装程序并立即生成它们。

包装程序的第一次运行创建了 days_since_new_friend(自新朋友以来天数)、newfriend_pcnt_chng(新朋友百分比变化)、tenure-scaled version of unfriend_per_month(按任期缩放的每月取消关注版本)以及表示总点赞和踩数(标记为意见数)的指标:

run_churn_listing.py —chap 7 —listing 3 4 6 8 —insert

第二条命令运行所有版本的比率指标以创建 adview_per_post(每篇帖子的广告查看次数)、reply_per_message(每条消息的回复次数)、like_per_post(每篇帖子的点赞次数)、unfriend_per_newfriend(每新增朋友取消关注次数)和 dislike_pcnt(踩数百分比)指标:

run_churn_listing.py —chap 7 —listing 1 —version 1 2 3 4 5 6 —insert

创建所有指标后,你应该能够运行列表 8.1 并获取图 8.5 中所示的加载矩阵。

8.3 拟合流失模型

现在您的数据已准备就绪,您也知道了回归预测模型的工作原理,现在是时候运行算法以获得匹配您数据的权重和偏移量。找到匹配数据的权重和偏移量被称为拟合模型。

定义:拟合统计模型意味着找到关键参数的值,使模型尽可能接近样本数据。拟合模型有时也被称为训练模型。

我将向你展示如何读取结果,然后如何创建它们。

8.3.1 逻辑回归结果

图 8.6 显示了在模拟流失数据集上拟合逻辑回归得到的权重和偏移量。(此结果将在您运行列表 8.2 后生成。)文件中的每一行都显示了一个指标或一组指标的测量结果。结果由以下两个数字组成:

  • 参与度权重

  • 对指标对保留(流失)概率的影响的测量,我称之为保留影响

参与度权重是小的数字,通常小于 1,正如我之前所建议的。这很有道理,因为参与度权重和指标得分的乘积将加起来等于总参与度,其规模类似于得分。正权重表示指标(或组)与增加的参与度相关联,而负权重表示指标与减少的保留率(增加的流失率)相关联。因此,设置模型以预测保留率使其更容易理解:表示好事的正数比负数更直观,如果你设置了模型以预测流失,所有增加参与度的东西都将有负权重。

启示:预测保留概率比预测流失概率更容易理解,因为这样,数值意义上的正权重与参与度意义上的积极结果相关联。

指标对流失的影响测量显示为保留概率的百分比变化。我将在稍后展示计算的细节,但首先,我将解释它的含义。

定义:一个指标或一组指标的保留影响是指它使客户在这个指标上比平均值高出一个标准差时,保留概率的差异,假设所有其他指标都是平均的。

如果一个指标的保留影响是 2%,那么在一个指标上比平均值高出一个标准差且在其他所有指标上平均的客户,其预测保留概率比平均保留概率高 2%。这个概率比平均流失概率低 2%,所以你可以用任何一种方式描述它,只要你能跟踪影响的方向。流失影响不是统计学课程中教授的标准指标,但我发现它在向商业人士解释逻辑回归模型时很有用。

要点:一个指标对保留和流失概率的影响对于向商业人士传达回归结果非常重要。

当你解释流失和保留影响时,请记住两件事:

  • 如果一个指标低于平均值而不是高于平均值,它对流失率的影响大约是相等且相反的。

  • 由于 S 曲线塑造概率预测的方式,多个高于(或低于)平均值的指标累积效应对流失率有递减的影响。对于高于平均值的指标,如果它比平均值高出超过一个标准差,那么这个指标在平均流失率上的影响将小于结果显示的两倍。

偏移量的结果是在图 8.6 底部增加了一行:偏移量列中的数字不是权重,而是偏移量的数量。

图 8.6 模拟数据集的逻辑回归输出

我之前建议,为了达到大约 90%的保留率,偏移量大约为 2。在图 8.6 中,你看到模拟保留率的偏移量为 3.7,这大约是 95.4%。在偏移量列中的数字是完美平均客户的保留概率预测。回想一下,一个完美平均客户在所有指标得分上都是零,所以在方程 8.2 中,对于概率(Pretain = S(E + off)),唯一的项就是偏移量。

注意,对于完美平均客户,预测概率为 2.4%(100% - 97.6%,图 8.6 中的保留率),但在模拟中,流失率约为 4.6%(参见第五章,图 5.12)。你可能预期一个完美平均客户的流失概率将与整体或平均流失率相等,但情况并非如此。

注意:对于完美平均客户的预测流失概率通常接近整体流失率,但不等于它。

这是计算流失影响的方法。如果一个客户是完美平均的,他们在所有得分上都是零,保留的概率将等于方程 8.4:

方程 8.4

另一方面,如果一个客户在所有方面都完全平均,除了在单个指标或组上比平均水平高一个标准偏差,那么权重与分数的乘积将正好等于他们在比平均水平高一个标准偏差的那个分数上的权重。在这种情况下,如果变量 w 代表参与方程中一个权重的值,方程 8.4 将导致方程 8.5:

方程 8.5

活跃度影响是通过计算每个参与权重方程 8.4 和 8.5 之间的差异来计算的。

8.3.2 逻辑回归代码

列表 8.2 提供了逻辑回归分析的代码。这个列表远不止拟合回归,这只有两行。其余的代码准备数据,对结果进行一些分析,并保存一切。列表分为七个函数,按它们被调用的顺序描述如下:

  • logistic_regression_analysis(主函数)—在调用辅助函数创建数据后,该函数创建sklearnLogisticRegression对象,并调用fit方法来运行模型拟合。然后它调用更多的辅助函数来分析和保存结果。

  • prepare_data—该函数加载保存的分组分数数据集,并分离表示活跃度的列。活跃度指示器被反转,以便表示保留。使用分组分数是一个由默认参数控制的选项,因为(在第九章中)它用于加载其他文件。

  • save_regression_summary—该函数创建一个DataFrame,其中一列是回归模型权重和偏移量,另一列是标准偏差影响。请注意,此方法(以及接下来的两个方法)有一个可选的扩展参数,用于在第九章中保存额外的版本;这是图 8.6 所示的数据表。它调用calculate_impacts来获取活跃度影响数值,然后从LogisticRegression对象中获取权重;这些值存储在一个名为coef_的字段中。(coef是系数的简称,它是一个乘以另一个数的通用术语。)然后将这些结果与度量指标和组的名称结合在一个DataFrame中,然后保存。

  • calculate_impacts—此函数使用前面描述的方程计算一个标准偏差分数对保留概率的影响。它调用偏移量的s_curve函数,偏移量是回归对象的变量intercept_,以获取基线保留概率。它还调用偏移量和权重之间的差异的s_curve函数,该差异存储在回归对象的变量coef_中。该函数的结果是保留影响的向量以及基线保留概率。

  • s_curve—此函数实现了方程 8.2。

  • save_regression_model—此函数将回归对象保存到 pickle 文件中,以便可以重新加载并用于以后的预测。

  • save_dataset_predictions—此函数计算在创建模型所使用的数据集中的观测值上的流失和保留概率。它使用数据集作为参数在回归对象上调用predict_proba函数。结果保存在一个.csv 文件中,在第 8.3.5 节中进一步解释。

您应该以常规方式使用 Python 包装程序运行列表 8.2,并使用这些参数:

fight-churn/listings/run_churn_listing.py —chapter 8 —listing 2

程序会打印几行输出,告诉你它保存了三个结果的位置,它们是

  • 包含权重和一个标准差影响的文件

  • 包含模型 pickle 的文件

  • 包含历史流失和保留概率的文件

您应该打开汇总文件,churnsim_logreg_summary.csv,并确认它与图 8.5 相似。由于数据是随机模拟的,所以不会完全相同。我在第 8.3.5 节中更多地讨论了结果,以及如何在第 8.4 节中使用 pickle 文件。

列表 8.2 逻辑回归分析

import pandas as pd
import numpy as np
import os
from sklearn.linear_model import LogisticRegression
from math import exp
import pickle

def logistic_regression_analysis(data_set_path=''):
   X,y = prepare_data(data_set_path)                                  ①
   retain_reg = LogisticRegression(fit_intercept=True, 
      solver='liblinear', penalty='l1')                               ②
   retain_reg.fit(X, y)                                               ③
   save_regression_summary(data_set_path,retain_reg)                  ④
   save_regression_model(data_set_path,retain_reg)                    ⑤
   save_dataset_predictions(data_set_path,retain_reg,X)               ⑥

def prepare_data(data_set_path,ext='_groupscore',                     ⑦
                 as_retention=True):
   score_save_path = data_set_path.replace('.csv', '{}.csv'.format(ext))
   assert os.path.isfile(score_save_path), 'You must run listing 6.3 first'
   grouped_data = 
      pd.read_csv(score_save_path,index_col=[0,1])                    ⑧
   *y* = grouped_data['is_churn'].astype(np.bool)                       ⑨
   if as_retention: y=~y
   X = grouped_data.drop(['is_churn'],axis=1)                         ⑩
   return X,y

def calculate_impacts(retain_reg):                                    ⑪
   average_retain=s_curve(-retain_reg.intercept_)                     ⑫
   one_stdev_retains=np.array( 
      [ s_curve(-retain_reg.intercept_-c) 
       for c in  retain_reg.coef_[0]])                                ⑬
   one_stdev_impact = 
      one_stdev_retains - average_retain                              ⑭
   return one_stdev_impact, average_retain

def s_curve(x):    
   return 1.0 - (1.0/(1.0+exp(-x)))

def save_regression_summary(data_set_path,
                            retain_reg,ext=''):        
   one_stdev_impact,average_retain = 
      calculate_impacts(retain_reg)     
   group_lists = pd.read_csv(                                         ⑮
                             data_set_path.replace('.csv', '_groupmets.csv'),
                             index_col=0)
   coef_df = pd.DataFrame.from_dict(                                  ⑯
       {'group_metric_offset':  np.append(group_lists.index,'offset'),
        'weight': np.append(retain_reg.coef_[0],retain_reg.intercept_),
        'retain_impact' : np.append(one_stdev_impact,average_retain),
        'group_metrics' : np.append(group_lists['metrics'],'(baseline)')})
   save_path = 
      data_set_path.replace('.csv', '_logreg_summary{}.csv'.format(ext))
   coef_df.to_csv(save_path, index=False)
   print('Saved coefficients to ' + save_path)

def save_regression_model(data_set_path,retain_reg,ext=''):   
   pickle_path = 
      data_set_path.replace('.csv', '_logreg_model{}.pkl'.format(ext))
   with open(pickle_path, 'wb') as fid:
       pickle.dump(retain_reg, fid)                                   ⑰
   print('Saved model pickle to ' + pickle_path)
def save_dataset_predictions(data_set_path, 
                             retain_reg, X,ext=''):    
   predictions = retain_reg.predict_proba(X)                          ⑱
   predict_df = pd.DataFrame(predictions,                             ⑲
                             index=X.index,
                             columns=['churn_prob','retain_prob']) 
   predict_path = 
      data_set_path.replace('.csv', '_predictions{}.csv'.format(ext))
   predict_df.to_csv(predict_path,header=True)
   print('Saved dataset predictions to ' + predict_path)

① 调用辅助函数prepare_data

② 创建适合回归类型的对象

③ 根据流失数据拟合模型系数

④ 调用save_regression_summary以保存结果的摘要

⑤ 调用save_regression_model以保存回归对象

⑥ 调用save_dataset_predictions以进行预测

⑦ 将数据放入回归所需的形式

⑧ 加载数据集,设置索引

⑨ 分离结果并将其转换为 True 以保留

⑩ 分离指标

⑪ 平均值以上一个标准差的冲击影响

⑫ 完美平均客户的流失率

⑬ 对于每个系数,计算其影响

⑭ 影响是平均值以上一个标准差的概率差异。

⑮ 在摘要中的每个组中重用指标

⑯ 创建一个结合结果的 DataFrame

⑰ 通过 pickle 保存对象

predict_proba预测流失和保留

⑲ 创建新的 DataFrame 并保存预测

列表 8.2 中的LogisticRegression对象需要几个参数。fit_intercept=True告诉逻辑回归你将在模型中包含一个偏置。这是可选的,因为逻辑回归还有其他用途,你不会包含偏置。其他参数solver='liblinear'penalty='l1'控制了用于找到权重和偏置的方法。你可以使用不同的方法来拟合模型,但本书侧重于逻辑回归在对抗流失( churn)中的应用(我不会深入细节)。这些参数对应于称为岭回归(也称为 Tikhonov 正则化)的方法,你可以在统计学教科书或网上找到更多关于它的解释。

定义 岭回归是一种现代的回归参数拟合方法,当许多指标可以相互关联时表现良好。

当有大量指标可以相互关联时,岭回归表现良好,这使得它适用于典型的客户流失数据。你可以在第九章中了解更多关于岭回归和LogisticRegression对象参数的信息。但首先,让我们谈谈如何向你的商业同事解释回归结果。

8.3.3 解释逻辑回归结果

回归结果展示了哪些指标或相关指标组对客户流失和留存影响最大。这一发现对于与你的商业同事分享非常重要,因为它是对导致客户(以及不参与)与你的产品互动最多的行为或产品方面的客观估计。权重估计和留存概率影响的结果显示了相对重要性;两者讲述的是同一个关于影响最大的故事。这一结果在图 8.7 中得到了说明,该图显示了从对留存最有利到对留存最不利的权重和留存影响排序。

在图 8.7 中,你可以按权重或留存影响排序,排序顺序相同,如果你考虑 S 曲线的形状,这是有意义的。更多的参与度总是导致更高的留存概率,因此对参与度的影响(即权重所代表的)越大,对留存概率的影响就越大。

图片

图 8.7 回归系数与留存概率影响比较

理解回归中的权重很重要,但我不建议与商业人士谈论它们。为了向你的商业同事解释不同行为对留存的影响,我建议你只使用留存概率的影响。回归使用的参与度和权重是抽象概念,并不存在,这可能会造成困惑。使用留存概率影响的优点是,留存概率是一个具体的业务指标,人们已经从留存率中理解了这一点。

在解释指标对留存和流失的影响之前,你需要确保商业人士理解以下概念:

  • 概率是什么以及你的产品的客户流失率和留存率是多少。在这个领域,特别是要确保人们理解以下内容:

    • 客户流失率等同于一个概率。

    • 客户流失率和留存概率的总和为 100%。

  • 标准差在一般意义上的含义。大多数人听说过标准差,但并没有具体的实际知识。他们需要理解,一个人如果比平均水平高一个标准差,就会明显高于平均水平,但并不极端。他们还需要理解,你也可以用标准差来谈论低于平均水平。

图 8.8 展示了向商业人士展示这一结果的好方法:柱状图。将保留影响放入柱状图中,可以轻松地看到相对重要性。我还建议按重要性从高到低对指标和组进行排序。用描述性名称标注指标组。

图片

图 8.8 柱状图解释行为对客户流失概率的影响

我喜欢向商业人士展示柱状图来解释保留概率的原因是,对参与度有利的指标或行为显示为正数,而对参与度不利的指标或行为显示为负数。同时,用客户流失概率来谈论也很方便。如果你发现社交信息使用中高于平均水平一个标准差会导致保留概率增加 2%,你更有可能说这降低了客户的流失概率从 10%到 8%,而不是将保留概率从 90%提高到 92%。在客户流失概率这个更小的数字上,影响更具体。

在向商业人士展示了展示行为高于平均水平影响的柱状图后,你应该确保向他们解释以下额外的事实:

  • 低于平均水平的效果与高于平均水平的效果大致相等且相反。这并不完全相等且相反,但不同指标相对影响将相同。

  • 如果客户的标准差高于平均水平多个倍数,那么回报递减,这意味着每个额外的标准差高于平均水平对客户流失或保留概率的影响将更小。

  • 在多个方面高于平均水平时,同样的回报递减也适用:综合流失概率降低将低于引用的保留概率影响的总和。

通常,这些点涵盖了人们的大部分疑问,并且应该给他们一个关于不同行为对保留和流失影响程度的好印象。

8.3.4 逻辑回归案例研究

图 8.9 展示了 Broadly 这个 SaaS 产品(帮助企业管理其在线存在)的逻辑回归案例研究的一个示例结果。案例研究中分析了大约 80 个指标。两个最大的组各包含约 20 个指标,两组在回归中都获得了强烈的正权重。五个较小的组和九个指标在分组中保持独立,其中一些指标在本书的早期案例研究中出现过,包括 account_tenure(账户期限)、billing_period(账单周期)和 detractor_rate(负面评价率)。

如图 8.9 所示,一个真实的案例研究可能包含比模拟更多的指标。尽管在模拟中分组相关指标似乎是不必要的,但在真实的案例研究中,分组相关指标是很重要的;否则,结果将包含太多指标而难以理解。在第 8.4 节中,你将看到分组对于理解具有大量指标的流失的另一种必要性。

TAKEAWAY 在回归分析中有许多指标时,分组相关指标有助于减少信息过载。

Broadly 案例研究中权重的另一个特点是,一些指标和组的权重很小,例如 0.01 或 0.03。相比之下,表中最强的参与权重约为 0.6,因此这些小权重是强大指标的 1/20 或更少,对参与度来说微不足道。这些小权重对应的是在群体分析中不会显示出与流失有显著关系的指标。你不会使用这类指标来细分客户或采取任何减少流失的措施。如果移除这些小权重,图 8.9 中的结果将更容易管理,但仍具有意义。在第九章中,你将学习如何通过使用一种同时最大化模型准确性的技术来从回归中移除小而无关的权重。

图片

图 8.9 逻辑回归对 Broadly 的示例结果

TAKEAWAY 许多指标显示相对较小的参与权重是正常的,它们对应的是对客户流失和留存不重要的指标。

如果你的数据生成一个类似于图 8.9 的表格,在制作条形图之前,你绝对应该移除低权重的指标。第九章将展示最佳方法。

8.3.5 校准和历史流失概率

列表 8.2 的另一个输出是回归分析,这是将流失概率预测模型应用于数据集的结果。这不是预测,因为对已经发生的事情进行预测没有意义;数据集中的客户已经流失或保留。我将把这些输出称为数据集的流失概率而不是预测。无论如何,查看这个输出是有教育意义的,这样你就可以知道在预测活跃客户时可以期待什么。

图 8.10 显示了一个小样本,它类似于数据集,其中每一行代表一个账户和一个观测日期。但与拥有其他列的指标不同,该图有一列用于流失概率,另一列用于保留概率。此外,由于数据集由历史观测值组成,单个账户可以在不同日期上出现多次,直到它流失。

图 8.10

图 8.10 显示了模拟数据集的历史流失概率估计。平均值是在电子表格或分析程序中计算的(未与数据集一起保存)。

这些历史概率对于检查模型也是很有用的。预测模型的一个重要检查是它产生的预测应该与你的产品实际观察到的流失率紧密对应。这个检查被称为模型校准。

校准的定义是指模型产生的估计流失和保留概率与客户实际流失程度之间的一致程度。

在 8.6.2 节中,你将了解如何使用流失概率预测来估计客户终身价值,这在决定干预措施时可能是一个重要的指标。正如其名所示,客户终身价值衡量的是客户在其一生中对你的价值。只有当模型校准良好时,客户终身价值估计才会准确。对于任何关于行为对流失影响的分析也是如此:如果模型没有校准,保留的影响就不会那么有用。

校准最重要的检查是模型预测的平均流失率应该与数据中的流失率相匹配。图 8.11 重复了对模拟数据集的流失率测量,该数据集是通过运行数据集摘要函数并使用以下参数从列表 5.2 生成的:

run_churn_listing.py —chap 5 —listing 2

将图 8.10 中的平均历史概率与图 8.11 中的流失率进行比较,你可以看到这些数字非常接近,在 1/100 的百分比范围内。你应该用你自己的模拟数据集以及你为实际案例研究工作的任何数据集进行类似的比较。对于既有流失又有保留的合理数量观测值且在指标中没有极端异常值或缺失值的数据集,你应该发现数据集的流失率和平均预测值总是非常接近。

图片

图 8.11 使用列表 5.2 生成的模拟数据集的历史客户流失概率估计

在图 8.6 中,您可以看到平均客户的流失概率为 2.4%(从 100%减去保留率,如图中所示为 97.6%),而不是 4.6%;平均客户的流失概率并不等于平均流失率。这是正常的。对于校准,您需要平均预测流失概率与实际流失率相匹配,但平均客户不必与平均流失率相匹配。事实上,异常流失概率的存在通常保证了平均客户不会有一个平均流失概率:平均客户的流失概率通常略低于流失率,就像社交网络模拟中那样。

此外,还有更高级的方法来衡量校准。例如,当您在制作流失客户群图表时将客户分为十分位,您可以在十分位中测试校准。采用这种方法,您将按预测的客户群平均预测值对客户进行排序,将他们分为 10 个客户群,然后检查每个客户群的真实流失率与客户群平均预测值相比如何。结果将告诉您您的预测模型是否很好地校准,以预测可能或不可能流失的客户(远离平均值的客户)。我不建议您定期进行这种校准检查,但这项技术是您应该了解的一个好方法。

如果您计划针对高或低流失概率的客户进行昂贵干预,您可能希望检查该级别的校准。那么,了解您的模型在估计这些客户的流失概率方面是否准确尤为重要。正如您在第九章中看到的,即使校准不完美,模型仍然可以识别出最和最不具风险的客户。

最后要注意的一点是:校准只是衡量您的模型与数据匹配程度的一个指标。第九章中介绍了衡量模型质量的其他重要指标。

8.4 预测客户流失概率

预测意味着对尚未发生的事情进行预测。在客户流失的背景下,预测意味着收集所有当前活跃的客户,并预测他们在下一次续订之前流失的概率。本节还涵盖了如何为使用分组指标进行细分准备数据,这是第六章中没有涉及的内容。

8.4.1 为预测准备当前客户数据集

预测当前客户的第一步是创建一个数据集,包括那些当前活跃的客户的最新所有指标。你在第四章学习如何提取这样的数据集以细分活跃客户时看到了如何做这件事。然后,在第七章中,你更新了数据集以包含更多指标,因此你需要更新代码来提取当前数据集,就像你更新代码在章节开头提取历史数据集一样。

列表 8.3 提供了提取第七章中创建的所有指标的代码。这个列表几乎与列表 4.6 相同,但包含更多指标。查询开头简短公用表表达式选择最新的可用日期;然后主 SELECT 语句使用你在第四章中(4.6 节)学到的扁平化聚合技巧。

列表 8.3 中有一个新元素:SELECT 限制在服务期限超过 14 天的账户。CTE account_tenures 选择至少有 14 天服务期限的所有账户,主 SELECT 中的内部连接将数据集限制在这些客户。这个约束确保在他们的指标被使用之前,客户至少被观察了几周。否则,大多数新客户由于观察期短,指标会较低。

在第七章中,你了解到服务期限较短的客户可以通过缩放获得更准确的第一月指标预测。你使用这项技术来更准确地估计罕见的指标 unfriend_per_month。对于你自己的案例研究,我建议你为所有指标使用这种模式,在这种情况下,你会匹配两周的最小观察期。(我没有要求你重新计算所有这些指标以节省时间。)

列表 8.3 修订后的当前数据集

WITH metric_date AS                                                  ①
(
   SELECT  max(metric_time) AS last_metric_time FROM metric
),
account_tenures AS (
   SELECT account_id, metric_value AS account_tenure
   FROM metric m INNER JOIN metric_date ON metric_time =last_metric_time
   WHERE metric_name_id = 8
   AND metric_value >= 14
)
SELECT s.account_id, metric_time,
SUM(CASE WHEN metric_name_id=0  THEN metric_value ELSE 0 END) 
    AS like_per_month,                                               ②
SUM(CASE WHEN metric_name_id=1  THEN metric_value ELSE 0 END) 
    AS newfriend_per_month,
SUM(CASE WHEN metric_name_id=2  THEN metric_value ELSE 0 END) 
    AS post_per_month,
SUM(CASE WHEN metric_name_id=3  THEN metric_value ELSE 0 END)
    AS adview_per_month,
SUM(CASE WHEN metric_name_id=4  THEN metric_value ELSE 0 END)
     AS dislike_per_month,
SUM(CASE WHEN metric_name_id=27 THEN metric_value ELSE 0 END)
     AS unfriend_per_month,                                          ③
SUM(CASE WHEN metric_name_id=6  THEN metric_value ELSE 0 END)
     AS message_per_month,
SUM(CASE WHEN metric_name_id=7  THEN metric_value ELSE 0 END)
     AS reply_per_month,
SUM(CASE WHEN metric_name_id=21 THEN metric_value ELSE 0 END)
     AS adview_per_post,                                             ④
SUM(CASE WHEN metric_name_id=30 THEN metric_value ELSE 0 END)
     AS reply_per_message,
SUM(CASE WHEN metric_name_id=31 THEN metric_value ELSE 0 END)
     AS like_per_post,
SUM(CASE WHEN metric_name_id=32 THEN metric_value ELSE 0 END)
     AS post_per_message,
SUM(CASE WHEN metric_name_id=33 THEN metric_value ELSE 0 END)
     AS unfriend_per_newfriend,
SUM(CASE WHEN metric_name_id=23 THEN metric_value ELSE 0 END)
     AS dislike_pcnt,                                                ⑤
SUM(CASE WHEN metric_name_id=24 THEN metric_value ELSE 0 END)
     AS newfriend_pcnt_chng,                                         ⑥
SUM(CASE WHEN metric_name_id=25 THEN metric_value ELSE 0 END)
     AS days_since_newfriend                                         ⑦
FROM metric m INNER JOIN metric_date d 
    ON m.metric_time =d.last_metric_time                             ⑧
INNER JOIN subscription s ON m.account_id=s.account_id
WHERE s.start_date <= d.last_metric_time                             ⑨
AND (s.end_date >=d.last_metric_time OR s.end_date IS null)
GROUP BY s.account_id, d.last_metric_time                            ⑩
ORDER BY s.account_id

① 这个 CTE 选择最新的日期。

② 使用扁平化聚合选择基本指标

③ 这是来自列表 7.7 的缩放指标。

④ 这是来自列表 7.1 的比率指标。

⑤ 这是来自列表 7.1 的版本 2 的百分比指标。

⑥ 这是来自列表 7.4 的百分比变化指标。

⑦ 这是来自列表 7.6 的 days_since_event 指标。

⑧ 选择最新日期的指标

⑨ 通过订阅进行 JOIN 以确保只有活跃账户。

⑩ GROUP BY 聚合操作完成了数据的扁平化。

你应该运行列表 8.3,使用 Python 包装程序和以下参数:

run_churn_listing.py —chap 8 —listing 3

运行列表 8.3 将当前客户及其指标保存到一个文件中。本章没有包含输出示例,因为到现在为止,你已经知道数据集的样子了。

在 8.1 节中,你回顾了在使用回归之前准备数据集的所有步骤(特别是参见图 8.4)。这些步骤如下:

  1. 计算数据集的统计数据。

  2. 使用统计数据将指标转换为分数。

  3. 保存一个参数表,总结用于制作分数的参数。

  4. 使用相关矩阵找到组,并创建加载矩阵。

  5. 使用加载矩阵计算所有组的平均评分。

但是,当重复当前客户数据集的过程时,有一个关键的区别:你不想计算新的统计数据将指标转换为评分。你也不想创建一个新的加载矩阵来分组相关的指标。对于当前数据集,你想要重复使用你在第五章和第六章分析历史客户数据集时从中得出的相同统计数据和加载矩阵的过程。你必须为当前客户重用相同的参数和加载矩阵,以确保你放入回归中的当前客户数据集中的每一列都与你放在历史数据集中的相同列具有相同的意义。

考虑一下,如果你在当前客户数据集上计算一个新的加载矩阵,并发现不同的组数会发生什么。这可能发生在当前数据集的度量指标与历史数据集的度量指标相关性不同的情况下。你将无法将当前客户的分组度量指标映射到回归从历史数据集拟合所期望的指标。即使是用于评分的平均值和标准差也应该是在历史数据上计算得出的。因为用于缩放的均值和标准差并非来自当前客户数据,对于当前客户的评分,均值可能不是正好为零,标准差可能不是正好为一。但只要这些差异反映了当前客户与历史客户之间的真实差异,这个结果就是正确的。当前客户数据集的评分过程涉及以下四个步骤:

  1. 重新加载从历史客户数据集中保存的评分参数。

  2. 使用历史数据集的统计数据将当前客户指标转换为评分。

  3. 重新加载从历史数据集创建的加载矩阵。

  4. 使用重新加载的加载矩阵为当前客户计算平均组评分。

现在,你可以看到为什么保存评分的列表将所有这些细节都保存在表格中的第二个原因:如果你打算进行客户流失和保留概率预测,你将需要再次使用相同的信息。

要点:当你为预测准备当前客户数据集时,你需要重用你在最初分析历史数据集时创建的评分参数和加载矩阵。

列表 8.4 给出了评分当前客户数据集的代码。(同样,本章没有展示输出示例。)由于列表 8.4 必须重新加载由早期列表创建的大量数据,它包含一个按名称和列表编号重新加载单个数据集的功能。在加载当前客户数据集以及旧的评分参数和加载矩阵后,列表 8.4 执行以下主要步骤:

  1. 通过确保数据集、得分参数和加载矩阵中命名的度量列匹配来验证输入。如果您在创建数据集的不同版本、计算统计数据和不同版本的分组时迭代,这些输入可能会不同步。

  2. 使用列表 7.5 中的转换,在得分参数表中指示的列上转换偏斜和厚尾列。

  3. 使用得分参数表中的平均值和标准差从度量中减去平均值并除以标准差。此任务在新的辅助函数 score_current_data 中完成。

  4. 将缩放后的数据乘以加载矩阵以计算度量组的平均值。此步骤发生在新的辅助函数 group_current_data 中。

  5. 保存结果。

最后一个辅助函数调用准备了一个用于细分的数据集版本,这将在列表之后进行描述。

列表 8.4 重新评分当前数据集

import pandas as pd
import numpy as np
import os
from listing_7_5_fat_tail_scores import 
   transform_fattail_columns, transform_skew_columns                      ①

def rescore_metrics(data_set_path=''):

   load_mat_df = reload_churn_data(data_set_path,
      'load_mat','6.4',is_customer_data=False)                            ②
   score_df = reload_churn_data(data_set_path,
      'score_params','7.5',is_customer_data=False)                        ③
   current_data = reload_churn_data(data_set_path,
      'current','8.3',is_customer_data=True)                              ④
   assert set(score_df.index.values)==set(current_data.columns.values),
       "Data does not match score params"                                 ⑤
   assert set(load_mat_df.index.values)==set(current_data.columns.values),
        "Data does not match load matrix"                                 ⑥

   transform_skew_columns(current_data,
      score_df[score_df['skew_score']].index.values)                      ⑦
   transform_fattail_columns(current_data,
      score_df[score_df['fattail_score']].index.values)                   ⑧
   scaled_data = score_current_data(current_data,score_df,data_set_path)
   grouped_data = group_current_data(scaled_data, load_mat_df,data_set_path)
   save_segment_data(grouped_data,current_data,load_mat_df,data_set_path)

def score_current_data(current_data,score_df, data_set_path):
   current_data=current_data[score_df.index.values]                       ⑨
   scaled_data=(current_data-score_df['mean']) / 
      score_df['std']                                                     ⑩
   score_save_path=data_set_path.replace('.csv','_current_scores.csv')
   scaled_data.to_csv(score_save_path,header=True)
   print('Saving score results to %s' % score_save_path)
   return scaled_data

def group_current_data(scaled_data,load_mat_df,data_set_path):
   scaled_data = scaled_data[load_mat_df.index.values]                    ⑪
   grouped_ndarray = np.matmul(scaled_data.to_numpy(), 
                               load_mat_df.to_numpy())                    ⑫
   current_data_grouped = pd.DataFrame(grouped_ndarray,                   ⑬
                                       columns=load_mat_df.columns.values,
                                       index=current_data.index)
   score_save_path=                                                       ⑭
      data_set_path.replace('.csv','_current_groupscore.csv')
   current_data_grouped.to_csv(score_save_path,header=True)
   print('Saving results to %s' % score_save_path)
   return current_data_grouped
def save_segment_data(current_data_grouped, 
                      current_data, load_mat_df, data_set_path):
   group_cols =                                                           ⑮
      load_mat_df.columns[load_mat_df.astype(bool).sum(axis=0) > 1]
   no_group_cols =                                                        ⑯
      load_mat_df.columns[load_mat_df.astype(bool).sum(axis=0) == 1]
   segment_df =                                                           ⑰
      current_data_grouped[group_cols].join(current_data[no_group_cols])
   segment_df.to_csv(data_set_path.replace('.csv',
                     '_current_groupmets_segment.csv'),header=True)

def reload_churn_data(data_set_path, suffix,
                      listing,is_customer_data):                          ⑱
   data_path = data_set_path.replace('.csv', '_{}.csv'.format(suffix))
   assert os.path.isfile(data_path),                                      ⑲
      'Run {} to save {} first'.format(listing,suffix)
   ic = [0,1] if is_customer_data else 0                                  ⑳
   churn_data = pd.read_csv(data_path, index_col=ic)
   return churn_data

① 导入列表 7.5 中定义的转换函数

② 使用 reload_churn_data 重新加载加载矩阵

③ 重新加载评分期间保存的参数

④ 加载使用列表 8.3 创建的当前客户数据

⑤ 检查数据与评分参数之间的一致性

⑥ 检查数据与加载矩阵之间的一致性

⑦ 转换任何被确定为偏斜的列

⑧ 转换任何被确定为厚尾的列

⑨ 确保数据集列与得分参数列匹配

⑩ 从平均值中减去并除以标准差

⑪ 确保数据集列与加载矩阵顺序匹配

⑫ 应用加载矩阵以计算平均组得分

将结果转换为 DataFrame

⑭ 保存结果

⑮ 确定分组度量列

⑯ 确定未分组的度量列

⑰ 为细分创建数据集版本

⑱ 包装验证和加载先前保存数据的步骤

⑲ 确保文件存在,如果不存在则打印消息

⑳ 客户数据文件有两个索引列。

您应按常规方式运行列表 8.4,使用 Python 包装程序,以便您可以为计算您自己的当前客户预测做好准备。以下是参数:

run_churn_listing.py —chap 8 —listing 4

8.4.2 为细分准备当前客户数据

在你学习关于分组度量标准时,没有涵盖的一个主题是如何创建一个将度量标准分组到平均分数中的当前客户数据集。如果你的组织中的商业人士要根据度量分数的平均值来制定干预措施,这就是你需要做的事情。如果你阅读了 8.4.1 节,你可以看到我为什么等到现在才解释它的原因:这个过程并不完全直接。现在你已经重新处理了当前数据集以进行预测,你可以重用你在那里所做的(这就是为什么这项技术被包括在列表 8.4 的最后部分)。但我不建议你使用你的预测数据集进行细分。相反,我建议以下混合版本的数据集:

  • 使用分数来衡量所有度量组。

  • 使用常规(自然尺度)度量标准来衡量那些未被分组的任何度量标准。

这种方法的优点是它对商业人士来说更容易使用。分组有助于减少度量标准的数量,如果你按照第六章(6.4 节)中的建议解释了分数和度量组,那么商业人士应该准备好解释和使用度量分数和组。然而,当度量标准不在其自然尺度上时,通常很难进行细分,所以如果你不需要将度量标准转换为分数进行细分,可能更好不要这么做。

给定当前的度量标准数据集、分数和加载矩阵,列表 8.4 中的辅助函数save_segment_data获取组列,然后添加原始未缩放的度量标准。这个函数只有几行 Pandas 操作,但它可以大大简化试图减少客户流失的商业同事的工作。

你可能会想,对于事件少于 10 个且度量标准不多于 10 个的模拟数据集,使用分组度量标准进行细分的想法并没有太多意义。常规度量标准更容易理解,分组只移除少量度量标准。对于模拟数据集,你可能是对的。但是,如果你在拥有数十(或数百)个事件和度量标准的产品或服务上工作,你的商业同事可能真的需要使用平均分数来减少来自这么多度量标准的信息过载。

8.4.3 使用保存的模型进行预测

在本节中,你将学习如何对当前客户进行预测。图 8.12 显示了这种输出将看起来像什么示例。它与历史客户数据集的预测输出类似,但现在,你只有一个观察日期,每个客户只有一个观察值。

图片

图 8.12 当前客户数据集(列表 8.5)的预测输出

第 8.5 节的第二个输出是流失预测分布的可视化显示,这种显示在图表中称为直方图(图 8.13)。直方图通过将分布划分为范围并显示每个范围内观察值的数量(通过图表上条形的高度表示)来帮助可视化分布。与群体图不同,直方图中用于划分客户观察值的范围是固定的,并且每个范围内的客户数量不同。直方图不是显示固定大小群体的平均流失率,而是显示具有特定(预测)流失率的群体的大小。

在图 8.13 中,你可以看到客户流失预测的范围在 0%到 5%之间,流失预测的客户最多。大多数客户的流失概率小于 20%,但有一小部分客户的流失概率较高(在 20%到 50%之间)。你在第七章看到肥尾分布时学习了分布尾部的术语。图 8.13 中具有较高流失概率的客户狭窄条带被称为流失概率分布的尾部。

图 8.13 模拟预测和客户流失概率的分布

第 8.5 节提供了进行预测的代码。以下是主要步骤:

  1. 加载由第 8.2 节保存的逻辑回归对象的 pickle。

  2. 加载由第 8.4 节保存的当前客户分组分数数据集。

  3. 在逻辑回归对象上调用 predict_proba 函数,传入客户数据集作为 NumPy ndarray。结果是关于客户流失和保留概率的两个列的 ndarray 预测。

  4. 将预测保存到类似于图 8.12 所示的文件中。

  5. 创建并保存一个类似于图 8.13 所示的客户流失概率直方图。直方图是在一个单独的函数中创建的。直方图函数调用 matplotlib.pyplot 包的 hist 函数,然后在保存结果为图像之前添加适当的注释。直方图的计数也保存在一个文件中。

预测的列表与拟合回归模型的列表类似,从算法任务的角度来看,它们通过单个函数调用到包对象。但大部分工作是在准备数据和分析并保存结果。

第 8.5 节:当前客户数据集上的预测

import pandas as pd
import os
import pickle
import matplotlib.pyplot as plt

from listing_8_4_rescore_metrics 
   import reload_churn_data                                            ①

def churn_forecast(data_set_path=''):
   pickle_path =                                                       ②
      data_set_path.replace('.csv', '_logreg_model.pkl')
   assert os.path.isfile(pickle_path), 
      'You must run listing 8.2 to save a logistic regression model first'
   with open(pickle_path, 'rb') as fid:
       logreg_model = pickle.load(fid)

   current_score_df = reload_churn_data(data_set_path,                 ③
                           'current_groupscore','8.4',is_customer_data=True)

   predictions =                                                       ④
      logreg_model.predict_proba(current_score_df.to_numpy())

   predict_df =                                                        ⑤
      pd.DataFrame(predictions, index=current_score_df.index,
                   columns=['churn_prob', 'retain_prob'])
   forecast_save_path = 
      data_set_path.replace('.csv', '_current_predictions.csv')

   print('Saving results to %s' % forecast_save_path)
   predict_df.to_csv(forecast_save_path, header=True)
   forecast_histogram(data_set_path,predict_df)                        ⑥

def forecast_histogram(data_set_path,predict_df,ext='reg')
   plt.figure(figsize=[6,4])
   n, bins,_ = plt.hist(predict_df['churn_prob'].values,               ⑦
                        bins=20)
   plt.xlabel('Churn Probability')                                     ⑧
   plt.ylabel('# of Accounts')
   plt.title(
       'Histogram of Active Customer Churn Probability ({})'.format(ext))
   plt.grid()
   plt.savefig(
      data_set_path.replace('.csv', '_{}_churnhist.png'.format(ext)), format='png')
   plt.close()
   hist_df=pd.DataFrame({'n':n,'bins':bins[1:]})                       ⑨
   hist_df.to_csv(data_set_path.replace('.csv', '_current_churnhist.csv'))

① 重新使用第 8.4 节中的数据加载函数。

② 使用 pickle 重新加载保存的对象模型。

③ 重新加载使用第 8.4 节创建的当前客户组分数。

predict_proba 使用模型进行预测。

⑤ 将预测保存到新的 DataFrame 中。

⑥ 调用辅助函数 forecast_histogram

⑦ 创建直方图图,并返回结果数据。

⑧ 在图上提供注释。

⑨ 将直方图结果保存到文件中以便更仔细地检查。

你应该在模拟数据集上运行列表 8.5,并确认你得到的结果与图 8.12 和图 8.13 中所示的结果相似。假设你已经创建了当前客户分组度量分数(使用列表 8.4),你可以通过以下命令从列表 8.5 创建预测:

run_churn_listing.py —chap 8 —listing 5

8.4.4 预测案例研究

图 8.14 展示了从你在整本书中看到的案例研究中预测的流失概率的一些示例直方图。这些直方图与图 8.13 中模拟结果的基本特征相似:一个代表大多数流失概率范围的较小峰值,以及由具有更高流失概率的客户组成的尾部。

图片

图 8.14 预测流失概率的案例研究分布

在图 8.14 中,三个案例研究展示了流失概率分布尾部出现的三种相当常见的变异形式:

  • 在客户流失概率分布中,如果具有较高流失概率的客户数量足够多,以至于在直方图中可以清晰地显示出来,我们称之为“厚尾”。

  • 当尾部存在某些流失概率范围,且客户数量异常高时,我们称之为“尖锐尾部”。

  • 多峰分布是描述此类分布的另一种方式,尽管你通常只在直方图形状中有两个或更多高度相似的峰值时,才将流失概率分布描述为多峰。没有此类案例研究的示例可用。

  • 如果大多数客户都集中在狭窄的概率范围内,但有一小部分客户的流失概率显著更高,我们称之为“薄尾”。在薄尾的情况下,这些客户数量如此之少,以至于在直方图中不可见;这时你需要查看保存的 .csv 文件,以确切了解这类客户的具体数量。

实际案例研究产生的流失概率分布通常比模拟结果(如图 8.13 所示)看起来更不光滑、更不规律。

8.4.5 预测校准和预测漂移

当你第一次拟合回归模型时,我向你展示了如何通过比较平均流失概率预测与历史数据集中的流失率来检查校准。你也应该检查为当前客户数据集做出的预测的校准。但在这种情况下,你不能将平均值与当前客户数据集中的流失率进行比较,因为当前客户数据集中还没有客户流失(当然)。相反,你可以通过使用你在第二章中学到的方法,将当前客户数据集中的平均预测与历史数据集中的流失率或对最近客户进行的流失测量进行比较。

总结:通过比较平均预测流失率与历史数据集流失概率或最近的流失率测量值(考虑到最近测量时间可能存在的季节性)来检查预测校准。

图 8.15 的顶部显示了这种比较的一个示例,针对模拟数据集。在这种情况下,它显示当前数据集上的平均流失概率预测比历史数据集中的平均流失预测和流失率低约三十分之一个百分点(4.3%与 4.6%相比)。这种差异可能看起来不多,但它是明显的,尤其是考虑到历史平均值和预测如此接近。

当你发现当前的预测与历史数据有显著差异时,你应该进一步调查以确保你理解造成差异的原因。什么可能导致这种差异?流失概率模型基于客户指标,因此如果当前的预测与历史预测不同,当前的指标必须在某些方面与历史指标不同。

总结:如果当前客户流失概率预测与历史流失概率预测不匹配,那一定是因为当前和历史上的指标之间存在差异。

当你发现这种类型的差异时,你可以通过比较当前指标与历史指标来调查它。最简单的方法是使用你已知的如何创建的数据集汇总统计信息。图 8.15 的底部说明了从历史数据集汇总统计信息与当前数据集汇总统计信息的比较。这种比较是在一个简单的电子表格中进行的。你可以做同样的事情或者编写自己的简短脚本。要创建当前数据集中的汇总统计信息,你可以在 Python 包装程序中运行带有参数—chapter 5 —listing 2 —version 4的额外汇总统计信息列表版本。

图 8.15 中的指标比较显示,当前数据集中的客户指标平均值略高于历史数据集中客户指标的平均值(在大多数情况下约为 5%)。新朋友百分比变化指标显示很大的变化,因为它在新账户中估计为零,这是由于计算变化需要历史记录的要求。当前样本有更多具有所需历史记录的租户,导致平均值的差异。由于大多数客户指标都有积极影响,当前数据集中的指标较高解释了当前数据集较低的流失概率预测。

在模拟数据集的情况下,当前数据集和历史数据集之间平均指标的差异是正确的,但没有意义。在模拟中,该服务最近才出现,因此客户的平均服务期限正在增加。同时,大多数指标没有对最近注册的用户进行调整,因此新客户(平均而言)的指标略低。从这些考虑因素来看,可以说当前数据集上的平均流失预测是合理的,与历史预测的差异不值得担心。

对于真实产品的流失分析,如果您发现当前客户的指标与历史数据集客户显著不同,您应该进行一些额外的质量保证,以确保结果正确。如果您在当前数据集和历史数据集之间存在合法的指标差异,可以合理地得出结论,您的当前客户流失率可能与您过去看到的流失率不同。另一方面,如果由于产品或市场环境发生重大变化,您的当前客户的行为与历史客户的行为有显著不同,您应该对流失预测的可靠性持怀疑态度。您可能必须等到在当前条件下可以形成新的历史数据集,才能可靠地预测流失概率。

另一个问题是在当前数据集中存在异常值可能会导致某些流失概率与历史数据集不同,从而改变平均值。您可以通过比较总结统计中的最大值和更高百分位数来评估指标极端异常值之间的差异(如图 8.15 所示,未展示)。这个区域是另一个您在评估差异时必须运用自己判断的地方。

图片

图 8.15 校准和预测漂移

8.5 流失预测的陷阱

您现在知道如何拟合流失概率模型,以及如何在正常情况下预测活跃客户的流失。本节涵盖了一些您应该注意的陷阱,因为它们可能会在某些情况下阻止您获得最佳结果。

8.5.1 相关指标

您已经了解了关于分组相关指标的知识,以及在使用客户数据集进行预测之前,您应该将这种分组作为指标群体流失分析的一部分。到目前为止,我已向您说明分组相关指标是可取的,因为它使您的结果更容易理解。但是,使用回归模型为使用分组添加了一个新的必要性:回归算法是在假设您在其中使用的指标不是高度相关的条件下设计的。因为回归不适用于指标之间的高度相关性,如果它们是高度相关的,结果可能就不合理。

要点:回归分析是为不相关或中等相关的指标设计的。不要在回归分析中使用高度相关的指标。

在回归分析中不使用高度相关的指标的一个原因是,这会使参与度权重更难以解释。高度相关的指标使得参与度权重更难以解释,因为当你考虑一个相关指标的客户流失概率影响时,你必须记住它通常与其他相关指标的影响一起出现。假设两个指标的相关性为 0.75。在这种情况下,如果一个客户在一个指标上的标准差高于平均水平,那么他们预期在另一个指标上的表现也会非常好。当你推理不同行为对保留率的影响时,你需要考虑这些关系。即使指标之间只有弱相关性或中等相关性,这一事实仍然成立;影响较小,因此忽略它并不成问题。

如果你以前学过回归分析,你可能记得一个叫做共线性的条件。共线性与相关性相关;它是两个指标之间或数据中某些指标组的总和之间的完全相关条件。(求和是在指标如总数的情况下,但共线性指的是某些指标相加可能导致相关对的条件,而不是你使指标总和。)共线性是一些回归算法的严重问题,可能导致它们失败。但较新的回归算法,如你在列表 8.2 中使用的岭回归,通常不会出现那种失败,即使数据包括相关或共线性的指标。

如果你在回归分析中使用高度相关的指标,会出现一个更微妙的问题。有时,如果你使用多个高度相关的指标并且没有将它们平均到组中,你可能会发现回归产生的权重与参与度和流失率之间的关系毫无意义。一些指标被分配了有利于保留率的权重,而一些指标被分配了不利于保留率的权重,但显然,所有指标都应该有类似的影响力。

图 8.16 是一个来自 Klipfolio 真实案例研究的例子,Klipfolio 是一个用于创建和共享公司指标仪表板的 SaaS 工具。该案例研究有四个版本的指标,以略微不同的方式衡量仪表板查看次数:每天仪表板查看次数、使用固定周期计算的每月仪表板查看次数、使用账户期限缩放指标周期的每月仪表板查看次数,以及每月每个用户的仪表板查看次数。这四个指标高度相关。

图片

图 8.16 来自相关指标的误导性回归权重

所有四个度量在同期分析中都与保留显示出强烈的关系。通常,这些度量应该被分组到一个平均分数中,但如果这种分组没有完成,并且四个度量作为独立度量放入回归中,就会出现一个奇怪的结果:其中两个度量获得正值,表明行为增加了参与度,而另外两个度量获得负值,表明行为减少了参与度。然而,这些权重没有意义。

奇怪的是,这种模型通常与所有参与度权重都有意义的模型一样好,有时甚至更好。相反的权重是由于相关度量的参与强度之间的相关性和不平衡造成的,这些相关度量是数据中的真实模式。但通常创建这种类型的模型并不是一个好主意。你将在第九章中了解准确度比较和不可解释的机器学习模型。

这种情况发生在你使用第六章中提到的度量分组算法的高相关阈值时。通常的动机是保持相关度量分开,试图通过观察哪个回归权重最大来确定流失和保留的相对重要性。讽刺的是,这种分析可能导致无意义的权重,从而阻碍你看到有意义的关系。解决方案是使用第六章和第七章中描述的方法,将相关度量分组为平均值,然后通过形成两个度量的比率并检查比率与流失和保留的关系来调查度量之间的关系是否显著。如果比率与原始对中的任何一部分都不相关,它可以在回归中进行测试。

8.5.2 异常值

另一个可能阻止你在流失预测中获得最佳结果的陷阱发生在你的数据中存在极端异常值时。在第三章中,当你学习创建度量时,我向你展示了一些检测和删除包含错误或不适当数据的记录的技术。现在你将要了解一些不同的事情:如何处理数据在它是正确的测量但极端到在某些方面导致流失预测问题的情况。如果你使用用于偏斜和厚尾数据的转换将所有度量转换为分数,这种情况就不太常见。这些转换减少了异常值的问题,因为转换后的分数比原始分布的极端值要少。此外,如果你不使用这些转换,极端异常值会非常常见,这就是为什么我建议将其作为标准做法。尽管如此,我还是要提醒你两个潜在的问题,这样你就会知道迹象以及如何处理它们。

异常值可能会在模型拟合和模型拟合后的预测中引起问题。许多统计学和数据科学课程强调异常值对模型拟合造成的问题。但在大多数流失预测用例中,我在预测时看到更多与异常值相关的问题。当回归中使用的观测值较少时,异常值会导致模型拟合出现严重问题。如果你在回归中少于 100 个观测值,并且存在极端异常值,你的结果可能会受到异常值的影响。但大多数流失场景都有数千个数据点。如果你有数万个观测值,几个异常值通常对回归的影响很小。

在任何情况下,你可能会注意到预测中存在极端异常值,当你预测接近 0%或接近 100%的流失或保留概率时,你可能会看到它们。图 8.17 说明了我在说什么。

图片

图 8.17 异常值导致的流失概率

我之前提到,账户的流失概率接近 100%的情况很少见,所以如果一个账户的流失概率预测值高于 99%(甚至 100%),那么这个结果很可能是由于一个或多个指标上的极端异常值造成的。对于预测流失概率恰好为 0%的账户也是如此。流失概率恰好为 0%或 100%是没有意义的,因为在现实世界中,总有可能客户会流失或保留。

如果你知道这些极端概率可能并不准确,最简单的方法就是忽略它们。记住,显示 100%流失概率的账户不太可能流失,但它可能仍然处于相当大的风险之中,这是值得了解的。我通常遇到的问题是极端预测可能会分散商家的注意力。商家可能会着迷于知道为什么某些账户的流失概率如此之高或如此之低,这可能会使他们怀疑模型的其他合理预测。

要点:在向商家展示模型结果时,极端异常值的预测可能会引起他们的困惑和怀疑。

当极端异常值是当前数据集中的真实客户时,问题在于你不能像构建历史数据集时那样移除它们。通常,对当前客户进行流失概率预测的背景是细分或其他分析,其中你需要为所有当前活跃的客户进行预测。通常,极端异常值是高风险或低风险客户;建模可能会夸大真实的风险水平。保持客户并允许你对他们的预测更加合理的解决方案被称为异常值裁剪。

定义 异常值裁剪是指降低极端异常值,使它们仍然接近可能值的高(或低)端,但并不那么极端。

异常值裁剪与相关概念异常值移除不同,因为在数据中异常值被保持在较低的水平。图 8.18 展示了异常值裁剪最常见的方法。异常值裁剪通过将高于 99th 百分位数的指标观测值设置为 99th 百分位数值来修改这些观测值。根据定义,这种转换只影响你应用的每个指标观测值的 1%。原本高于 99th 百分位数的值与分布中的大多数值相比仍然很高,但并不像之前那么极端。通常,异常值修剪用于高指标值,但同样的方法也可以用于低指标值。在计数指标中,极端低的指标值是不常见的,因为最小值通常是零,但它们可以出现在比率或百分比变化指标中。

图片

图 8.18 异常值裁剪

列表 8.6 展示了一个示例代码,你可以使用它对数据集进行异常值裁剪。在列表 8.6 中,裁剪作为将当前客户数据集转换为组分数的一部分过程进行,因此列表 8.6 是列表 8.4 的一个变体。使用的裁剪阈值是所有指标的 1st 和 99th 百分位数,这些百分位数在保存的数据集摘要统计中。裁剪发生在数据加载之后和指标转换为分数之前。否则,列表 8.6 与列表 8.4 相同。

注意,列表 8.6 裁剪了所有变量,但创建一个只裁剪你在参数中指定的选定组变量的类似函数并不困难。一般来说,只有当异常值足够极端以至于会导致不合理的预测时,才需要裁剪数据。话虽如此,在一些具有许多事件和指标的实时数据集中,很难精确地识别出哪些极端指标导致了不合理的预测。在这种情况下,快速解决方案是按照列表 8.6 所示进行,并对所有数据进行裁剪。

列表 8.6 Python 中的裁剪分数

import pandas as pd
import numpy as np
from listing_7_5_fat_tail_scores                                ①
   import transform_fattail_columns, transform_skew_columns
from listing_8_4_rescore_metrics 
   import reload_churn_data                                     ②

def clip_hi_cols(data, hi_vals):                                ③
   for col in hi_vals.index.values:
       data.loc[data[col] > hi_vals[col],col] 
          = hi_vals[col]                                        ④

def clip_lo_cols(data, lo_vals):                                ⑤
   for col in lo_vals.index.values:
       data.loc[data[col] < lo_vals[col],col] 
          = lo_vals[col]                                        ⑥

rescore_metrics(data_set_path):                                 ⑦

   current_data =                                               ⑧
      reload_churn_data(data_set_path,'current','8.2',is_customer_data=True)
   load_mat_df = reload_churn_data(data_set_path,
      'load_mat','6.4',is_customer_data=False)
   score_df = reload_churn_data(data_set_path,
      'score_params','7.5',is_customer_data=False)
   stats = reload_churn_data(data_set_path,
      'summarystats','5.2',is_customer_data=False) 
   stats.drop('is_churn',inplace=True)                          ⑨
   assert set(score_df.index.values)==set(current_data.columns.values),
      "Data does not match transform params"
   assert set(load_mat_df.index.values)==set(current_data.columns.values),
      "Data does not match load matrix"
   assert set(stats.index.values)==set(current_data.columns.values),
      "Data does not match summary stats"

   clip_hi_cols(current_data, stats['99pct'])                   ⑩
   clip_lo_cols(current_data, stats['1pct'])                    ⑪

   transform_skew_columns(current_data, 
      score_df[score_df['skew_score']].index.values)            ⑫

   transform_fattail_columns(current_data, 
      score_df[score_df['skew_score']].index.values)

   current_data=current_data[score_df.index.values]
   scaled_data=(current_data-score_df['mean'])/score_df['std']

   scaled_data = scaled_data[load_mat_df.index.values]
   grouped_ndarray = np.matmul(scaled_data.to_numpy(), 
                               load_mat_df.to_numpy())
   current_data_grouped = pd.DataFrame(grouped_ndarray,
                                       columns=load_mat_df.columns.values, 
                                       index=current_data.index)

   score_save_path=data_set_path.replace('.csv','_current_groupscore.csv')
   current_data_grouped.to_csv(score_save_path,header=True)
   print('Saving results to %s' % score_save_path)

① 重新使用胖尾和偏斜变换函数

② 重新使用加载客户流失数据的辅助函数

③ 裁剪数据中高于 hi_vals 参数值的值

④ 将高于阈值的值设置为阈值

⑤ 裁剪数据中低于 lo_vals 参数值的值

⑥ 将低于阈值的值设置为阈值

⑦ 此函数与列表 8.4 中的函数类似。

⑧ 重新加载保存的数据、加载矩阵、参数和统计信息

⑨ 不要在摘要统计中使用客户流失度量!

⑩ 裁剪高于 99th 百分位数的值

⑪ 裁剪低于 1st 百分位数的值

⑫ 此列表的其余部分与列表 8.4 相同。

如果您需要使用截断法因为您有一些极端的预测,您可能还想检查异常值是否对回归本身有显著影响。为此,您应该在创建历史数据集得分的函数中使用列表 8.6 中的截断函数。

8.6 客户终身价值

现在您已经了解了如何预测客户流失概率,我将向您介绍一个客户流失预测的绝佳应用:估计客户终身价值(CLV)。CLV的估计让您知道客户在其整个生命周期内对您有多大的价值。这些信息对于评估您的获取和保留努力的回报至关重要。可能会让您感到惊讶,但估计CLV的关键是客户流失率。客户流失概率预测(如您已学会如何制作的预测)允许您根据每个客户单独调整CLV的估计。

8.6.1 客户终身价值(CLV)的含义

首先要理解的是,CLV是对预期客户价值的预测,而不仅仅是特定客户过去付款的总和。

客户终身价值是指您预期客户在整个生命周期内对您的业务的价值,包括您预见的收入和成本。此预测包括未来的付款。

CLV 需要包括客户预期带来的收入以及获取和保持客户作为客户的成本,如图 8.19 所示。

图 8.19 CLV 的组成部分

客户获取成本(CAC)是指获取每位客户所花费的市场营销和销售总金额。CAC 通常取决于客户是通过哪个渠道或活动获取的。销售成本(COGS)是指维护现有客户服务的总支出,包括云计算成本和提供客户支持的成本。COGS可能取决于客户类型。

注意:在本节中,假设您已了解您客户的 CAC 和COGS。本节的重点是 CLV 的持续部分,它取决于客户流失率。

客户在其生命周期内的收入是持续的,您之前将其称为月度持续收入(MRR)。现在,我将泛指收入为持续收入(RR),不考虑时间段,可能是每月或每年。持续收入可以包括订阅付款以及由广告、应用内购买或使用费产生的任何收入。

客户终身价值这个名字意味着它结合了客户预期生命周期内的所有成本和持续收入。您可以将CLV写成方程 8.6 所示的形式:

方程 8.6

在本节中的方程中,Σ 符号代表其下标所指示的所有项的总和,因此 Σ[lifetime] 表示整个客户生命周期内的所有收入和成本的总和。

这就是 CLV。接下来,我需要教你们关于与 CLV 相关的第二个生命周期价值,它忽略了获取成本和第一期的收入和成本。

定义 未来生命周期价值(FLV)是指在第一期之后的任何时间点,重复付款和维护成本的总和。

当你们在评估已经注册并试图决定保留他们的价值时,想使用 FLV(不包含获取成本和第一期的收入和成本)。如果你们试图决定客户注册后的价值,获取成本是不相关的(在财务术语中是沉没成本)。同样,注册的付款从未面临流失风险,因此你们忽略第一期的付款(和成本)。相反,你们只关心注册后的预期未来重复收入和成本,如方程 8.7 所示

方程 8.7

其中,求和符号 Σ[future] ... 表示未来预期的所有付款。对于图 8.19 中显示的付款和成本序列,未来的付款是指在注册期之后的付款。但这个估值在整个客户生命周期内保持不变,不考虑过去周期内的重复收入和成本。

总结:在任何客户生命周期的点上,FLV 仅基于预期的未来付款,过去所有的成本和收入被视为沉没成本或收益。

FLV 特别适用于客户流失;你们将用它来评估流失干预措施的投资回报。此外,请注意,CLVFLV 之间的差异仅是获取成本和一期的重复收入(方程中的 RR)以及一期的成本(方程中的 COGS)。这两个生命周期价值计算之间的差异不是一个估计或预测,因为这些是已知量,因为你可以从你的会计系统中的数据计算出它们。相比之下,FLV 是对未来的一种预测或估计,因为客户的未来生命周期并不确定。因此,当你们谈论 CLV 时,重点通常在 FLV 上。

总结:FLV 强调了 CLV 的未来视角,用于保留,忽略了获取和过去的收入和成本。

由于 FLV 是难以估计的部分,并且与流失和保留密切相关,因此 FLV 将成为本章的焦点。正如我提到的,你们可以通过减去 CAC 和一期的 COGS 然后加上 RR(方程 8.8)从 FLV 中得到 CLV

方程 8.8

关于定义的一个需要注意的最后一点是,COGS 通常由方程 8.9 中定义的边际来概括:

方程式 8.9

大多数公司用利润率来总结他们的成本,并且假设您知道客户的利润率。根据这个定义,您可以通过结合方程式 8.8 和 8.9 来重写未来终身价值(FLV)公式,得到方程式 8.10:

方程式 8.10

注意事项:如果您不知道客户的利润率,不要担心,因为关于客户终身价值(CLV)未来终身价值(FLV)的所有其他说明都适用,并且当您知道利润率时,您始终可以在 8.6.2 处添加利润率。

8.6.2 从流失到预期客户终身价值

要计算用于未来终身价值(FLV)公式(方程式 8.11)中使用的客户的预期 RR 总和,您需要估计客户的预期未来终身价值。您可能会认为估计客户的预期终身价值会很复杂,您可能需要像回到数据库一样测量流失时的平均账户期限。但只要您知道客户的流失概率,估计客户终身价值就很简单。我会先告诉你答案,然后再说服你这是正确的。如果您知道客户有一定的流失概率,客户的预期未来终身价值由方程式 8.11 给出:

方程式 8.11

注意,在方程式 8.11 中,终身价值的单位与测量流失率的时间周期相同:月份或年份(通常是)。

摘要:用简单的话说,方程式 8.11 表明,如果流失概率是按月预测的,客户的预期终身价值是流失概率的倒数。如果流失概率是按年预测的,客户的预期终身价值是年流失概率的倒数。

如果流失概率是每月 5%,预期客户终身价值是 1.0/0.05 = 20 个月。如果流失概率是每年 30%,预期客户终身价值是 1.0/0.30 = 3.33 年。

这听起来很简单,确实如此,只要您知道客户的流失概率。以下是方程式 8.11 为什么有道理的原因:

  • 如果每个客户的终身价值是 20 个月,流失率会是多少?如果每个客户每 20 个月流失一次,流失率将是 1/20,即每月 5%。

  • 如果每个客户的终身价值都是三年,流失率会是多少?如果每个客户每三年流失一次,流失率将是三分之一,即每年 33%。

这些例子表明(从平均意义上讲),客户流失率是终身价值的倒数,而方程式 8.11 则是将这种关系颠倒过来。实际上,即使客户的流失概率完全相同,也不是每个客户都有相同的终身价值。许多外部因素和缺失信息阻止您完美地估计流失概率。但对于每个客户来说,预期终身价值在估计或预测的意义上是流失概率的倒数。

如果您想了解更多关于为什么预期生命周期是流失率的倒数的原因,您应该了解指数衰减模型,这是近似值推导的地方。

注意 如果您没有对个别客户的流失概率进行预测,可以使用方程 8.11 中的平均流失率来估计平均客户生命周期。

8.6.3 CLV 公式

下一个步骤并不是确切的FLVCLV,而是一个中间步骤:客户生命周期的预期总利润,不包括 CAC。将预期生命周期(方程 8.11)与利润的利润率方程(方程 8.10)结合起来,得到方程 8.12:

方程 8.12 方程 8.12

预期生命周期利润(不包括获取成本)是利润率乘以 RR 除以流失率。注意,这里的 mRR 是利润率(m)乘以持续收入(RR),而不是 MRR(每月持续收入)。(这种表示方法虽然不幸,但却是标准的。)注意,从流失概率中得到的预期生命周期可能是一个不均匀的数字,通常情况下是这样的。如果月流失率是 12%,预期生命周期是 1/.12 = 8.3 个月。但是,您需要将这个生命周期乘以每期的利润来得到预期生命周期利润。这个结果是合理的,因为估计是一个平均值。在预期生命周期为 8.3 个月的例子中,没有客户会支付 8.3 期,但有些人可能支付 8 期,有些人支付 9 期,所以平均是 8.3。

还要注意,FLV不依赖于账户时长或客户成为客户的时间长短。无论客户在获取后是否完成了第一个周期,还是第 100 个周期,都无关紧要:预期的未来生命周期利润只取决于流失概率。(可能流失概率的预测依赖于账户时长,因此可能存在二级的间接影响。)因此,FLV是前瞻性的,因为账户时长只有在影响流失概率预测时才重要,而流失概率预测也是前瞻性的。

要点 FLV 并不直接依赖于客户成为客户的时长。它是一个前瞻性估计。在评估保留客户的价值时,只有预期的未来收入才是重要的。

无论如何,方程 8.12 还不是FLV,因为它是在整个生命周期内的持续支付利润。但FLV应该忽略第一个支付周期的支付(因为那些支付构成了每个客户确定成本和支付的一部分)。答案是减去一期的 RR(和 COGS)以得到方程 8.13:

方程 8.13 方程 8.13

另一方面,要从方程 8.12 中得到CLV,需要减去获取成本,从而导致方程 8.14:

方程 8.14 方程 8.14

警告:许多人单独使用方程 8.12 来计算CLV,因为它是一个简单的简化。但方程 8.12 单独并不是CLVFLV的正确公式!由于方程 8.13 和 8.14 都从它中减去,所以方程 8.12 在这两种情况下都是高估。因为它是一个高估,所以方程 8.12 有时用于向外部投资者报告CLV,但不用于评估客户获取或保留的投资回报。

你应该意识到对于低流失率的公司,还需要一种额外的FLV形式。如果年流失率低于 20%,预计客户的寿命将超过五年。如果预计客户会停留这么长时间,那么假设他们的FLV是他们预期寿命所建议的全部金额是不合理的。在如此长的寿命期间,客户做出所有这些支付的风险比单纯的流失还要多。可能会发生经济衰退,或者新的竞争对手可能会改变市场,或者五年或更长时间内可能发生任何其他事情。

处理这种不确定性的方法是添加一个类似于用于评估资本投资项目的现金流量折现因子。解释公式超出了本书的范围,但它看起来像方程 8.15:

方程 8.15

方程 8.15 中的公式使用保留率而不是流失率,分母中的折现变量是公司用于评估长期投资的折现率。(如果您的公司没有这样的折现率,您可能不需要使用这个公式。)关于这种长客户寿命版本的FLVCLV的详细信息,我推荐 Sunil Gupta 和 Donald Lehman 在《互动营销杂志》上发表的“Customers as Assets”。文章可在www0.gsb.columbia.edu/mygsb/faculty/research/pubfiles/721/gupta_ customers.pdf下载。

摘要

  • 流失概率的预测是通过一种称为逻辑回归的模型完成的。

  • 逻辑回归模型将保留率视为参与度的 S 型函数。

  • 为了预测的目的,参与度被建模为一系列参与度权重与每个指标得分的乘积。

  • 逻辑回归算法确定最适合数据的参与度权重。

  • 如果它预测的流失概率与观察到的实际客户流失率一致,则流失预测模型是校准的。

  • 你通过比较平均预测与用于创建模型的数据库中的流失率来检查预测的校准。

  • 要对当前活跃的客户进行预测,你必须以与在拟合模型之前对历史客户数据集进行转换相同的方式进行当前客户数据集的转换。

  • 任何客户的预期寿命等于其预测的流失概率的倒数,单位与流失概率相同(月份或年份)。

  • 预期寿命可以用来估算客户终身价值(CLV)。

9 预测准确性和机器学习

本章涵盖的内容

  • 计算预测准确性的测量值以预测客户流失

  • 在历史模拟中回测模型

  • 设置最小指标贡献的回归参数

  • 通过测试(交叉验证)选择回归参数的最佳值

  • 使用 XGBoost 机器学习模型预测客户流失风险

  • 使用交叉验证设置 XGBoost 模型的参数

你知道如何预测客户流失的概率,也知道如何检查预测的校准。预测模型的一个重要测量指标是,那些预测为高度风险客户的客户,是否真的比那些预测为安全客户的客户风险更高。这种预测性能通常被称为准确性,尽管你会看到,测量准确性的方法不止一种。

在第一章中,我告诉你,使用预测模型预测客户流失不是本书的重点,因为在许多情况下它并不有帮助。本书的重点是拥有一个好的指标集,根据行为将客户分为健康和不健康的人群。但有几个原因说明为什么拥有准确的预测流失率预测是有好处的,所以本章将完善你的技能集,并确保你在必要时能够准确预测。

有时候,准确预测客户流失风险是有用的,尤其是在干预措施特别昂贵的情况下。例如,与产品专家进行现场培训的支出将比发送电子邮件更高。如果你选择客户进行现场培训以降低流失风险,那么只选择具有高流失风险的客户是有意义的,这样你只招收具有合适风险特征的客户。或者,你可能不会选择风险最高的客户,因为它们可能已经无法挽救;通常,选择风险高于平均水平但不是最高的客户会更好。(此外,你可能还会通过特定的指标筛选客户,以确保他们将从这种假设的培训中受益。)

另一个值得你花时间准确预测客户流失的原因是,这样做可以验证你的整个数据和数据分析过程;你可以将你的预测准确性与已知的基准进行比较,正如我在本章中解释的那样。如果你发现你的流程性能低于典型水平,那么这个结果表明你需要纠正你的数据或流程的某些方面。例如,你可能需要改进你的数据清洗方式,移除无效示例,或者你可能需要计算更好的指标。另一方面,如果你发现你的分析性能位于基准的高范围内,你可以有信心你已经进行了彻底的分析,可能没有太多可以发现的。你甚至可能发现你的准确性不可思议地高,这可能会表明你需要对你的数据准备进行纠正和改进,例如增加你用来进行观察的展望期(第四章)。

本章组织如下:

  • 第 9.1 节解释了衡量预测准确性的方法,并教你一些对客户流失特别有用的准确性度量。

  • 第 9.2 节教你如何使用历史模拟来计算准确性度量。

  • 第 9.3 节回到第八章的回归模型,并解释了你可以如何使用可选的控制参数来控制回归使用的权重数量。

  • 第 9.4 节教你如何根据准确性测试结果选择回归控制参数的最佳值。

  • 第 9.5 节教你如何使用名为 XGBoost 的机器学习模型来预测客户流失风险,该模型通常比回归更准确。你还将了解机器学习方法的某些陷阱,并看到来自真实案例研究的基准结果。

  • 第 9.6 节涵盖了使用机器学习模型进行预测时涉及的一些实际问题。

各节相互关联,因此你应该按顺序阅读。

9.1 测量客户流失预测的准确性

首先,你将学习在客户流失预测的背景下准确性意味着什么以及如何衡量它。实际上,衡量客户流失预测的准确性并不简单。

9.1.1 为什么不使用标准的准确性度量来衡量客户流失

当你谈论预测(如客户流失概率预测)的准确性时,"准确性"这个词既有一般意义也有特定意义。首先,一般定义。

定义(在一般意义上)准确性意味着预测的正确性或真实性。

所有衡量流失预测准确率的方法都涉及将风险预测与实际流失事件进行比较,但衡量准确率的方法有很多。更令人困惑的是,有一种特定的预测准确率测量方法被称为准确率。这种测量是具体的,但它对于流失来说并不是一个有用的测量,正如你将要看到的。我将从这个测量方法开始,我将称之为标准准确率测量,以避免与准确率的更普遍含义混淆。(当我提到准确率时,我指的是这个词的普遍意义。)

图 9.1 展示了标准准确率测量。在第八章中,你学习了如何为每个客户分配流失或保留预测概率。标准准确率测量进一步假设,基于这些预测,将客户分为两组:预期保留的客户和预期流失的客户。在我解释完标准准确率测量后,我将回到如何将客户分为这两组的问题。

图片

图 9.1 标准准确率测量

在将客户分为预期保留和预期流失组后,分配的类别将与实际情况进行比较。为了定义标准准确率测量,你需要使用以下术语:

  • 真阳性的预测(TP)是指预测到的流失确实发生了。

  • 真阴性的预测(TN)是指预测到的保留确实保持不变。

  • 假阳性的预测(FP)是指预测到的流失却未发生流失。

  • 假阴性的预测(FN)是指预测到的保留却发生了流失。

使用这些定义,标准准确率测量被定义为以下内容。

定义 标准准确率是指预测中为真阳性或真阴性的百分比。在方程中,这将是标准准确率 = (真阳性数量 + 真阴性数量) / 总数量。

标准准确率旨在表示在特定字面上的预测正确率:即类别分配中实现的比例。这听起来合理,但实际上,标准准确率不适用于衡量流失预测的有效性。在流失方面,标准准确率有两个问题:

  • 流失事件很少见,因此标准准确率主要由非流失事件主导。

  • 标准准确率测量的基本假设是将客户分为两组:预期流失和预期保留。但这种划分并不是客户细分用例的有用描述。

我将详细解释这些问题。

标准准确率通常受非流失客户的影响,因为流失客户很少见,所以真正的正面预测对标准准确率比率的分子部分不可能有太大影响。因此,这种测量方法并不总是能很好地展示预测是否恰当。为了说明这一点,请注意,有一种简单的方法可以获得很高的标准准确率测量值,如图 9.2 所示。如果你预测没有任何客户会流失(所有客户都属于非流失组),那么你将会有大量的真正负预测。如果你将所有真正负预测正确分配,那么得到的准确率就是保留率,你将获得一个很高的标准准确率测量值,而不需要预测任何关于流失的信息。

图 9.2 操纵流失的标准准确率测量

图 9.2 操纵流失的标准准确率测量

要点:由于流失事件很少见,因此标准准确率测量方法不适用于流失预测,因为可以通过预测没有人会流失来操纵这种测量。更普遍地说,流失客户的准确率对测量贡献很小。

对于标准准确率测量中的这种弱点,一种可能的补救措施是增加基于不仅真正正面和真正负面,还包括假正面和假负面的测量。然而,我也不推荐这种方法,因为标准准确率测量对于流失用例的不适用性还有另一种方式。计算标准准确率依赖于你将客户分为两组:预期流失客户和预期保留客户的假设。将预测分为两个互斥的组对于某些预测用例是标准的,但在客户流失的情况下很少这样做。

如本章开头所述,流失和保留预测最常见用例是选择客户进行相对昂贵的干预措施以减少流失。在这种情况下,流失或保留概率就像其他细分指标一样被使用,即组织干预措施的部门根据该指标对客户进行排序,然后使用自己的标准选择最合适的客户。例如,如果干预措施有特定的预算,那么部门可能会选择固定数量的最有可能流失或最不可能流失的客户。一种常见的策略是选择那些风险高于平均水平但仍使用产品的客户,因为这些最有可能流失且不使用产品的客户可能无法挽救。你(数据人员)并没有像标准准确率测量所假设的那样将客户分为预期流失客户和非流失客户。

要点:流失预测用例依赖于使用流失预测提供的排名作为细分指标,但并不涉及将客户分为两组:预期流失客户和非流失客户。

因为实际的客户流失用例依赖于模型按风险对客户进行排名的能力,而不是将他们分成两个群体本身,所以转向更准确地反映这种情况的替代(非标准)准确性测量方法更有意义。如第 9.1.2 节所述,这些测量方法也解决了标准准确性测量中由于客户流失的罕见性引起的问题。

9.1.2 使用 AUC 测量客户流失率预测准确性

对于客户流失率,你应该首先使用的准确性测量方法是曲线下面积(AUC),这里的曲线指的是一种称为接收者操作曲线的分析技术。这种命名是不幸的,因为 AUC 是对指标计算方式的技术描述,但并没有清楚地传达其含义。但每个人都使用这个名字,所以我们别无选择,只能继续使用;我不会再提到接收者操作曲线,因为它对于理解或应用这个指标并不是必要的。正如你将看到的,我的建议是甚至不要向你的商业同事提及这个测量方法。如果你想要更多细节,很容易在网上找到资源。

AUC 的含义比其名称简单,如图 9.3 所示总结。与标准准确性测量一样,你从一个为每个客户都进行了预测并且知道哪些客户流失的数据集开始。考虑以下测试。取一个已经流失的客户和一个没有流失的客户。如果你的模型很好,它应该预测流失客户的流失风险比未流失客户要高。如果模型确实这样做了,那么认为这次比较是成功的。现在考虑每个可能的比较。一个接一个地,将每个客户流失与每个非客户流失进行比较,看看模型是否预测了真实的客户流失具有更高的流失风险。成功预测的整体比例就是 AUC。

图 9.3

图 9.3 使用 AUC 测量准确性

定义 AUC 是模型在所有客户流失和非客户流失的成对比较中,预测客户流失比非客户流失具有更高流失风险的比较百分比。

AUC 避免了标准准确性中的问题,即预测流失并不重要,因为流失在人口中只占很小的比例。在 AUC 的计算中,准确预测流失是核心,因为每个比较都涉及一个流失客户,即使流失客户只占数据的一小部分。同时,AUC 基于风险的排名,不需要将客户人工分类为两个群体。

如果你考虑 AUC 的定义,那个测量可能涉及很多比较。成对比较的总数是流失客户数和非流失客户数的乘积。幸运的是,有一种更有效的方法来进行计算,涉及到接收者操作曲线,但我不打算教你如何使用它。相反,你将使用一个开源包来进行计算(列出 9.1)。确实,AUC 的计算成本比标准准确率指标要高,但差异并不足以引起担忧。

如果你运行列出 9.1,你将在图 9.4 中看到简短的输出——这是一个初步演示。你将在本章中使用 AUC 测量。

图 9.4 列出 9.1 计算预测模型 AUC 的输出

为了演示 AUC,列出 9.1 重新加载了你在第八章中保存的逻辑回归模型;它还重新加载了用于训练模型的数据库(带有标记流失和保留的历史数据集,而不是当前客户数据集)。模型的 predict_proba 函数用于创建预测,并将这些预测传递给来自 sklearn.metrics 包的 roc_auc_score 函数。你应该使用以下标准命令和这些参数在自己的保存数据和回归模型上运行列出 9.1:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 1 

列出 9.1 计算预测模型 AUC

import os
import pickle
from sklearn.metrics import roc_auc_score                              ①
from listing_8_2_logistic_regression import prepare_data               ②

def reload_regression(data_set_path):                                  ③
   pickle_path = data_set_path.replace('.csv', '_logreg_model.pkl')
   assert os.path.isfile(pickle_path), 'Run listing 8.2 to save a log reg model'
   with open(pickle_path, 'rb') as fid:
       logreg_model = pickle.load(fid)
   return logreg_model

def regression_auc(data_set_path):
   logreg_model = reload_regression(data_set_path)                     ④
   X,y = prepare_data(data_set_path)                                   ⑤
   predictions = logreg_model.predict_proba(X)                         ⑥
   auc_score = roc_auc_score(y,predictions[:,1])                       ⑦
   print('Regression AUC score={:.3f}'.format(auc_score))

① sklean 函数可以用来计算 AUC。

② 重新使用列出 8.2 中的 prepare_data 函数

③ 重新加载回归模型 pickle

④ 调用 reload_regression 函数

⑤ 调用列出 8.2 中的 prepare_data 函数

⑥ predict_proba 返回概率预测。

⑦ 调用计算 AUC 的函数

你应该会发现回归模型的 AUC 大约是 0.7,这引发了是否 0.7 是好的的问题。AUC 是一个百分比,就像准确率一样,100% 是最好的。如果你有 100% 的 AUC,所有流失客户的风险排名都会高于所有非流失客户。但你永远不会找到一个 AUC 接近那个高度的真正流失预测系统。

另一方面,考虑最坏的情况。零百分比听起来很糟糕,但这个结果意味着你将所有非流失客户排名高于流失客户。如果你这么想,这个结果倒是可以接受,因为那时你可以将你的模型作为完美的保留预测器。然而,很可能在你的模型设置中出了些问题,导致它做出了反向预测。

事实上,最差的 AUC 是 0.50,这意味着你的预测就像抛硬币一样:一半时间正确,一半时间错误。如果一个预测模型的 AUC 是 0.5,那么它的性能最差——与随机猜测相同。

摘要 AUC 范围从 0.5(相当于随机猜测,没有预测能力)到 1.0(完美地将流失客户与非流失客户进行排序)。

表 9.1 显示了您可以考虑的健康和不健康的 AUC 基准列表。一般来说,流失预测 AUC 在约 0.6 到 0.8 的范围内是健康的。如果它低于 0.6 或高于 0.8,可能存在问题,您需要检查模型中的数据。您可能不会认为高精度会引发担忧,但它可能会。我将在第 9.2.3 节中更多关于这个主题。

表 9.1 流失预测 AUC 基准

AUC 结果 诊断
< 0.45 存在问题!模型正在预测反向。检查您的数据以及计算 AUC 的代码;是否使用了预测概率结果的错误列?
0.45-0.55 与随机猜测(0.5)没有区别。检查您的数据。
0.55-0.6 优于随机猜测,但并不理想。检查您的数据,收集更好的事件,或制定更好的指标。
0.6-0.7 弱度可预测的流失的健康范围。
0.7-0.8 高度可预测的流失的健康范围。
0.8-0.85 极度可预测的流失。这个结果对于消费品来说可能是可疑的,通常只有具有信息性事件和高级指标的商业产品才可能实现。
> 0.85 可能存在问题。通常,流失并不是这么可预测的,即使是对于商业产品。检查您的数据,确保您没有使用过短的领先时间来构建数据集,并且没有前瞻性事件或客户数据字段(在第 9.2.3 节中描述)。

注意 表 9.1 中的 AUC 基准仅适用于客户流失。对于其他问题域,预测 AUC 的预期范围可能更高或更低。

AUC 将在本章的其余部分中使用,但首先,你应该意识到另一种非标准的准确性度量:提升。

9.1.3 使用提升测量流失预测准确性

AUC 是一个有用的指标,但它有一个缺点:它是抽象的,难以解释。我建议使用另一个指标来衡量流失准确性,主要是因为它对商业人士来说很容易理解。事实上,这个指标,被称为提升,起源于市场营销。我将首先解释提升在市场营销中的通用用途,然后解释其在流失中的应用。

定义 提升是由于某种处理相对于基线引起的响应的相对增加。

如果访问网站的 1%的人注册了产品,而促销活动将注册率提高到 2%,那么促销活动引起的提升是 2.0(2%除以 1%)。根据这个定义,提升 1.0 表示没有改进。关于提升需要注意的一点是,它强调的是相对于基线的改进,因此它适合于测量原本就很少发生的事情的改进。对于测量预测模型的准确性,可以使用提升的一个更具体的版本,称为顶级十分位提升。

定义 预测流失模型的顶级十分位提升是指预测为最有可能流失的顶级十分位客户流失率与整体流失率之比。

图 9.5 说明了这个定义。顶部十分位提升类似于常规的升力测量,但基线是整体流失率,处理方式是根据模型选择了最危险的 10%的客户。

图 9-05

图 9.5 使用提升测量准确性

重要:因为这个定义是流失预测中最常见的提升定义,当我使用“提升”这个词时,你应该从上下文中意识到我指的是顶部十分位提升。

为什么整体流失率是基线?如果你随机猜测,这就是预测流失的准确性。如果你有 5%的流失率,如果你随机选择客户,你将发现 5%的时间会有流失。如果你能做得比随机猜测更好(提升大于 1.0),你的结果会改善。你可能会回应说你可以做得比随机猜测更好,你很可能可以,特别是如果你使用基于数据驱动指标的分段,就像你在本书中学到的那些。但关键是,整体流失率是所有公司的一个合理的基线,无论你还在做些什么。

吸收:顶部十分位提升对于测量准确性是有益的,因为它强调了从低基线预测水平上的改进。

列表 9.2 展示了如何使用 Python 计算提升,假设你已经保存了一个模型(如列表 9.1 所示)。同样,如图 9.6 所示,输出只是一个结果的简单打印,仅作为演示。

图 9-06

图 9.6 列表 9.2 的输出(提升)

列表 9.2 没有使用开源包来计算升力。在撰写本文时,没有开源包能进行这种计算,因此我在函数calc_lift中为你实现了一个。计算升力的步骤如下:

  1. 验证数据以确保你有足够数量的不同预测。

  2. 计算样本中的整体流失率。

  3. 按照流失风险预测对预测进行排序。

  4. 定位顶部十分位的位置。

  5. 计算顶部十分位中的流失数量和顶部十分位流失率。结果是顶部十分位流失率除以整体流失率。

我提供的提升计算至少需要 10 个独特的值或水平来预测。如果预测不足,可能会因为数据质量差或模型指定不当而出现问题。数据质量差或模型错误最常见的表现是所有账户都得到相同的预测,但其他变体也是可能的。10 个值的准则是一个经验法则,而不是一个硬性规则。(原则上,预测应该允许你选择最有可能进行比较的 10%的客户。例如,在计算提升的目的上,只要精确的 10%的人口得到一个预测或另一个,那么模型只提供两个不同的预测也是可以接受的。10 个独特值的经验法则可以捕捉到最严重的模型或数据失败,而且精确匹配条件实际上并不必要。)

列表 9.2 计算预测模型提升

from listing_8_2_logistic_regression 
   import prepare_data                                       ①
from listing_9_1_regression_auc 
   import reload_regression                                  ②
import numpy

def calc_lift(y_true, y_pred):                               ③
   if numpy.unique(y_pred).size < 10:                        ④
       return 1.0
   overall_churn = sum(y_true)/len(y_true)                   ⑤
   sort_by_pred=
      [(p,t) for p,t in sorted(zip(y_pred, y_true))]         ⑥
   i90=int(round(len(y_true)*0.9))                           ⑦
   top_decile_count=
      sum([p[1] for p in sort_by_pred[i90:]])                ⑧
   top_decile_churn = 
      top_decile_count/(len(y_true)-i90)                     ⑨
   lift = top_decile_churn/overall_churn
   return lift                                               ⑩

def top_decile_lift(data_set_path):
   logreg_model = reload_regression(data_set_path)           ⑪
   X,y = prepare_data(data_set_path,as_retention=False)      ⑫
   predictions = logreg_model.predict_proba(X)
   lift = calc_lift(y,predictions[:,0])                      ⑬
   print('Regression Lift score={:.3f}'.format(lift))

① 使用列表 8.2 中的 prepare_data 函数

② 使用列表 9.1 中的 reload_regression 函数

③ 参数是真实流失结果和预测的序列。

④ 确保预测是有效的

⑤ 计算整体流失率

⑥ 对预测进行排序

⑦ 计算第 90 百分位的索引

⑧ 计算最十分之一的流失

⑨ 计算最十分之一的流失率

⑩ 返回最十分之一流失与整体流失的比率

⑪ 加载模型,并生成类似于列表 8.1 中的预测

⑫ 加载数据,但不将结果反转为保留

⑬ 调用提升计算函数

你应该使用以下参数运行列表 9.2 来自行检查结果:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 2 

你应该会发现回归模型在模拟数据上实现了大约 4.0 的提升。我已经提到,最小提升是 1.0,这表明你的模型并不比随机猜测好,因为它找不到比整体流失率更多的流失。提升小于 1.0 类似于 AUC 小于 0.5,这意味着你的模型在预测风险时是反向的,因为最危险的十分之一比整体样本有更少的流失。

如果最危险的十分之一客户中只包含流失的客户,你也可以推断出可能的最大提升。提升将是 100%除以整体流失率。因此,最大提升取决于整体流失率。以下是一些例子:

  • 如果流失率是 20%,最大可能的提升将发生在预测的最十分之一都是流失的情况下。那么提升将是 5(100%/20% = 5)。

  • 如果流失率是 5%,最大提升将出现在所有 5%的流失都在最十分之一预测组中。那么最十分之一的流失率将是 50%,提升将是 10(50%/5% = 10)。

模式是,整体流失率越高,可能的最大提升越低。你不可能接近那些最大值,但流失率和更典型提升值之间的关系是相同的。

TAKEAWAY 总体流失率越高,您应该期望的预测模型的提升效果越低。

表 9.2 列出了在真实流失率预测用例中可以期望找到的提升度基准。与 AUC 不同,提升度值的合理范围取决于流失率。如果流失率低,则更容易获得相对较大的提升度。如果流失率高(大于 10%),提升度可能较低。正如前一段所述,当流失率高时,最大提升度会降低。这一特性也适用于期望较低的提升度得分,因为您不太可能在顶部十分位找到如此多的流失率。对于低流失率产品,健康的提升度范围在 2.0 到 5.0 之间,而对于高流失率产品,健康的范围大约在 1.5 到 3.0 之间。

表 9.2 流失率预测提升度基准

低流失率(< 10%)提升度结果 高流失率(> 10%)提升度结果 诊断
< 0.8 < 0.8 存在问题!模型预测方向错误。检查您的数据以及计算提升度的代码。是否使用了预测概率结果的错误列?
0.8-1.5 0.8-1.2 随机猜测(1.0),或者与随机猜测没有太大区别。检查您的数据。
1.5-2.0 1.2-1.5 优于随机猜测,但并不理想。检查您的数据,收集更好的事件,或创建新的指标。
2.0-3.5 1.5-2.25 弱可预测流失的健康范围。
3.5-5.0 2.25-3.0 高度可预测流失的健康范围。
5.0-6.0 3.0-3.5 极度可预测的流失。对于消费者产品来说,这个结果令人怀疑,通常只有具有良好事件和指标的商务产品才可能实现。
> 6.0 > 3.5 可能存在问题。通常,即使是对于商务产品,流失率也不应该是如此可预测的。检查您的数据,确保您没有使用过短的领先时间来构建数据集,并且没有前瞻性事件或客户数据字段(在第 9.2.3 节中描述)。

我喜欢在向商业人士解释准确度时使用提升度,因为这个术语直观且与他们已经理解的指标相关。但是,提升度有一个问题:它可能不稳定,尤其是在小数据集上。您用于预测的指标或模型中的微小变化可能会在结果中造成大的变化。

WARNING 提升度可能不稳定,尤其是在小数据集上。结果可能在不同时间段和预测模型之间有显著差异。为了测量提升度,您应该有数千个观察值和数百个流失率在数据集中(或更多)。流失率越低,您需要的观察值越多,以便使提升度测量稳定。

假设你只有 500 个客户观察结果和 5% 的 churn 率,因此你只有 25 个 churn。在这种情况下,提升是基于这 25 个中有多少在风险预测的前 10%,基线预期(平均)为 2.5。从顶部十分之一中增加或减少几个 churn 会对提升产生重大影响。通常,当你有数千个观察结果或更多时,你应该使用提升。AUC 避免了这类问题,因为它始终使用数据集中的每个 churn,并最大化其使用(通过将每个 churn 与每个非 churn 进行比较)。

吸取经验:使用 AUC 来评估你自己的模型准确性。使用提升来向商业人士解释 churn 准确性。

提升的另一个优点是它使预测 churn 的不精确业务听起来更有说服力。比较以下两个陈述:

  • 这个模型比基线模型好三倍。

  • 这个模型将 churn 率为 70% 的客户评为比 churn 率为 0% 的客户风险更高。

尽管这两个陈述都暗示了比随机猜测更高的相同水平改进,但三倍是一个比 70% 更令人印象深刻的统计数据。

9.2 历史准确性模拟:回测

现在你已经知道了衡量 churn 预测准确性的正确方法以及 churn 预测中的典型情况。但我忽略了一个重要细节:你应该在哪些观察结果上衡量准确性。与分析的许多部分一样,对于 churn 来说,情况略有不同。

9.2.1 回测的“什么”和“为什么”

之前,我通过计算使用你创建模型的相同数据集上的预测准确性来演示你学到的准确性度量方法。然而,这种演示并不是最佳实践;从某种意义上说,就像测试一个学生已经见过的题目一样,因为相同的客户观察结果被用来拟合模型。预测的最佳实践是在未用于拟合模型的观察结果上测试模型的准确性。这种类型的测试被称为样本外测试,因为它测试了算法确定模型时未在数据样本中给出的观察结果。

通常,新客户观察结果的准确性低于用于模型拟合的观察结果。样本内和样本外准确性的差异取决于许多因素。对于 churn 问题上的回归,差异通常很小;对于第 9.5 节中展示的机器学习模型,使用样本内观察结果进行测试可以导致对准确性的高估。

吸取经验:预测模型应该在未用于拟合模型的样本外数据上测试。

你需要等待看到模型在实时客户上的预测效果如何,以此来判断其准确性吗?等待是可行的,你也应该这样做,但有一个更简单的方法:在拟合模型时,保留一些观测值,然后在这些保留的观测值上测试准确性。这样你就可以看到模型在没有等待获取新客户的情况下,对新客户预测的准确性。测试完成后,你可以在不保留任何观测值的情况下,使用所有数据重新拟合模型,并使用这个最终版本对活跃客户进行真正的预测。

下一个问题是要使用哪些数据以及你应该保留多少数据用于测试。测试客户流失预测模型准确性的最现实的方法是使用历史模拟。这个过程被称为回测,如图 9.7 所示。

图片

图 9.7 测量预测性能的回测过程

定义:回测是对预测模型准确性的历史模拟,就像它被反复拟合并用于在过去连续的时期内进行样本外预测一样。

下面是回测的工作原理:

  1. 决定一个过去的时间点,这个时间点大约是你数据集覆盖期间的一半到三分之一的时长。

  2. 使用所有对应于该日期之前时间点的观测值来拟合你的模型。

  3. 使用从你选择的测试日期起一至三个月内的观测值。这个程序测试的是,如果模型在过去预测客户流失但仍然对模型拟合后进入的客户数据进行预测,其准确性会是多少。

  4. 假设你拥有更多数据,将目标日期提前到测试期的末尾。

  5. 通过在第一次拟合的所有数据上重新拟合模型,并加上用于测试的观测值,重复这个过程,并对下一个月到三个月进行测试。

我对客户流失预测的建议与大多数数据科学和统计学课程中所教授的内容略有不同,这些课程很少提及回测。学生们通常学习一种随机洗牌的程序来创建样本外测试,但这些测试并不关注时间因素。回测的程序起源于华尔街的金融预测。回测的创建是由于观察到市场一直在变化,因此预测模型在随机洗牌的准确性测试上的表现与在实际预测上的表现不同。基于现实历史模拟的准确性测试最能准确地估计模型如果在当时是实时运行的话,其表现会如何。

实时预测准确度与洗牌数据测试不同的原因在于,如果经济条件发生变化,例如在经济衰退开始时,衰退前的实时模型拟合可能在新衰退条件下预测效果不佳。为了使模型表现更好,新的条件必须观察一段时间;然后可以对模型进行重新拟合。但在洗牌数据测试中,就好像你拟合了一个通过观察未来事件来了解衰退的模型。这样的模型可能看起来预测效果很好,但实际结果可能会比测试更差。

同样的推理适用于客户流失预测。如果你的市场、产品或竞争在数据集覆盖的时间段内发生变化,那么在变化之后准确预测客户流失可能会很困难。如果你对数据进行洗牌,你可能会得到与当时为你的客户进行预测时不同的结果。最现实的模拟是在事件发生顺序中让模型运行并通过数据外样本进行预测。你可能不知道在观察期间驱动你的客户流失行为条件是否发生变化。但你所不知道的可能会对你造成伤害,因此回测是最佳实践。尽管我描述的历史模拟听起来很复杂,但开源包会为你处理所有细节。

9.2.2 回测代码

开源 Python 包提供运行历史模拟的函数,如第 9.2.1 节所述。你提供数据包你的数据和你要拟合的模型类型,并告诉它你想要将数据分成多少个测试。

图 9.8 展示了历史模拟的示例输出,包括每个外样本测试的升度和 AUC 以及平均值。对于模拟数据集,你可能会发现回测中的 AUC 和升度与样本内数据的 AUC 和升度相似,但对于真实产品数据集来说,情况可能并非如此。

图 9.8 回测输出(列表 9.3)

在图 9.8 中,每个测试周期被称为一个分割,这指的是数据被分割成用于拟合模型的数据库和用于测试的保留数据集。

定义:分割是将数据集划分为单独的部分以进行模型拟合和测试的通用术语。

列表 9.3 包含了生成图 9.8 所示输出的 Python 代码。此列表包含了许多与第八章中回归拟合代码和第 9.1 节中讨论的准确度测量相同的元素。但来自 sklearn.model_selection 包的三个重要新类:

  • GridSearchCV—一个执行各种预测模型测试的实用工具。类名来源于它通过交叉验证(GridSearchCV 中的 CV)的过程专门搜索最佳模型的事实。你将在第 9.4 节中了解更多关于交叉验证的内容;现在,你使用该对象测试单个模型。

  • TimeSeriesSplit—一个辅助对象,告诉 GridSearchCV 测试应通过历史模拟进行,而不是另一种类型的测试(通常是随机洗牌)。类名为 TimeSeriesSplit,但我建议你坚持使用你商业同事最可能理解的原始华尔街术语:回测。

  • scorer—一个包装评分函数的对象。当你使用 GridSearchCV 的非标准评分函数时,你必须将其包装在这样的对象中。这个任务很简单:调用为此目的提供的 make_scorer 函数。你通过在创建 scorer 对象时传递评分函数作为参数。在列表 9.3 中,这种技术用于计算最高十分位的提升。

除了 TimeSeriesSplit,创建 GridSearchCV 所需的参数包括回归模型对象和一个包含两个准确度测量函数的字典。提升测量函数通过评分对象传递,AUC 评分函数作为字符串传递(因为此评分对象是 Python 标准)。

控制测试细节的其他参数包括以下内容:

  • return_train_score—控制是否也要测试样本内准确度(也称为训练准确度)

  • param_grid—测试参数以找到更好的模型(你将在第 9.4 节中了解更多关于此内容)

  • refit—告诉模型在所有数据上重新拟合一个最终模型(你将在第 9.4 节中这样做)

在其他方面,列表 9.3 结合了你已经看到的元素:加载数据和准备数据、创建回归模型和保存结果。需要注意的是,测试是通过在 GridSearchCV 上调用 fit 函数而不是在回归对象本身上触发的。

列表 9.3 使用 Python 时间序列交叉验证进行回测

import pandas as pd
from sklearn.model_selection 
   import GridSearchCV, TimeSeriesSplit                                   ①
from sklearn.metrics import make_scorer                                   ②
from sklearn.linear_model import LogisticRegression

from listing_8_2_logistic_regression 
   import prepare_data                                                    ③
from listing_9_2_top_decile_lift 
   import calc_lift                                                       ④

def backtest(data_set_path,n_test_split):

   X,y = prepare_data(data_set_path,as_retention=False)                   ⑤

   tscv = TimeSeriesSplit(n_splits=n_test_split)                          ⑥

   lift_scorer = 
      make_scorer(calc_lift, needs_proba=True)                            ⑦
   score_models = 
      {'lift': lift_scorer, 'AUC': 'roc_auc'}                             ⑧

   retain_reg = LogisticRegression(penalty='l1',                          ⑨
                                   solver='liblinear', fit_intercept=True)

   gsearch = GridSearchCV(estimator=retain_reg,                           ⑩
                          scoring=score_models, cv=tscv,
                          return_train_score=False,  param_grid={'C' : [1]}, refit=False)

   gsearch.fit(X,y)                                                       ⑪

   result_df = pd.DataFrame(gsearch.cv_results_)                          ⑫
   save_path = data_set_path.replace('.csv', '_backtest.csv')
   result_df.to_csv(save_path, index=False)
   print('Saved test scores to ' + save_path)

① 这些类运行测试。

② 定义了一个自定义的评分函数:提升分数

③ 重新使用列表 8.2 来重新加载数据

④ 重新使用列表 9.2 来计算提升

⑤ 加载数据,将结果作为流失标志

⑥ 创建一个控制拆分的对象

⑦ 创建一个包装提升函数的评分对象

⑧ 创建一个字典,定义评分函数

⑨ 创建一个新的 LogisticRegression 对象

⑩ 创建一个 GridSearchCV 对象

⑪ 运行测试

⑫ 将结果保存到 DataFrame 中

你应该在社交网络模拟(第八章)的自己的数据上运行列表 9.3,并确认你的结果与图 9.8 中的结果相似。使用 Python 包装程序,运行命令如下:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 3 

9.2.3 回测考虑事项和陷阱

对于模拟,只使用了两个测试,因为整个数据集只覆盖了六个月。如果为更大的数据集指定了更多的测试,额外的结果将作为同一文件中的额外列出现。但在回测预测流失时,通常只使用几个分割点进行测试。相比之下,你可能已经学会的随机打乱测试的程序通常需要 10 个或更多的随机测试。你应该根据你的数据样本覆盖的时间长度以及你可能会重新拟合模型的频率来选择分割点的数量。

尽管你可能乐观地认为你每个月都会重新拟合一个新的模型,但现实中,许多公司“设置后即忘”。即使你非常坚定,在你完成初步开发后,你可能一年内只重新拟合自己的模型几次。(每两个月重新拟合一次模拟模型可能是过于乐观的;我使用这个例子是为了演示目的。)此外,频繁的模型更改会让商业人士感到困惑。事实上,一些公司要求每年重新拟合生产模型,以防止当业务指标与模型输出相关联时“移动目标”。例如,如果客户支持代表的薪酬与降低流失概率相关联,那么模型必须在整个财政年度保持固定。

如果你担心使用几个分割点进行测试不如使用 10 个测试那样严格,请不要担心。这些测量应该以我在第一章中提倡的敏捷性和简约性精神来进行。使用几个测试就能告诉你是否预测得很好,或者是否需要在你的模型上做更多的工作;做更多的测试是浪费时间。此外,如果大量的测试分割意味着在模型运行时进行不切实际的模型重新拟合率,你的测试可能会高估你在现实世界中实现的准确性,而在现实世界中你重新拟合的频率较低。

在回测准确性时需要注意的一个其他陷阱是由于在数据库或数据仓库中记录时间时的错误而可能产生的负面影响。这个问题主要发生在事件、订阅或其他客户数据记录在数据库中添加时被回溯时。在这种情况下,你会计算历史指标,并使用可能不会在实时预测活跃客户时可用信息来运行测试。这种错误被称为预测中的前瞻性错误或偏差。

定义 前瞻性偏差是在使用在实时预测活跃客户时不会可用信息的历史模拟来估计准确性时发生的一种错误。

警告 事件、订阅或其他客户数据的回溯记录可能导致你的预测中出现前瞻性偏差,并使回测看起来比你在实时预测中实现的准确性更高。

解决前瞻性偏差的方法是意识到你数据库中记录的任何回溯,并在必要时,在计算指标的事件选择时使用自定义滞后来纠正它。例如,如果你知道所有事件都是在一周后加载到数据仓库中,并且回溯到事件发生的时间,你应该在计算你的指标时包括这个延迟。技巧是,当你运行历史分析时,你不会注意到一周的延迟,但当你尝试实时预测客户流失概率时,你会发现自己所有的指标都过时了一周。

9.3 回归控制参数

在测量你预测的准确性之后,你可能想知道是否有任何方法可以使预测更准确。我提到过的另一个问题是,回归可能导致许多小的权重出现在不重要的指标上。你有一种调整回归的方法,可以帮助解决这两个问题。

9.3.1 控制回归权重的强度和数量

在第八章中,我提到回归模型可能会有太多很小的权重,并且你可以移除它们。这种技术在第 9.9 图中有说明,该图显示了来自社交网络模拟(第八章中的第 8.7 图)的回归权重的相对大小。大多数权重都大于 0.1,一个权重是 0.00,两个权重是 0.01;这些 0.01 的权重是多余的。两个小的权重可能看起来不是问题,但请记住,真实数据可能有十几个或更多的更小的权重,这可能会使你和企业人士更难理解结果。

图 9.9 回归产生的小权重可以被移除。

可能看起来最简单的事情就是将这些非常小的权重设置为零。但关于哪些权重要保留,哪些要移除的决定可能并不那么明确。此外,如果移除了某些权重,其他权重也应该重新调整。回归算法有一种更原则性的方法来处理这种情况,即通过一个参数来控制算法可用于分配到所有指标的总权重。

当控制参数设置为高值时,回归权重往往较大,零的个数较少。当控制参数设置为低值时,权重往往较低,参数设置得越低,零的权重越多。精确的权重由算法优化。不幸的是,这个控制参数没有好、普遍接受的名字。因为回归只有一个相关的参数,我将称之为控制参数。方便的是,Python 代码将这个参数称为(大写)C,所以称之为控制参数是清晰的。

定义 回归控制参数设置回归产生的权重的大小和数量。更高的C设置会产生更多和更大的权重,而更低的C设置会产生更少和更小的权重。

Python 的命名 C 来自回归算法中称为成本参数的东西。它被称为成本,因为算法包括对权重大小的惩罚成本。但是文档中说明成本是 1/C,所以 C 是成本的倒数或倒数。有一个参数被称为成本,但成本对于较低的参数值更高,这很令人困惑,所以我坚持称其为控制参数,或 C

9.3.2 使用控制参数的回归

列表 9.4 展示了使用控制参数的新版本的回归。此列表重用了列表 8.2(第八章)中的所有辅助函数,因此内容不多。唯一的区别是列表 9.4 在函数调用中取 C 的值,在创建对象时传递它,并将其作为扩展传递给输出文件。输出文件与列表 8.2 生成的文件相同。

您应该在您的模拟数据上运行列表 9.4。为了看到设置 C 参数的效果,您可以运行三个版本。这三个版本的 C 参数分别设置为 0.02、0.01 和 0.005。使用 Python 包装程序运行这些版本,如下所示使用 version 参数:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 4 —version 1 2 3

在图 9.10 中比较了运行列表 9.4 的两个版本的结果,以及原始回归(列表 8.2)的结果:

  • 在原始列表中,除了一个权重外,所有权重均不为零,最大权重的绝对值为 0.68。

  • C 参数设置为 0.02 时,四个权重为零,最大权重的绝对值为 0.61。

  • C 参数设置为 0.005 时,八个权重为零,最大权重的绝对值为 0.42。

图 9.10 比较了不同控制参数 C 值导致的回归权重

C 参数减少时,整体模式就是这样发生的。

列表 9.4 使用控制参数 C 的回归

from sklearn.linear_model import LogisticRegression
from listing_8_2_logistic_regression 
   import prepare_data, save_regression_model                              ①
from listing_8_2_logistic_regression 
   import save_regression_summary, save_dataset_predictions

def regression_cparam(data_set_path, C_param):                             ②
   X,y = prepare_data(data_set_path)
   retain_reg = LogisticRegression( C=C_param,                             ③
                                    penalty='l1',
                                    solver='liblinear', fit_intercept=True)
   retain_reg.fit(X, y)                                                    ④
   c_ext = '_c{:.3f}'.format(C_param)                                      ⑤
   save_regression_summary(data_set_path,retain_reg,ext=c_ext)             ⑥
   save_regression_model(data_set_path,retain_reg,ext=c_ext)
   save_dataset_predictions(data_set_path,retain_reg,X,ext=c_ext)

① 此列表使用了列表 8.2 中的所有辅助函数。

② 回归还有一个额外的参数,C。

③ 在创建回归时传递参数

④ 按照列表 8.2 进行回归拟合

⑤ 将参数添加到结果文件名中

⑥ 调用保存函数

注意,算法对 C 参数设置的响应是不规则的。将 C 参数从 1 减少到 0.02 会从回归结果中移除两个额外的指标,而从 0.02 减少到 0.005 会再移除三个。根据算法中参数的定义,您需要考虑控制参数在 1.0(默认值)以下和零以上的变化范围,但影响随着参数的减小而以对数尺度变化。

当我说影响在对数尺度上变化时,我的意思是参数的变化必须在参数的对数中显著不同,才能在算法中产生重大影响。从 1.0 到 0.9 的变化不会太大,而从 1 到 0.1 的变化可能大约与从 0.1 到 0.01 的变化相同。在像 [1, 0.9, 0.8, ..., 0.1] 这样的线性尺度上测试 1 和 10 之间的参数范围是不高效的,因为最佳值可能低于 0.1,而且你可能在 0.9 和 0.8 这样的值之间看不到太大的变化。相反,你应该测试以除数因子递减的参数,例如除以 10:[1, 0.1, 0.01, 0.001]。你需要减小到多小才能看到正确的影响取决于你的数据。如果你想更详细地搜索参数空间,可以除以更小的因子,如 2,例如 [0.64, 0.32, 0.16, ...]。

吸收要点 当你检查 C 的较小值时,你必须检查比 1.0 小一个数量级的值。通常,一个大约为 1.0 的 C 参数会给所有(或大多数)与流失有一点关系的指标分配权重。为了减少非零权重的数量,尝试 C 的值如 0.1、0.01 和 0.001。

9.4 通过测试(交叉验证)选择回归参数

到目前为止,你可能想知道控制参数应该降低到多低。删除权重小的指标是有意义的,但应该在什么点停止?这个决定最好通过查看每个参数设置产生的准确性来做出。

9.4.1 交叉验证

你可以从回归中删除权重小的指标,这对准确性影响不大,这应该不会让你感到惊讶。(因为这些权重是小的,所以它们没有太大影响。)更令人惊讶的是,删除一些指标可以提高准确性。

你将尝试不同的 C 参数值,并使用该参数进行回测,以查看生成的模型有多准确。同时,你可以检查回归中有多少指标获得了零和非零权重。图 9.11 展示了这一过程。在机器学习和统计学中,这类过程的通用术语是交叉验证。

图片

图 9.11 选择回归参数的交叉验证

定义 交叉验证是通过比较使用不同参数创建的模型的准确性和其他特征来优化预测模型的过程。

交叉验证是数据科学和机器学习中的一个常见任务,而CV在您之前介绍的GridSearchCV对象中的含义。名称中的GridSearch部分指的是典型的交叉验证是在一系列或多个参数序列上进行的。如果有两个参数,每个参数都有其自己的值序列,那么这两个序列的组合将定义一个网格。实际上,可以有任意数量的参数。对于回归模型,您将进行一个参数的交叉验证。稍后,您将使用更高维度的交叉验证来进行机器学习模型。

9.4.2 交叉验证代码

图 9.12 显示了交叉验证的主要结果,它绘制了运行回归时从C参数的值序列中得到的 AUC、提升和权重数量。这个结果证实了可以移除小权重指标,而准确度不会受到影响:在准确度发生任何明显变化之前,指标的数量可以从 13 减少到 9。在模拟中,当移除不那么重要的指标时,提升略有增加,但 AUC 没有增加。

图 9.12 交叉验证结果图

图 9.12 交叉验证结果图

列表 9.5 包含了生成图 9.12 的代码。列表中包含多个函数定义,但请注意,大部分代码是用于绘图和分析的。Python 开源包只需几行代码就处理了交叉验证。列表 9.5 中的函数如下:

  • crossvalidate_regression—这个主要函数执行交叉验证,几乎与列表 9.4 中的相同。最重要的区别是传递了一个C参数值的序列,而不是单个值。另一个区别是在GridSearchCV对象的fit函数返回后,调用辅助函数执行额外的分析和保存结果。

  • test_n_weightsGridSearchCV对象在回测中对每个参数进行模型准确性的测试,但它不会测试回归返回的权重数量。调用一个单独的循环来在每个C参数的序列中拟合回归,并计算非零权重的数量。这是在完整数据集上进行的,因此它不是一个回测,而是对最终模型的一个测量。

  • plot_regression_test—这个函数通过结合 AUC、提升和具有非零权重的指标数量,创建了图 9.12 所示的图表。

  • one_subplot—这个辅助函数创建并格式化每个子图。

列表 9.5 还保存了图 9.12 的结果,如图 9.13 所示。这个结果是GridsearchCV(如第 9.2 节所述)的输出,但不是单行,而是每个测试的C参数值在表中占一行。还有一个额外的列,包含测试权重数量的结果。多个参数的交叉验证输出显示,标记为 rank_test_lift 和 rank_test_AUC 的列是指具有不同参数值的模型在准确度指标上的排名。(当你在第 9.2 节首次看到这些列时,其中一些可能看起来是多余的。)

图 9.13 交叉验证结果表

您应使用以下命令行参数运行列表 9.5 以生成自己的图表,如图 9.12 所示,并生成一个类似于图 9.13 的.csv 文件:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 5

列表 9.5 回归C参数交叉验证

import pandas as pd
import ntpath
import numpy as np
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt

from listing_8_2_logistic_regression import prepare_data
from listing_9_2_top_decile_lift import calc_lift

def crossvalidate_regression(data_set_path,
                             n_test_split):                                ①

   X,y = prepare_data(data_set_path,as_retention=False)
   tscv = TimeSeriesSplit(n_splits=n_test_split)
   score_models = {                                                        ②
      'lift': make_scorer(calc_lift, needs_proba=True), 
      'AUC': 'roc_auc'
   }
   retain_reg = LogisticRegression(penalty='l1', 
                                   solver='liblinear', fit_intercept=True)
   test_params = {'C' : [0.64, 0.32, 0.16, 0.08,                           ③
                         0.04, 0.02, 0.01, 0.005, 0.0025]}
   gsearch = GridSearchCV(estimator=retain_reg,                            ④
                          scoring=score_models, cv=tscv, 
                          verbose=1,return_train_score=False,
                          param_grid=test_params, refit=False)
   gsearch.fit(X,y)
   result_df = pd.DataFrame(gsearch.cv_results_)                           ⑤
   result_df['n_weight']= 
      test_n_weights(X,y,test_params)                                      ⑥
   result_df.to_csv(data_set_path.replace('.csv', '_crossval.csv'), index=False)
   plot_regression_test(data_set_path,result_df)                           ⑦

def test_n_weights(X,y,test_params):                                       ⑧
   n_weights=[]
   for c in test_params['C']:                                              ⑨
       lr = LogisticRegression(penalty='l1',C=c,                           ⑩
                               solver='liblinear', fit_intercept=True)
       res=lr.fit(X,~y)                                                    ⑪
       n_weights.append(
          res.coef_[0].astype(bool).sum(axis=0))                           ⑫
   return n_weights

def plot_regression_test(data_set_path, result_df):                        ⑬
   result_df['plot_C']=result_df['param_C'].astype(str)                    ⑭
   plt.figure(figsize=(4,6))
   plt.rcParams.update({'font.size':8})
   one_subplot(result_df,1,'mean_test_AUC',                                ⑮
               ylim=(0.6,0.8),ytick=0.05)
   plt.title(                                                              ⑯
      ntpath.basename(data_set_path).replace(
                                       '_dataset.csv',' cross-validation'))
   one_subplot(result_df,2,'mean_test_lift',                               ⑰
               ylim=(2, 6),ytick=0.5)
   one_subplot(result_df,3,'n_weight',                                     ⑱
               ylim=(0,int(1+result_df['n_weights'].max())),ytick=2)
   plt.xlabel('Regression C Param')                                        ⑲
   plt.savefig(data_set_path.replace('.csv', '_crossval_regression.png'))
   plt.close()
def one_subplot(result_df,plot_n,var_name,ylim,ytick):
   ax = plt.subplot(3,1,plot_n)                                            ⑳
   ax.plot('plot_C', var_name,                                             ㉑
           data=result_df, marker='o', label=var_name)
   plt.ylim(ylim[0],ylim[1])                                               ㉒
   plt.yticks(np.arange(ylim[0],ylim[1], step=ytick))                      ㉓
   plt.legend()
   plt.grid()

测试拆分的数量是一个参数。

② 分数函数将升力包装在评分对象中。

③ 不是测试一个 C 参数,而是测试一个列表

创建交叉验证对象,并调用 fit 方法

⑤ 将结果放入 DataFrame

添加另一个包含权重测试结果的列

使用 plot_ regression_ test 绘制一个图表

测试不同 C 参数的权重数量

9.9 循环遍历参数

使用一个 C 值创建逻辑回归

拟合模型

⑫ 计算非零权重的数量

从回归测试的结果中制作一个图表

C 参数的字符串版本,用作 x 轴

⑮ 调用辅助函数以绘制 AUC

在三个子图中的第一个上方添加标题

调用辅助函数以绘制升力

⑱ 调用辅助函数以绘制非零权重的数量

在第三个子图后添加一个 x 标签

开始由参数指定的子图

将命名变量与 C 参数的字符串版本进行绘图

根据参数设置 y 轴限制

根据参数设置刻度

9.4.3 回归交叉验证案例研究

图 9.14 显示了来自真实公司案例研究的回归交叉验证示例。非零权重的数量以百分比而不是计数显示;否则,这些结果与图 9.12 的阅读方式相同。

图 9.14 交叉验证案例研究结果

以下是一些案例研究结果的有趣特征:

  • 预测的 AUC 范围在 0.6 到 0.8 之间。

  • 预测的升力范围在 2.0 到 3.5 之间。

  • 对于三个案例研究中的两个,当许多指标从回归中获得零权重时,AUC 和升力有明显的改进。(这是一个简单性也有利于准确性的明显例子。)在这些情况下,C参数的最佳值在约 0.02 到 0.08 的范围内。与包含所有特征相比,AUC 的改进是几个百分点。

  • 对于第三次模拟,所有指标都达到了最优的 AUC;删除任何指标都会导致准确性的显著下降。

这些结果是典型的,但你在实际案例研究中可能会看到比我这里呈现的更多样性。

9.5 使用机器学习预测流失风险

到目前为止,你已经学习了使用回归进行预测,其中预测是通过将指标乘以一组权重来进行的。你也可以使用其他类型的预测模型来预测流失,这些模型统称为机器学习。没有官方的定义来界定什么是机器学习模型,但为了本书的目的,我使用以下定义。

定义:机器学习模型是指任何具有以下两个特征的预测算法:(1)算法通过处理样本数据来学习做出预测(与使用由人类程序员设置的规则进行预测相比),(2)算法不是回归算法。

第二个条件可能看起来很奇怪,因为回归算法显然满足第一个条件。这种区别是历史性的,因为回归方法比机器学习方法早了几十年。

9.5.1 XGBoost 学习模型

这本书只涵盖了一个机器学习算法——XGBoost,但拟合模型和预测的技术同样适用于你可能会考虑的多数其他算法。XGBoost 算法基于决策树的概念,如图 9.15 所示。

图 9.15 使用规则树进行预测

定义:决策树是一种预测结果(例如客户的流失或未流失)的算法,它由由规则或测试组成的二叉树组成。

决策树中的每个测试都只取一个指标,并检查它是否大于或小于一个预定的切割点。对于观察结果(例如客户的观察结果)的预测是通过从树的根开始并执行第一个测试来确定的。测试的结果决定了从节点到二级测试的哪个分支进行。所有测试的结果确定了一条通过树的路,并且树的每个叶子节点都有一个指定的预测。

小决策树看起来很简单,它们曾经被认为是易于理解的机器学习模型。但在实践中,对于具有许多指标的数据库,大型决策树变得难以解释。幸运的是,没有人需要阅读树中的规则来做出预测。

使用样本数据进行预测时,使用算法测试指标并确定切点以优化性能。如果回测显示结果准确,你可以使用决策树进行预测,而不必过于关注规则的实质。存在解释决策树的方法,但它们超出了本书的范围。如果你有多个指标,最好通过前面章节中展示的分组和回归方法来理解指标对客户流失可能性影响,因此我不会在解释决策树上花费时间。

除了难以解释之外,决策树在预测准确性方面也不再是尖端技术。但决策树实际上是更准确机器学习模型的构建块。一个例子是随机森林,如图 9.16 所示。

图 9.16 使用规则树森林进行预测

图 9.16 使用规则树森林进行预测

定义 随机森林是一种通过随机生成大量决策树(森林)来预测结果(如客户的流失)的算法。所有树都试图预测相同的输出,但每棵树都根据不同的学习规则进行预测。最终的预测是通过平均森林的预测得出的。

随机森林是所谓的集成预测算法的一个例子,因为最终的预测是由一组其他机器学习算法的组合得出的。集成意味着作为一个整体而不是个别评估的一组。随机森林是一种简单的集成类型,因为每一棵树在结果中都有平等的投票权,并且随机添加额外的树。提升是一种机器学习算法的名称,它在集成(如随机森林)的基础上进行了一些重要的改进。

定义 提升是一种机器学习集成,其中集成成员被添加以纠正现有集成的错误。

与随机森林中随机添加决策树不同,在提升集成中,你创建每一棵新树是为了纠正现有集成中做出的错误答案,而不是在正确示例上重新预测。在提升算法内部,连续生成的树用于纠正先前树未能正确分类的观察结果。此外,在投票中分配给连续树的权重是为了最好地纠正错误,而不是像随机森林中那样进行平等的投票。这些改进使得决策树提升森林比真正的随机决策树森林更准确。

XGBoost(即极端梯度提升)是一种机器学习模型,在撰写本文时,它是用于通用预测的最受欢迎和最成功的模型。XGBoost 之所以受欢迎,是因为它提供了最先进的性能,并且拟合模型的算法相对较快(与其他提升算法相比,但不如回归快)。关于 XGBoost 算法的详细信息超出了本书的范围,但网上有许多优秀的免费资源。

9.5.2 XGBoost 交叉验证

像 XGBoost 这样的机器学习算法可以做出准确的预测,但这种准确性伴随着一些额外的复杂性。复杂性之一是算法具有多个可选参数,您必须正确选择这些参数才能获得最佳结果。XGBoost 的可选参数包括控制单个决策树如何生成的参数,以及控制不同决策树的投票如何组合的参数。以下是 XGBoost 最重要的几个参数:

  • max_depth—每个决策树中规则的最高深度

  • n_estimator—生成决策树的数量

  • learning_rate—强调最佳树投票权重的程度

  • min_child_weight—投票中每个树的最低权重,无论其表现如何

由于没有直接选择这么多参数值的方法,因此这些值是通过样本外交叉验证来设置的。您在 9.4 节中的回归控制参数上使用了这种方法。

吸收要点:最先进的机器学习模型有如此多的参数,确保您选择最佳值的唯一方法是对大量参数进行交叉验证。也就是说,您测试每个参数的每个可能的值序列,并选择在交叉验证测试中具有最佳值的那些。

图片

图 9.17 XGBoost 代码输出

图 9.17 显示了这样一个交叉验证结果的示例。

图 9.17 是通过在早期章节中使用的模拟社交网络数据集上运行列表 9.6 创建的。它与您看到的用于选择回归C参数的交叉验证结果类似,但它有更多的列和行:

  • 由于有四个参数参与了测试:max_depthn_estimatorlearning_rateminimum_child_weight,因此有四列参数。

  • 输出表中有很多行——精确地说,有 256 种参数组合。当您检查列表 9.6 时,256 种参数组合的原因变得清晰:测试是在四个参数上进行的,每个参数的值序列有四个条目。组合的总数是每个参数值的数量的乘积——在这种情况下,4 × 4 × 4 × 4 = 256。

您应该在您自己的模拟数据上运行列表 9.6,使用以下常用的 Python 包装程序命令以及这些参数:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 6

如果 XGBoost 模型的交叉验证所需时间比回归模型长得多,请不要感到惊讶。有更多的参数组合需要测试,每次拟合模型时,过程都会显著变长。确切的时间可能因硬件而异,但与我相比,XGBoost 模型拟合所需的时间大约是回归模型的 40 倍。如图 9.8 所示,回归模型平均只需要几百毫秒就能拟合;图 9.17 显示 XGBoost 模型的拟合大约需要 1 到 4 秒。

注意:XGBoost 是一个独立的 Python 包,所以如果你之前没有使用过它,在运行列表 9.6 之前需要安装它。

列表 9.6 XGBoost 交叉验证

import pandas as pd
import pickle
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit
from sklearn.metrics import make_scorer
import xgboost as xgb                                                          ①

from listing_8_2_logistic_regression import prepare_data
from listing_9_2_top_decile_lift import calc_lift

def crossvalidate_xgb(data_set_path,n_test_split):

   X,y = prepare_data(data_set_path,ext='',as_retention=False)                 ②
   tscv = TimeSeriesSplit(n_splits=n_test_split)
   score_models = {'lift': make_scorer(calc_lift, needs_proba=True), 'AUC': 'roc_auc'}

   xgb_model = xgb.XGBClassifier(objective='binary:logistic')                  ③
   test_params = { 'max_depth': [1,2,4,6],                                     ④
                   'learning_rate': [0.1,0.2,0.3,0.4],                         ⑤
                   'n_estimators': [20,40,80,120],                             ⑥
                   'min_child_weight' : [3,6,9,12]}                            ⑦

   gsearch = GridSearchCV(estimator=xgb_model,n_jobs=-1, scoring=score_models, ⑧
                          cv=tscv, verbose=1, return_train_score=False,                                   
                          param_grid=test_params,refit='AUC')                  ⑨
   gsearch.fit(X.values,y)                                                     ⑩

   result_df = pd.DataFrame(gsearch.cv_results_)                               ⑪
   result_df.sort_values('mean_test_AUC',ascending=False,inplace=True)         ⑫
   save_path = data_set_path.replace('.csv', '_crossval_xgb.csv')
   result_df.to_csv(save_path, index=False)
   print('Saved test scores to ' + save_path)

   pickle_path = data_set_path.replace('.csv', '_xgb_model.pkl')               ⑬
   with open(pickle_path, 'wb') as fid:
       pickle.dump(gsearch.best_estimator, fid)                                ⑭
   print('Saved model pickle to ' + pickle_path)

① 导入 XGBoost,它是一个独立的包。

② 这部分功能与列表 9.5 中的内容基本相同:回归交叉验证。

③ 创建一个用于二元结果的 XGBClassifier 对象。

④ 测试树深度从 1 到 6。

⑤ 测试学习率从 0.1 到 0.4。

⑥ 测试估计器的数量从 20 到 120。

⑦ 测试从 3 到 12 的最小权重。

⑧ 使用 XGBoost 模型对象创建 GridSearchCV 对象,并测试参数。

⑨ 在交叉验证后根据 AUC 重新拟合最佳模型。

⑩ 将值作为非 DataFrame 传递,以避免在编写此内容时的已知包问题。

⑪ 将结果转移到 DataFrame 中。

⑫ 对结果进行排序,以便最佳 AUC 值排在第一位。

⑬ 创建最佳结果的 pickle 文件。

⑭ 最佳结果位于 GridSearchCV 对象的 best_estimator 字段中。

列表 9.6 中的代码与用于回归交叉验证的列表 9.5 中的代码类似。主要步骤是

  1. 准备数据。

  2. 创建一个模型实例(在这个例子中,是一个 XGBoost 模型)。

  3. 定义要使用的准确度测量函数(提升和 AUC)。

  4. 定义要测试的参数序列。

  5. 将准备好的参数传递给 GridSearchCV 对象并调用 fit 函数。

  6. 保存结果(与回归交叉验证一样,没有进行额外的分析)。

列表 9.6 与列表 9.5 中的回归交叉验证之间有一个重要且稍微微妙的不同之处在于,数据集是从原始未缩放的指标创建的,并且它不使用分数或组,就像你在回归中做的那样。对于 XGBoost(或决策树通常)来说,没有理由重新缩放指标,因为规则中的切点在指标上同样有效,无论规模或偏斜如何。此外,分组相关指标不会提供任何好处;实际上,它可能会损害此类机器学习模型的性能。分组相关指标对解释有益,并避免了相关指标在回归中可能引起的问题。

另一方面,对于 XGBoost 来说,多种指标是有益的,相关性无害。(如果两个指标相关,任何一个都可以作为树中的合适规则节点。)因此,从第八章的prepare_data函数使用空扩展参数调用,以便它加载原始数据集而不是分组得分(默认行为)。

9.5.3 XGBoost 准确性与回归比较

由于 XGBoost 需要更长的时间来拟合更多的参数,你应该预期它在预测准确性方面会有所提高。这种预期在图 9.18 中得到证实,该图比较了回归和 XGBoost 模型在模拟以及第一章中介绍的公司三个真实案例研究数据集上的 AUC 和提升。AUC 的提高范围从 0.02 到 0.06,XGBoost 总是比回归产生更准确的预测。在提升方面,提高范围是 0.1 到 0.5。

图 9-18

图 9.18 回归和 XGBoost 提升比较

这些改进是否显著?记住,你可能在流失预测中看到的 AUC 范围大约在 0.6 到 0.8 之间。因此,最大 AUC 比最小 AUC 高出 0.2,从相对意义上讲,AUC 提高 0.02 表示整体可能范围提高了 10%。同样,AUC 提高 0.05 表示在同类中最差和最好之间的差异的 25%,所以这些改进是显著的。尽管如此,即使使用机器学习,预测仍然不完美,这就是为什么我在第一章建议使用机器学习预测流失不太可能达到机器学习领域的一些炒作。

总结:尽管机器学习算法可以产生比回归更准确的预测,但由于主观性、信息不完整、稀有性和影响流失时机的额外因素,流失总是难以预测。

9.5.4 高级指标与基本指标比较

另一个重要的问题是,你可以将多少准确性的提高归因于你在第七章中创建高级指标所做的努力。到目前为止,你可能认为,由于高级指标在队列分析中显示出与流失的关系,它们必须提高了模型。但是,正如你想要通过展示你的模型可以预测样本外数据来验证你的数据和建模,确认你创建更多指标的工作在经验上有所贡献是有意义的。

要在模拟的社会网络数据集上进行比较,你可以在第四章的原始数据集上运行交叉验证测试命令的额外版本。也就是说,你运行的数据集不包括第七章的高级指标——你只使用第三章的基本指标。要在基本指标数据集上运行回归交叉验证,请使用以下命令:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 5 —version 1

结果是一个类似于图 9.13 所示的交叉验证表。您可能会发现,任何模型的最大精度对于基本指标数据比对于高级指标数据要低一些。如图 9.19 中的条形图所示,我使用基本指标进行的回归模拟的最大精度是 0.63;对于带有高级指标的模拟数据回归,最大 AUC 是 0.75。创建高级指标所花费的时间是值得的。事实上,使用高级指标进行的回归精度显著优于使用带有基本指标的 XGBoost,而当机器学习算法使用高级指标时,额外的改进相对较小。

图片

图 9.19 使用基本和高级指标比较 AUC

您可以通过运行带有这些参数的 XGBoost 交叉验证命令的第二版来对 XGBoost 模型执行相同的检查:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 6 —version 1

在这种情况下,您可能会发现 XGBoost 预测在高级指标下表现略好。我使用带有基本指标的 XGBoost 得到了 0.774 的 AUC,而带有高级指标的 XGBoost 得到了 0.797;归因于高级指标的改进是 0.023。

图 9.18 也包含了在第一章中介绍的三家真实公司案例研究中进行的预测的类似比较。这些比较显示了带有和未带有高级指标时的精度之间的不同关系。这三个案例说明了您在自己的案例研究中可能遇到的场景范围:

  1. 在第一个案例研究中,高级指标显著提高了回归精度,但 XGBoost 没有获得任何改进,XGBoost 整体表现最佳。这个结果表明,您不能总是期望高级指标会提高机器学习。

  2. 在第二个案例研究中,回归和 XGBoost 都因添加高级指标而显著改进。带有高级指标的回归精度与带有基本指标的 XGBoost 精度大致相同。带有高级指标的 XGBoost 精度是所有中最高的,比带有基本指标的回归提高了约 0.1。

  3. 在第三个案例研究中,使用高级指标进行的回归精度高于未使用高级指标的 XGBoost。但所有中最高的精度是通过使用高级指标的 XGBoost 实现的:比基本指标和回归提高了 0.1。这个案例研究与社会网络模拟最相似。

这些案例表明,如果您将 churn 预测的高精度视为首要任务,那么机器学习和高级指标都非常重要。根据我的经验,高级指标通常可以提高回归和 XGBoost 等机器学习模型的 churn 预测精度。

9.6 使用机器学习预测细分客户

列表 9.6 找到了一组参数,可以生成一个高精度的机器学习模型。程序还保存了最佳模型到一个 pickle 文件中。如果您想使用该模型对活跃客户进行预测,您需要重新加载保存的模型并在活跃客户列表上使用它。代码在列表 9.7 中演示。列表 9.7 实际上与列表 9.5 相同,您使用它来使用保存的回归模型进行预测。该列表执行以下操作:

  1. 重新加载保存的模型 pickle 文件

  2. 加载当前客户数据集

  3. 在模型上调用predict_proba函数,并将数据作为参数传递

  4. 将结果保存为预测的 DataFrame 和总结结果的直方图

与列表 9.6 中的 XGBoost 分类类似,数据保持其原始形式,未缩放且未分组。数据准备以进行预测必须与模型训练时的数据准备方式相匹配。

列表 9.7 XGBoost 预测

import pandas as pd
import os
import pickle
from listing_8_4_rescore_metrics import reload_churn_data
from listing_8_5_churn_forecast import forecast_histogram

def churn_forecast_xgb(data_set_path):
   pickle_path = 
      data_set_path.replace('.csv', '_xgb_model.pkl')                   ①
   assert os.path.isfile(pickle_path), 
      'Run listing 9.6 to save an XGB model'
   with open(pickle_path, 'rb') as fid:
       xgb_model = pickle.load(fid)

   curren_df = reload_churn_data(data_set_path,                         ②
                                 'current','8.3',is_customer_data=True)
   predictions = 
      xgb_model.predict_proba(current_df.values)                        ③
   predict_df = pd.DataFrame(predictions,                               ④
                             index=current_df.index,
                             columns=['retain_prob','churn_prob'])

   forecast_save_path = 
      data_set_path.replace('.csv', '_current_xgb_predictions.csv')
   print('Saving results to %s' % forecast_save_path)
   predict_df.to_csv(forecast_save_path, header=True)

   forecast_histogram(data_set_path,                                    ⑤
                      predict_df,ext='xgb')

① 重新加载 pickle 文件中保存的 XGBoost 模型

② 重新加载当前客户指标数据

③ 进行预测

④ 从预测中创建 DataFrame

⑤ 列表 8.5 中的此函数生成直方图。

列表 9.7 还创建了 XGBoost 对当前客户流失预测的直方图。它没有显示,因为它与您使用相同函数为回归模型制作的流失概率预测图相似。

注意:您应该检查 XGBoost 预测的校准和分布,就像您在第八章中学习如何对回归预测进行校准一样。

对于社交网络模拟,XGBoost 预测的分布和校准与回归相似,但这种结果是巧合,不是您可以始终期望的。您不能期望 XGBoost 预测像回归预测那样校准和分布,因为 XGBoost 预测概率不是与回归预测概率相同意义上的概率。

回想一下,校准是指您的预测与事件发生的真实概率相一致的性质。另一方面,由 AUC 和提升度衡量的准确性取决于预测的排序或排名,而不是精确值。回归模型设计得使预测概率校准到样本数据,同时尽可能准确。当 XGBoost 模型给出预测概率时,它是集成决策树的加权投票。这些投票被优化以对流失风险进行排名——XGBoost 在这方面是成功的,如准确性结果所示。但集成决策树的投票并不是为了产生校准到实际流失率的预测。

吸收点:XGBoost 不一定提供校准的流失概率预测。XGBoost 模型是针对以分类流失来衡量的准确性进行优化的,而不是匹配观察到的流失率。

由于 XGBoost 模型预测的不可靠校准,XGBoost 预测不适合用于估计客户终身价值,如第八章所示。

警告:不要使用 XGBoost 预测客户终身价值或任何其他依赖于 churn 概率预测与实际 churn 概率匹配的用例。这同样适用于大多数机器学习模型:阅读你所使用的模型的文献,以确认它是否产生除了准确之外还经过校准的预测。

摘要

  • 由于 churn 的罕见性,churn 预测的准确性不能使用标准准确性测量来衡量。

  • 曲线下面积(AUC)是模型将 churn 评为比非 churn 风险更高的百分比,考虑所有 churn 和非 churn 的配对。

  • 提升是 churn 风险预测中最高十分之一 churn 率与整体 churn 率的比率。

  • AUC 和提升是衡量 churn 预测准确性的良好指标。

  • 应在未用于训练模型的样本上衡量准确性。

  • 对于 churn,准确性应在反映产品和市场条件可能随时间变化的回测(历史)模拟中衡量。

  • 本书所教授的回归模型包括一个控制参数,该参数设置权重的整体大小和非零权重的数量。

  • 通过测试使用不同回归参数值的模型版本,可以找到用于回归控制参数的最佳值。

  • 通过测试设置预测模型参数称为交叉验证。

  • 对于回归,你选择控制参数的值,以最小化非零权重的数量并帮助或不会损害准确性。

  • 通常,在回归中,可以分配零权重给大部分指标;准确性要么提高,要么不会变差。

  • 机器学习模型是一个从数据(非编程)中拟合的预测模型,它不是回归模型。

  • 决策树是一种简单的机器学习模型,通过分析具有度量比较规则的树来预测客户。

  • XGBoost 是一种最先进的机器学习模型,它使用决策树的集成并加权它们的预测以最大化准确性。

  • XGBoost 和其他机器学习模型有许多参数必须通过交叉验证来设置。

  • XGBoost 预测的准确性通常超过回归预测的准确性。

  • 除了基本指标外,使用高级指标通常会使回归和机器学习模型的预测更准确。

  • XGBoost 的 churn 概率预测未校准到实际 churn 率,因此 XGBoost 的 churn 预测不应用于客户终身价值或其他依赖于匹配实际 churn 概率的用例。

10 人口统计学和公司人口统计学

本章涵盖

  • 创建包含人口统计学或公司人口统计学信息的数据库

  • 将日期信息转换为区间并分析其与流失的关系

  • 分析文本类别与流失之间的关系

  • 使用人口统计学或公司人口统计学信息预测流失概率

  • 使用人口统计学或公司人口统计学信息细分客户

现在,你已经了解了如何使用客户行为数据来细分客户,以便创建干预措施来提高参与度。这些策略是提高客户参与度和保留率最重要的策略,这也是为什么它们是本书的重点。但降低客户流失率的一种其他方法并不是关于干预现有客户:找到一开始就更有可能参与的新客户。识别那些倾向于更积极参与的客户的事实,然后集中客户获取努力,寻找更多类似客户。这些事实通常被称为人口统计数据(关于个人的数据)和公司统计数据(关于公司的数据)。为了这次讨论的目的,我使用以下定义。

定义:人口统计学是关于个人客户的实际情况,而公司人口统计学是关于作为公司的客户的实际情况。

人口统计学和公司人口统计学通常是关于客户的不变事实,或者仅偶尔变化的事实。人口统计学和公司人口统计学不包括产品使用或订阅衍生指标,但可以包括关于客户如何注册或关于客户用于访问在线服务的硬件的事实。通常,面向消费者(B2C)或直接面向消费者(D2C)的公司使用人口统计数据,而面向企业(B2B)的公司使用公司统计数据。正如你将看到的,人口统计学和公司人口统计学在通常可用的具体信息方面有所不同。但无论哪种情况,该信息的特点都是相似的,因此处理人口统计学和公司人口统计学的方法是相同的。

注意:本章使用 GitHub 仓库中书籍的社交网络模拟示例(github.com/carl24k/fight-churn),这是一个消费者产品。因此,我通常讨论人口统计学,但同样的技术也适用于公司人口统计学。

需要注意的是,针对人口统计数据进行定位是减少客户流失最不直接的方法,因为它不能帮助你的现有客户变得更加活跃。你有时可以影响客户的行为,但你不能改变他们的人口统计或企业统计数据!此外,针对收购的定位通常影响有限,因为大多数产品和服务的客户无法仅通过一个或几个首选渠道获得。尽管如此,这种方法可以在一段时间内推动客户流失率的变化,因此尝试你所能利用的每一种方法都是值得的。

本章的组织结构如下:

  • 第 10.1 节描述了典型的人口统计和企业统计数据类型以及包含它们的数据库模式,并教你如何将此类数据作为数据集的一部分提取出来。

  • 第 10.2 节展示了如何使用类别队列分析单独分析文本人口统计数据字段,这与指标队列分析略有不同,因为它使用了一个新概念:置信区间。

  • 第 10.3 节教你如何通过组合来处理大量的人口统计类别。

  • 第 10.4 节展示了如何分析日期字段与其与客户流失的关系(在将日期转换为时间间隔后,与指标队列分析相同)。

  • 第 10.5 节教你如何在数据包括人口统计数据字段时,拟合客户流失概率模型,如回归和 XGBoost。

  • 第 10.6 节将第 10.5 节中的建模扩展到使用人口统计字段进行预测和细分活跃客户。

注意:本章节中没有使用任何真实个人信息。所有示例均由模拟数据创建,旨在与我所参与的真实案例研究相似。

10.1 人口统计和企业统计数据集

首先,我将解释我所说的“人口统计和企业统计数据”究竟是什么,以及它与你在本书大部分内容中看到的指标有何不同。然后,我将使用社交网络模拟来展示创建包含人口统计数据和指标的典型数据集的方法。

10.1.1 人口统计和企业统计数据的类型

表 10.1 提供了人口统计和企业统计数据的示例。尽管这个表格涵盖了最常见的例子,但还有许多其他可能性。正如你所看到的,某些类型的数据在消费者和企业中都是常见的,但形式略有不同。例如,个人有出生日期,公司有成立日期;家庭有成员数量,公司有员工数量。其他项目是针对消费者或企业的特定项目,例如个人的教育水平或公司的行业。表 10.1 还显示了列出的项目的数据类型。

表 10.1 人口统计和企业统计数据示例

人口统计 企业统计 数据类型
出生日期 成立日期 日期
销售渠道 销售渠道 字符串
居住地 公司注册地或地理 字符串
职业 行业或垂直领域 字符串
硬件和操作系统信息 技术堆栈信息 字符串
家庭成员数量 员工数量 数量
达到的教育水平 公司阶段(初创公司、融资轮次或上市) 字符串
性别 B2B 或 B2C 商业模式 字符串

原则上,使用人口统计数据和指标来理解客户流失和细分客户之间没有太大的区别。

总结:为了理解客户流失并基于人口统计数据形成客户细分,您根据人口字段值形成客户群组,并比较每个客户群组的流失率。

非数值类型是相对于指标而言,在人口统计数据和公司统计数据中需要单独技术的原因。如果您正在查看数值人口统计数据,技术与方法相同,只是数据来源不同。

10.1.2 社交网络模拟的账户数据模型

由于人口统计数据与每个账户相关联且很少改变,因此将其存储在单个数据库表中是标准的,该表按账户 ID 索引,如图表 10.2 所示。图表 10.2 包括一些属于社交网络模拟的人口字段:

  • 渠道(简称销售渠道)——销售渠道指的是客户如何找到产品并注册。所有用户都通过一种方法注册,因此渠道是一个必填字段,在社交网络模拟中没有空值。在模拟的社交网络数据集中,不同的销售渠道如下:

    • 应用商店 1

    • 应用商店 2

    • 网络注册

  • 出生日期——许多产品要求客户输入他们的出生日期作为他们达到(或超过)使用产品最低年龄的声明。因为所有用户都被要求输入某些信息,所以出生日期是一个必填字段,在社交网络模拟中没有空值。

  • 国家——用户的居住国家通常可以从用户的支付信息或他们在软件中的本地化选择中推导出来。在社交网络模拟中,用户来自 20 多个国家,这些国家由两位字符代码表示(来自国际标准化组织 ISO 3166-1 alpha-2 标准)。对于社交网络模拟,国家字段可以包含缺失值(数据库中的空值)。假设这是一个可选设置;一些用户懒得设置它。

这些三个字段代表展示本章技术所需的最低集合。在实际产品中,可能存在更多字段,尽管这些字段的数目因产品领域而异。许多 B2B 公司对其客户了解很多,但对于需要最少注册要求的消费产品,人口统计数据可能很稀疏。

表 10.2 典型账户数据模式(续

类型 备注
account_id integerchar 连接到订阅、事件和指标的账户 ID
channel char 客户购买应用的渠道
date_of_birth date 客户在注册时输入的用于年龄验证的出生日期
country char 用户居住的国家,由两个字符字符串表示
... ... ...
optional fields char, float, int, 或 date 可选;平台特定

在本节的其余部分,我将向您展示如何将数据放入这样的模式中,以便用于对抗流失。

10.1.3 人口统计数据集 SQL

给定一个以账户 ID 为键的人口统计数据模式,第一步是从数据库中导出它以及您通常为指标创建的数据集。这样,您可以重用所有现有的代码,显示账户何时续订以及谁已经流失。此外,您最终会将人口统计数据与指标合并到一个单一预测模型中,通过一起导出指标和人口统计字段,您将拥有所有需要的东西。

图 10.1 显示了此类数据提取的典型结果。与自第四章以来使用的数据集一样,每一行都以账户 ID、观察日期和流失指标开始。人口统计字段在这些字段之后和指标之前。

图片

图 10.1 社交网络模拟数据集,包含人口统计字段(列表 10.1 的结果)

列表 10.1 显示了创建类似于图 10.1 所示的数据集的 SQL 语句。与数据库中的 date_of_birth 字段不同,数据集包含一个名为 customer_age 的字段。列表 10.1 介绍的新技术是将出生日期的日期字段转换为年数的时间间隔:客户的年龄。

要点:您将人口统计数据字段转换为时间间隔,因为这样数字间隔就可以像指标一样用于客户分析和细分。

在高层次上,转换是通过从观察日期减去人口统计数据或反之亦然来完成的:

  • 当人口统计数据在过去(例如出生日期)时,您从观察日期中减去人口统计数据,结果是表示观察时人口统计字段时间的正间隔。

  • 如果人口统计数据在未来(例如大学毕业日)时,从观察日期减去未来日期以保持间隔为正。然后间隔表示从观察日期到人口统计数据日期的时间。

由于出生日期在过去的日期中,列表 10.1 从观察日期中减去出生日期以获取客户的年龄。在 PostgreSQL 中,通过使用带有'days'参数的date_part函数将间隔转换为年,以获取天数间隔,然后除以 365(注意类型转换)。

列表 10.1:导出包含人口统计数据字段的数据集

WITH observation_params AS                                           ①
(
   SELECT  interval '%metric_interval' AS metric_period,
   '%from_yyyy-mm-dd'::timestamp AS obs_start,
   '%to_yyyy-mm-dd'::timestamp AS obs_end
)
SELECT m.account_id, o.observation_date, is_churn,
a.channel,                                                           ②
a.country,                                                           ③
date_part('day',o.observation_date::timestamp                        ④
        - a.date_of_birth::timestamp)::float/365.0 AS customer_age,
SUM(CASE WHEN metric_name_id=0 THEN metric_value else 0 END)
    AS like_per_month,
SUM(CASE WHEN metric_name_id=1 THEN metric_value else 0 END)
    AS newfriend_per_month,
SUM(CASE WHEN metric_name_id=2 THEN metric_value else 0 END)
    AS post_per_month,
SUM(CASE WHEN metric_name_id=3 THEN metric_value else 0 END)
    AS adview_per_month,
SUM(CASE WHEN metric_name_id=4 THEN metric_value else 0 END)
    AS dislike_per_month,
SUM(CASE WHEN metric_name_id=34 THEN metric_value else 0 END)
    AS unfriend_per_month,
SUM(CASE WHEN metric_name_id=6 THEN metric_value else 0 END)
    AS message_per_month,
SUM(CASE WHEN metric_name_id=7 THEN metric_value else 0 END)
    AS reply_per_month,
SUM(CASE WHEN metric_name_id=21 THEN metric_value else 0 END)
    AS adview_per_post,
SUM(CASE WHEN metric_name_id=22 THEN metric_value else 0 END)
    AS reply_per_message,
SUM(CASE WHEN metric_name_id=23 THEN metric_value else 0 END)
    AS like_per_post,
SUM(CASE WHEN metric_name_id=24 THEN metric_value else 0 END)
    AS post_per_message,
SUM(CASE WHEN metric_name_id=25 THEN metric_value else 0 END)
    AS unfriend_per_newfriend,
SUM(CASE WHEN metric_name_id=27 THEN metric_value else 0 END)
    AS dislike_pcnt,
SUM(CASE WHEN metric_name_id=30 THEN metric_value else 0 END)
    AS newfriend_pcnt_chng,
SUM(CASE WHEN metric_name_id=31 THEN metric_value else 0 END)
    AS days_since_newfriend
FROM metric m INNER JOIN observation_params
ON metric_time BETWEEN obs_start AND obs_end
INNER JOIN observation o ON m.account_id = o.account_id
   AND m.metric_time > (o.observation_date - metric_period)::timestamp
   AND m.metric_time <= o.observation_date::timestamp
INNER JOIN account a ON m.account_id = a.id                          ⑤
GROUP BY m.account_id, metric_time, observation_date, 
         is_churn, a.channel, date_of_birth, country                 ⑥
ORDER BY observation_date,m.account_id

① 本列表的大部分内容与列表 7.2 和 4.5 相同。

② 从账户表中获取渠道字符串

③ 从账户表中获取国家字符串

④ 从观察日期中减去出生日期

⑤ 与账户表连接

⑥ 在 GROUP BY 子句中包含人口统计数据字段

列表 10.1 的大部分内容与您之前用于提取数据集的列表相同:从观察表中选择观察日期,并通过聚合与度量指标连接,以简化数据。列表 10.1 的其他新方面如下:

  • 查询在账户表(表 10.1)上执行一个INNER JOIN,以选择渠道、国家和出生日期的字段。

  • 由于这些人口统计数据字段在账户表中每个账户只有一个,因此不需要对这些字段进行聚合。相反,人口统计数据字段包含在GROUP BY子句中。

您应该在社交网络模拟上运行列表 10.1 以创建本章其余部分将使用的数据集。假设您正在使用 Python 包装程序运行列表,则命令是

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 1 

列表 10.1 的结果(保存在输出目录中)应类似于本节开头图 10.1。

跟踪人口统计数据和公司统计数据变化以及避免前瞻性偏差

在本节中,我描述了将人口统计数据存储为单个、不变的值。但并非所有的人口统计数据或公司统计数据都是真正不变的:人们和公司可能会搬家,公司可能会达到新的发展阶段,人们可能会达到更高的教育水平,等等。为了更好地模拟这些变化,一些公司以时间敏感的方式跟踪人口统计数据,要么通过在账户表中添加有效日期戳,要么通过在账户本身之外跟踪人口统计数据字段(在数据仓库术语中称为缓慢变化维度)。由于这些更高级的方法并不常见,我在本书中不涉及它们。如果这种情况是您的情况,列表 10.1 被修改为将人口统计数据的有效日期与观察日期连接起来。

更复杂的方法之所以可能具有优势,是因为在某些情况下,当种群字段不是静态的时将其视为静态,可能会导致使用种群字段预测客户流失时出现一种前瞻性偏差。你会在历史数据集中看到有关客户的一些信息,并配以过去流失或续订的状态,但在非历史背景下(在观察时间戳时),你不会知道这些信息。为了举例说明,从公司人口统计学的角度来看,考虑初创公司或上市公司的公司阶段。上市的公司必须成功,不太可能倒闭和流失。如果数据包括过去上市的公司,公司人口统计数据会将其识别为上市公司,因为当你创建数据集时,这是当前的状态。但只有成功的初创公司才会上市,因此数据变得有偏见。

这种偏差也可能给模型带来不切实际的预测准确性。话虽如此,这类场景通常是一个二级效应,这为通常忽略种群和公司人口统计数据的时间变化成分的做法提供了理由。

10.2 带有种群和公司人口统计类别的流失群体

现在你已经拥有了一个包含种群数据的数据集,你将通过比较种群群体的流失率来查看种群数据与流失之间的关系。在章节开始时,我告诉你有三种类型的种群字段:日期、数字和字符串。之前,我展示了你应该将日期转换为数值区间。在群体分析中,只有两种类型:数字和字符串。

使用数值种群数据的流失群体分析与基于指标的群体分析完全相同,我将在第 10.4 节简要展示。这一节是关于新主题,即通过使用由字符串描述的种群信息来比较群体中的流失率。

10.2.1 种群类别的流失率群体

这一节是关于种群类别的,所以我从定义开始。

定义:在本书中,类别是指由字符串描述的种群字段的一个可能值。

在社交网络模拟中,与频道字段关联的类别是 appstore1、appstore2 和 web。与国家字段关联的类别是两位字符代码,例如 BR、CA 和 CN。在种群字段中可能缺少一个值,因此你可以认为每个字段没有值(数据库中的 null)是一个额外的类别。

注意:对于每个种群字段,客户只能属于一个类别,或者没有类别值。

原则上,根据人口统计类别进行的流失率群体分析很简单:为每个类别定义一个群体,并计算流失率。但是,由类别和指标构成的群体之间存在重要的差异。因此,在比较由类别定义的群体的流失率时,你需要更加小心。以下是基于指标和基于类别的群体之间的一些重要差异:

  • 对于指标,群体有一个由指标给出的自然顺序。在大多数情况下,类别没有有意义的顺序。因此,基于类别的群体更难解释,因为你不能将你在类别间看到的趋势用作解释流失率差异的指南。

  • 对于产品使用指标,你有自然的预期,例如“使用越多,流失率越低”和“使用成本越高,流失率越高”。但对于类别来说,没有明显的预期。

  • 当你定义指标群体时,你保证每个群体都有显著的观测值比例——通常是 10%或更多。而对于基于类别的群体,没有保证每个群体中可能捕获的数据的最小或最大百分比。

根据我的经验,基于人口统计的群体与基于产品使用指标的群体相比,与流失率的关系较弱。

总结:在根据人口统计类别对群体进行流失率比较时,你必须比根据指标对群体进行流失率比较时更加小心。

当我说“小心”时,我的意思是你需要依赖强有力的证据来确保差异是显著的。因此,你将使用一种称为置信区间的新的技术来进行比较。

10.2.2 流失率置信区间

为了在人口统计群体之间更小心地进行流失率比较,你不仅应该计算每个群体的流失率;你还应该估计每个群体的最佳和最坏情况下的流失率。这个过程被称为计算置信区间。

定义:对于像流失率这样的指标,置信区间是从流失率最佳(最低)估计到最坏(最高)估计的范围。

理解置信区间首先需要认识到你在客户上计算的流失率并不是你想要测量的流失率。考虑以下情况:

  • 你想知道的是,在所有可能的、符合你群体人口统计类别的客户中,流失率会是多少。这个估计将是该类型客户未来流失的最佳估计。

  • 你只能测量你看到过流失率的客户。

这种情况在图 10.2 中得到了说明。你不能确定你在过去客户中看到的流失率就是未来客户的流失率。你可能在将来看到不同的流失率。也许你在过去很幸运,得到了比平均水平更好的客户,或者情况可能正好相反;你永远不知道。但你可以期待以下两点:

  • 在整个客户群体中,你将看到的流失率应该接近你在过去看到的,假设你在每个群体中观察到了合理的客户数量。

  • 你看到的客户越多,你过去看到的流失率应该越接近整个群体的流失率。换句话说,你看到的客户越多,关于整个群体可能流失率范围的不确定性就越小。

因此,人们通常将置信区间视为围绕测量出的流失率的范围,这个范围被认为是最佳和最坏情况场景的中心附近(但,正如你将看到的,不一定在中心)。为了描述测量出的流失率以及最佳和最坏情况的估计,我们将使用以下定义。

定义:过去客户的测量流失率被称为期望值,它被认为是普遍流失率最可能的价值。上限置信区间是从期望流失率到最坏情况估计的范围,该范围由该范围的大小或最坏情况流失率减去期望流失率来描述。下限置信区间是从最佳情况估计到期望流失率的范围,该范围由该范围的大小或期望流失率减去最坏情况估计来描述。

图 10.2 展示了普遍流失率、你的估计以及估计的上限和下限置信区间的差异。

图片

图 10.2 置信区间评估最佳和最坏情况场景。

我说每个群体的流失率应该接近这类客户的普遍流失率,假设你已经观察到了足够多的这类客户。多少算是足够,在第五章中进行了详细讨论:理想情况下,你希望观察每个类别的数千名客户,但数百名可能就足够了。

当你使用置信区间时,你所使用的客户数量将转化为置信区间的宽度。你测量的客户越多,围绕流失率的范围就越窄。在第 10.2.3 节中,你将学习如何计算置信区间并进行比较。

吸收要点:因为你不能计算普遍流失率的测量值,所以你将根据可用的数据计算普遍流失率的最佳和最坏情况估计。

10.2.3 使用置信区间比较人口统计群体

图 10.3 展示了使用置信区间比较人口群体示例,这是社交网络模拟中渠道类别的结果。基本思想与你在前几章中看到的度量群体图相同,但有一些显著的不同:

  • 数据以柱状图的形式显示,而不是折线图。每个群体的流失率通过每个柱子的高度来表示。

  • 每个柱子上方和下方都有两条线,表示置信区间的范围。在图中表示置信区间的线通常被称为误差线或触须。

  • x 轴仍然标识着群体,但现在它是一个字符串标签,显示该群体代表的类别。

图 10.3 带有置信区间的渠道流失率(列表 10.2 的输出)

在类别群体图中,你不仅可以看到预期的普遍流失率,还可以看到最佳和最坏情况的估计,因此你应该使用置信区间作为比较类别流失率差异重要性的指南。这种技术被称为统计显著性。

定义:如果某个类别的最佳流失率(下置信区间)大于另一个类别的最坏流失率(上置信区间),则两个不同类别之间的流失率差异在统计学上具有显著性。在这种情况下,两个置信区间不会重叠。

考虑到图 10.3,你会说 appstore1 和 appstore2 类别之间的流失率差异在统计学上具有显著性,因为置信区间相距甚远。appstore2 的最坏情况流失率约为 3.5%,而 appstore1 的最佳情况流失率约为 4.5%,因此两者没有接触。

但 appstore1 和网站客户之间的流失率差异在统计学上处于边缘,因为置信区间几乎接触。网站渠道的最佳流失率约为 5.4%,而 appstore1 的最坏流失率也约为 5.4%。根据严格的定义,你可能会说这种差异在统计学上并不显著。但在实践中,统计显著性并不是一条硬性规则。如果你有理由认为差异是显著的,即使置信区间有轻微的重叠,你仍然可以采取行动来处理流失率的变化。在这种情况下,我会说 appstore2 如此不同的事实使得渠道之间的差异以及由此产生的网站和 appstore1 之间的差异更具可信度。正如你将在图 10.4 中看到的,网站和 appstore2 的流失率置信区间仅相差 0.02%,这在图中是看不出来的。但置信区间是否重叠或仅以如此小的幅度不重叠,不应影响你的解释。

TAKEAWAY 在实践中,当置信区间的边缘几乎接触或略有重叠时,流失率差异是否具有统计学意义并不是非黑即白。

列表 10.2 显示了生成图 10.3 的代码。列表 10.2 由一个主函数category_churn_cohorts组成,该函数调用三个辅助函数:

  • prepare_category_data—加载数据,并用字符串'-na-'填充任何缺失的类别。这个字符串清楚地标记了任何缺失类别的客户。

  • category_churn_summary—计算流失率及其置信区间,并将所有结果放入一个DataFrame中,该DataFrame保存为.csv 文件。(计算细节见下文。)

  • category_churn_plot—以条形图的形式绘制结果,显示置信区间并添加注释。通过设置条形函数的yerr参数添加置信区间,该参数代表y误差条。

列表 10.2 使用置信区间分析类别流失率

import pandas as pd
import matplotlib.pyplot as plt
import os
import statsmodels.stats.proportion as sp

def category_churn_cohorts(data_set_path, cat_col):                       ①
   churn_data =                                                           ②
      prepare_category_data(data_set_path,cat_col)
   summary =                                                              ③
      category_churn_summary(churn_data,cat_col,data_set_path)
   category_churn_plot(cat_col, summary, data_set_path)                   ④

def prepare_category_data(data_set_path, cat_col): 
   assert os.path.isfile(data_set_path),
      '"{}" is not a valid dataset path'.format(data_set_path)
   churn_data = pd.read_csv(data_set_path,index_col=[0,1])
   churn_data[cat_col].fillna('-na-',inplace=True)                        ⑤
   return churn_data

def category_churn_summary(churn_data,                                    ⑥
                           cat_col, data_set_path):
   summary = churn_data.groupby(cat_col).agg(                             ⑦
      {
         cat_col:'count',
         'is_churn': ['sum','mean']
      }
   )

   intervals = sp.proportion_confint(summary[('is_churn','sum')],
                                     summary[ (cat_col,'count')],
                                     method='wilson')                     ⑧

   summary[cat_col + '_percent'] = (                                      ⑨
      1.0/churn_data.shape[0]) * summary[(cat_col,'count')]

   summary['lo_conf'] = intervals[0]                                      ⑩
   summary['hi_conf'] = intervals[1]

   summary['lo_int'] =                                                    ⑪
      summary[('is_churn','mean')]-summary['lo_conf']
   summary['hi_int'] =                                                    ⑫
      summary['hi_conf'] - summary[('is_churn','mean')]
   save_path =                                                            ⑬
      data_set_path.replace('.csv', '_' + cat_col + '_churn_category.csv')
   summary.to_csv(save_path)
   return summary

def category_churn_plot(cat_col,                                          ⑭
                        summary, data_set_path):
   n_category = summary.shape[0]

   plt.figure(figsize=(max(4,.5*n_category), 4))                          ⑮
   plt.bar(x=summary.index,
           height=summary[('is_churn','mean')],                           ⑯
           yerr=summary[['lo_int','hi_int']].transpose().values,
           capsize=80/n_category)                                         ⑰
   plt.xlabel('Average Churn for  "%s"' % cat_col)                        ⑱
   plt.ylabel('Category Churn Rate')
   plt.grid()
   save_path = 
      data_set_path.replace('.csv', '_' + cat_col + '_churn_category.png')
   plt.savefig(save_path)
   print('Saving plot to %s' % save_path)

① 主函数用于类别分析和绘图

② 辅助函数prepare_category_data读取数据集。

③ 调用category_churn_summary进行数据分析

④ 调用category_churn_plot来制作图表

⑤ 用字符串'-na-'填充任何缺失值

⑥ 使用category_churn_summary分析类别

⑦ 使用 Pandas 聚合函数按类别分组数据

⑧ 计算置信区间

⑨ 将类别计数除以总行数

⑩ 将结果复制到摘要 DataFrame 中

⑪ 下置信区间 = 均值 - 下置信界限

⑫ 上置信区间 = 上置信度 - 均值

⑬ 保存结果

⑭ 使用category_churn_plot来绘制结果

⑮ 根据类别数量调整图表大小

⑯ 流失率百分比是条形的高度。

⑰ Y 误差条由置信区间给出。

⑱ 注释图表并保存

您应该运行 Python 包装程序来生成自己的图表,如图 10.3 所示,对于模拟数据集。包装程序的命令及其参数如下

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 2

在列表 10.2 中转向队列流失率计算的细节,平均流失率是在category_churn_summary函数中计算的,使用 Pandas 的DataFrame groupbyagg函数:

summary = churn_data.groupby(cat_col).agg({cat_col:'count','is_churn': ['sum','mean']}) 

以下是对这一密集行细节的分解:

  1. groupby函数以类别作为分组变量被调用。此函数的结果是一个专门的DataFrameGroupBy对象,可以用来根据分组检索不同的结果。

  2. 在分组后,通过在DataFrameGroupBy上调用聚合函数agg来找到所需的度量。DataFrameGroupBy要创建的结果在字典中指定,其中每个字典键是要计算聚合函数的列,键的值是一个或多个聚合函数。在这种情况下,你使用以下内容:

    {
    cat_col :'count',
    'is_churn': [
    'sum',
    'mean']
    }
    
    • 字典中的第一个条目表示包含类别(变量cat_col)的列应该通过计数进行聚合。对于每个类别,显示数据集中具有该类别的行数。

    • 字典中的第二个条目表示包含流失指标的列应该通过汇总流失数并计算均值进行聚合,这导致该类别的观察流失率。

函数调用的结果是包含每类一行以及包含三个聚合结果的列的DataFrame。列通过元组标记,结合列和聚合。标记为cat_col,'count'的列包含类别的行数,例如,而标记为'is_churn','mean'的列包含流失指标的均值,即流失率。

列表 10.2 中的函数category_churn_summary使用statsmodels模块计算置信区间。使用的函数是statsmodels.stats .proportion.proportion_confint,用于计算二元试验(从统计学的角度来看,衡量流失率就是二元试验)的百分比测量结果的置信区间。proportion_confint函数将每个类别的计数和流失观察数(通过选择聚合结果的DataFrame中的元组标记来传递)作为参数。

如本章开头所述,观察数和流失数是使用统计学计算置信区间的依据。调用proportion_confint时还传递了可选的方法参数method= 'wilson'。计算置信区间的威尔逊方法对于流失率是最好的选择,因为它已知在二元试验(在这种情况下,流失)的事件比例较小时会产生最准确的结果。我不会详细介绍威尔逊方法如何计算置信区间,但网上有许多很好的资源。

图 10.4 显示了类别流失群体分析的数据文件输出,其中包含置信区间。此输出包含用于生成通道群体条形图(图 10.3)和更多详细信息的所有信息。在此文件中可用而条形图中不可用的重要信息是每个通道的观察百分比。大多数通过不同渠道获取客户的组织已经对通过每个渠道获取的客户百分比有很好的了解。在这种情况下,您应该将数据集中的数字与销售部门为质量保证所测量的数字进行比较(以确保数据馈送没有问题等)。

图 10.4 类别流失群体通道字段的数据输出

列表 10.2 的文件输出也显示了低置信区间和高置信区间的范围。在图 10.4 中,你可以看到高区间略大于低区间。这种不对称性是由于流失概率是一个小百分比。如果流失率为 50%,置信区间的范围将是对称的。

在模拟社交网络中,具有类别划分的另一个人口统计字段是国家。图 10.5 显示了国家类别的流失群体图。国家类别的流失群体结果与渠道的流失群体图不同,因为国家数量更多。由于一些国家只有很少一部分客户,因此一些置信区间的范围相对于流失率来说较大。实际上,由于置信区间较大,国家之间没有统计上显著的流失率差异。国家类别中的所有置信区间与其他类别的置信区间重叠很大。(图 10.5 没有显示置信区间略有重叠的案例。)

图片

图 10.5 国家群体流失率及置信区间(列表 9.2 的输出)

图 10.6 显示了国家群体流失分析的数据文件输出。它显示大多数国家的数据不到 10%,有些国家甚至只有 1%。客户观察数量最少的国家,其流失率的置信区间最大。SE 只有 1%的观察值(236 个观察值),测量的流失率为 5.9%,置信区间的下限是 3.6%,上限是 9.7%,跨度大约为 6%。另一方面,US 代表了 15%的观察值(3,710 个观察值),观察到的流失率相似,为 5.3%,置信区间从 4.7%到 6.1%,跨度仅为 1.5%。

图片

图 10.6 国家字段类别流失群体的数据输出

图 10.5 和 10.6 中的结果显示,拥有过多的类别会对进行有效的流失群体分析造成问题。第 10.3 节教你一种简单而有效的方法来处理这个问题。

置信区间的显著性水平

函数proportion_confint还有一个参数:显著性水平,我在代码中将其保留为默认值。如果你查看proportion_confint的文档,你会发现默认的显著性水平是 0.05。此参数对应于人们所说的 95%置信水平,表示真实总体流失率在最佳和最坏情况估计定义的范围内的不确定性程度。

就像统计学中的大多数事情一样,最佳和最坏情况的流失率都是估计值,显著性水平决定了这些估计值也可能错误的可能性。当人们说“95%置信度”时,他们是在说 100%减去这个显著性水平。换句话说,有 5%的可能性,真实的普遍流失率不在所声明的界限内,有 95%的可能性,普遍流失率在界限内。

将显著性水平参数降低到 0.05 以下会导致置信区间更大,或者最佳和最坏情况估计之间的差异更大。如果你使用较低的显著性水平,两个类别之间的流失率差异需要更大才能被视为具有统计学意义(通过置信区间不接触来实现)。另一方面,较高的显著性水平(大于 0.05)会使置信区间更小,这将更容易说差异具有统计学意义,但你将不太确定该类别的普遍流失率是否在所声明的界限内。

选择显著性水平和解释置信区间是统计学中的一个有争议的话题,我试图给你一些简单的最佳实践。我的建议是将显著性参数保留为默认值。原则上,你应该为具有大量类别(超过几十个)的人口统计字段使用较低的显著性水平。这样,你会在确定哪些差异是显著的时应用更严格的准则。

在第 10.3 节中,我将教你另一种处理大量类别的方法:将那些不太常见的类别分组。总的来说,我的建议是不要更改此参数。我之所以在这里提到它,是因为你可能被问及你使用什么显著性水平来计算置信区间。(答案是,你使用标准的 0.05 显著性水平。)

10.3 分组人口统计类别

在第 10.2.3 节中,我向你展示了如果你有很多类别,你面临的风险是罕见类别中的观察数量可能太小,无法产生有用的结果。在观察数量较少的情况下,置信区间可能会变得很大,这取决于你可用于工作的数据量。如果你有数百万客户,你甚至可以对罕见类别中的结果具有统计学意义。尽管如此,信息过载也可能是一个问题,出于这个原因,查看较少的类别也可能是有利的。

10.3.1 使用映射字典表示组

解决具有大量类别且代表数据小部分的难题,可以通过分组相关罕见类别来实现。例如,可以将国家分组到地区。图 10.7 通过使用 Python 字典展示了如何将国家映射到地区。图 10.7 中的字典实际上是从地区到国家列表的映射,因为这种映射是表达这种关系的更有效方式。

图 10.7

图 10.7 将分组模拟的国家类别映射到地区

基于图 10.7 的代码位于本书的 GitHub 仓库中,位于文件 fight-churn/listings/conf/socialnet_listings.json 中;请查找第十章部分以及关键字 listing_10_3_grouped_category_cohorts。我将在稍后详细介绍选择这种特定映射的原因,但现在,我将向您展示这种分组如何帮助进行类别群体分析。

10.3.2 分组类别群体分析

图 10.8 显示了基于地区而不是国家重新运行群体分析的结果。由于分组,现在有六个类别。如果你查看与图表一起的数据输出(图中未显示),你会看到每个新类别代表的数据不少于 10%;现在最小的类别是没有国家的客户(-na-),占 11%。由于观察次数增加,图 10.8 中每个类别的置信区间大小比国家单独时(图 10.5)更小。

要点 如果你的人口统计数据包括罕见类别,可以通过分组相关类别来简化。这种方法可以减少流失率置信区间和信息过载。

尽管置信区间较小,但图 10.8 显示在任何地区之间,流失率没有统计学上的显著差异。每个地区的流失率置信区间与其他所有置信区间显著重叠。在这个模拟数据集中没有统计学上的显著差异并不意味着你不会在自己的产品或服务中发现重要关系。

图 10.8

图 10.8 展示了按地区分组的国家类别群体流失情况

列表 10.3 提供了执行分组和重新运行类别群体分析的代码。此列表使用类别群体流失分析的所有辅助函数(无分组),并添加了一个新函数来执行分组:group_category_column。此函数有两个主要部分:

  • 第一部分反转映射字典,使其成为从国家到地区的映射,而不是从地区到国家的映射。反转字典可以通过 Python 的单行表达式完成,使用双列表推导。第一个列表推导遍历曾是地区的键,第二个列表推导遍历每个键中的值,即国家。从结果中形成将旧值映射到旧键(国家到地区)的字典。

  • 在映射字典被反转后,使用DataFrameapply函数在DataFrame中创建一个新的列。apply函数接受另一个函数作为参数,该函数应用于列中的所有元素。在这种情况下,目的是查找反转字典中的值(如果存在的话);否则,返回原始值。将此函数应用于列的结果是,将映射到区域组中的每个国家,任何不属于的国家将按原样复制。在此映射之后,列表 10.3 中的代码使用了列表 10.2 中的分析和绘图函数,这些函数对未分组的类别进行了类别队列分析。

group_category_column函数通过在原始列名称前添加单词 group 并为结果删除原始列来为新列命名。

列表 10.3 分组类别队列分析

import pandas as pd
import os

from listing_10_2_category_churn_cohorts import category_churn_summary, 
   category_churn_plot, prepare_category_data                              ①

def grouped_category_cohorts(data_set_path,                                ②
                             cat_col, groups):
   churn_data = prepare_category_data(data_set_path,cat_col)
   group_cat_col =                                                         ③
      group_category_column(churn_data,cat_col,groups)
   summary =                                                               ④
      category_churn_summary(churn_data,group_cat_col,data_set_path)
   category_churn_plot(group_cat_col, summary, data_set_path)

def group_category_column(df, cat_col, group_dict):                        ⑤
   group_lookup = {                                                        ⑥
                     value: key for key in group_dict.keys() 
                                for value in group_dict[key]
                  }
   group_cat_col = cat_col + '_group'                                      ⑦
   df[group_cat_col] = df[cat_col].apply(lambda x:                         ⑧
                                group_lookup[x] if *x* in group_lookup else x)

   df.drop(cat_col,axis=1,inplace=True)                                    ⑨
   return group_cat_col                                                    ⑩

① 此列表重新使用了列表 10.2 中的辅助函数。

② 主函数大部分与常规类别图相同。

③ 调用辅助函数将类别列映射到组

④ 列表 10.2 中的辅助函数分析类别。

⑤ 此函数使用映射字典将类别映射到组中。

⑥ 反转字典

⑦ 为组列创建新名称

⑧ 使用 DataFrame 的 apply 方法和 lambda 转换数据

⑨ 删除原始类别列

⑩ 返回新的列名称作为结果

您应该运行列表 10.3 以创建自己的队列分析,其中国家被分组到地区。使用通常的 Python 包装程序命令和这些参数执行此操作:

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 3

您应该得到一个与图 10.8 在定性上相似的结果,但不要期望在创建自己的版本时得到每个组的具体流失率。原因是模拟中,国家与流失和参与没有关系,因此是随机的。(相信我:我知道,因为我创建了模拟。)尽管您应该得到与图 10.8 中相似大小的置信区间,但不要期望得到相同的流失率。

注意:本书大部分内容都避免了让你分析那些与客户流失无关的模拟数据,以节省你生成和探索无意义数据的时间。但在实际产品和服务的真实数据中,你应该预期会找到一些与客户保留和流失无关的事件和人口统计信息。

警告:不要将本书 GitHub 仓库中的社交网络模拟结果作为你自己的产品或服务的预期指南。这些例子是为了演示如何在真实数据上使用方法而设计的看起来很现实的数据集,但仅此而已。模拟结果不能预期预测任何真实产品或服务的任何结果。

10.3.3 设计分类组

现在你已经知道了如何实现用于群体分析的分类分组,我将给你一些建议,告诉你如何选择这样的分组。首先,考虑这种情况:你没有很多数据,所以你正在分组分类以在你的群体中找到足够的观察结果(这样你最终会得到围绕流失率的合理大小的置信区间)。如果这是你的情况,你没有基于你自己的数据做数据驱动决策的选择;你没有足够的数据来分析分类之间的差异,这就是问题所在。在这种情况下,你应该根据你对分类之间关系的了解来分组分类。除了国家地区示例之外,你可能想要使用的合理分组包括以下内容:

  • 如果你有很多操作系统版本的分类,你可以根据主要版本将它们分组。

  • 如果你有一些行业部门的分类,你可以将相关的行业分组,例如将银行和金融放在一个组中,将消费品和零售放在另一个组中。

  • 如果你有一些职业的分类,你可以将相关的领域分组,例如将医生和牙医放在一个组中,将软件工程师和数据科学家放在另一个组中。

  • 如果你有一些教育水平的分类,你可以将罕见的教育水平,如硕士学位、博士学位等分组。

记住,你的目标是合理地分组罕见分类,并试图了解任何关系。如果你发现了一些关系,你总是可以修改你的分组,以利用你发现的任何结构(如本节后面所述)。

还要注意,你不必盲目遵循标准的群体定义:你应该根据你产品或服务的具体细节进行定制。在我从国家到地区的映射中,我做出了以下编辑决策:

  • 我没有将中国(CN)包括在亚太地区(APac)组中,因为中国本身就代表了超过 10%的数据样本,这本身就足够了。

  • 我选择将墨西哥(MX)与拉丁美洲(LaAm)而不是北美(LaAm)包括在内,因为如果这是一个真实的社会网络,我预计语言和文化与参与度的关系会比地理与参与度的关系更为显著。(如果我的产品或服务与工业制造和运输有关,我可能更关注地理关系而不是文化关系。)

这些是一些你可能想要使用的考虑因素。关于这个主题的最后一项建议如下。

警告:不要过度思考你的类别分组,也不要花太多时间在上面。记住分析中需要敏捷。做一些能让你得到可管理结果的第一步,从你的业务同事那里获取反馈,然后从那里迭代。

另一方面,考虑你拥有足够的数据来为每个流失率提供狭窄的置信区间,你的问题是来自太多类别(或在你第一次尝试分组后,你得到了类似的结果)的信息过载。然后你可以采取更数据驱动的方法:

  • 在未分组的类别上运行类别队列分析;然后使用第一次迭代中看到的流失率来决定在第二次迭代中使用的组:

    • 根据你的知识将相关且具有相似流失率的类别分组。

    • 在这个背景下,类似的流失率意味着这两个类别在流失率上没有统计学上的显著差异。(置信区间重叠。)

    • 如果两个流失率在统计学上存在显著差异(置信区间不重叠),即使你知道这些类别是相关的,也不要将它们分组。

  • 你仍然应该使用如上所述的知识分组。不要仅仅因为两个类别在流失率或其他指标上有相似之处就分组。

你还可以使用第 10.5 节中描述的相关性分析作为评估你的组与其与其他指标关系相似性的额外方法。但正如你将看到的,你用于指标的分组算法不适用于类别,我不建议使用自动化的方法进行此类分组。

如果你有很多类别(数百或数千个)无法通过设计基于你知识的分组方案来处理,那么这些信息很可能对你的抗流失斗争没有帮助。商人们可能不会将客户细分到如此令人困惑的类别中。

10.4 基于日期和数字人口统计的流失分析

如我之前提到的,你应该以与度量相同的方式查看数值人口信息,在 10.1 节中,我教了你如何将日期类型的人口和企业信息轻松转换为数值区间,因此你也可以使用度量风格的队列分析来处理日期类型的人口数据。因为你在第五章中学习了如何分析数值客户数据,所以这一节将是一个简短的演示。

社交网络模拟的的人口信息包括客户在注册时输入的出生日期,并将 10.1 列表中的此日期转换为社交网络模拟数据集中的数值字段:customer_age。图 10.9 显示了在客户年龄上运行标准指标队列分析的结果。该图显示,在社交网络模拟中,客户年龄越高,流失率越高。最低年龄队列的平均年龄约为 15 岁,流失率约为 4%,而较高年龄队列(60 岁以上)的平均流失率约为 5.5%。队列间流失率的变化略有不规则,但与发现老年客户流失率更高的事实一致(与第五章和第七章中展示的行为影响相比,这种影响较弱)。

图片

图 10.9 客户人口年龄队列分析

要从你模拟的数据中创建自己的图 10.9 版本,你必须重新使用指标队列列表 5.1(文件名为 listing_5_1_cohort_plot.py)。配置已经有一个版本,你可以按照以下方式运行:

run_churn_listing.py —chapter 5 —listing 1 —version 17 

你的结果可能与图 10.9 有所不同,因为关系并不强,数据是随机模拟的。这个例子表明,在你从数据集中提取数值格式的人口信息后,你可以像分析度量一样使用队列来分析它。

指标队列的置信区间

我根据趋势的一致性来解释指标队列,但到现在为止,你可能已经意识到你可以在指标队列图中的每个点周围添加置信区间。我通常不这样做,因为这会使图表过于杂乱,难以向商业人士展示,而且通常对于解释流失率之间的关系来说并不必要。但是,置信区间可以帮助解释趋势和显著性较弱时的指标队列图。以下是我使用过的一种策略:

  1. 将度量分为三个队列。你正在比较低、中、高度量指标的客户。大型群体有助于使置信界限变窄。

  2. 绘制队列平均值与置信界限,并查看置信界限是否重叠。如果置信区间重叠,则在低、中、高度量指标的客户之间存在统计学上显著的差异。

我把这个练习留给感兴趣的读者。

10.5 使用人口数据进行流失率预测

你已经学会了分析单个人口统计字段与其对客户流失和保留关系的技术。与指标一样,你可能想查看所有人口统计字段对流失的影响,以了解组合如何预测流失。此外,你应该测试将人口统计或公司统计数据与你的指标相结合的预测。为此,你需要将字符串形式的人口统计信息转换为等效的数值形式,因为你所学的回归和 XGBoost 预测算法仅需要数值输入。

10.5.1 将文本字段转换为虚拟变量

为了使用你的字符串类型人口统计信息进行预测,你需要通过一种称为虚拟变量的技术将其转换为数值数据。

定义:虚拟变量是一个二元变量,表示一个类别中的成员资格,其中1表示该类别中的所有客户,而0表示不在该类别中的所有客户。

如果你在一个计算机科学或工程项目中学习数据科学,你可能已经学过这种技术,称为独热编码。

图 10.10 显示了创建虚拟变量的过程。使用虚拟变量类似于将指标数据展平以创建数据集。在这种情况下,字符串人口统计字段在意义上是一个高数据格式,因为所有可能的类别都存储在一列中(使用字符串)。为了用原始数据中的每个唯一字符串添加一个虚拟变量列来替换字符串列,每个列是该字符串类别的虚拟变量:所有具有特定字符串的客户在该类别的列中得1,在其他所有列中得0。然后你删除原始字符串列,剩下的就是一个纯粹数值的数据集,它仍然代表与包含字符串的数据集相同的类别信息。

图片

图 10.10 将字符串变量展平为虚拟变量列

图 10.11 显示了创建社交网络模拟的虚拟变量的结果。你可以看到,渠道和国家字符串类别的标签已从数据集中移除。取而代之的是,一组只包含零和一的新的列代表这些类别。图 10.11 还显示了按地区分组的虚拟变量列,这与之前一样。国家仍然分组,因为关于稀疏类别过多的问题同样适用于预测,就像你在单独查看国家时的情况一样。

图片

图 10.11 创建模拟社交网络数据集的虚拟变量结果

列表 10.4 提供了创建与图 10.11 中类似的数据集的代码,其中包含虚拟变量。创建虚拟变量是 Pandas DataFrame(称为get_dummies)的标准功能。此函数会自动检测数据集中所有字符串类型的列,并用适当的二进制虚拟变量替换它们。虚拟变量列的名称是通过将原始列名与类别字符串连接起来创建的。

列表 10.4 创建虚拟变量

import pandas as pd

from listing_10_3_grouped_category_cohorts import group_category_column    ①
def dummy_variables(data_set_path, groups={},current=False):
   raw_data = pd.read_csv(data_set_path,                                   ②
                          index_col=[0, 1])

   for cat in groups.keys():                                               ③
       group_category_column(raw_data,cat,groups[cat])                     ④

   data_w_dummies = 
      pd.get_dummies(raw_data,dummy_na=True)                               ⑤

   data_w_dummies.to_csv(                                                  ⑥
      data_set_path.replace('.csv', '_xgbdummies.csv')
   New_cols = sorted(list(set(                                             ⑦
                  data_w_dummies.columns).difference(set(raw_data.columns))))
   cat_cols = sorted(list(set(                                             ⑧
                  raw_data.columns).difference(set(data_w_dummies.columns))))
   dummy_col_df =                                                          ⑨
      pd.DataFrame(new_cols,index=new_cols,columns=['metrics'])
   dummy_col_df.to_csv(
      data_set_path.replace('.csv', '_dummies_groupmets.csv'))
   if not current:                                                         ⑩
      new_cols.append('is_churn')
   dummies_only = data_w_dummies[new_cols]                                 ⑪
   save_path =                                                             ⑫
      data_set_path.replace('.csv', '_dummies_groupscore.csv')
   print('Saved dummy variable (only) dataset ' + save_path)
   dummies_only.to_csv(save_path)

   raw_data.drop(cat_cols,axis=1,inplace=True)                             ⑬
   save_path = data_set_path.replace('.csv', '_nocat.csv')
   print('Saved no category dataset ' + save_path)
   raw_data.to_csv(save_path)

① 从列表 10.3 导入分组类别映射函数

② 读取原始数据

③ 映射字典的键是要映射的类别。

④ 调用分组映射函数

⑤ 使用 Pandas get_dummies 函数

⑥ 此版本的数据集用于 XGBoost 预测。

⑦ 通过集合差集确定虚拟变量列

⑧ 通过集合差集确定原始类别列

⑨ 保存列列表以与分组数据集保持一致性

⑩ 如果不是用于当前客户,则包括流失

⑪ 仅保存包含虚拟变量的数据集

⑫ 以与常规数据集一致的方式命名数据集

⑬ 保存没有人口统计类别的数据集

在列表 10.4 中调用包函数get_dummies不仅仅是发生的事情。首先,列表 10.4 应用了你在第 10.2 节中学到的可选的类别分组。然后,它以三个版本保存:包含原始指标和任何数值人口统计信息的部分,仅包含虚拟变量的部分,以及全部内容。每个版本都有其目的,如下所述:

  • 指标和数值人口统计信息必须转换为分数并通过指标分组算法运行。此过程应在没有虚拟变量的情况下进行。

  • 单独保存虚拟变量便于对虚拟变量本身进行回归分析。

  • 包含所有内容的版本用于 XGBoost,它使用未转换的指标与虚拟变量一起使用。

这些点将在本章的其余部分进一步解释,但到目前为止,我将专注于解释列表 10.4 的其余部分。此代码主要是 Pandas 库的机械使用,分离数据集的部分。唯一的技巧是使用集合和与集合差异相关的操作来确定哪些列是通过创建虚拟变量而添加的。

列表 10.4 保存了具有不同文件扩展名的多个数据集版本:

  • 带有后缀.dummies 的文件是仅包含虚拟变量的数据集。此文件也以后缀.groupscore 保存,因为当你使用回归代码时,将期望遵循此约定。列的列表也以后缀.groupmets 保存,因为回归代码也将期望这样做,尽管对于虚拟变量,将没有组。

  • 带有后缀 .nocat 的文件是包含数值指标和人口统计字段的文件。此文件简单保存,将通过常规评分和分组运行。

  • 带有后缀 .xgbdummies 的文件将由 XGBoost 交叉验证重新加载。

您应该运行列表 10.4 来创建自己的数据集版本,将字符串类别替换为虚拟变量(以及之前描述的文件)。如果您使用 Python 包装程序,请使用通常的命令形式和这些参数:

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 4

您的结果应类似于图 10.11,尽管精确的账户和其人口统计数据将不同,因为数据是随机生成的。

10.5.2 仅使用分类虚拟变量预测流失

现在您有一个包含人口统计数据虚拟变量的数据集,尝试使用仅包含人口数据的回归模型进行流失预测是有教育意义的。这个练习旨在增加您对人口变量对流失概率综合影响的理解。正如您将看到的,如果您想要尽可能准确地预测流失,应使用人口统计数据虚拟变量和度量一起,如第 10.5.4 节所述。

如果您运行回归交叉验证,然后在最佳 C 参数下拟合模型,您得到的结果如图 10.12 所示。结果显示,人口统计数据虚拟变量对流失率有弱预测性。交叉验证中找到的最佳 AUC 测量值约为 0.56,最大提升值约为 1.5。如果您回忆第九章的内容,使用度量结果的回归导致了 AUC 高于 0.7 和提升高于 4.0。可以采用低 C 参数值,然后删除大多数虚拟变量而不会显著影响 AUC,但提升值在 C 参数值较高时最佳:0.32 或更高。

图 10.12 还显示了将 C 参数设置为 0.32 时的回归系数和保留概率的影响。两个应用商店频道的虚拟变量被分配了相当大的权重,分别转化为 1.2% 和 2.8% 的积极保留影响(减少流失)。网络频道获得零权重,这反映了它具有最高的流失率,因为其他两个频道都显示出积极的影响。在这种情况下,零权重意味着它类似于默认值或基线,而其他类别代表改进。

get_dummies 函数还创建了一个对于不可用的频道(nan)的变量,并且这个频道也获得了零权重,因为在数据集中,所有客户都分配了频道。(当设置 na_default 参数时,Pandas 为每个变量创建一个 nan 列。)这些效果与你在类别群体图(图 10.3)中看到的流失率差异一致。

图 10.12 还显示了国家组虚拟变量的系数和保留影响显著减小。在这种情况下,CN、Eur 和缺失数据有轻微的正保留影响(流失率较低),而 LaAm 和 APac 有负保留影响(流失率较高)。再次强调,这些结果与你在国家组群体图(图 10.8)中看到的结果一致。

图片

图 10.12 展示了使用虚拟类别变量数据集的回归结果

图 10.12 是从前几章的列表中创建的,已经为你准备好了配置版本来做这件事。要创建图 10.12 的回归交叉验证图,请使用以下回归交叉验证命令,版本 4:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 5 —version 2

要找到固定C参数为 0.32 的系数,请使用以下命令运行固定C值的回归:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 4 —version 4

你的交叉验证结果应该类似于图 10.12,你的渠道系数结果也应该如此,这些渠道是随机分配给客户的,但在模拟中它们会产生一致的结果。你可能因为国家组的小权重和影响而得到不同的结果,因为在模拟中它们是随机的。

10.5.3 将虚拟变量与数值数据结合

在前面的章节中,我提到当你使用从类别派生出的虚拟变量时,你不能使用你在度量时使用的分组类型。相反,我建议将虚拟变量与度量分开,并按常规处理度量。在本节中,我将详细介绍原因和这个过程。我首先解释一些关于涉及虚拟变量的相关性的事实,因为这有助于阐明为什么你不应该将类别虚拟变量与度量一起分组。

图 10.13 显示了社会网络模拟中与渠道和国家的相关矩阵部分,这些部分与人口统计类别相关联。(你还没有运行代码来创建这个相关矩阵,但很快你将这么做。)图 10.13 省略了与度量到度量相关性的相关矩阵部分。一个可能让你感到惊讶的显著特征是类别;每个字段的虚拟变量与其他字段的虚拟变量呈负相关。这在渠道字段中尤其如此,该字段只有三个类别,相关性低至-0.74。对于国家组,区域之间的负相关性大约为-0.2。

图片

图 10.13 展示了社会网络模拟人口统计类别的相关矩阵

负相关原因:类别之间的负相关性是由于类别成员的排他性:如果一个客户属于一个类别,它会给该类别的虚拟变量赋予1,并要求他们从同一字段的其他虚拟变量中具有0。这种二元指标的排他性导致从相关系数的定义来看,负的测量相关性:当一个虚拟变量取高值(1)时,其他虚拟变量取低值(0)。这解释了为什么你用于指标变量的分组方式不会将同一人口字段的人口统计类别分组。该算法使用高相关性来指示变量应形成组成员。

相关性分析:考虑到图 10.13 的其余部分,人口统计类别虚拟变量与指标大多不相关,但有一些例外:

  • 相关性:渠道 appstore1 和 web 与消息和回复有负相关性。

  • 正相关性:渠道 appstore2 与消息和回复有正相关性。

  • 正相关性:渠道 web 也与帖子有正相关性。

分析:当你使用人口统计类别来理解客户流失和保留时,使用虚拟变量查看相关矩阵可能是值得的,因为它可以揭示关于你的客户如何使用产品的不同群体的一些信息。但即使它们相关,你也不应将人口统计虚拟变量与你的指标组别分组。

总结:人口统计虚拟变量与其他指标之间的相关性可以帮助你更好地了解你的客户,但你不应将虚拟变量与其他虚拟变量或指标分组。

回顾:在第六章中,我建议你使用指标之间的相关性作为评估指标相关性和确定哪些应该分组的方法。但有几个原因说明这种方法不适用于从人口统计类别创建的虚拟变量:

  • 相关性系数:你可以计算 0/1 二进制变量的相关系数,但相关系数并不适用于此目的。在统计学中,其他指标更适合测量二进制变量之间的相关性。当你用虚拟变量计算相关系数时,它并不是衡量相关性的好方法。

  • 相关性解释:人口统计类别与使用相关性分组的行为方式不同。当两种行为(例如使用两个产品功能)相关时,通常它们是单个活动或过程的一部分。因此,用得分的平均值来表示整个过程是合理的,但这通常不适用于人口统计类别和任何其他指标。

原因:因此,我的建议是,如果你想使用人口统计虚拟变量来预测客户流失,你应该保持所有虚拟变量与组别分开。

总结:在标准准备过程中运行度量数值人口统计字段,不使用人口统计虚拟变量,然后在最后将它们与虚拟变量结合。

此结果如图 10.14 所示。

图片

图 10.14 一个数据集中包含度量组、度量分数和类别

要创建如图 10.13 所示的自己的数据集,第一步是在具有度量数值人口统计信息的版本的数据集上运行你在早期章节中学到的数据准备过程。有一个列表配置版本为你准备,只需一个命令即可完成:

fight-churn/listings/run_churn_listing.py —chapter 8 —listing 1 —version 3 

在处理度量后,将它们与虚拟变量结合。列表 10.5 中显示的新函数是 Pandas DataFrame 操作的直接应用。从度量产生的组分数与虚拟变量的文件合并。合并是通过 Pandas DataFramemerge 函数执行的,使用两个 DataFrame 的索引进行 INNER JOIN。列表 10.4 的最后一步是将列出组度量的 DataFrame 与虚拟变量的名称结合;这样的文件将被用于在合并数据集上运行回归的代码所期望。

列表 10.5 合并虚拟变量与分组度量分数

import pandas as pd

def merge_groups_dummies(data_set_path):

   dummies_path =                                                         ①
      data_set_path.replace('.csv', '_dummies_groupscore.csv')
   dummies_df =pd.read_csv(dummies_path,index_col=[0,1])
   dummies_df.drop(['is_churn'],axis=1,inplace=True)                      ②

   groups_path =                                                          ③
      data_set_path.replace('.csv', '_nocat_groupscore.csv')
   groups_df = pd.read_csv(groups_path,index_col=[0,1])

   merged_df =                                                            ④
      groups_df.merge(dummies_df,left_index=True,right_index=True)
   save_path =                                                            ⑤
      data_set_path.replace('.csv', '_groupscore.csv')
   merged_df.to_csv(save_path)
   print('Saved merged group score + dummy dataset ' + save_path)

   standard_group_metrics = pd.read_csv(                                  ⑥
      data_set_path.replace('.csv', '_nocat_groupmets.csv'),index_col=0)
   dummies_group_metrics = pd.read_csv(                                   ⑦
      data_set_path.replace('.csv', '_dummies_groupmets.csv'),index_col=0)
   merged_col_df =                                                        ⑧
      standard_group_metrics.append(dummies_group_metrics)
   merged_col_df.to_csv(data_set_path.replace('.csv', '_groupmets.csv'))

① 加载包含虚拟变量数据集的文件

② 删除流失列

③ 加载度量组分数

④ 合并虚拟变量和度量组分数

⑤ 将合并的文件以组分数的名称保存

⑥ 从仅度量数据中加载组度量列表

⑦ 加载虚拟变量列表

⑧ 将两个度量列表合并并保存

你应该在模拟的社会网络数据集上运行列表 10.5,为 10.5.4 节中的预测做准备。使用以下参数向 Python 包装程序发出常规命令:

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 5

运行列表 10.5 后,其中一个结果应该是一个类似于你在图 10.14 中看到的数据集。现在,既然你已经创建了合并后的数据集,你可以创建一个类似于我在本节开头(图 10.13)展示的相关矩阵。使用以下命令和这些参数发出相关矩阵列表配置的版本:

fight-churn/listings/run_churn_listing.py —chapter 6 —listing 2 —version 3 

使用参数配置版本 3 运行列表 6.2 创建了如图 10.13 所示的相关矩阵的原始数据。图 10.13 的格式是在电子表格程序中完成的(如第六章所述)。

10.5.4 结合人口统计和度量进行客户流失预测

现在你已经创建了一个结合组度量分数和人口统计类别虚拟变量的数据集,你可以运行回归或机器学习模型来预测客户流失概率。图 10.15 显示了回归的结果。

C参数的交叉验证显示,在准确性受到影响之前,许多变量可以被分配为零权重。图 10.15 还显示了当C参数设置为 0.04 时回归产生的权重。几乎所有的人口统计虚拟变量都有零权重和保留影响(以及一些度量指标也是如此)。

图 10.15 是通过使用第九章的列表创建的。要在包含虚拟变量和度量指标的数据集上运行自己的回归,你可以使用准备好的配置版本。要运行图 10.15 中显示的回归C参数(列表 9.5)的交叉验证,请使用以下命令:

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 5 —version 3 

要在包含虚拟变量和度量指标的数据集上运行固定C参数(列表 9.4)为 0.04 的回归,请使用以下命令

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 4 —version 5 

这些命令产生与图 10.15 类似的结果,尽管你可能在国家群体虚拟变量上有不同的权重,因为它们在模拟中被随机分配。

你可能会想知道为什么图 10.15 中的回归系数显示渠道人口统计变量对客户流失预测没有影响,但在本章早期,无论是带有置信区间的队列流失分析还是虚拟变量的回归,都显示渠道对客户流失(和保留)有很强的预测性。这里发生了什么?回归中有什么问题吗?

图 10.15

图 10.15 为结合度量分数和类别虚拟变量的数据集的回归结果

没有什么问题。当与行为指标一起考虑时,该渠道不会提供关于客户流失的额外信息,回归也发现了这一点。客户渠道与某些行为相关,在模拟中,行为导致客户流失和保留。当你单独看渠道时,它与流失率相关,但当与回归中的行为指标结合时,回归算法会自动确定最解释性的因素并去除其他因素。回归正确地确定,通过观察指标而不是渠道,客户参与度最可预测。

要点:人口统计类别通常与客户流失和参与度相关,因为来自不同人口统计的客户行为不同。但如果你使用详细的行为指标,你通常会发现行为是预测性预测中保留的潜在驱动因素。

我曾告诉你们,理解人口统计和公司统计是应对客户流失的次要方法,因为行为可以通过干预(有时)进行修改,但人口统计却不能(永远不能)。人口统计通常对预测客户流失没有帮助,这也是我强调在应对客户流失时理解行为与指标的原因。即使人口统计字段对预测客户流失没有用,也不会影响其在应对客户流失中的主要用途。

要点 如果您在群体分析中看到人口统计与保留率之间存在强烈的关系,您应该在您的获取努力中强调您最好的人口统计。即使这些相同的人口统计在包含行为指标的回归中不是预测参与度的指标,这也无关紧要。

警告 不要假设您自己的产品或服务的流失数据将显示与我在此处从模拟中展示的完全相同的结果。社交网络模拟是为了模仿我在研究客户流失时最常见的结果,但总会有例外,您的产品可能就是其中之一。

如果您发现您自己的人口统计在考虑了行为指标后仍然强烈预测流失,您应该检查您的数据是否可以改进。确保所有相关的客户行为都通过您的事件表示,并且您的指标充分捕捉了您的事件与流失之间的关系。与未测量的行为相关的人口统计相关性可能导致即使在包含指标的情况下,人口统计预测流失的结果。如果是这样,您最好找出这些行为是什么,以便您可以测量它们并尝试改善它们。

您还可以测试人口统计变量在像 XGBoost 这样的机器学习模型中对预测的改进程度。这种实验的结果如图 10.16 所示。人口统计变量使 XGBoost 的 AUC 增加了约 0.005,或者说增加了 1%的一半。图 10.16 还显示了回归 AUC 的改进,这个改进甚至更小(但仍然是一个改进)。

图片

图 10.16 带有人口数据的准确度比较

要点 最高预测准确度来自使用人口统计数据和详细客户指标的 XGBoost。XGBoost 可能比回归更认为人口统计在预测中更有帮助。

要在图 10.16 中重现 XGBoost 的结果,您可以使用以下命令运行 XGBoost 交叉验证列表配置版本(listing_9_6_crossvalidate_xgb.py):

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 6 —version 2 

注意,列表和配置创建了包含人口统计变量的 XGBoost 的结果。如果您一直在跟随,您应该已经找到了其他模型和数据集的准确度。

10.6 使用人口统计数据细分现有客户

本章的最后一个主题是如何将人口统计信息作为细分客户努力的一部分。作为数据人员,您不负责定义细分或与客户互动,但您确实需要提供数据,以便业务人员能够有效地完成工作。用于细分客户的最终数据集应包括以下要素:

  • 所有在最近可用日期活跃的客户

  • 指标组的分数

  • 未分组的指标的原生(未缩放)指标值

  • 以字符串格式表示的类别人口统计信息

  • 在适当的地方分组类别

  • 流失预测概率(可选)

图 10.17 是一个具有所有这些特征的示例数据集。

创建这样的数据集需要几个步骤:

  1. 从数据库中提取当前客户的全部指标和人口统计信息。

  2. 使用历史数据中的分数参数和加载矩阵重新处理指标信息以形成组。

  3. 保存一个包含所有所需特征的版本的数据集。

注意,这个过程还会创建一个为活跃客户进行流失概率预测准备好的数据集。该版本结合了所有指标的分数和数值人口统计数据,但人口统计类别的虚拟变量。

图片

图 10.17 按指标分组得分、指标和人口统计信息对客户进行分段的示例数据集

列表 10.6 提供了提取当前活跃客户所有指标和人口统计数据的 SQL 语句。这个列表几乎与第四章和第八章中类似的列表相同,所以我只会简要解释。SQL 程序的主要部分是对指标进行聚合以简化指标。新功能是在账户表上进行连接,并选择渠道国家和出生日期。出生日期被转换为表示客户年龄的年数的时间间隔(遵循本章前面创建包含人口统计数据的历史数据集时使用的模式)。

列表 10.6 导出当前活跃客户的指标和人口统计数据

WITH metric_date AS                                               ①
(
   SELECT  max(metric_time) AS last_metric_time FROM metric
),
account_tenures AS (
   SELECT account_id, metric_value AS account_tenure
   FROM metric m INNER JOIN metric_date ON metric_time =last_metric_time
   WHERE metric_name_id = 8
   AND metric_value >= 14
)
SELECT s.account_id, d.last_metric_time AS observation_date,
a.channel,                                                        ②
a.country,                                                        ③
date_part('day',d.last_metric_time::timestamp                     ④
    - a.date_of_birth::timestamp)::float/365.0 AS customer_age,
SUM(CASE WHEN metric_name_id=0 THEN metric_value else 0 END)
    AS like_per_month,
SUM(CASE WHEN metric_name_id=1 THEN metric_value else 0 END)
    AS newfriend_per_month,
SUM(CASE WHEN metric_name_id=2 THEN metric_value else 0 END)
    AS post_per_month,
SUM(CASE WHEN metric_name_id=3 THEN metric_value else 0 END)
    AS adview_per_month,
SUM(CASE WHEN metric_name_id=4 THEN metric_value else 0 END)
    AS dislike_per_month,
SUM(CASE WHEN metric_name_id=34 THEN metric_value else 0 END)
    AS unfriend_per_month,
SUM(CASE WHEN metric_name_id=6 THEN metric_value else 0 END)
    AS message_per_month,
SUM(CASE WHEN metric_name_id=7 THEN metric_value else 0 END)
    AS reply_per_month,
SUM(CASE WHEN metric_name_id=21 THEN metric_value else 0 END)
    AS adview_per_post,
SUM(CASE WHEN metric_name_id=22 THEN metric_value else 0 END)
    AS reply_per_message,
SUM(CASE WHEN metric_name_id=23 THEN metric_value else 0 END)
    AS like_per_post,
SUM(CASE WHEN metric_name_id=24 THEN metric_value else 0 END)
    AS post_per_message,
SUM(CASE WHEN metric_name_id=25 THEN metric_value else 0 END)
    AS unfriend_per_newfriend,
SUM(CASE WHEN metric_name_id=27 THEN metric_value else 0 END)
    AS dislike_pcnt,
SUM(CASE WHEN metric_name_id=30 THEN metric_value else 0 END)
    AS newfriend_pcnt_chng,
SUM(CASE WHEN metric_name_id=31 THEN metric_value else 0 END)
    AS days_since_newfriend
FROM metric m INNER JOIN metric_date ON m.metric_time =d.last_metric_time
INNER JOIN account_tenures t ON t.account_id = m.account_id
INNER JOIN subscription s ON m.account_id=s.account_id
INNER JOIN account a ON m.account_id = a.id                      ⑤
WHERE s.start_date <= d.last_metric_time
AND (s.end_date >=d.last_metric_time OR s.end_date IS null)
GROUP BY s.account_id, d.last_metric_time, 
    a.channel, a.country, a.date_of_birth                        ⑥
ORDER BY s.account_id

① 列表的大部分内容与列表 4.6 和 8.3 相同。

② 来自账户表的渠道字符串

③ 来自账户表的国别字符串

④ 从观察日期中减去出生日期

⑤ 与账户表进行 JOIN 操作

⑥ 在 GROUP BY 子句中包含人口统计字段

您可以通过运行以下命令和这些参数在自己的模拟社交网络数据集上运行列表 10.6 来创建自己的当前客户数据集文件:

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 6

列表 10.7 展示了将当前客户的原始数据转换为可用于预测和分段的版本的 Python 程序。列表 10.7 的大部分内容与第八章中看到的转换类似,并包括第七章、第八章和第十章中的几个辅助函数。但列表 10.7 还包括一些新步骤以适应人口统计数据。

列表 10.7 中的一个重要新技术是我所说的在历史数据和当前数据集中对齐虚拟变量。Pandas 的get_dummies函数(从列表 10.4 的dummy_variables调用)为数据框中的每个类别创建虚拟变量列,但历史数据集和当前数据集中的类别可能不匹配。通常,历史数据集有足够的客户观察结果,你会在几个客户中看到罕见的类别,但当前数据集将拥有更少的客户,可能不包含任何罕见类别的示例。在这种情况下,历史数据集将有一个当前数据集没有的列。这种情况会导致你在尝试在当前数据集上预测客户流失概率时失败。

如果一个类别在历史上不再使用且不再存在于当前数据集中,同样的问题会发生。如果新类别开始使用,则会出现相反的问题:历史数据集可能缺少该类别,而只有当前数据集包含它。总之,对齐类别做两件事:

  • 对于历史数据集中缺失的任何类别,添加一个包含零的新虚拟变量列。这样,当前数据集在列上与历史数据集等效,零是无人参与的类别的正确分类值。

  • 从当前数据集的虚拟变量中删除在历史数据集中缺失的任何类别。同样,此步骤使历史数据集和当前数据集中的列对齐。如果该类别在历史数据集中不可用,你不知道它是否以及如何预测客户流失,因此删除它是正确的预测目的。

总体而言,列表 10.7 中的主要步骤是

  1. 从本章前面(列表 10.4)运行dummy_variables创建列表,使用当前数据集的路径。此代码保存了数据的三种版本:

    • 只有用于进一步评分和分组的数值字段

    • 只有用于稍后与分数和组合并的虚拟变量

    • 将数值字段和虚拟变量一起使用,这是 XGBoost 使用的(此文件是从dummy_variables函数中保存的)

  2. 加载从当前数据集派生的虚拟变量。

  3. 运行align_dummies辅助函数,该函数负责处理两组虚拟变量之间的不一致性。

  4. 加载由dummy_variables函数从当前数据创建的仅包含数值字段的数据集。同时加载从历史数据集创建的加载矩阵和分数参数。将此当前数据集通过你在第八章中学到的重新处理步骤运行:

    1. 转换任何偏斜的列。

    2. 转换任何具有厚尾的列。

    3. 重新缩放数据,使所有字段都是接近 0 均值的分数,接近 1 的标准差。

    4. 通过使用在历史数据上创建的加载矩阵,结合任何相关的指标。

  5. 将虚拟变量与指标分组和得分数据合并,并保存此版本的数据集。这个版本可以用于预测当前客户的流失概率。

  6. 创建一个用于业务人员细分的数据集版本。此数据集版本结合以下元素:

    • 分组指标的得分

    • 那些未分组的原始(未转换)指标

    • 人口统计类别的原始字符串(而不是虚拟变量)

列表 10.1 准备包含人口统计字段的当前客户数据集

import pandas as pd

from listing_7_5_fat_tail_scores 
   import transform_fattail_columns, transform_skew_columns
from listing_8_4_rescore_metrics 
   import score_current_data, group_current_data, reload_churn_data
from listing_10_4_dummy_variables import dummy_variables
def rescore_wcats(data_set_path,categories,groups):

   current_path = data_set_path.replace('.csv', '_current.csv')

   dummy_variables(current_path,groups, current=True)                      ①
   current_dummies = reload_churn_data(data_set_path,
      'current_dummies_groupscore',  '10.7',is_customer_data=True)
   align_dummies(current_dummies,data_set_path)                            ②

   nocat_path = 
      data_set_path.replace('.csv', '_nocat.csv')                          ③
   load_mat_df = reload_churn_data(nocat_path,
                                   'load_mat','6.4',is_customer_data=False)
   score_df = reload_churn_data(nocat_path,
                                'score_params','7.5',is_customer_data=False)
   current_nocat = reload_churn_data(data_set_path,'current_nocat','10.7',is_customer_data=True)
   assert set(score_df.index.values)==set(current_nocat.columns.values),
          “Data to re-score does not match transform params”
   assert set(load_mat_df.index.values)==set(current_nocat.columns.values),
          “Data to re-score does not match loading matrix”
   transform_skew_columns(current_nocat,
      score_df[score_df['skew_score']].index.values)
   transform_fattail_columns(current_nocat,
      score_df[score_df['fattail_score']].index.values)
   scaled_data = score_current_data(current_nocat,score_df,data_set_path)
   grouped_data = group_current_data(scaled_data, load_mat_df,data_set_path)

   group_dum_df =                                                          ④
      grouped_data.merge(current_dummies,left_index=True,right_index=True)
   group_dum_df.to_csv(                                                    ⑤
      data_set_path.replace('.csv','_current_groupscore.csv'),header=True)

   current_df = reload_churn_data(data_set_path,
                                  'current','10.7',is_customer_data=True)
   save_segment_data_wcats(                                                ⑥
      grouped_data,current_df,load_mat_df,data_set_path, categories)

def align_dummies(current_data,data_set_path):

   current_groupments=pd.read_csv(                                         ⑦
      data_set_path.replace('.csv','_current_dummies_groupmets.csv'),
      index_col=0)

   new_dummies = set(current_groupments['metrics'])
   original_groupmets =                                                    ⑧
       pd.read_csv(data_set_path.replace('.csv','_dummies_groupmets.csv'),
                   index_col=0)

   old_dummies = set(original_groupmets['metrics'])
   missing_in_new = old_dummies.difference(new_dummies)                    ⑨
   for col in missing_in_new:                                              ⑩
       current_data[col]=0.0
   missing_in_old = new_dummies.difference(old_dummies)                    ⑪
   for col in missing_in_old:                                              ⑫
       current_data.drop(col,axis=1,inplace=True)

def save_segment_data_wcats(current_data_grouped, current_data,
                            load_mat_df, data_set_path, categories):
   group_cols =                                                            ⑬
      load_mat_df.columns[load_mat_df.astype(bool).sum(axis=0) > 1]
   no_group_cols =                                                         ⑭
      list(load_mat_df.columns[load_mat_df.astype(bool).sum(axis=0) == 1])
   no_group_cols.extend(categories)                                        ⑮
   segment_df =                                                            ⑯
      current_data_grouped[group_cols].join(current_data[no_group_cols])

   segment_df.to_csv(
      data_set_path.replace('.csv','_current_groupmets_segment.csv'),
      header=True)

① 在当前数据集上运行 dummy_variables 函数

② 调用辅助函数以将当前虚拟列与历史虚拟列对齐

③ 准备不带类别的当前数据

④ 将分组得分数据与虚拟数据合并

⑤ 使用原始数据集名称保存结果

⑥ 使用该函数准备用于细分的数据

⑦ 从文件列出当前虚拟变量创建一个集合

⑧ 从文件列出原始虚拟变量创建一个集合

⑨ 差分设置发现原始数据中有虚拟列但当前数据中没有。

⑩ 对于新数据中缺失的任何虚拟变量,添加一个零列

⑪ 差分设置发现当前数据中有虚拟列但原始数据中没有。

⑫ 在当前数据中删除但不在原始数据中的虚拟列

⑬ 分组列有多个加载矩阵条目。

⑭ 标准指标列有一个加载矩阵条目。

⑮ 将类别变量名称添加到列表中

⑯ 创建细分数据

你可以使用以下命令和这些参数使用 Python 包装程序运行列表 10.7:

fight-churn/listings/run_churn_listing.py —chapter 10 —listing 7

此代码为当前客户数据创建三个文件,用于之前描述的目的:

  • 使用回归进行预测

  • 使用 XGBoost 进行预测

  • 通过业务人员进行细分

如果你想使用回归模型进行预测,请使用列表 8.5(文件名为 listing_8_5_churn_forecast.py),使用以下命令和这些参数:

fight-churn/listings/run_churn_listing.py —chapter 8 —listing 5 —version 2

如果你想使用 XGBoost 模型(文件名为 listing_9_7_churn_forecast_xgb.py)进行预测,请使用

fight-churn/listings/run_churn_listing.py —chapter 9 —listing 7 —version 2

关于你的业务同事将使用的客户细分数据集,重要的是要认识到,对于业务人员来说,即使与流失和保留无关,人口统计数据也很重要。例如,营销部门将需要为针对不同国家或地区的客户的不同参与活动编写不同的副本。在一个大型组织中,营销部门可能通过自己的系统访问所有这些信息,但我包括所有这些信息是为了完整性。

吸收要点:即使与参与和保留无关,人口统计信息也可能与设计客户干预措施相关。

摘要

  • 人口和公司数据是关于客户的事实,这些事实不会随时间改变,就像度量一样。人口/公司字段的类型可以是日期、数字或字符串。

  • 客户的日期类型信息可以转换为区间,并使用与度量相同的技巧进行分析。

  • 要比较由人口类别字符串定义的群体中的流失率,你使用的是对流失率的最优和最差估计的置信区间。

  • 当围绕其流失率的置信区间不重叠时,说不同类别中的流失率有统计学上显著的差异。

  • 如果你有很多类别代表占客户人口比例很小的部分,你应该在分析之前将这些相关类别分组。

  • 通常使用先验知识对人口类别进行分组,并且可以使用字典有效地表示这种映射。

  • 要在回归或机器学习预测中使用人口类别,将它们转换为二元虚拟变量的列。

  • 虚拟变量不与度量得分分组,但调查度量中虚拟变量之间的相关性可以提供有用的信息。

  • 使用人口信息可以提高预测准确性,但通常与基于行为的度量相比,它是一个次要的贡献。

11 领导对抗客户流失的斗争

本章涵盖

  • 从数据到数据驱动的客户流失减少规划

  • 在自己的数据上加载数据并运行书中的代码

  • 将书中的列表迁移到你的生产环境中运行

我想以感谢你们一直陪伴我,并到达这个阶段开始这最后一章。我希望你在前几章中学到的知识是有趣的,并且你感觉你学到了很多可以应用的知识。在这短短的最后一章中,我将尝试给你一些建议,帮助你将所学应用到实践中。

在第 11.1 节中,我将书中分散的战略性客户流失对抗建议结合起来,并给你一个采取各种策略的步骤清单。

然后,我将概述你需要采取的实际步骤,以便在自己的数据上使用书中的技术。你可以选择两条路径,这两条路径是下一两个章节的主题:

  • 第 11.2 节介绍了如果你想在书中使用的 PostgreSQL 模式中加载自己的数据,并在自己的数据上运行书中的列表,需要采取的步骤。

  • 第 11.3 节描述了另一种选择,即将书中的列表(SQL、Python 或两者兼有)移植到你的生产环境中。

之后,我不能再教你更多了;将取决于你如何将你的知识应用到实践中,并在过程中找到更多资源。第 11.4 节描述了一些可供学习的资源。

11.1 规划你自己的客户流失对抗战

本书涵盖了大量的技术,并提供了适用于许多场景的建议。但事实是,你只需使用书中的一些技术,就可以在典型的产品或服务中产生很大的影响。现在,我将退回到更高的层面,给你一些建议,关于使用哪些技术和如何应用它们。

在整本书中,我提到了各种减少客户流失的策略,我在这里总结了:

  • 产品改进——大多数公司已经使用数据来改进他们的产品,通常是通过调查或焦点小组收集的。尽管调查通常只捕捉到少数参与者的观点,但客户流失实际上是对所有用户的调查,他们通过行动投票。产品经理和内容制作人可以将客户流失分析结果作为对客户偏好的宝贵新见解(无需进行调查或焦点小组)。

  • 参与式营销——如果一家公司使用电子邮件营销,它可以向客户发送参与式电子邮件。利用客户流失数据的目标客户,鼓励他们使用他们尚未使用的功能,为高级用户提供高级技巧等等。关键在于利用数据使接收到的沟通对客户有价值。

  • 定价与包装——对于收取费用的产品,定价和包装至关重要。生产此类产品的公司应使用高级指标来了解客户获得(或未获得)的价值以及这种价值与流失之间的关系。这些信息有助于公司在设计新的定价和包装方案时满足各种客户群体。

  • 客户成功与支持——大型公司可能有一个客户支持部门,帮助报告有困难的客户。对流失有更大影响的是数据驱动的、主动的客户成功功能,在客户请求之前帮助客户。跟踪您的流失与账户期限的关系,并确保在为时已晚之前让客户获得入职体验。

  • 渠道定位——如果您通过多个渠道找到客户,了解哪个渠道在参与度和留存方面提供最佳客户可能是有价值的。

表 11.1 总结了最常见的流失减少策略以及它们与您在这本书中学到的技术之间的关系。

表 11.1 数据驱动的流失减少

减少流失策略 核心概念/客户指标 章节
产品改进 最大化最佳功能。使最佳功能易于查找。 基于产品使用事件的度量群体识别吸引和失去兴趣的产品功能。 3, 5, 8
参与式营销 推广最佳功能。使用针对性的产品洞察。 度量群体提供健康产品使用水平的基准。使用指标进行客户细分以进行定位。 3, 5, 7
定价与包装 区分定价以提供价值而不打折。理解不同功能/内容的使用之间的关系。 单位成本和单位价值指标识别从产品中获得良好/糟糕价值的客户。考虑货币化有价值的产品功能组合。 6, 7
客户成功与支持 帮助有需要的客户。主动识别失败的客户。 度量群体为健康使用水平提供基准。使用回归或机器学习预测客户风险水平。 3, 5, 8, 9
渠道定位 识别最佳客户渠道。寻找类似者。 具有置信区间的群体流失率识别最佳(最差)销售渠道和成功的人口统计/企业统计指标。 10

但不要被长长的策略列表和其中一些策略需要多种技术才能实施的事实所吓倒。我想强调的是,您不需要使用这本书中的每一个减少流失策略。此外,从小处着手比感到不知所措而什么都不做要好。这本书教授了大量技术,因为我想要涵盖各种常见的场景和陷阱。但您不一定需要解决所有这些问题,才能对公司的流失产生重大影响。

吸收要点:从小处着手并交付一些成果,比尝试一切却一无所获要好。本书中的技术是前置的,这样你就不必使用大多数技术就能获得大部分好处。

我不想贬低更高级的技术,因为它们可能很有用,但好处是前置的,即大部分好处来自于本书开头教授的技术。我估计,如果你的公司能通过第三章并交付正确的漏损率和一套精心设计的客户指标,那么公司可以几乎获得使用数据减少漏损的一半好处。这里的“一半好处”是指使用所有后续章节中所有高级技术公司实现的好处的一半。(当然,实现全部好处还需要各个业务单元在决策中使用这些指标。)如果你能通过第五章并使用指标队列分析基本指标,你的公司可能将获得使用数据推动漏损减少倡议可能总收益的三分之二。第 11.1.2 节概述了你需要采取的步骤。

11.1.1 数据处理和分析清单

在教授这些技术时,我有时会改变步骤的顺序,以便更容易理解概念。表 11.2 展示了实现我所称的数据驱动漏损对抗基础级别的步骤,包括所有步骤,直到基于事件数据的客户指标交付当前客户名单。如果你能完成这些步骤并将结果传达给公司中的业务人员(如第 11.1.3 节所述),你将实现数据驱动漏损减少几乎一半的好处。

表 11.2 数据驱动漏损对抗基础级别清单

步骤 步骤描述 章节 节(s)
1 漏损率 2 B2C 订阅的 2.4,无订阅的 2.5,B2B 订阅的 2.6
2 事件数据质量保证(QA) 3 3.8
3 标准行为指标 3 3.5, 3.6, 3.10, 3.11
4 指标 QA 3 3.7
5 当前客户指标数据集 4 4.5

如果你完成了表 11.2 中所有的基础步骤,你就可以继续使用本书中更高级的技术了。表 11.3 列出了高级数据驱动漏损对抗技术的步骤,从创建分析数据集和分析与漏损关系的基本指标开始。目标是创建高级指标,这些指标可以揭示更多关于参与度和留存率的重要信息。

表 11.3 高级数据驱动漏损对抗清单 (继续)

步骤 步骤描述 章节 节(s)
6 数据集创建 4 4.1-4.4
7 数据集摘要统计和 QA 5 5.2
8 指标队列分析 5 5.1
9 漏损行为分组和分析 6 所有
10 高级行为指标创建和分析 7 7.1-7.4
11 高级指标质量保证 3 3.7
12 当前客户高级指标数据集 8 8.4

本书第八章和第九章中的预测技术通常仅比前两部分提供少量额外的好处。这些技术最有可能被习惯于使用高级分析的公司的经验丰富的统计学家、数据科学家或机器学习工程师应用。因此,我将这些技术称为极端数据驱动流失斗争。表 11.4 提供了采取步骤的检查清单。

表 11.4 极端数据驱动流失斗争的预测检查清单

步骤 步骤描述 章节 节点
13 对回归模型进行交叉验证以找到最佳参数。 9 9.4
14 在最佳参数下对完整数据集进行回归分析。 9 9.3
15 创建包含流失预测和终身价值的当前客户列表。 8 8.4, 8.6
16 对 XGBoost 模型进行交叉验证以找到最佳参数。 9 9.5
17 创建当前客户 XGBoost 预测。 9 9.6

最后是识别最佳渠道或人口统计和公司统计类别以减少流失的技术。所有这些技术都在第十章中讨论,不需要您使用任何高级技术:您可以单独使用人口统计/公司统计技术,或者与书中其他技术结合使用。表 11.5 列出了使用人口统计或公司统计的步骤,从步骤 1 开始。表 11.5 中的步骤从步骤 1 重新开始,因为这些步骤不依赖于表 11.2、11.3 和 11.4 中的步骤。

表 11.5 使用人口统计/公司统计对抗流失的检查清单

步骤 步骤描述 章节 节点
1 导出包含人口统计/公司统计信息的历史数据集。 10 1
2 对人口统计/公司统计类别进行分类群体分析。 10 2
3 对数值人口统计/公司统计数据进行指标群体分析。 5 1
4 创建包含人口统计/公司统计信息的当前客户列表。 10 6
5 可选:用于预测的人口统计/公司统计数据。 10 5

11.1.2 业务沟通检查清单

第 11.1.1 节提供了数据驱动对抗流失的技术步骤检查清单。但关于业务背景呢?在本节中,我将回顾和总结您应该如何与您的业务同事合作,以及您应该尝试实现哪些成果。

表 11.6 列出了与实现数据驱动流失斗争基础阶段相对应的业务参与建议点。这些步骤与表 11.2 中描述的技术步骤相一致。数据处理和分析的每个阶段都会向业务提供一到多个可交付成果。你应该确保这些讨论能够导致减少流失的行动(因为数据人员不能单独完成这项工作)!

表 11.6 向业务传达流失数据基础清单

步骤 步骤描述 业务参与
1 流失率 讨论流失率计算方法。展示月度和年度流失率。如果业务有现有的流失计算,比较结果,并就衡量产品或服务流失的最佳方法达成一致。
2 事件数据 QA 展示主要事件的每日事件计数图。展示每月每个账户的事件摘要。与业务人员达成一致,这些事件正确反映了业务,并且不会过度受不良数据的影响。决定任何改进数据收集的步骤。
3, 4 指标和 QA 展示指标时间窗口的选择和理由。展示指标时间序列 QA。展示指标汇总统计。达成一致,这些指标是客户行为的可接受总结。商定客户健康状况的简单标准。(例如:健康状况良好的客户是在最常见的客户指标上高于平均水平的客户。)决定任何改进数据收集或指标公式的步骤。
5 当前客户指标数据集 提供带有指标的客户名单。审查高、典型和低使用账户的样本。

我不是演讲教练,所以不会尝试详细说明你应该如何与业务人员沟通。记住我的建议,清楚地标记你展示的数据,并确保数据可读。至于所涉及的努力程度,我估计典型的数据人员大约需要一天时间来整理表 11.6 中描述的结果,并将它们组装成可接受的演示(文档或幻灯片集)。可能需要大约一个小时的时间与一组业务人员进行审查,你应该计划至少进行一次后续讨论,以回答问题并商定下一步行动。

TAKEAWAY 与业务互动最重要的成果是让业务人员使用简单的标准来评估客户健康状况的指标。

如果你已经阅读了这本书中的所有技术,你可能会认为在没有进行队列分析的情况下决定客户健康标准并不是非常数据驱动。我同意,用度量队列来理解客户健康会更好。但我想再次强调的是,做总比什么都不做好。如果你只能实现表 11.6 中描述的基础结果,你已经取得了很大的成就。如果你遵循了书中队列和相关性分析部分中的推理,你应该期待每个主要客户行为都会与增加参与度和降低流失率相关联。(所有主要行为很可能高度相关。)所以最重要的是让企业人士思考这些指标以及如何提高客户参与度。这就是为什么拥有这些指标可以如此强大。

话虽如此,如果可能的话,我鼓励你将你的用户流失斗争提升到下一个层次。表 11.7 总结了进行更高级的数据驱动用户流失斗争所需的企业参与度。准备这些会议可能比准备关于基本数据驱动用户流失斗争的会议更耗时。你需要展示更多结果,并需要准备解释一些统计概念,例如相关度、分数和平均值。如果你在向非技术受众传达技术结果方面没有太多经验,我建议你进行练习。

表 11.7 向企业高级沟通用户流失数据的清单

步骤 步骤描述 企业参与
6 数据集创建 解释领先时间概念以及你如何为公司数据选择领先时间。解释数据集概念。展示你的数据集中有多少观测值以及有多少用户流失。
7 可选:数据集汇总统计和 QA 如果步骤 4(度量 QA)中的汇总统计是可接受的,则跳过展示这些统计。
8 度量队列分析 展示基于主要事件度量指标和基于订阅度量指标(如果有)的度量队列流失图表。就度量指标的健康目标水平达成一致。讨论哪些行为导致参与度和保留,以及它们在现有数据中反映得如何。讨论由结果建议的降低流失率策略。(例如:推广或生产更多产品功能以及针对培训的目标客户群体。)
9 用户流失行为分组和分析 展示相关度度量散点图的示例,并解释相关度。展示基本度量的度量相关度热图。展示通过聚类算法找到的度量分组。展示分组平均度量分组的度量队列。
11 高级行为指标创建和分析 讨论设计高级指标时做出的选择。展示高级指标的指标群体分析。如有必要,更新指标群体的行为分组结果。(例如:展示新的相关矩阵和指标群体的任何变化。)讨论由结果建议的降低流失率策略。(例如:基于效率或成功指标针对参与或培训的细分市场。)讨论由单位成本指标分析建议的替代定价策略。
12 当前客户高级指标数据集 提供带有指标的客户列表。审查高、典型和低使用账户的样本。

如果您能实现表 11.7 中的结果,您将获得数据驱动客户流失斗争的 90%的好处(据我估计)。由于它处于基础层面,最重要的是让业务人员查看精心设计的客户指标,理解它们,并使用它们来做出决策。

关于本书第三部分的预测技术,这些技术需要更多的努力向您的业务同事解释。尽管我已尽力通过提供背景信息来使这些材料易于理解,但向您的业务同事解释回归预测和梯度提升等概念是一项您需要培养的才能(如果您还没有这样做的话)。如果您是使用其他领域高级分析的公司中的数据科学家或机器学习工程师,您应该将客户流失分析方法与您现有的预测分析作为参考来展示,并解释客户流失情况的变化。

11.2 在您自己的数据上运行本书的列表

在您自己的数据上运行本书的列表(加载到本书模式中)可能是获得您自己结果的最快方式。如果您的产品数据格式与本书中描述的模式相似,并且您的客户数量足够少,以至于可用的 PostgreSQL 数据库可以处理,这种方法将有效。

11.2.1 将您的数据加载到本书的数据模式中

本书使用了两种主要类型的基础数据:

  • 客户订阅(第二章)

  • 产品使用事件(第三章)

您应查阅表 2.1 和 3.1,这些表格显示了订阅和事件表的模式,以检查您的数据是否具有所需的字段。请记住,在您自己的系统中,字段名称可能不同。

第一步是创建一个新的 PostgreSQL 模式,您可以选择一个名称。GitHub 仓库包含一个脚本,可以为您完成这项工作:fight-churn/data-generation/py/churndb.py。(如果您创建了模拟数据集并在书中运行了列表,您必须已经在最初运行了这个脚本。)关于如何运行该脚本的最新详细信息可以在本书 GitHub 仓库的 README 文件中找到:www.github.com/carl24k/fight-churn

在创建模式之后,你需要将你的数据放入其中。将数据导入 PostgreSQL 数据库有许多方法。我倾向于使用图形界面工具,并且在使用免费的工具 PGAdmin (www.pgadmin.org) 上有成功的经验。更多详细信息可在本书 GitHub 仓库的 README 文件中找到。

WARNING 所有数据库导入工具都很挑剔,如果数据具有意外的特征,它们将会失败。需要注意的事项包括日期和时间格式、分隔符和空值指定符。

如果在加载每个数据源的数据时需要多次令人沮丧的尝试,请不要感到惊讶。好消息是,在你弄清楚你自己的数据的特性之后,加载应该会更容易。但是,如果你计划将来重新进行你的分析,你可能需要重复这个过程。在这种情况下,你可能应该选择一个脚本化的数据加载方法。

TIP 编写一个脚本来自动化你对数据进行转换以便将其加载到 PostgreSQL 中的操作。避免使用手动方法,如搜索/替换,因为你可能需要多次将数据加载到数据库中。

11.2.2 在自己的数据上运行列表

在你的数据加载完毕后,你需要使用你指定的参数运行列表。有两种简单的方法:

  • 使用 GitHub 仓库中随书提供的 Python 包装程序。

  • 编写你自己的包装程序,将书籍列表作为模块导入。

如果你使用本书的包装程序,你需要创建一个配置文件,指定在从包装程序运行时为每个列表提供的参数。这样一个文件被用来配置代码运行社交网络模拟:fight-churn/listings/conf/socialnet_listings.json。如果你不熟悉 JSON(JavaScript 对象表示法),你只需要知道它是一种用于存储键值对的简单格式。JSON 并非用于存储参数,但常常被用于此目的。它具有直接且简单映射到 Python 字典的优势。

如果你查看 socialnet7_listings.json 文件,你会找到一个每个章节的键:chap1、chap2 等等。每个章节键映射到该章节的参数嵌套对象。在每个章节对象内部,每个列表都由一个字符串对象指定,例如“list1”、“list2”等等。每个这样的键映射到一个包含列表名称和各种参数的字段的对象。每个列表的对象还包含你在整本书中运行的所有附加版本参数。要在自己的数据上运行 Python 包装程序,你需要为你的模式创建一个 JSON 配置文件,文件名中包含模式名称(如社交网络模拟);然后填充适合你的数据的参数定义。如果你想在多个模式上运行列表,这种方法非常有用。(在编写本书时,我在多个模拟版本以及公司案例研究中运行了列表。)

由于你可能只有一个模式来运行数据,另一个好的方法是编写自己的 Python 包装程序来调用你想要使用的所有列表函数。因为书中每个 Python 列表都是一个单独的文件中的函数,所以启动一个新的 Python 代码文件并导入书籍列表函数是直接的。(第 8、9 和 10 章中有许多书籍列表导入其他列表的例子。)因为你正在编写自己的程序,你可以将所有自己的参数作为变量存储在程序中,或者让该程序从你已经在使用的任何其他数据库或键值存储中获取它们。

吸收要点:为大多数试图使用本书列表且修改最少的公司编写自定义 Python 包装程序可能是最佳选择。

编写自己的包装程序的优势在于,你可以将任何其他你需要用于数据的自定义处理以及你在过程中的不同步骤的控制结合在一起。但是,使用这种方法,你还将不得不编写自己的方法来将变量绑定到 SQL 列表中并将它们发送到你的数据库模式,或者重构我编写的包装程序中的示例。(我毫不怀疑,我的许多读者能做得比我更好!)

无论你是直接运行书籍中的列表还是编写一个包装程序,你必须编写自己的数据提取 SQL(列表 4.5)版本。该示例是硬编码到书中的度量标准。要查看一个自动生成模式中所有度量标准 SQL 的脚本示例,请参阅我为我的客户案例研究使用的脚本:fight-churn/dataset-export/py/observe_churn.py。

11.3 将本书的列表移植到不同的环境

有很多很好的理由说明你可能不想使用书中提供的代码,而希望重用这些技术。在这种情况下,你正在考虑将书中的代码列表移植到不同的环境中。这是一个很大的主题,我必须承认自己不是这方面的专家。我能做的最好的事情就是尝试给你提供一些指导方针。话虽如此,这种工作对于专业软件开发人员来说是他们的日常生计,而且还有许多其他资源可供你使用。你可以搜索关于将代码移植到你所选择系统的书籍或在线资源,或者雇佣一个承包商或顾问,例如。

11.3.1 移植 SQL 代码列表

如果你在一个除 PostgreSQL 之外的数据库中有大量数据,你应该考虑做任何必要的事情来将本书的代码列表移植到你的数据库中。因为本书的 SQL 代码大量使用了公共表表达式(CTEs),如果其他数据库也支持 CTEs,这个过程将会容易得多。

你可能不想使用 PostgreSQL,因为它有性能限制。如果你还没有选择数据库,我建议你了解一下 Presto,这是一个开源的大数据分布式 SQL 引擎(prestodb.io)。Presto 支持 CTEs,将本书的 SQL 代码移植到 Presto 上将是一个低效劳的任务。

如果你需要将本书的 SQL 代码移植到不支持 CTEs 的数据库上,代码将需要进行修改。如果可能的话,你可以用临时表替换 CTEs,这将允许你保持代码的布局和流程。如果你的系统不允许使用临时表,可以使用子查询来完成代码的移植。同样,这是一个很大的主题;我建议搜索针对你所工作的系统的特定资源。

11.3.2 移植 Python 代码列表

你可能还想考虑的一个选项是将 Python 代码列表重构,使其成为你自己的软件框架的一部分。本书中的代码列表是为了以一系列小步骤清晰地教授主题而编写的。因此,这种代码在几乎所有其他方面都承认是次优的,如果你选择不使用其当前形式,我当然不会介意。

如果你想要移植代码,当然最容易的方式是保持其在 Python 中。这个过程就像编写你自己的包装程序并进行一些重构(在不改变核心逻辑的情况下移动代码)。如果你想要将代码移植到另一种语言,你将面临更大的任务。无论你是重构还是将代码移植到另一种语言,这类事情都可能很棘手。

警告:移植分析代码存在风险;计算或分析函数中看似微小的差异可能会对结果产生重大影响。最大的风险是那些表面上看起来正确但实际上包含计算错误的结果,这些错误会改变指标或分析结果的意义。

我建议您在迁移代码或重构 Python 中的每个函数时,通过在社交网络模拟数据上重新运行分析来测试每个函数。确保您可以使用自己的代码重现结果,或者您对任何差异的原因有很好的理解。

吸收要点:使用社交网络模拟的结果作为代码迁移的回归测试。

11.4 学习更多并保持联系

在您开始自己的客户流失对抗战之前,我将为您提供一些更多信息。

11.4.1 作者的博客网站和社交媒体

我维护自己的博客,在那里我发布有关客户流失的信息和更新:

fightchurnwithdata.com

到现在为止,您应该知道书中所有的代码都可以在我的 GitHub 仓库中找到:

github.com/carl24k/fight-churn

我在 Twitch 上直播客户流失分析演示:

www.twitch.tv/carl24k

此外,请通过社交媒体与我保持联系:

  • 推特:@carl24k

  • 领英:in/carlgold

我很乐意听听您在用数据对抗客户流失方面的经验和结果!

11.4.2 客户流失基准信息的来源

在这本书中,我没有提供任何关于真实公司客户流失率的信息。此类信息可用于将您的客户流失率与同行进行基准比较。在撰写本文时,我知道一些在线资源提供了基于数据的平均客户流失率:

所有这些报告都是免费的,但如果您需要提供电子邮件地址才能下载文档,请不要感到惊讶。(如果您提供的是公司电子邮件地址,您可能会收到销售人员的后续联系,但他们足够聪明,不会打扰那些用个人电子邮件地址注册下载报告的人。)

警告:典型的基准客户流失率因产品或服务的类型而异,因此在将您的公司产品与基准进行比较之前,请确保您的产品与基准中的产品相似。

基准可以是有用的,但它们并不完美。报告涵盖了不同类型的公司,所以如果你在报告之间发现基准流失率的不同,不要感到惊讶。仔细阅读报告,了解每个报告中涵盖的公司和产品类型,并选择与你的公司和产品最接近的基准。

11.4.3 关于流失的其他信息来源

如果你在网上搜索“流失”,你会找到很多链接,因为现在很多人都知道流失是一个大问题。但事实是,与这本书中的信息相比,这些链接中的大部分信息都是基础的,而且你将找到的许多文章都是对需要付费产品的 thinly disguised advertisements。话虽如此,在撰写本文时,我推荐一个免费资源,可能不会出现在你的搜索结果中(或者可能被广告埋没):ChurnFM (www.churn.fm),这是一个专注于流失的播客。

如果你知道任何其他用于对抗流失的免费资源,请通过社交媒体联系我。

11.4.4 帮助对抗流失的产品

并非意外,一些产品专门设计来帮助解决对抗流失的不同方面。本书的重点是通过使用开源工具和自己的数据来理解流失,但你应该知道以下产品类别存在:

  • 客户支持平台——帮助组织入职和保留策略的软件

  • 信用卡重试自动化——重试失败的信用卡以最小化非自愿流失

  • 退出调查和自动化——软件用于调查用户取消订阅的原因,并给他们最后一次机会保留订阅

你可以通过按类别名称搜索轻松找到关于所有这些产品类别的更多信息。

摘要

  • 书中技术的益处是前置的,所以你不必使用所有这些技术就能获得大部分好处。

  • 一家公司通过使用一组良好的数据驱动客户指标来对抗流失,可以获得最大的好处。

  • 与业务同事分享流失分析的结果是流失对抗过程中的重要部分——如果业务人员不采取行动,流失率就不会降低。

  • 在你的数据上运行书中列表的最快方式是将你的数据加载到与书中使用的类似 PostgresSQL 模式中,并创建一个配置来运行书的包装程序。

  • 对于希望在生产过程中重用代码列表的公司,最佳实践通常是编写自己的包装程序,该程序导入并运行列表。

  • 如果你想要将书中的代码移植到自己的生产环境中运行,可以使用书中描述的社会网络模拟作为回归测试。

  • 在线关于客户流失的信息大部分是产品的广告,但也有一些免费资源,包括作者的网站、客户流失率基准和播客。

posted @ 2025-11-22 09:01  绝不原创的飞龙  阅读(14)  评论(0)    收藏  举报