Skip to main content

用 AI 分析两年订单:销售额还在涨,老客户却在少买

· 34 min read
wubx
Data AI

我一直想知道,一个不会写 SQL 的业务人员,能不能靠 AI 把公司的订单数据分析清楚,交出一份领导愿意看的东西。光想没用,我自己试了一次。

数据用的是一份真实的公开订单数据:一家英国网店两年的全部交易,100 多万行。Evot 通过 Databend Cloud 的 Agent Skill 操作数据库。

前面的对话里,我们定好了导入到哪个库,Evot 也说明了写库只会新建一张表。在这个基础上,我说的是:

帮我把 https://archive.ics.uci.edu/dataset/502/online+retail+ii 数据加载到 Databend Cloud,建表导入数据,先完整跑一遍,看看能得出什么有价值的结论。

没有告诉它表结构,没有定义什么叫销售额、什么叫流失,也没有告诉它要分析哪些问题。从数据下载完到写出分析记录,它用了 7 分钟。这句话离开前面的对话就不够用了,想自己试的话,文末的“怎么复现”给了一段完整的指令。

结果里最有用的一条:这家店两年销售额只涨了 2.4%,看上去很平稳;但有 730 个老客户第二年的消费减少了一半以上,少花的钱比所有流失客户第一年花的加起来还多。同时也让我看到一个店铺可能存在的生命周期。

下面按实际过程写:它做了什么,哪里出了错,最后交出了什么。文中的数字都能对应到保存下来的查询结果。

数据​

数据来自 UCI 机器学习数据库的 Online Retail II。这家网店主要卖礼品,不少客户是批发商。时间从 2009 年 12 月 1 日到 2011 年 12 月 9 日,金额单位是英镑。下载下来是一个 zip 压缩包,里面只有一个 Excel 文件 online_retail_II.xlsx,解压后 45.6 MB。Excel 里有两个工作表,每个工作表一年。

每一行是订单里的一个商品,有这些字段:发票号、商品编码、商品描述、数量、下单时间、单价、客户编号、国家。

这是一个运营人员很容易拿到的数据形态:从系统里导出来的明细表,没人整理过。

导入​

Evot 自行完成

Evot 先下载并解压 zip 包,用 pandas 读出 Excel 的两个工作表 Year 2009-2010 和 Year 2010-2011,分别是 525,461 行和 541,910 行。它把两个表合并成 1,067,371 行,加了一列记录每行来自哪个工作表,再转成 Parquet 文件。然后它生成一个预签名地址,把文件上传到 Databend Cloud 的用户 Stage,在 wubx 库里新建 online_retail_ii 表,用 COPY INTO 导入。

第一次 COPY INTO 失败了。它加了一个 MATCH_BY_COLUMN_NAME 选项,Databend 的这个语法里不支持,直接报了语法错误。表是空的,没有造成影响。它去掉这个选项重试,导入 1,067,371 行,0 个错误,和 Excel 里的行数一致。

动手之前,它先说明这一步是写操作,只新建一张表,不改动、不删除已有的表,我确认后它才开始。建表用的是 CREATE TABLE,不是 CREATE OR REPLACE,库里如果已经有同名的表,会报错而不是覆盖。

数据清洗:直接加总会多算 127 万​

Evot 自行完成

Evot 没有直接算销售额,先查了几类异常数据。下面这些问题,只看导出的表几乎看不出来。

两个工作表重叠了 9 天​

它先统计完全重复的行。在本地用 pandas 数是 12,133 行;在数据库里按除工作表以外的所有字段分组,重复的组却有 32,248 个,两个数对不上。区别在于,本地统计时带上了“来自哪个工作表”这一列。它据此去查两个工作表的时间范围,发现第一年的表一直到 2010 年 12 月 9 日,第二年的表从 2010 年 12 月 1 日开始,中间这 9 天的 1,088 张发票、22,523 行,在两个表里各出现一次。不处理的话,2010 年 12 月的销售额会算两遍,多出 43.9 万英镑。

商品编码里混着运费和调账​

它把不是 5 位数字开头的商品编码列出来,按金额排序,找到了邮费(POST、DOT)、运费(C2)、亚马逊平台费、银行手续费、坏账调整、人工调整、折扣、样品、礼品卡和测试商品。邮费和运费合计 44.8 万英镑;平台费、手续费、调账这些合计是负 54.7 万,正负混在一起。

取消订单、零单价和匿名客户​

记录规模
取消的订单,发票号以 C 开头7,405 张,金额相当于销售额的 3.65%
单价为 0 或负数6,207 行
没有客户编号占销售额的 13.1%,这部分没法做客户分析

把这些都处理掉,两年的商品销售额是 1,970 万英镑。如果只排除取消订单、数量和单价小于等于 0 的行,直接加总是 2,097 万,多算了 127 万,也就是 6.5%。

它把清洗口径写成了一段 SQL,后面每个分析都以这段 SQL 开头,口径保持一致。完整的 SQL 放在文末附录。结论一到四按下单金额计算,没有扣除取消;结论五按扣除取消后的净额计算。两种算法的差别见后面“Evot 出错的地方”。

对业务同事来说,这里有一件实际可做的事:拿到结论之前,先让 AI 说明它排除了哪些数据、每一类影响多少金额。上面这些问题你未必看得出来,但可以要求它列给你看。

结论一:销售额看着平稳,老客户在少买​

Evot 自行完成

清洗后,两年一共 39,516 张订单,5,852 个能识别的客户,平均每单 499 英镑。

每年 9 到 11 月是旺季,11 月的销售额是 2 月的 2.7 到 2.9 倍。按“12 月到次年 11 月”算两个完整年度,第一年 943 万英镑,第二年 966 万英镑,只增长了 2.4%。

只看这两个数,会觉得生意稳住了。Evot 接着把第一年有消费的 4,239 个客户分成两组,看他们第二年的情况,又单独统计了第二年的新客户:

客户人数第一年消费第二年消费
第二年没再买(流失)1,531105.9 万0
第二年继续买(留存)2,708730.5 万679.4 万
第二年的新客户1,5850145.4 万

流失客户占了 36%,但他们第一年的消费只占 12.7%,其中 869 个本来就只下过一单。

问题在留存客户里。2,708 个继续购买的客户中,有 730 个第二年的消费比第一年少了一半以上,一共少花了 137.5 万英镑,比所有流失客户第一年的消费(105.9 万)还多。

新客户带来的 145.4 万,没有完全补上流失的 105.9 万和老客户净减少的 51.1 万。所以能识别客户的总消费,其实是下降了 1.4%。

如果运营只盯着“流失了多少客户”,会漏掉这个更大的问题。

结论二:收入集中在少数客户身上​

Evot 自行完成

前 1% 的客户,大约 58 个,贡献了 31.8% 的销售额;前 10% 贡献 63.8%,前 20% 贡献 77.1%。后一半的客户加起来只有 6.5%。

下单 11 次以上的 864 个客户,贡献了 64.5% 的销售额。买过两次以上的客户占 72.4%,他们贡献了 96.7% 的销售额,人均消费 3,913 英镑,只买过一次的客户人均 345 英镑。

这和“不少客户是批发商”对得上。几百个老客户撑起了大半的生意,他们的消费变化,值得每个月看一次。

结论三:90 天没回购,一半以上的人后来还会回来​

Evot 自行完成

很多公司判断流失用一条固定规则,比如“90 天没下单就算流失”。Evot 统计了 2011 年 6 月之前首次下单的 4,908 个客户,看他们隔多久下第二单:

首单后多久下第二单占比
30 天内22.3%
90 天内46.3%
180 天内62.2%
观察期内任何时候77.0%

90 天内没回购的客户里,57.2% 后来还是回来了。也就是说,按 90 天这条线,会把一半多的人判错。

这个比例对早期客户偏高,因为首单越早,观察的时间越长,这一点我没有校正。但从 30 天到 180 天的分布已经能看出,客户之间的下单节奏差别很大,用同一条线去判断,误判会很多。

结论四:一份高价值流失风险名单,并且回测过​

Evot 自行完成

前面的分析说明问题在哪,运营真正能拿去用的,是一份名单。

Evot 站在 2011 年 9 月 1 日这一天,取过去 12 个月消费排前 20% 的客户,一共 865 个。判断规则按每个客户自己的节奏来定:距离上次下单的天数,超过他平均下单间隔的 2 倍,并且至少 45 天,就标记为风险;下单不到 3 次、没法算间隔的,超过 90 天没下单就标记。

一共标记出 157 个客户,他们过去 12 个月的消费合计 73.6 万英镑。

这份名单有没有用,可以直接验证:数据里有 9 月 1 日之后的真实订单。回测结果:

分组客户数9 月之后回来下单的比例
前 20%,没有标记70889.7%
前 20%,标记为风险15768.2%
其余客户,没有标记1,55156.5%
其余客户,标记为风险1,90832.5%

被标记的高价值客户,回来的比例低了 21.5 个百分点,规则确实能把风险高的人挑出来。但它不精准:157 个人里有 107 个后来还是回来了。真正没回来的是 50 个,他们过去 12 个月一共消费了 33.1 万英镑。

所以这份名单适合交给销售或客服,一个一个打电话问清楚原因,不适合直接拿去群发优惠券,否则大部分优惠会发给本来就会回来的人。

另外要说明,回测期是 9 月到 12 月 9 日,正好是旺季,回来的比例会偏高。

结论五:风险在哪里​

再次追问,想做一下数据的洞察

看完上面四条,我问 Evot:能不能从这份数据里预测出这家店什么时候倒闭?或是店铺的生命周期。

它的回答是不能。这份数据只有销售明细,没有进货成本、毛利、房租人工、负债和现金,而一家店会不会倒闭,取决于利润和现金流。从销售数据本身也看不到倒闭的迹象:2011 年 11 月是整份数据里销售额最高的一个月,145.8 万英镑。2011 年 12 月只有 61.5 万,是因为数据只到 12 月 9 日,只有 9 天。

预测不了时间,但能看出哪里需要盯紧。下面是它从数据里找出的几处风险,数字按扣除取消后的净额计算。

增长停了,靠新客户补缺口​

扣除取消后,两个完整年度的销售额从 918.9 万到 935.1 万,只增长 1.8%,也就是 16.3 万。老客户在少买,缺口靠新客户补。第二年的新客户里,有一半左右(778 个)当年只下过一单,他们以后还会不会买,现在看不出来。

老客户在变冷:这是客户的生命周期,不等于店铺在衰退​

结论一说老客户在少买。我追问了一句:这是不是说明店铺也有生命周期,开始走下坡路了?

为了回答这个问题,Evot 把第二年还在买的老客户,按第一次出现在数据里的时间分成四组,看他们第二年比第一年多买还是少买:

第一次出现客户数第一年消费第二年消费变化消费减半的比例
2009-12 到 2010-021,214534.9 万429.6 万-19.7%34.4%
2010-03 到 0561296.4 万86.4 万-10.3%25.8%
2010-06 到 0834739.6 万57.0 万+43.9%22.2%
2010-09 到 1152538.5 万81.3 万+110.9%13.9%

越早出现的客户,第二年下滑越明显。最早的一组有 1,214 个客户,第二年少买了 19.7%,三分之一以上的人消费减半。这符合客户的生命周期:进来之后买得越来越多,过一段时间开始变冷。

后两组的增长要打折扣看。2010 年秋天才来的客户,第一年只有一两个月的消费,第二年是完整的一年,算出来自然会涨。

客户在变老,和店铺在衰退,是两回事。客户一批批变冷,只要不断有新客户进来、慢慢长成老客户,店铺整体可以一直稳定。判断店铺有没有进入衰退期,要看新客户能不能接上,还要看利润,而这两点这份数据都回答不好:

  • 新客户的数量看不准。按季度数,2010 年第二季度有 816 个新客户,2011 年第二季度只有 325 个,看上去少了一半多。但数据是从 2009 年 12 月开始的,在那之前就已经是客户、只是隔很久才下一单的人,第一次出现在数据里时,也会被算成新客户。所以 2010 年的新客户数是偏高的,“新客户减半”这个结论不能下。
  • 新客户的质量只能粗略比较。同样在春季进来的两批新客户,2010 年那批前 180 天人均消费 936 英镑,60.3% 下了第二单;2011 年那批是 840 英镑和 55.8%。后一批差一些,但 2010 年那批里混着前面说的老客户,差距会被放大。
  • 没有利润数据。销售额持平的店,毛利在变薄的话,处境也可能在变差,这份数据看不出来。

所以能说的是:这家店的增长停住了,最早的那批老客户在变冷,需要专门维护。不能说的是:这家店已经开始衰退。2011 年 11 月还是两年里销售额最高的一个月。要判断是平台期还是下坡路,至少还要一两年的数据,再加上成本和利润。

另外,“新客户减半”这个数,是业务同事最容易直接拿去汇报的那种结论:查询本身没有错,数字也是真的,错在没有想到数据的起点。这类问题数据库不会报错,只能靠追问“这个数据是从什么时候开始记的,之前发生的事会不会被算进来”。

头部客户波动很大​

消费最多的 10 个客户贡献了 16.2% 的销售额。这 10 个客户两年合计基本持平,但内部差别很大:有 3 个明显少买,第一大客户从 31.9 万降到 24.8 万,一个爱尔兰客户从 18.4 万降到 11.4 万,还有一个英国客户从 12.7 万降到 6.0 万,合计少了 20.8 万,比全店两年的增长额还多;另有 2 个客户大幅增长,合计多了 24.8 万,正好把缺口补上。

也就是说,全店的增长有相当一部分取决于少数几个大客户当年买得多还是少。

海外收入押在一两个客户身上​

英国占可识别客户销售额的 83.6%。英国以外排第一的爱尔兰,只有 3 个客户,贡献了 3.5%,其中最大的一个占爱尔兰的一半以上;排第二的荷兰有 22 个客户,贡献了 3.3%,其中一个客户就占了荷兰的 95.8%。这个客户不买了,荷兰的收入基本就没了。

旺季占了全年三分之一以上​

9 到 11 月的销售额,两年分别占全年的 36.1% 和 37.5%。一个旺季没做好,全年很难补回来。

取消在增加,还有整单取消的大单​

取消金额占销售额的比例,从第一年的 2.56% 升到第二年的 3.15%。最大的两张取消单都是整单取消:一张是 2011 年 12 月 9 日,一个客户下了 8 万多件、16.8 万英镑的单,12 分钟后全部取消;另一张是 2011 年 1 月 18 日,7.7 万英镑,16 分钟后全部取消。这两个客户平时每单只有几十英镑。这种单如果只看下单金额,会让一个小客户看起来像大客户,也会让当月销售额虚高。

越来越多的销售额找不到客户​

没有客户编号的销售额,从第一年的 11.3% 升到第二年的 14.6%。这部分客户没法做复购分析,也没法维护。

这几条都还不是“出了问题”,但每一条都可以变成每个月看一次的指标:老客户的消费变化、新客户的二次购买率、头部 10 个客户的消费变化、单一国家里最大客户的占比、取消率和大额取消单、匿名销售占比。其中老客户和新客户这两项连起来看,才能回答店铺是在平台期还是在走下坡路。

Evot 出错的地方​

Evot 自行完成 ,自动纠错

这次也有几处错误。

