📊 第1章 数据分析基础
📄 一句话概括

一份手工登记的 12 行流水,金额有三种写法、有重复行、有空值。 统一写法、去重、剔空三步走完只剩 8 行——原始表里有三分之一是白占地方的。 这四步(熟悉工具、明确目的、获取数据、处理数据)全在「算」之前,也全是最容易被跳过的地方。

📝 本章和前面几章的关系

教材把数据分析拆成八步。这一篇讲前四步,下一篇讲后四步。 前四步有个共同点:都不产出结论,但决定后面能不能得出结论。

一、第一步:熟悉工具

工具不用一次学全。手上这份活是「汇总一下地区销售额」,Excel 都够用。 但工具决定你能问什么问题:只能做透视表的人,问不出「按周重采样」这种问题。 所以熟悉工具的真实含义是:知道有哪些动作是可能的,用不用得上另说。

这一篇先不写代码,用最笨的办法把数据看清楚——先数它有多少行、多少种写法。

二、第二步:明确目的

目的得写成一句话,而且这一句要能被数据检验。 本例的目的:【各地区销售额各是多少,一共多少。】 这句话定死了两件事:按「地区」这个维度分组,算「销售额」这个指标的合计。

目的不写下来的后果很具体:看到「华东 1,200」这一行,你会不确定它是 1200 元还是 1200 件, 于是可能把它记成两笔。

三、第三步:获取数据

获取不是「把文件打开」,是「先数清楚里面有什么花样」。 这 12 行里有四种地区写法(四个城市片区),金额有三种形态:「1,200」「1200元」「1200」。 还有 1 行金额是空的。一共 12 行,只有 8 行是干净可用的。

先数清楚,再动手改。反过来做的代价是:改完了不知道改对没有,因为不知道原来有多少种。

四、第四步:处理数据

处理有三件事,顺序不能换:先统一写法,再去重,最后剔空。 统一写法:把「1,200」「1200元」「1200」都削成整数 1200,去掉逗号和「元」字。 去重:地区和金额都一样的两行,是同一笔记录被登记了两次。这一步去掉 3 行。

剔空:剩下 1 行金额是空的,无法确认就剔除,不要猜。

步骤 行数 少了什么
原始 12 —
统一写法后 12 只是变了写法,行数不变
去重后 9 3 行重复
剔空后 8 1 行空值

顺序为什么不能换?先去重的话,「1,200」和「1200元」会被当成两条不同的记录,重复去不掉。 先剔空也一样:空值那一行可能和另一行重复,剔完了才发现,就少了一条真实记录。

五、清洗完才算得出这三个数

清洗后的 8 行,按地区汇总,得到四个数:

地区 销售额(元) 占比
华南 3,280 40.9%
华东 2,060 25.7%
华北 2,030 25.3%
西南 640 8.0%

合计 8,010 元,平均每行 1,001 元。这两个数只有在清洗之后才算得对。 不清洗会怎样? 下一篇的 Demo 里有一组对照:同一份数据,只跳空值不去重,合计变成 11,390 元,多了 3,380 元。 误差不是来自算法,是来自数据。这句话在第 3 章还会再说一遍,届时用的是 Pandas。

六、动手 Demo:从一张脏表走到能算的数

12 行手工流水:金额有三种写法、有 3 行重复、有 1 行空值。 按「统一写法 → 去重 → 剔空」三步走,每步之后都打一次行数。

RAW = ["华东,1,200", "华北,980", "华南,1200元", "华东,860",
       "西南,", "华北,1,050", "华南,1320", "华东,1200元",
       "西南,640", "华北,980", "华南,760", "华东,1,200"]

def parse(s):
    s = s.strip().replace(",", "").replace("元", "")
    return int(s) if s else None

rows = [(r.split(",", 1)[0], parse(r.split(",", 1)[1])) for r in RAW]
print("统一写法后 %d 行" % len(rows))

seen, keep, drop = set(), [], 0
for a, v in rows:
    if (a, v) in seen:
        drop += 1
        continue
    seen.add((a, v))
    keep.append((a, v))
print("去重去掉 %d 行,还剩 %d 行" % (drop, len(keep)))

final = [(a, v) for a, v in keep if v is not None]
print("剔空后 %d 行" % len(final))

agg = {}
for a, v in final:
    agg[a] = agg.get(a, 0) + v
for a, v in sorted(agg.items(), key=lambda x: -x[1]):
    print("%s  %d 元  %.1f%%" % (a, v, v * 100.0 / sum(agg.values())))
print("合计 %d 元" % sum(agg.values()))

