查看全课大纲(第 43 / 45 节)

子图制作(二)

商品、行为与客户价值:三类子图分析

小象实战讲义 · ECharts 数据可视化

上一节完成了用户流量趋势。本节继续从商品和行为两个角度回答问题:哪些品类和单品贡献了更多点击,用户在哪个环节流失,以及如何用 R、F 两个维度识别值得重点运营的客户。三组图分别对应三个业务问题,而不是为了把所有图表放进同一张页面。

💡 核心导读

  • 商品维度:按品类和单品聚合点击次数,用 Top10 饼图识别头部集中度。
  • 行为维度:把点击、收藏/加购、支付转换成百分比漏斗,定位最大流失环节。
  • 客户价值:用最近购买时间和购买频次的中位数二值化,构造四类 RFM 客户。
  • 图表解释边界:区分“点击热度”“行为转化”和“客户价值”,避免用一张图回答三个不同问题。

一、商品维度:Top10 品类与 Top10 单品

项目 notebook 先按 item_category 或 item_id 统计点击记录,再按次数降序取前 10 条。类别 ID 和单品 ID 都是脱敏标识;如果业务方提供名称映射,才能在图上显示中文名称。

下面用确定性小样本复现同一逻辑。十个品类与十个单品的点击量依次为 8、7、6、5、4、3、2、2、1、1,便于核对排序是否正确。

import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import Page, Pie
from pyecharts.globals import CurrentConfig

CurrentConfig.ONLINE_HOST = "assets/"

product_specs = [
    (2101, 10, 8),
    (2102, 20, 7),
    (2103, 30, 6),
    (2104, 40, 5),
    (2105, 50, 4),
    (2106, 60, 3),
    (2107, 70, 2),
    (2108, 80, 2),
    (2109, 90, 1),
    (2110, 100, 1),
]

product_rows = []
for item_id, category_id, click_count in product_specs:
    for offset in range(click_count):
        product_rows.append(
            {
                "user_id": 3000 + offset,
                "item_id": item_id,
                "item_category": category_id,
            }
        )

click_df = pd.DataFrame(product_rows)
top_categories = (
    click_df.groupby("item_category")["user_id"]
    .count()
    .sort_values(ascending=False)
    .head(10)
)
top_items = (
    click_df.groupby("item_id")["user_id"]
    .count()
    .sort_values(ascending=False)
    .head(10)
)

category_pairs = [(str(category), int(count)) for category, count in top_categories.items()]
item_pairs = [(str(item), int(count)) for item, count in top_items.items()]

category_pie = (
    Pie()
    .add("类别", category_pairs, radius=["32%", "66%"])
    .set_global_opts(
        title_opts=opts.TitleOpts(title="Top10 商品类别"),
        legend_opts=opts.LegendOpts(is_show=False),
    )
    .set_series_opts(label_opts=opts.LabelOpts(formatter="{d}%"))
)

item_pie = (
    Pie()
    .add("单品", item_pairs, radius=["32%", "66%"])
    .set_global_opts(
        title_opts=opts.TitleOpts(title="Top10 单品"),
        legend_opts=opts.LegendOpts(is_show=False),
    )
    .set_series_opts(label_opts=opts.LabelOpts(formatter="{d}%"))
)

Page(layout=Page.SimplePageLayout).add(category_pie, item_pie).render("product_top10.html")

print(f"商品点击样本: {len(click_df)} 条")
print("Top3 类别:", list(category_pairs[:3]))
print("Top3 单品:", list(item_pairs[:3]))
商品点击样本: 39 条
Top3 类别: [('10', 8), ('20', 7), ('30', 6)]
Top3 单品: [('2101', 8), ('2102', 7), ('2103', 6)]

怎么读这两张饼图:类别图回答“资源应优先投向哪类商品”,单品图回答“哪些具体商品是头部流量入口”。两张图都只展示头部集合,百分比的分母是 Top10 总量,不是全站点击总量,因此不能把图中占比直接解释成全站市场份额。

二、行为维度:点击到支付的漏斗

原始项目按 behavior_desc 统计点击、收藏、加入购物车和支付的记录数。收藏与加购没有严格先后关系,所以漏斗把它们合并为“收藏/加购”阶段,再计算相对点击量的百分比。