SQL 写错了两次。一次是前面导入时用了不支持的 COPY 选项。另一次在算复购率和客户集中度时,它在清洗口径的 WITH 子句后面又写了一个 WITH,两条查询都报了语法错误。这两次都是数据库直接拒绝执行,它改完重跑,没有产生错误结果。这类错误不可怕,因为会报错。

有一处算错了,而且不会报错。它在分析记录里写,新客户带来的 145.4 万“差不多正好填上”流失和老客户少花的钱。写完后它又把数字核对了一遍,发现实际要填的是 157.0 万,差了 11.6 万,这正是总消费下降 1.4% 的原因,于是改了过来。数据库只检查 SQL,不检查文字里的结论,这类错误只能靠复核。

还有一处口径问题,是做风险分析时才发现的。结论一到四用的是下单金额,没有扣除取消。直到查取消单,才看到有两张 7.7 万和 16.8 万英镑的大单在十几分钟内被整单取消,这两笔都被算进了销售额。改用扣除取消后的净额重算,前 1% 客户的占比从 31.8% 变成 31.0%,前 20% 从 77.1% 变成 76.6%;消费减半的老客户从 730 个变成 726 个,少花的钱从 137.5 万变成 132.7 万,仍然比流失客户第一年的消费(103.4 万)多。数字有变化,结论没有变。但更稳妥的做法,是一开始就按净额算。

还有一处规则上的漏洞,它当时没有提。风险名单里有个客户,3 张订单在同一天,算出来的平均间隔是 0 天,所以超过 45 天就被标记了。这个人后来确实没再回来,但标记他的理由并不成立。规则在这类边界情况下会给出看上去合理、其实站不住的结果。

给业务同事的四个建议​

我自己会写 SQL,但这次没有插手,只看它怎么做。做完之后,我觉得业务同事要做好这件事,靠的不是会不会写代码,而是会不会追问。下面几件事,不需要任何技术背景:

第一,先问清楚排除了什么,以及这份数据能不能回答你的问题。让它列出清洗时排除的每一类数据,以及各影响多少金额,这次如果不处理,销售额会多算 6.5%。再问一句数据缺什么:这次问“店会不会倒闭”,它的回答是缺成本和利润,判断不了;问“新客户是不是减少了”,要先想到数据从哪天开始记。

第二,要求每个数字都能对上查询。让它把每条 SQL 和结果都保存下来。拿去汇报之前,挑两三个关键数字让它重算一遍,或者换一种方法算。

第三,结论要落到能执行的东西上。“老客户在少买”是一个发现,“这 157 个客户请销售本周逐个联系”才能推动工作。名单还要附上判断依据:上次下单时间、平时多久买一次。

第四,权限和数据要先管好。这次写数据库之前,它先说明只新建一张表,等我确认了才动手;这种确认要坚持。如果用的是公司真实的客户数据,姓名、电话这类字段不要交给 AI,用客户编号做分析,最后再和 CRM 关联。

我的体会​

从这个数据分析中感觉:老客户维护住了,规矩立住了,才能越做越大,用户吃着也不烧心。这家店两年里有一大半生意来自几百个老客户,他们少买的钱,比流失客户花的还多。规矩不用复杂:每个月看一次老客户和新客户的变化,有风险的客户找人一个一个问清楚,取消和匿名销售有人盯着。这些事 AI 能帮着把数算出来、把名单拉出来,但打电话、改流程,还得靠人。

局限​

这是公开数据集,客户编号是脱敏后的数字,没有客户名称和联系方式。13.1% 的销售额没有客户编号,所有和客户有关的结论都不包括这部分。

风险名单的规则是经验规则,2 倍间隔、45 天、90 天这几个阈值都没有调优;回测只用了一个时间点,回测期又是旺季。

清洗时排除哪些商品编码,是看了编码和描述后判断的。换一个人来做,口径可能会有小的差别。

怎么复现​

需要准备三样东西。

一个 Databend Cloud 账号。国内站是 app.databend.cn,国际站是 app.databend.com。在控制台侧边栏底部的“账户 API 令牌 → 访问令牌 → 生成新令牌”里生成一个个人访问令牌(国际站是 Account API Tokens → Access Tokens),在“Admin → Organization Profile”里记下组织 ID。

