综合项目一:分析一年的记账本
3 道练习题·预计 60 分钟·做对一题解锁下一段
前言
八章学下来,读进来、看清楚、挑出来、洗干净、算一算、拼起来、画出来,做数据分析的零件都已经齐了。零件齐了,能拼出什么来呢?这一章不学新东西,就用这些零件做一个完整的分析,回答一个问题:2026 年这一年的钱,花得怎么样?
还记得第 1 章说的六个步骤吗?提问、获取、清洗、探索、分析、结论。前面每一章,练的都只是其中的一两步;这一章,咱们要从头走到尾。
具体怎么走呢?和爬虫课的综合项目一样,节奏是这样的:先把问题拆清楚,再一个问题一个问题地算;每算出一个数,都检查一下对不对;最后,把结论写成一份别人看得懂的报告。
学这一章之前,需要先学完数据分析课第 1~8 章,尤其是 第 6 章 的分组汇总和 第 8 章 的画图。
目录
这一章会做:
- 一、提问——把「花得怎么样」拆成五个具体的问题
- 二、获取和检查——写一个
load()函数 - 三、一共花了多少,花在哪儿
- 四、哪几个月花得多,为什么
- 五、吃饭和周末
- 六、写成一份报告
- 七、常见坑
- 八、自己查一次文档——用
pct_change()算比上个月多了多少 - 九、小结与回看
做完这一章,你会有一份从数据里一步步推出来、每个数都经得起追问的分析报告。
一、提问
「今年的钱花得怎么样?」这个问题,能直接拿数据来回答吗?其实不能,因为它太笼统了,不知道该算什么。第 1 章说过,碰到这种问题,要先把它变成一个个能用数据回答的具体问题。
那该怎么拆呢?可以拆成下面这五个:
| 问题 | 要算的数 | 用到的本事 |
|---|---|---|
| 1. 一年一共花了多少,平均每月多少? | 总额、月平均 | sum() |
| 2. 钱主要花在哪儿? | 各类别的金额和占比 | groupby() 、比例 |
| 3. 哪几个月花得多?多在哪儿? | 每月金额,每月各类别金额 | dt.month 、pivot_table() 、画图 |
| 4. 吃饭花了多少?三顿饭里哪顿最该省? | 三餐的笔数、合计、平均 | 筛选、agg() |
| 5. 周末是不是比工作日花得多? | 周末、工作日每天平均花多少 | dt.dayofweek 、分组 |
你看,拆完以后,每个问题要算什么数、用哪个方法,就都清楚了。
不过,拆问题的时候,还有两件事值得想一想:
- 这份数据能回答吗? 比如「钱花得值不值」,账本里没有收入,也不知道每样东西买得开不开心,就回答不了。只有能答的,才放进清单。
- 答出来有什么用? 比如问题 4、5,都指向同一件事:要是想省钱,该从哪儿下手。所以问题不是越多越好,每一个都要能帮你做决定。
问题拆好了,接下来就照着这张表,一个一个地算。不过在算之前,得先把数据读进来,看看它靠不靠得住。
二、获取和检查
五个问题,每一个都要先读进记账本,再把日期变成真正的日期。同样的几行代码,与其每次都写一遍,不如把它们写成一个函数:
import pandas as pd
def load(path):
"""读进记账本,加上月份、是否周末两列"""
ledger = pd.read_csv(path)
ledger['date'] = pd.to_datetime(ledger['date'])
ledger['month'] = ledger['date'].dt.month
ledger['weekend'] = ledger['date'].dt.dayofweek >= 5
return ledger
ledger = load('ledger_2026.csv')
print(ledger.head(3))输出的结果:
date item category cost payment month weekend
0 2026-01-01 房租 居住 2500.0 银行卡 1 False
1 2026-01-01 水电燃气 居住 156.0 支付宝 1 False
2 2026-01-01 早饭 餐饮 13.0 支付宝 1 False函数里除了读文件、转日期,还多加了两列,都是给后面的问题准备的:
month是月份,问题 3 要用。weekend用的是第 5 章学的dt.dayofweek:5 是星期六,6 是星期日,大于等于 5 就是周末。比较的结果,是一串True和False。问题 5 要用到它。
数据读进来了,可先别急着算。第 5 章说过,拿到数据的第一件事,是给它做个体检:
import pandas as pd
def load(path):
"""读进记账本,加上月份、是否周末两列"""
ledger = pd.read_csv(path)
ledger['date'] = pd.to_datetime(ledger['date'])
ledger['month'] = ledger['date'].dt.month
ledger['weekend'] = ledger['date'].dt.dayofweek >= 5
return ledger
ledger = load('ledger_2026.csv')
print(len(ledger), ledger.isna().sum().sum(), ledger.duplicated().sum())
print(ledger['date'].min(), ledger['date'].max(), ledger['date'].nunique())
print(ledger['cost'].min(), ledger['cost'].max())输出的结果:
1354 0 0
2026-01-01 00:00:00 2026-12-31 00:00:00 365
3.0 2500.0三行输出,咱们一行一行地看:
- 第一行:一共 1354 笔,没有空值,也没有重复的行。这里的
isna().sum()算出的是每一列的空值个数,再sum()一次,就是整张表一共有几个空值。 - 第二行:日期从 1 月 1 日到 12 月 31 日,一共 365 个不同的日子。也就是说,每天都记了账,没有漏掉的日子。
- 第三行:金额最少 3 元,最多 2500 元,那是房租。没有负数,也没有离谱的值。
体检下来,这份账本是干净的,可以放心地分析了。要是体检发现了问题,就该先用第 5 章的办法,把数据洗干净再说。
另外,把读数据写成 load() 函数,还有一个好处:明年有了 ledger_2027.csv ,只要换一个文件名,后面的分析一行都不用改。
数据准备好了,下一节就从最基本的两个问题算起。
三、一共花了多少,花在哪儿
先算问题 1 和问题 2:一年一共花了多少?钱主要花在哪儿?这两个问题都要用到全年的总额,所以放在一起算:
import pandas as pd
def load(path):
"""读进记账本,加上月份、是否周末两列"""
ledger = pd.read_csv(path)
ledger['date'] = pd.to_datetime(ledger['date'])
ledger['month'] = ledger['date'].dt.month
ledger['weekend'] = ledger['date'].dt.dayofweek >= 5
return ledger
ledger = load('ledger_2026.csv')
total = ledger['cost'].sum()
print(f'全年合计 {total:,.1f} 元,平均每月 {total / 12:,.1f} 元')
by_category = ledger.groupby('category')['cost'].sum().sort_values(ascending=False)
share = (by_category / total).round(3)
print(share)
print(share.sum())输出的结果:
全年合计 69,195.5 元,平均每月 5,766.3 元
category
居住 0.475
餐饮 0.341
购物 0.072
娱乐 0.046
交通 0.025
学习 0.024
医疗 0.017
Name: cost, dtype: float64
1.0代码里有两处值得说一说:
by_category / total:一个 Series 除以一个数,每个值都会除一遍,得到的就是各类别的占比。注意,它和第 4 章的value_counts(normalize=True)不一样:那个是按笔数算的比例,这里是按金额算的。- 最后打印
share.sum(),是顺手做个检查:占比加起来应该是 1。这回正好是 1.0,没有多也没有少。每算出一组数,都找一个办法验一验,这是这一章要养成的习惯。
数都验过了,可以下结论了:
- 一年花了将近 7 万元,平均每月五千七百多元。
- 居住占了将近一半,47.5%。房租、水电这些是固定开销,每个月都得交,短期内很难省下来。
- 吃饭占了三分之一,34.1%。剩下的购物、娱乐、交通、学习、医疗,加起来还不到两成。
这告诉我们:真想省钱,能动的主要就是吃饭和购物这两块。
知道了钱花在哪儿,下一节接着看钱是什么时候花的:哪几个月花得多?在那之前,先做一道练习,算一算吃饭和购物这两块一共占了多少。
推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai