Python 环境加载中…

把数据洗干净

3 道练习题·预计 50 分钟·做对一题解锁下一段

完整通读本章 →

教学 01 / 09· 已读

前言

上一章结尾说过,这一章要换一份数据。为什么要换呢?因为前面几章用的记账本,是一份整理好的数据:每个日期都是同一种写法,每个金额都是数字,也没有空着的地方。

可真实的数据,很少有这么规矩的。手机上随手记的账,有时金额后面多打了一个「元」,有时手一抖记了两遍,有时忘了选类别,有时又多按了几个 9。这些问题要是不解决,后面算出来的数,就全都不可信了。

把数据收拾干净的过程,叫数据清洗。它不像画图那样好看,却是分析里最花时间、也最见功夫的一步。在第 1 章说的六个步骤里,它排在第三。

这一章要用的,是 ledger_2026_raw.csv :11 月上半月的一份原始账单,一共 64 行。它在网页里已经准备好了,直接读就行。

学这一章之前,需要先学完 数据分析课第 4 章 。

目录

这一章会学到:

  • 一、先做个体检——问题都藏在哪儿
  • 二、空着的值——删掉还是补上
  • 三、重复的行——duplicated() 和 drop_duplicates()
  • 四、统一格式——去掉「元」,把文字变成数字和日期
  • 五、明显不对的值——异常值,以及一份完整的清洗代码
  • 六、常见坑
  • 七、自己查一次文档——to_datetime() 的 format='mixed'
  • 八、小结与回看

学完这一章,拿到一份乱糟糟的数据,你知道该从哪儿下手,一步一步把它收拾干净。

教学 02 / 09· 已读

一、先做个体检

拿到一份乱糟糟的数据,先别急着改。就像看病要先体检一样,得先弄清楚问题都藏在哪儿。拿什么来体检呢?第 2 章学的那几招,正好派上用场。先用 info() 看看整体的情况:

Python· 可在网页运行
import pandas as pd

raw = pd.read_csv('ledger_2026_raw.csv')
raw.info()

输出的结果:

文本
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 64 entries, 0 to 63
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype 
---  ------    --------------  ----- 
 0   date      64 non-null     object
 1   item      64 non-null     object
 2   category  61 non-null     object
 3   cost      62 non-null     object
 4   payment   64 non-null     object
dtypes: object(5)
memory usage: 1.3+ KB

一眼就能看出两个问题:

  • 一共 64 行,可 category 只有 61 个非空值,cost 只有 62 个,说明这两列都有空着的地方。
  • cost 的类型是 object ,不是数字。第 2 章说过,这说明金额这一列里,混进了不是数字的东西。

不过,info() 只告诉我们哪里有问题,却看不出问题具体长什么样。所以再用 head(8) 看看开头 8 行:

Python· 可在网页运行
import pandas as pd

raw = pd.read_csv('ledger_2026_raw.csv')
print(raw.head(8))

输出的结果:

文本
         date      item category    cost payment
0  2026-11-01      房租     居住    2500  银行卡
1  2026-11-01  水电燃气     居住   229.5  支付宝
2  2026-11-01      早饭     餐饮       8  支付宝
3  2026-11-01      午饭     餐饮  22.5元    微信
4  2026-11-01      晚饭     餐饮      27    微信
5  2026-11-02      早饭     餐饮     10   支付宝
6  2026-11-02      午饭     餐饮      29  支付宝
7  2026-11-02      晚饭      NaN      58    微信

仔细看,问题就出来了:

  • 行号为 3 的那一行,金额写成了 22.5元 。
  • 行号为 5 的那一行,10 有点歪,因为它前后多了空格。
  • 行号为 7 的那一行,类别是 NaN ,也就是空着。

光看开头 8 行,就找出了三个问题,后面还有多少呢?一个一个地找就太慢了。所以接下来,咱们一类一类地收拾,先从空着的值开始。

教学 03 / 09

二、空着的值

先来收拾空着的值。第一步,是弄清楚每一列各空了几个。isna() 能判断每个值是不是空的,得到一串 True 和 False 。和第 3 章一样,再 sum() 一下,就知道有几个空的:

Python· 可在网页运行
import pandas as pd

raw = pd.read_csv('ledger_2026_raw.csv')
print(raw.isna().sum())

输出的结果:

文本
date        0
item        0
category    3
cost        2
payment     0
dtype: int64

这里是对整张表用 isna() ,再 sum() ,所以每一列有几个空值,一下子全出来了:类别空了 3 个,金额空了 2 个。

光知道个数还不够,还得看看具体是哪几行。把它们挑出来:

Python· 可在网页运行
import pandas as pd

raw = pd.read_csv('ledger_2026_raw.csv')
print(raw[raw['cost'].isna()])
print(raw[raw['category'].isna()])

输出的结果:

文本
          date  item category cost payment
21  2026-11-06  早饭     餐饮  NaN    微信
52  2026-11-13  地铁     交通  NaN  支付宝
          date  item category cost payment
7   2026-11-02  晚饭      NaN   58    微信
31  2026-11-08  午饭      NaN   26  支付宝
55  2026-11-14  打车      NaN   29    微信

那这些空值该怎么处理呢?其实没有标准答案,要看缺的是什么:

  • 金额空了:比如那顿早饭,到底吃了多少钱?没法知道。瞎猜一个数,只会让结果更不准。所以这种就删掉。
  • 类别空了:晚饭、午饭肯定是餐饮,打车肯定是交通,这是能推断出来的。所以这种就补上。

先说删。删掉用 dropna() ,再用 subset 说明看哪几列:这几列有空的,才删。

Python· 可在网页运行
import pandas as pd

raw = pd.read_csv('ledger_2026_raw.csv')
has_cost = raw.dropna(subset=['cost'])
print(len(raw), len(has_cost))

输出的结果:

文本
64 62

64 行删掉了金额空着的 2 行,剩下 62 行。

再说补。补上用 fillna() 。它可以填同一个值,比如 fillna('未分类') ;也可以拿另一列来填,同一行的值对着填进去。这里先用 map() 按名称查出类别,再拿查出来的结果去填:

Python· 可在网页运行
import pandas as pd

raw = pd.read_csv('ledger_2026_raw.csv')
guess = raw['item'].map({'午饭': '餐饮', '晚饭': '餐饮', '打车': '交通'})
raw['category'] = raw['category'].fillna(guess)
print(raw.loc[[7, 31, 55]])
print(raw['category'].isna().sum())

输出的结果:

文本
          date  item category cost payment
7   2026-11-02  晚饭     餐饮   58    微信
31  2026-11-08  午饭     餐饮   26  支付宝
55  2026-11-14  打车     交通   29    微信
0

这段代码有四个地方要说一下:

  • map(字典) 会拿这一列的每个值去查字典:午饭查到餐饮,打车查到交通,查不到的就是 NaN 。
  • fillna(guess) 只填空着的地方,原来有类别的,它不会动。
  • raw['category'] = ... 是把结果放回去,替换掉原来那一列。
  • loc[[7, 31, 55]] 方括号里是一个行号的列表,能一次挑出这三行,方便看看补得对不对。

最后输出的 0 说明,三个空着的类别都补上了。

那要是实在推断不出来呢?先做一道练习:把空着的类别都填成「未分类」。

练习 1 / 3实在推断不出来,就写「未分类」题目有问题?

读进 ledger_2026_raw.csv ,把类别(category)空着的地方都填成「未分类」,放回原来那一列,然后打印每一列还有几个空值。

运行后的输出应该是:

文本
date        0
item        0
category    0
cost        2
payment     0
dtype: int64
main.py
可编辑
做对当前题解锁下一段 ·03

推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai

学习目录