📊 第9章 网站 App 平台注册用户分析
📄 一句话概括

按月看,3 月和 9 月是两个尖峰——但这是造数据时写进去的。 按渠道看,前三名拿走七成七,剩下两个不到四分之一。 「其他」排第一是个坑:它是八个小城市合起来的,不该和单项放一张榜上比。

📝 本章和前面几章的关系

这一篇把粒度变细:按月看季节、按渠道看结构、按城市看集中度。 最后给整个项目收个尾:这个项目真正教会的是什么。

一、按月:两个尖峰

按月份分组,十二个月的人数一目了然。 3 月 37 人、9 月 39 人最高,2 月只有 10 人。 3 月和 9 月是两个尖峰,和「开学季、年初换工作」的直觉对得上。

但要说清楚:季节性是造数据时写进去的,真数据里有没有,得算完才知道。

二、渠道:前三名拿走七成七

搜索引擎 87 人占 29.00%,社交媒体 85 人占 28.33%,广告投放 59 人占 19.67%。 前三名合起来 77.00%,自然访问和老用户推荐加起来不到四分之一。 这就是第 7 章的贡献度分析:少数几项撑起大头,长尾一堆但分量轻。

结论往哪用?搜索引擎和社交媒体是主战场,广告投放性价比要单独算账。

三、城市:「其他」排第一是个坑

按城市数,「其他」59 人排第一,占 19.67%。 但它不是一座城,是八个小城市合起来的。 名义上的第一名名不副实。这是汇总分组的常见坑:合并项不该和单项比。

去掉「其他」,前三城是杭州 42、北京 39、成都 38,合起来 39.67%。 「其他」不能扔,也不能直接比。要么拆开,要么在结论里单独说明它的构成。

四、小结:这个项目真正教会什么

第一,数据不在手边时,先解决「怎么取」,再谈分析。取数是半个项目。 第二,连数据库只有一行代码,难的是知道库在哪、表怎么设计、字符集对不对。 第三,同一份数据,SQL 和 pandas 都能算,选一条路走到底。

第四,年度看趋势、月度看季节、渠道看结构,三个角度拼起来才是完整结论。 第五,每个数都要能说清口径:哪张表、哪个时间段、算的是行数还是非空值。

五、动手 Demo:新注册用户分析与小结

按月、按渠道、按城市各 groupby 一次,看三种粒度下的结论。 城市那一节会暴露「其他」这个合并项的坑。

import numpy as np
import pandas as pd

chan = ["搜索引擎", "社交媒体", "广告投放", "自然访问", "老用户推荐"]
cw = [0.32, 0.26, 0.18, 0.15, 0.09]
city = ["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉", "其他"]
dw = [0.16, 0.14, 0.11, 0.12, 0.10, 0.09, 0.08, 0.20]

rng = np.random.default_rng(2026)
plan = [(2021, 30), (2022, 42), (2023, 58), (2024, 78), (2025, 92)]
w = np.array([0.5, 0.6, 1.5, 1.0, 0.9, 0.8, 0.9, 1.1, 1.4, 1.0, 0.9, 1.0])
w = w / w.sum()
rows = []
uid = 1
for year, n in plan:
    for m in rng.choice(np.arange(1, 13), size=n, p=w):
        day = int(rng.integers(1, 29))
        hh = int(rng.integers(0, 24))
        mm = int(rng.integers(0, 60))
        rows.append((uid, "用户%03d" % uid,
                     "%04d-%02d-%02d %02d:%02d:00" % (year, m, day, hh, mm),
                     str(rng.choice(chan, p=cw)), str(rng.choice(city, p=dw))))
        uid += 1
df = pd.DataFrame(rows, columns=["user_id", "user_name", "reg_time",
                                "channel", "city"])
df["month"] = df["reg_time"].str.slice(5, 7).astype(int)

mc = df.groupby("month").size()
print("最高 %d 月 %d 人,最低 %d 月 %d 人"
      % (mc.idxmax(), mc.max(), mc.idxmin(), mc.min()))

cc = df.groupby("channel").size().sort_values(ascending=False)
print("渠道前三合起来 %.2f%%" % (cc.iloc[:3].sum() / len(df) * 100))

ct = df.groupby("city").size().sort_values(ascending=False)
print("排第一的是 %s,%d 人" % (ct.index[0], ct.iloc[0]))
real = ct.drop("其他")
print("去掉其他,前三城 %s 合起来 %.2f%%"
      % ("、".join(real.index[:3]), real.iloc[:3].sum() / len(df) * 100))