一个能执行命令的 AI Agent,我用的是 Evot。你可以用任意的 AI Agent 如 Codex、Claude Code、Pi、OpenCode 等。按 Databend 的 Agent Skills 文档 安装 databend-cloud skill。令牌按它的引导在本地终端里保存,不要发到聊天里。

本地有 Python 3。Excel 转 Parquet 要用 pandas、openpyxl 和 pyarrow,没有的话 Agent 会提示安装。

然后把下面这段话发给 Agent,尖括号里的内容换成你自己的:

用 databend-cloud skill 连接 Databend Cloud <国内站或国际站>,组织 ID 是 <你的组织 ID>,计算集群用 default。

数据是 UCI 的 Online Retail II:
https://archive.ics.uci.edu/static/public/502/online+retail+ii.zip
这是一家英国网店 2009-12 到 2011-12 的订单明细,Excel 里有两个工作表。

把两个工作表合并后,导入到 <你的库名>.online_retail_ii 这张新表。
只能新建表,不要改动或删除库里已有的表。执行写操作之前,先告诉我要执行什么。

导入后核对行数和 Excel 是否一致。然后完整分析一遍,看看能得出什么有价值的结论。
每条 SQL 和查询结果都保存到本地文件。

这段话是按我和 Evot 当时的对话整理出来的,补上了对话里已经定好的数据集、库名和写库规则,没有加任何关于清洗或分析方法的提示。我没有拿这段话从头再跑一遍。不同的模型、不同的运行,结果都会不一样,它不一定会自己发现两个工作表重叠,也不一定会做风险名单。

这次下面这些都是 Evot 自己做的。如果你那边没做,可以接着这样问:

清洗时排除了哪些数据?每一类影响多少金额?直接加总和清洗后的销售额差多少?
两个工作表的时间范围有没有重叠?如果有,重叠部分是不是重复记录?
把第一年有消费的客户分成流失和留存两组,留存客户里消费下降超过一半的有多少人、少花了多少钱?和流失客户比哪个影响大?
站在 2011-09-01 这一天,按每个客户自己的下单间隔,列出过去 12 个月消费前 20% 的客户里有流失风险的人,导出 CSV。再用 9 月之后的真实订单回测,被标记的人有多少后来回来了。
把总销售额和留存客户少花的金额,换一种算法再算一遍,看看是否一致。

拿到结果后,可以和本文的数字对照:清洗后销售额 1,970 万英镑,重叠的发票 1,088 张,消费减半的老客户 730 个。差别比较大的话,先对清洗口径,附录里有完整的 SQL。

附录:清洗口径​

后面所有分析都以这段 SQL 开头:

-- 1. 去掉 2009-2010 表里与 2010-2011 表重叠的 2010-12-01 之后的记录
-- 2. 去掉运费、手续费、折扣、坏账调整、礼品卡、测试商品等非商品行
-- 3. 去掉单价 <= 0 的行
-- 4. 销售 = 非取消发票且数量 > 0;取消 = 发票号以 C 开头
WITH base AS (
SELECT *, quantity * price AS amount, invoice LIKE 'C%' AS is_cancel
FROM wubx.online_retail_ii
WHERE NOT (source_sheet = 'Year 2009-2010' AND invoice_date >= '2010-12-01')
AND stock_code NOT IN ('POST','DOT','M','AMAZONFEE','B','BANK CHARGES','D','C2',
'CRUK','ADJUST','ADJUST2','S','TEST001','TEST002','PADS')
AND stock_code NOT LIKE 'gift%'
AND price > 0
),
sales AS (SELECT * FROM base WHERE NOT is_cancel AND quantity > 0),
cs AS (SELECT * FROM sales WHERE customer_id IS NOT NULL)

💬 社区支持
有问题与我们的团队联系:Slack

微信:82565387

wubx 的微信二维码