简介:这份Python源码示例面向希望用编程管理个人财务的初学者与数据分析爱好者,通过读取日常记账数据,完成消费分类汇总与可视化呈现,帮助使用者看清自己的消费方向。压缩包共3个文件,包含1个py主程序、1个xlsx记账数据表和1个txt使用说明,整体约20KB,体积轻巧,下载后即可对照说明运行调试。项目以Pandas完成数据清洗、聚合与分类,用matplotlib、seaborn绘制柱状图、饼图和趋势线,直观展示食品、交通、娱乐等类别的占比与时间变化;脚本还可自动读取表格、按规则归类并定期更新结果,减少手工统计。标签中涉及的爬虫与游戏数据思路,也可迁移到电商订单抓取或玩家付费行为分析等场景。目前已有191人学习,适合作为数据分析入门练手项目,在实操中熟悉数据处理流程与可视化技巧。
1. 记账三年还在用 Excel 拉透视表?这套 Python 源码把消费方向拆到骨头里
每个月月底打开记账 App 导出 CSV,然后丢进 Excel 拉透视表,看这个月餐饮花了多少、交通花了多少——这事我干了三年。直到某天想回答一个更实际的问题:过去一年我的消费结构到底在往哪个方向漂移?是外卖越点越多,还是订阅服务在悄悄吃掉预算?Excel 透视表给不了这个答案,因为它只会做静态汇总,不会做趋势归因。
这套「数据分析和图标-可视化分析日常记账数据总结个人消费方向-Python源码示例」要解决的正是这个问题:把记账 App 导出的流水明细,用 Python 做清洗、分类、聚合,再通过可视化把消费方向的变化趋势画出来。它适合两类人:一是已经有一年以上记账数据、想从「记了」升级到「看懂了」的普通用户;二是正在学 Python 数据分析与可视化实践、需要一个真实数据集练手的入门者。核心链路不复杂——pandas 做数据清洗和分组聚合,matplotlib 和 seaborn 出图,最后用一份可复用的脚本把整个流程固化下来。下面按实际落地顺序拆开讲。
2. 从记账 App 导出到 pandas DataFrame:数据清洗的四个关键动作
2.1 先搞清楚你的记账数据长什么样
不同记账 App 导出的 CSV 字段名差异很大,但核心信息就那么几列:交易时间、交易类型(支出/收入/转账)、金额、分类、备注。常见做法是先把 CSV 读进来,用df.head()和df.info()看一眼实际结构,再决定怎么处理。我一般会先做一次字段映射,把不同 App 的列名统一成标准字段,这样后续脚本换数据源时不用大改。
import pandas as pd # 读取记账 App 导出的 CSV,注意编码问题 df = pd.read_csv("bill_export.csv", encoding="utf-8-sig") # 先看前 5 行和字段类型 print(df.head()) print(df.info()) # 字段映射:把不同 App 的列名统一 column_map = { "交易时间": "trade_time", "交易类型": "trade_type", "金额(元)": "amount", "分类": "category", "备注": "note" } df = df.rename(columns=column_map) # 只保留支出记录,转账和收入单独处理 df_expense = df[df["trade_type"] == "支出"].copy() print(f"支出记录数:{len(df_expense)}")这段代码的逻辑很直接:先原样读入,确认字段名和数据类型,再做重命名。encoding="utf-8-sig"是为了处理 Excel 导出 CSV 时带的 BOM 头,不加这个参数第一列列名会多一个不可见字符,后面按列名取值直接报 KeyError。trade_type == "支出"这个过滤条件要根据你实际 App 的取值来改,有的 App 写的是「付款」或「消费」。
2.2 金额列和日期列的清洗不能省
金额列最常见的坑是带货币符号或千分位逗号,比如「¥1,234.50」。pandas 读进来会当成 object 类型,不做处理后面没法求和。日期列同理,有的导出格式是「2024/1/5 14:30」,有的是「2024-01-05」,统一转成 datetime 类型才能按月分组。
# 清洗金额列:去掉货币符号和千分位逗号 df_expense["amount"] = ( df_expense["amount"] .astype(str) .str.replace("¥", "", regex=False) .str.replace(",", "", regex=False) .astype(float) ) # 清洗日期列:统一转成 datetime df_expense["trade_time"] = pd.to_datetime( df_expense["trade_time"], format="mixed" ) # 派生月份列和星期列,后面分组要用 df_expense["month"] = df_expense["trade_time"].dt.to_period("M") df_expense["weekday"] = df_expense["trade_time"].dt.dayofweek # 检查清洗结果 print(df_expense[["trade_time", "amount", "month"]].describe()) print(df_expense["amount"].isna().sum(), "条金额缺失")format="mixed"是 pandas 2.0 之后才支持的参数,能自动推断每条记录的日期格式。如果你用的 pandas 版本较老,需要手动指定 format 或者用pd.to_datetime(df["trade_time"])让它自己猜。dt.to_period("M")生成的是 Period 类型,按月分组聚合时比直接用字符串截取更可靠,不会出现「2024-1」和「2024-01」被当成两个不同月份的情况。
2.3 分类字段的二次归并
记账 App 自带的分类往往太细,比如「餐饮」下面分了「早餐」「午餐」「晚餐」「夜宵」「零食」「饮料」,做消费方向分析时反而看不清结构。我一般会建一个映射表,把细分类归并成 6 到 8 个大类,比如「餐饮」「交通」「居住」「购物」「娱乐」「医疗」「订阅服务」「其他」。这个映射表建议单独存一个 CSV,方便随时调整。
# 分类归并映射表 category_group = { "早餐": "餐饮", "午餐": "餐饮", "晚餐": "餐饮", "夜宵": "餐饮", "零食": "餐饮", "饮料": "餐饮", "地铁": "交通", "公交": "交通", "打车": "交通", "房租": "居住", "水电": "居住", "物业": "居住", "衣服": "购物", "日用品": "购物", "数码": "购物", "电影": "娱乐", "游戏": "娱乐", "旅行": "娱乐", "会员": "订阅服务", "订阅": "订阅服务", "云盘": "订阅服务", } df_expense["category_group"] = ( df_expense["category"] .map(category_group) .fillna("其他") ) # 看一下归并后的分布 print(df_expense.groupby("category_group")["amount"].agg(["sum", "count"])).map()对没有匹配到的分类会返回 NaN,用.fillna("其他")兜底。这里有个细节:如果你的记账数据里分类名称有前后空格,.map()会匹配不上,建议先做一次df["category"] = df["category"].str.strip()。归并后的分布用groupby加agg一次看完金额合计和笔数,能快速判断哪些类别值得深入分析。
3. 消费方向的可视化:用 matplotlib 和 seaborn 画出趋势和结构
3.1 月度消费趋势图:折线图加滚动均值
最基础的一张图是按月汇总的总支出折线图。但直接画原始月度数据往往波动很大,看不出趋势,我一般会叠加一条 3 个月滚动均值线,把短期波动压下去,让方向性变化浮出来。
import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams["font.sans-serif"] = ["SimHei"] matplotlib.rcParams["axes.unicode_minus"] = False # 按月汇总总支出 monthly = df_expense.groupby("month")["amount"].sum().reset_index() monthly["month_str"] = monthly["month"].astype(str) monthly["rolling_3m"] = monthly["amount"].rolling(3).mean() fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(monthly["month_str"], monthly["amount"], marker="o", label="月度支出", alpha=0.6) ax.plot(monthly["month_str"], monthly["rolling_3m"], linewidth=2.5, label="3月滚动均值", color="red") ax.set_xlabel("月份") ax.set_ylabel("支出金额(元)") ax.set_title("月度消费趋势") ax.legend() plt.xticks(rotation=45) plt.tight_layout() plt.savefig("monthly_trend.png", dpi=150) plt.show()rolling(3).mean()是滚动窗口计算,窗口大小设 3 是因为记账数据按月看,3 个月刚好覆盖一个季度,既能平滑波动又不至于太滞后。matplotlib.rcParams["font.sans-serif"] = ["SimHei"]是解决中文显示方块的标配,Windows 上用 SimHei,macOS 上换成 "Arial Unicode MS",Linux 上如果没有中文字体需要额外安装。axes.unicode_minus = False是防止负号显示成方块,虽然消费数据一般没有负数,但加上不亏。
3.2 消费结构堆叠图:看各大类占比怎么变
光看总支出不够,还得看结构。堆叠面积图或者堆叠柱状图能把各大类每月的金额叠在一起,一眼看出哪个类别在膨胀、哪个在收缩。
# 按月和分类汇总 pivot = ( df_expense .groupby(["month", "category_group"])["amount"] .sum() .unstack(fill_value=0) ) pivot.index = pivot.index.astype(str) # 堆叠面积图 fig, ax = plt.subplots(figsize=(12, 6)) pivot.plot.area(ax=ax, stacked=True, alpha=0.8, colormap="tab20") ax.set_xlabel("月份") ax.set_ylabel("支出金额(元)") ax.set_title("消费结构月度变化") ax.legend(loc="upper left", bbox_to_anchor=(1, 1), fontsize=9) plt.tight_layout() plt.savefig("category_structure.png", dpi=150) plt.show().unstack(fill_value=0)把长格式转成宽格式,行是月份、列是分类,缺失的组合填 0。plot.area(stacked=True)直接出堆叠面积图,colormap="tab20"在分类数不超过 20 时颜色区分度够用。如果分类超过 10 个,建议只保留金额前 8 的类别,剩下的归入「其他」,否则图例会挤成一团。
3.3 分类占比环形图加同比标注
单月或者单年的分类占比用环形图展示比较直观,但纯环形图只能看静态结构。我一般会在环形图旁边加一个同比变化标注,比如「餐饮占比 32%,较上季度 +4%」,这样一眼能看出消费方向在往哪偏。
# 取最近一个完整月份的数据 latest_month = df_expense["month"].max() current = df_expense[df_expense["month"] == latest_month] cat_sum = current.groupby("category_group")["amount"].sum().sort_values(ascending=False) fig, ax = plt.subplots(figsize=(8, 8)) wedges, texts, autotexts = ax.pie( cat_sum.values, labels=cat_sum.index, autopct="%1.1f%%", startangle=90, pctdistance=0.85, wedgeprops=dict(width=0.4, edgecolor="w") ) ax.set_title(f"{latest_month} 消费结构") plt.tight_layout() plt.savefig("category_pie.png", dpi=150) plt.show()wedgeprops=dict(width=0.4)是画环形图的关键参数,width 控制环的宽度,设 0.4 左右视觉上比较舒服。pctdistance=0.85控制百分比文字离圆心的距离,太近会叠在环上。startangle=90让第一个扇区从 12 点方向开始,符合阅读习惯。
4. 避坑与排查:记账数据分析里最容易翻车的五个地方
4.1 金额求和结果对不上,差了几块钱
现象:Python 算出来的月度总支出和记账 App 里显示的对不上,差额不大但就是不一致。
原因:最常见的是退款记录没处理。很多 App 把退款记成一笔负数的支出或者单独的收入记录,如果只过滤trade_type == "支出",退款那笔可能被漏掉或者被当成正常支出加了进去。另一个原因是金额列有隐藏的空格或不可见字符,astype(float)之前没清干净。
解决:先检查退款记录的trade_type取值,如果是「退款」就单独拿出来从总支出里减掉。金额列清洗时加一步.str.strip(),再用pd.to_numeric(errors="coerce")代替astype(float),把无法转换的值变成 NaN 而不是直接报错,然后检查 NaN 的数量和来源。
4.2 中文标签显示成方块
现象:图表里所有中文都变成一个个小方块,英文和数字正常。
原因:matplotlib 默认字体不含中文字形,需要手动指定一个系统中存在的中文字体。
解决:Windows 上用SimHei或Microsoft YaHei,macOS 上用Arial Unicode MS或PingFang SC,Linux 上先fc-list :lang=zh看有哪些中文字体可用,然后设成对应的字体名。如果设了还是方块,清一下 matplotlib 的字体缓存:rm -rf ~/.cache/matplotlib。
4.3 月份排序乱了,10 月排在 2 月前面
现象:折线图的 x 轴月份顺序不对,「2024-10」出现在「2024-2」前面。
原因:月份列是字符串类型时,pandas 按字典序排列,「1」开头的排在「2」前面,导致 10 月、11 月、12 月的位置错乱。
解决:用dt.to_period("M")生成 Period 类型再转字符串,Period 类型本身有正确的时序。或者在绘图前把月份列转成pd.Categorical并指定有序类别。最稳妥的做法是排序时用原始的 datetime 列,绘图时再用格式化后的字符串做标签。
4.4 分类归并后「其他」占比超过 40%
现象:归并后的分类分布里,「其他」这一类金额占比特别高,说明大量记录没匹配上映射表。
原因:映射表的 key 和实际数据里的分类名称不完全一致,可能是多了空格、大小写不同、或者 App 更新后改了分类名称。
解决:先df[df["category_group"] == "其他"]["category"].value_counts()看哪些分类落入了「其他」,然后把这些分类名补进映射表。建议映射表用 CSV 维护而不是硬编码在脚本里,改起来不用动代码。
4.5 数据量大了之后脚本跑得慢
现象:一年以上的记账数据,几千条记录时脚本秒出结果,几万条时明显变慢。
原因:主要慢在groupby和apply上,尤其是用apply做逐行处理时,pandas 的循环开销很大。
解决:能用向量化操作就不用apply,比如金额清洗用.str.replace()链式调用而不是apply(lambda x: ...)。分组聚合时尽量用内置的sum、mean等聚合函数,避免自定义函数。如果数据量真的很大,考虑用pd.read_csv时指定dtype和usecols,减少内存占用和读取时间。
5. 把分析脚本变成可复用的月度报告工具
5.1 用配置文件管理分类映射和图表参数
脚本写死分类映射和图表尺寸,下个月换个数据源就得改代码。我一般会把映射表和图表参数抽到一个config.yaml或者config.json里,脚本启动时读进来。这样调整分类归并规则或者换配色方案时不用碰 Python 代码。
import json with open("config.json", "r", encoding="utf-8") as f: config = json.load(f) category_group = config["category_group"] chart_config = config["chart"] # 用配置里的参数控制图表 figsize = tuple(chart_config["figsize"]) dpi = chart_config["dpi"]配置文件里放三块内容:分类映射表、图表尺寸和 DPI、输出目录路径。这样同一套脚本可以跑不同人的记账数据,只需要换配置文件和 CSV 就行。
5.2 自动生成月度报告:把关键指标算出来
除了出图,我还会在脚本最后算几个关键指标,输出到一个文本文件或者直接打印出来。这些指标比图更直接地回答「消费方向有没有变化」这个问题。
| 指标 | 计算方式 | 看什么 |
|---|---|---|
| 月度总支出 | 当月 amount 求和 | 绝对值是否异常 |
| 环比变化率 | (本月-上月)/上月 | 短期波动 |
| 3月滚动均值 | rolling(3).mean() | 趋势方向 |
| 最大分类占比 | 分类求和后取最大 | 结构集中度 |
| 订阅服务占比 | 订阅类金额/总支出 | 固定支出压力 |
| 餐饮外卖占比 | 餐饮类金额/总支出 | 弹性支出变化 |
这张表里的指标不需要全算,选三四个你真正关心的就行。我一般会重点看「3月滚动均值」和「订阅服务占比」,前者告诉你消费在涨还是在降,后者告诉你固定支出有没有在悄悄膨胀。
5.3 一个我踩过的坑:别在脚本里直接覆盖原始数据
早期我图省事,清洗完直接df.to_csv("bill_export.csv")覆盖了原始文件,结果某次分类映射改错了,想回滚都回不去。后来改成清洗后的数据写到cleaned_bill.csv,原始文件永远不动。这个习惯看起来多余,但翻车一次就知道后悔药没处买。
另外,脚本里所有涉及金额的计算,建议在最后统一做一次round(2),避免浮点数精度问题导致输出一堆123.45000000000002这种数字。虽然不影响分析结论,但报告里看着难受。
5.4 验证分析结果是否可信的两个方法
第一个方法是交叉验证:用 pandas 算出来的月度总支出,和记账 App 里自带的月度统计对一下,差额在几块钱以内说明清洗逻辑没问题。第二个方法是抽样检查:随机抽 10 条记录,手动核对金额、分类、日期是否和原始数据一致。这两个方法花不了几分钟,但能避免在错误数据上做出一堆好看但没用的图。
这套脚本我从最早的一百多行堆到一个文件里,到现在拆成清洗、分析、可视化三个模块加一个配置文件,前后改了七八版。最大的体会是:记账数据分析的价值不在于图多好看,而在于你能不能坚持每个月跑一次,然后根据结果调整下个月的消费决策。工具只是工具,用起来才有意义。希望帮到你。
本文还有配套的精品资源,点击获取