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

子图制作(一)

流量指标可视化:从日趋势到小时规律

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

在上一节完成数据准备后,我们得到了结构清晰的电商行为数据集。本节从“用户”这一分析维度出发,把点击记录聚合成 PV(页面浏览量)和 UV(独立访客数),再用折线图观察日变化与小时规律,并处理两者量级不同的问题。

💡 核心导读

  • 理解指标口径:PV 统计点击次数,UV 按用户去重统计,两者不能混为同一个指标。
  • 完成时间聚合:使用 pandas 分别按日期和小时计算 PV、UV。
  • 实现双 Y 轴:使用 extend_axis 和 yaxis_index 让不同量级的系列共享横轴、独立使用纵轴。
  • 从图到业务结论:识别活动峰值和晚间活跃时段,并说明图表结论必须回到数据口径验证。

一、PV 与 UV:先定义口径,再画图

  • PV(Page View):一次页面访问或一次点击记一条,适合衡量整体交互量和活动热度。
  • UV(Unique Visitor):统计周期内按 user_id 去重后的用户数,适合衡量实际触达人数。

同一个用户的多次点击会累加 PV,但在 UV 中只计一次。因此,“PV 增长”不一定等于“来了更多新用户”,也可能是老用户访问得更频繁。后续图表要同时保留这两类信息。

二、用确定性小样本完成日、小时聚合

下面的数据围绕 12 月 10 日至 16 日构造,其中 12 日点击最多,用于演示活动峰值。每一天使用 4 个稳定用户,因此日 UV 恒为 4;点击统一分布在 18–21 点,用来展示晚间活跃规律。

import pandas as pd

daily_specs = [
    ("2024-12-10", 11),
    ("2024-12-11", 14),
    ("2024-12-12", 65),
    ("2024-12-13", 18),
    ("2024-12-14", 15),
    ("2024-12-15", 13),
    ("2024-12-16", 12),
]

event_rows = []
for date_text, pv_count in daily_specs:
    for offset in range(pv_count):
        event_rows.append(
            {
                "date": pd.Timestamp(date_text),
                "hour": 18 + (offset // 4) % 4,
                "user_id": 1000 + offset % 4,
            }
        )

click_df = pd.DataFrame(event_rows)
daily_flow = (
    click_df.groupby("date")
    .agg(pv=("user_id", "size"), uv=("user_id", "nunique"))
    .reset_index()
)
daily_flow["date"] = daily_flow["date"].dt.strftime("%m-%d")

hourly_flow = (
    click_df.groupby("hour")
    .agg(pv=("user_id", "size"), uv=("user_id", "nunique"))
    .reset_index()
    .sort_values("hour")
)

print("日流量聚合(示例):")
print(daily_flow.to_string(index=False))
print("\n小时流量聚合(示例):")
print(hourly_flow.to_string(index=False))
日流量聚合(示例):
 date  pv  uv
12-10  11   4
12-11  14   4
12-12  65   4
12-13  18   4
12-14  15   4
12-15  13   4
12-16  12   4

小时流量聚合(示例):
 hour  pv  uv
   18  43   4
   19  40   4
   20  39   4
   21  26   4

解读:12 月 12 日的 PV 明显高于其他日期,对应活动峰值;小时统计中 18 点最高,随后平稳下降。这里的小样本只验证聚合方法,真实项目仍要使用清洗后的完整数据集,不能把示例数值当成业务结论。

三、用双 Y 轴展示日 PV 与 UV

日 PV 在十级到几十级,UV 只有 4。如果两者共用一根纵轴,UV 会贴近底部。双 Y 轴保留同一时间横轴,同时为两个系列设置独立刻度,便于比较趋势是否同步。

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

CurrentConfig.ONLINE_HOST = "assets/"

dates = ["12-10", "12-11", "12-12", "12-13", "12-14", "12-15", "12-16"]
pv_values = [11, 14, 65, 18, 15, 13, 12]
uv_values = [4, 4, 4, 4, 4, 4, 4]

daily_chart = (
    Line(init_opts=opts.InitOpts(width="800px", height="420px"))
    .add_xaxis(dates)
    .add_yaxis(
        "日PV",
        pv_values,
        is_symbol_show=True,
        label_opts=opts.LabelOpts(is_show=False),
    )
    .extend_axis(
        yaxis=opts.AxisOpts(
            name="日UV",
            type_="value",
            min_=0,
            max_=6,
            position="right",
        )
    )
    .add_yaxis(
        "日UV",
        uv_values,
        yaxis_index=1,
        is_symbol_show=True,
        label_opts=opts.LabelOpts(is_show=False),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="日流量趋势"),
        tooltip_opts=opts.TooltipOpts(trigger="axis"),
        xaxis_opts=opts.AxisOpts(name="日期"),
        yaxis_opts=opts.AxisOpts(name="日PV", type_="value", min_=0, max_=70),
        legend_opts=opts.LegendOpts(pos_top="8%"),
    )
)

daily_chart.render("daily_traffic.html")
print("日趋势图:2 条系列,7 个日期")
日趋势图:2 条系列,7 个日期
按天汇总的流量趋势折线图,横轴为日期
图:日流量趋势:按天汇总的访问量折线,用于观察整体走势与异常日。手机端可左右滑动查看,点击图片可看全图。

配置关键点:

  • extend_axis(...) 创建额外纵轴,位置设为右侧。
  • yaxis_index=1 让“日 UV”映射到扩展轴;默认主轴索引是 0。
  • 两条系列共用一个 X 轴,因此能对齐同一天的活动峰值与用户规模。

四、小时流量图与解读边界

小时图沿用相同结构。这里 18 点 PV 最高,随后逐步下降;由于每个时段都只有 4 个用户,UV 曲线保持水平。它能说明“访问次数在晚间最集中”,但不能说明“晚间独立用户也更多”。

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

CurrentConfig.ONLINE_HOST = "assets/"

hours = ["18:00", "19:00", "20:00", "21:00"]
pv_values = [40, 38, 36, 34]
uv_values = [4, 4, 4, 4]

hourly_chart = (
    Line(init_opts=opts.InitOpts(width="800px", height="420px"))
    .add_xaxis(hours)
    .add_yaxis(
        "小时PV",
        pv_values,
        is_smooth=True,
        label_opts=opts.LabelOpts(is_show=False),
    )
    .extend_axis(
        yaxis=opts.AxisOpts(
            name="小时UV",
            type_="value",
            min_=0,
            max_=6,
            position="right",
        )
    )
    .add_yaxis(
        "小时UV",
        uv_values,
        yaxis_index=1,
        is_smooth=True,
        label_opts=opts.LabelOpts(is_show=False),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="小时流量趋势"),
        tooltip_opts=opts.TooltipOpts(trigger="axis"),
        xaxis_opts=opts.AxisOpts(name="小时"),
        yaxis_opts=opts.AxisOpts(name="小时PV", type_="value", min_=0, max_=45),
        legend_opts=opts.LegendOpts(pos_top="8%"),
    )
)

hourly_chart.render("hourly_traffic.html")
print("小时趋势图:2 条系列,4 个小时")
小时趋势图:2 条系列,4 个小时
按小时聚合的流量趋势折线图,横轴为0到23时
图:小时流量趋势:按小时聚合后能看出一天中的访问高峰时段。手机端可左右滑动查看,点击图片可看全图。

在真实数据中,应先用 groupby('date')、groupby('hour') 聚合,再设置合适的轴范围。若两个指标量级相差很大且业务不要求逐时同步比较,也可以拆成两张单轴图,避免双轴刻度造成误读。

📝 动手练一练

  1. 改口径:把 daily_flow 中的 UV 改成“点击次数不少于 2 次的用户数”,应如何调整?

    点击查看参考答案

    可先按日期和用户统计点击次数,再筛选次数不少于 2 的用户:

    per_user = click_df.groupby(["date", "user_id"]).size().reset_index(name="clicks")
    repeat_uv = (
        per_user[per_user["clicks"] >= 2]
        .groupby("date")["user_id"]
        .nunique()
        .reset_index(name="repeat_uv")
    )

    这会得到“重复访问用户数”,与标准 UV 口径不同,展示时必须在标题或指标卡中明确命名。

  2. 避免误读:若营销活动当天 PV 翻倍但 UV 不增长,你会怎样向业务方解释?

    点击查看参考答案

    说明增长主要来自现有用户访问得更频繁,而不是触达人数扩大。下一步应继续拆分新老用户、渠道来源、停留时长和转化率,不能只凭 PV 峰值判定活动带来了更多新用户。

本章小结

本节先统一 PV、UV 口径,再用确定性事件数据完成日期和小时聚合,最后用双 Y 轴折线图比较不同量级的趋势。图表负责暴露模式,数据口径负责保证结论成立;两者缺一不可。

📋 行动清单

  • 用 groupby 独立计算日 PV、日 UV、小时 PV、小时 UV。
  • 用 extend_axis 与 yaxis_index=1 完成一张双 Y 轴折线图。
  • 写出一条图表结论,并补充一条“这个结论不能证明什么”的边界说明。

—— 小象教研组