实跑输出:

--------------------------------------------------------------------
从一张脏表走到能算的数
--------------------------------------------------------------------

  ① 目的先写下来
  --------------------------------------------------------------------
  原始表是一份手工登记的销售流水,12 行,长这样:

| 行号 | 地区 | 销售额 |
|---:|---|---|
| 1 | 华东 | 1,200 |
| 2 | 华北 | 980 |
| 3 | 华南 | 1200元 |
| 4 | 华东 | 860 |
| 5 | 西南 | (空) |
| 6 | 华北 | 1,050 |
| 7 | 华南 | 1320 |
| 8 | 华东 | 1200元 |
| 9 | 西南 | 640 |
| 10 | 华北 | 980 |
| 11 | 华南 | 760 |
| 12 | 华东 | 1,200 |

  目的写成一句话:【各地区销售额各是多少,一共多少。】
  ! 目的不写下来,后面每一步都能跑偏——比如把「华东 1200」记成两笔。

  ② 获取:先看它有多少行、多少种写法
  --------------------------------------------------------------------
  读进来 12 行。地区写法有 4 种,金额写法有三种:「1,200」「1200元」「1200」。
  还有 1 行金额是空的。
  ! 获取这一步不是「把文件打开」,是「先数清楚里面有什么花样」。

  ③ 处理:三步,一步都不能省
  --------------------------------------------------------------------
  第一步 统一写法:把「1,200」「1200元」都削成整数 1200,这一行不算处理完。
  第二步 去重:地区和金额都一样的行只留一行,去掉 3 行。
  第三步 剔空:剩下的行里还有 1 行金额是空的,按「无法确认就剔除」处理。

| 步骤 | 行数 |
|---|---:|
| 原始 | 12 |
| 统一写法后 | 12 |
| 去重后 | 9 |
| 剔除空值后 | 8 |

  ! 三步做完才 8 行。原始 12 行里有 4 行是白占地方的。

  ④ 分析:这时候算平均才有意义
  --------------------------------------------------------------------

| 地区 | 销售额(元) | 占比 |
|---|---:|---:|
| 华南 | 3,280 | 40.9% |
| 华东 | 2,060 | 25.7% |
| 华北 | 2,030 | 25.3% |
| 西南 | 640 | 8.0% |

  四个地区合计 8,010 元,平均每行 1,001 元。
  ! 如果没统一写法、没去重、没剔空,这三个数一个都算不对。

  ⑤ 结果呈现:一句话加一张表
  --------------------------------------------------------------------
  给老板的话只有一句:【华南最高,占 40.9%;西南最低,只有 640 元。】
  底下附上面那张表,供他自己核。
  ! 呈现不是把过程全贴上去,是给他一个能立刻用的结论,再给他一条能核的路。

split(",", 1) 那个 1 是关键:不限次数的话,「华东,1,200」会被切成三段,金额只剩一个「1」。 去重用的是元组 (地区, 金额) 而不是整行字符串。 区别在第 3 章会讲:整行比较只能挡完全一样的,元组比较能挡「写法不同但内容一样」。

30 秒实验

  1. 如果先剔空再去重,最终行数会变吗?试着改一下顺序看看。
  2. 在这份数据里加一行「华东,1200」(没有逗号也没有「元」),去重还能去掉它吗?
  3. 想一想:为什么第 4 步「处理数据」不能放到第 6 步「验证结果」之后?

常见误区

  1. 先动手改,再想目的:不知道要算什么,就不知道哪些字段要保、哪些可以扔。
  2. 用整行字符串去重:写法不同的同一笔记录去不掉,这是最常见的漏。
  3. 空值随手填 0:0 是一个值,会让平均值偏低;「无法确认」应该剔除或单独标记。
  4. 改完不留原始副本:清洗是不可逆的,改错了没法回头。
自测三问(点开对答案) 1. 为什么「统一写法」必须放在「去重」前面? —— 因为写法不同会被当成不同记录。先统一,重复的才认得出是重复。 2. 12 行清洗完只剩 8 行,这是不是说明数据质量很差? —— 不全是。3 行重复是录入问题,1 行空值是登记漏了,都属于手工流水常见的毛病。 3. 如果不做清洗直接汇总,误差会偏向哪个方向? —— 偏高。重复的行会被重复计入,而空值那一行本来就没数,剔不剔都不影响合计。

数据洗干净了,接下来才是算。 下一篇走流程的后四步:分析、验证、呈现、应用,重点在第二步——怎么判断「这点差别不是碰巧」。

相关笔记