实跑输出:

--------------------------------------------------------------------
新注册用户分析与小结
--------------------------------------------------------------------

  ① 按月看:哪几个月人多
  --------------------------------------------------------------------

| 月份 | 注册人数 |
|---:|---:|
| 1 月 | 12 |
| 2 月 | 10 |
| 3 月 | 37 |
| 4 月 | 29 |
| 5 月 | 25 |
| 6 月 | 23 |
| 7 月 | 23 |
| 8 月 | 19 |
| 9 月 | 39 |
| 10 月 | 31 |
| 11 月 | 20 |
| 12 月 | 32 |

  最高 9 月 39 人,最低 2 月 10 人。
  3 月和 9 月是两个尖峰,和「开学季、年初换工作」的直觉对得上。
  ! 季节性是造数据时写进去的。真数据里有没有,得算完才知道,不能先认定。

  ② 渠道:新用户从哪来
  --------------------------------------------------------------------

| 渠道 | 人数 | 占比 |
|---|---:|---:|
| 搜索引擎 | 87 | 29.00% |
| 社交媒体 | 85 | 28.33% |
| 广告投放 | 59 | 19.67% |
| 自然访问 | 42 | 14.00% |
| 老用户推荐 | 27 | 9.00% |

  第一名 搜索引擎 占 29.00%,前三名合起来 77.00%。
  五个渠道,前三名拿走七成七,剩下两个不到四分之一。
  这就是第 7 章的贡献度分析:少数几项撑起大头,长尾一堆但分量轻。

  ③ 城市:集中在哪
  --------------------------------------------------------------------

| 城市 | 人数 | 占比 |
|---|---:|---:|
| 其他 | 59 | 19.67% |
| 杭州 | 42 | 14.00% |
| 北京 | 39 | 13.00% |
| 成都 | 38 | 12.67% |
| 上海 | 37 | 12.33% |
| 广州 | 32 | 10.67% |
| 深圳 | 32 | 10.67% |
| 武汉 | 21 | 7.00% |

  「其他」排第一,占 19.67%,但它不是一座城,是八个小城市合起来的。
  名义上的第一名名不副实。这是汇总分组的常见坑:合并项不该和单项放在一张榜上比。
  去掉「其他」,前三城是 杭州、北京、成都,合起来 39.67%。
  ! 「其他」不能扔,也不能直接比。要么拆开,要么在任何结论里单独说明它的构成。

  ④ 小结:这个项目教会什么
  --------------------------------------------------------------------
  第一,数据不在手边时,先解决「怎么取」,再谈分析。取数是半个项目。
  第二,连数据库只有一行代码,难的是知道库在哪、表怎么设计、字符集对不对。
  第三,同一份数据,SQL 和 pandas 都能算,选一条路走到底。
  第四,年度看趋势、月度看季节、渠道看结构,三个角度拼起来才是完整结论。
  ! 下一章换到电商销售数据,不再连库,改从 Excel 读——那是另一套麻烦。

sort_values(ascending=False) 之后 iloc[:3] 才是前三名,不排序拿的是前三行。 ct.drop("其他") 这一句是本节的重点:把合并项摘掉,排行榜才说得清。 join(real.index[:3]) 把三个城市名用顿号连起来,比逐个 %s 干净。

30 秒实验

  1. 把「其他」的权重从 0.20 降到 0.05,重跑,看城市榜怎么变。
  2. 把月份权重改成平均,看还有没有尖峰。
  3. 想一想:如果老板问「哪个渠道最值得加预算」,光有这五个数够不够?

常见误区

  1. 把「其他」当成一座城:它是合并项,和单项比没有意义。
  2. 不排序就取前三:iloc[:3] 取的是前三行,不是前三名。
  3. 看到季节尖峰就下结论:先确认数据里真有这个机制,再解释原因。
  4. 只报一个角度:年度、月度、渠道、城市各说一句,才叫完整的项目结论。
自测三问(点开对答案) 1. 为什么「其他」排第一不能当结论? —— 它是八个小城市合并出来的汇总项,和单个城市不在一个量级上比。 2. 渠道分析该往哪用? —— 看主战场在哪、长尾占多少,再加成本数据才能谈「值不值得投」。 3. 一个完整的项目结论至少要有几个角度? —— 这次是四个:年度趋势、月度季节、渠道结构、城市集中度。少一个都片面。

第 9 章收尾。第 10 章换到电商销售数据:不再连库,改从 Excel 读,还要做预测。

相关笔记