下面的样本沿用原课结论口径:点击 10000 条、收藏 322 条、加购 446 条、支付 148 条。

from pyecharts import options as opts
from pyecharts.charts import Funnel
from pyecharts.globals import CurrentConfig

CurrentConfig.ONLINE_HOST = "assets/"

pv = 10000
favor = 322
cart = 446
buy = 148

print(f"点击: {pv}")
print(f"点击→收藏: {favor / pv:.2%}")
print(f"点击→加入购物车: {cart / pv:.2%}")
print(f"点击→支付: {buy / pv:.2%}")
print(f"收藏/加购→支付: {buy / (favor + cart):.2%}")

interest_count = favor + cart
funnel_rates = [
    round(pv / pv * 100, 2),
    round(interest_count / pv * 100, 2),
    round(buy / pv * 100, 2),
]

behavior_funnel = (
    Funnel()
    .add(
        series_name="用户行为",
        data_pair=[
            ("点击", funnel_rates[0]),
            ("收藏/加入购物车", funnel_rates[1]),
            ("支付", funnel_rates[2]),
        ],
        tooltip_opts=opts.TooltipOpts(trigger="item", formatter="{b}: {c}%"),
        label_opts=opts.LabelOpts(is_show=True, position="outside"),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="用户行为漏斗分析"),
        legend_opts=opts.LegendOpts(is_show=False),
    )
)

behavior_funnel.render("behavior_funnel.html")
点击: 10000
点击→收藏: 3.22%
点击→加入购物车: 4.46%
点击→支付: 1.48%
收藏/加购→支付: 19.27%
用户行为漏斗图,从上到下各环节依次收窄
图:用户行为漏斗:从浏览到下单逐级收窄,直接看出流失最大的环节。手机端自动使用简化视图,点击图片可看大图。

结论不是“收藏和加购不重要”。从点击到兴趣行为只有 7.68%,这是最大的流失段;但一旦用户收藏或加购,后续支付转化达到 19.27%,显著高于直接点击到支付的 1.48%。因此运营重点应放在引导兴趣行为,并缩短收藏/加购到支付的路径。

三、客户价值:用 R、F 中位数构造四类客户

项目数据没有消费金额字段,因此只使用 R(Recency)和 F(Frequency):

  • R:距离最近一次支付的天数,越小越活跃;按中位数分组,小于等于中位数的记为 1。
  • F:支付次数,越大越忠诚;大于中位数的记为 1。
  • RF 组合:11 重要价值、10 重要发展、01 重要保持、00 重要挽留。

下面构造 8 个用户,并让四类客户各有 2 人,方便验证分组逻辑。

import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import Pie
from pyecharts.globals import CurrentConfig

CurrentConfig.ONLINE_HOST = "assets/"

snapshot_date = pd.Timestamp("2018-12-18")
user_specs = [
    (1, 1, 3),
    (2, 2, 3),
    (3, 3, 1),
    (4, 4, 1),
    (5, 10, 3),
    (6, 11, 3),
    (7, 12, 1),
    (8, 13, 1),
]

order_rows = []
for user_id, days_ago, frequency in user_specs:
    purchase_date = snapshot_date - pd.Timedelta(days=days_ago)
    for order_offset in range(frequency):
        order_rows.append(
            {
                "user_id": user_id,
                "item_id": 5000 + order_offset,
                "date": purchase_date,
            }
        )

orders = pd.DataFrame(order_rows)
last_purchase = orders.groupby("user_id")["date"].max()
recency = (snapshot_date - last_purchase).dt.days.rename("recency_days")
frequency = orders.groupby("user_id").size().rename("frequency")
rfm = pd.concat([recency, frequency], axis=1)

median_recency = rfm["recency_days"].median()
median_frequency = rfm["frequency"].median()
rfm["R"] = (rfm["recency_days"] <= median_recency).astype(int)
rfm["F"] = (rfm["frequency"] > median_frequency).astype(int)
rfm["RF"] = rfm["R"].astype(str) + rfm["F"].astype(str)
rfm["user_tag"] = rfm["RF"].map(
    {
        "11": "重要价值客户",
        "10": "重要发展客户",
        "01": "重要保持客户",
        "00": "重要挽留客户",
    }
)

tag_order = ["重要价值客户", "重要发展客户", "重要保持客户", "重要挽留客户"]
user_tag_count = rfm["user_tag"].value_counts().reindex(tag_order, fill_value=0)
user_tag_pie = (
    Pie()
    .add(
        series_name="客户分层",
        data_pair=[(tag, int(count)) for tag, count in user_tag_count.items()],
        radius=["32%", "68%"],
        label_opts=opts.LabelOpts(is_show=True, position="outside"),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="RFM 客户价值分层"),
        legend_opts=opts.LegendOpts(is_show=False),
    )
    .set_series_opts(
        tooltip_opts=opts.TooltipOpts(trigger="item", formatter="{b}: {d}%")
    )
)

user_tag_pie.render("rfm_segments.html")

print(f"RFM 中位数: R={median_recency:.2f}, F={median_frequency:.2f}")
print("用户价值分层:")
print(user_tag_count.to_string())
RFM 中位数: R=7.00, F=2.00
用户价值分层:
user_tag
重要价值客户    2
重要发展客户    2
重要保持客户    2
重要挽留客户    2
RFM 客户价值分层饼图,不同价值群体按占比分扇区
图:RFM 客户价值分层:按客户价值分群后的占比,高价值客户集中在少数扇区。手机端可左右滑动查看,点击图片可看全图。

四类客户的运营动作不同:

  • 重要价值客户:近期活跃且购买频繁,重点是保持体验、会员权益和稳定复购。
  • 重要发展客户:近期活跃但频次不高,重点是提高购买次数,例如首复购提醒和品类优惠。
  • 重要保持客户:历史频次高但近期沉默,重点是唤醒,例如消息推送和召回权益。
  • 重要挽留客户:近期与频次都偏低,需要持续曝光、优惠触达,并进一步识别流失原因。

四、把三组图放回同一个业务故事

商品 Top10 解释“用户把点击集中在哪些货上”,漏斗解释“兴趣行为在哪一步衰减”,RFM 解释“哪些客户值得优先运营”。三者可以并列展示,但不能用一个总百分比互相替代。

在真实项目中,先确认行为统计窗口、Top10 分母和 RFM 快照日期,再把结果交给大屏。否则同一张图换一天数据,百分比和客户分层都可能变化。

📝 动手练一练

  1. 修改 Top10:如果业务只看点击量不少于 5 的商品,应该在哪一步过滤?

    点击查看参考答案

    先按 item_id 聚合得到点击计数,再过滤 count >= 5,最后重新排序并取前 10。不能先截取前 10 再过滤,否则可能漏掉原本排在第 11 位但达到阈值的商品。

  2. 解释漏斗:收藏和加购为什么可以合并成一个阶段?

    点击查看参考答案

    在本项目中,收藏和加购都表示用户对商品产生了兴趣,二者没有严格先后顺序,也不要求同时发生。合并后得到“至少发生一种兴趣行为”的阶段,用于观察点击到支付的中间转化。若要研究两种行为各自的效率,应另画两张分渠道漏斗。

  3. 修改 RFM 阈值:把 R 的“小于等于中位数记 1”改成“小于中位数记 1”,会带来什么影响?

    点击查看参考答案

    等于中位数的用户会从高价值组移到低价值组,四类客户的人数分布可能改变。阈值本身就是业务规则,必须在报告中写明;比较不同周期的分层结果时,也要保证阈值定义一致。

本章小结

本节从商品、行为、客户价值三个问题出发:Top10 饼图观察头部集中度,行为漏斗定位流失环节,R/F 二值 RFM 划分客户。每组图都必须先固定分母、统计窗口和阈值,再解释业务含义。

📋 行动清单

  • 分别计算 Top10 品类和 Top10 单品,并说明百分比分母。
  • 计算点击到收藏、加购、支付,以及收藏/加购到支付的转化率。
  • 按 R、F 中位数构造 11/10/01/00 四组客户,并写出各组的一条运营动作。

—— 小象教研组