Python源码实战:pandas清洗账单+数据可视化分析个人消费方向
发布时间:2026/9/26 22:07:42来源:尧图网络
简介针对日常记账数据自动化分析与可视化需求这份Python源码示例适合想用编程管理个人消费的用户也适合数据分析入门者跟着动手实践。压缩包共3个文件包含Python脚本、Excel原始账本和程序使用说明整体仅20KB结构精简便于对照学习已有191人学习下载。示例围绕Pandas数据清洗与分类汇总、NumPy数值计算以及matplotlib/seaborn图表绘制展开演示如何从表格中提取消费类别、统计占比并生成柱状图或饼图同时引入自动化处理思路让记账分析不再依赖手工操作。通过运行脚本读者能掌握读取Excel、按食品/交通/娱乐等维度归类汇总、输出可视化结果的完整流程还可将方法迁移至电商订单、外卖记录等场景或结合网络爬虫自动抓取消费数据提升个人财务数据管理效率。1. 为什么一份Python源码就能把记账数据变成消费方向结论你的账单里塞满了“拼多多付款”“美团外卖”“滴滴出行”这类流水备注Excel里翻一年也看不出钱到底流向哪里。但如果你把这些日常记账数据交给pandas做一轮清理和聚合再用matplotlib画几张图消费方向其实可以在一屏之内讲清楚。标题里这份“数据分析和可视化分析日常记账数据总结个人消费方向”的Python源码示例zip做的就是这件事它把“记账数据→清洗→聚合→可视化分析→消费方向结论”这条链路串成可复用的代码你只需喂给它一张导出的账单CSV就能得到每个月钱花在哪些大类、占比如何变化。这篇文章按我实际跑这套方案的经验拆开来讲数据怎么整理、消费方向怎么算、图表怎么画以及最容易翻车的几个点。2. 记账数据整理从账单CSV到可分析的pandas表2.1 账单数据源与统一字段设计支付宝、微信、银行卡导出的账单表头完全不同。支付宝导出的是“交易时间、交易金额、交易分类、商品说明”微信导出的是“交易时间、交易类型、交易备注、商户单号”Excel手工记账更是千奇百怪。如果每个数据源都写一套读取逻辑这套Python源码示例就没法复用到下个月。所以第一步是先把所有账单统一成四个核心字段日期、金额、分类、备注。常见做法是我在源码包里放一个load_bill()函数专门处理CSV文件里最常见的干扰项BOM头、空行、表头偏移、编码问题。支付宝账单默认是UTF-8带BOM用Excel另存的CSV可能是GBK直接pd.read_csv会读出一堆乱码。我这里先按UTF-8-sig读取如果发现列名不对再回退到GBK。import pandas as pd def load_bill(path): # 支付宝/csv账单一般带BOM头Excel导出的可能是GBK编码 # 先尝试UTF-8-sig失败后用GBK读取 try: df pd.read_csv(path, encodingutf-8-sig) except UnicodeDecodeError: df pd.read_csv(path, encodinggbk) # 统一列名后续分析只依赖日期、金额、分类、备注四个字段 df.rename(columns{ 交易时间: 日期, 交易金额: 金额, 交易分类: 分类, 商品说明: 备注, 备注: 备注 }, inplaceTrue) # 只保留需要的列防止无关字段干扰内存 cols [c for c in [日期, 金额, 分类, 备注] if c in df.columns] df df[cols] df[日期] pd.to_datetime(df[日期], errorscoerce) return df这个函数的逻辑很直白先解决编码再解决列名映射最后把日期转成pandas的datetime类型。重点说一下errorscoerce遇到解析不了的时间字符串会变成NaT而不是直接抛异常后面清洗时可以统一丢弃。参数方面如果你用微信账单列名映射要换成“交易时间、交易金额、交易类型、交易备注”我在源码里是单独留了一个BILL_CONFIG字典而不是写死。2.2 清洗脏数据过滤退款、空值和不需要的交易类型很多人下载了支付宝账单直接跑聚合发现总支出比实际多出一大截。原因通常是账单里有“退款”和“资金互转”“信用卡还款”这类根本不算消费的记录。退款是负金额但它对应的原交易已经在支出里记过如果不处理相当于同一笔钱扣了两次。资金互转则是把钱从余额转到银行卡并不是消费。我习惯把它和金额清洗放在同一个函数里这样源码示例从load_bill()拿到原始数据后只需再调clean_bill()就得到干净表。def clean_bill(df): # 丢弃日期或金额为空的数据 df df.dropna(subset[日期, 金额]) # 退款记录备注中出现“退款/交易关闭”时金额保持为负但单独标记 refund_mask df[备注].astype(str).str.contains(退款|交易关闭, naFalse) df[is_refund] refund_mask # 过滤掉不算消费的类别这些项目会使消费方向分析偏掉 exclude_cats [资金互转, 信用卡还款, 余额宝, 理财] df df[~df[分类].isin(exclude_cats)] # 金额列可能是字符串比如“¥12.50”或“1,200.00” # 先去掉人民币符号和千分位逗号再转float df[金额] (df[金额].astype(str) .str.replace(¥, , regexFalse) .str.replace( , , regexFalse) .str.replace(,, , regexTrue) .astype(float)) return df这里有一个处理退款的关键点我没有直接删掉退款记录而是加上is_refund标记。因为在月度汇总时退款应该从当月支出里减掉而不是当做一个负支出直接累加。更稳妥的做法是先把退款按原交易去重再在月度总额中扣减。虽然百分比小但金额大时会让最后结论偏差几十上百元。金额清洗时我用了regexTrue去逗号因为千分位分隔符可能出现在金额里但¥是固定字符所以用regexFalse效率更高。2.3 消费类别归一化从商户名到真正的消费方向账单自带的“分类”字段通常不统一。支付宝可能给“餐饮美食”“交通出行”“购物娱乐”微信则是一堆“商户消费”“转账”等笼统标签。如果不做归一化做出来的饼图会有一堆“其他”根本看不出消费方向。所以我在源码里维护一个CATEGORY_KEYWORDS关键词映射表把常见商户或备注字样归成五到六个大类餐饮、交通、购物、居住、娱乐、其他。CATEGORY_KEYWORDS { 餐饮: [美团, 饿了么, 餐厅, 星巴克, 麦当劳, 瑞幸, 外卖], 交通: [滴滴, 地铁, 公交, 加油, 停车, 高铁, 机票], 购物: [淘宝, 京东, 拼多多, 天猫, 唯品会, 超市, 便利店], 居住: [房租, 水费, 电费, 燃气, 物业, 宽带], 娱乐: [电影, 游戏, bilibili, 爱奇艺, 腾讯视频, KTV], } def map_category(row): # 如果账单自带的分类已经是我们目标大类直接用 if row[分类] in CATEGORY_KEYWORDS: return row[分类] # 否则在备注里做关键词匹配这是最常见的兜底方案 note str(row[备注]) for cat, keywords in CATEGORY_KEYWORDS.items(): for kw in keywords: if kw in note: return cat return 其他 df[方向] df.apply(map_category, axis1)这段代码的要点是apply(map_category, axis1)逐行判断开销可以接受因为个人记账数据一年也就几千行。关键词顺序会影响结果比如“电影院”同时命中“购物”不会因为影院我只放在娱乐里而“超市”只在购物里。但像“盒马”这种既有餐饮又有购物的商户我建议单独处理否则就会变成“餐饮”和“购物”边界模糊。这种边界情况不用追求完美只要保证同一个连锁品牌都落到同一个方向结论就可比。提高准确性还有一个办法如果账单自带二级分类如“餐饮美食-火锅”可以用正则提取二级分类的首个词再二级映射到大类。但个人记账源码示例里维护关键词映射表是最容易理解和修改的方式。3. 用pandas算消费方向聚合、透视与环比3.1 月度支出趋势按自然月重采样有了干净数据第一个要算的通常是“每个月总共花了多少钱”。这不仅告诉你支出是涨是跌也是后续所有占比分析的分母。需要注意记账数据里的金额支出是负数收入是正数如果直接用df[金额].sum()会把工资和退款一起算进去那看的就是“结余”而不是“消费方向”。所以我先把支出过滤出来再对金额取绝对值。# 支出是负值先取绝对值方便后续画图与求和 expense df[(df[金额] 0) (~df[is_refund])].copy() expense[支出] expense[金额].abs() # 以日期为索引按自然月重采样求和 monthly_total ( expense.set_index(日期)[支出] .resample(ME) .sum() .fillna(0) )resample是pandas里时间重采样最常用的方法。ME表示月末频率pandas 2.0以上版本推荐用ME更早的版本要写M否则会收到警告。如果你有几个月没有消费记录重采样会得到0这对画图是好事折线图不会断掉。如果希望看到的是“自然周”而不是“自然月”把ME换成W即可但我个人不太建议按周因为房租、账单这类固定支出会周期性抖动。3.2 消费方向占比用透视表做多维聚合光看总支出还不够重点是搞清楚钱花在了哪个方向。我用两种方式做聚合一种是把整个时间范围内的支出按“方向”求和看总盘子里的占比另一种是通过pivot_table生成“月份 × 方向”的矩阵这样既能看方向变化也能看占比演变。# 按消费方向汇总 cat_total expense.groupby(方向)[支出].sum().sort_values(ascendingFalse) cat_ratio (cat_total / cat_total.sum() * 100).round(1) # 用透视表生成“月份 × 方向”的矩阵行是月份列是方向 monthly_cat expense.pivot_table( values支出, indexexpense[日期].dt.to_period(M), columns方向, aggfuncsum, fill_value0 ) # 把当月各方向金额转成百分比方便以后比较 monthly_cat_pct monthly_cat.div(monthly_cat.sum(axis1), axis0) * 100这个pivot_table有四个参数容易写错。第一个values必须是数值列index我用了to_period(M)把日期降成月周期避免每个月出现日期维度columns填方向最关键的aggfunc默认是mean如果你不写出来的结果是每个月每个方向的平均支出而不是总支出那就完全偏离目标了。所以aggfuncsum必须显式写出来。fill_value0是为了让没有交易的月份显示0而不是NaN后面做div时不会到处冒警告。拿到monthly_cat_pct后消费方向已经很直观比如3月餐饮占比45%4月餐饮占比52%5月降到38%。要总结个人消费方向我会再按总金额排序取前三个类别作为“主力消费方向”另一个指标是看各类别占比是否连续三个月上升或下降。3.3 环比变化找出消费方向里的“异常移动”前两步告诉了你“哪些方向花得多”但没告诉你“哪个方向变化最快”。比如某个月“购物”占比突然从20%跳到35%但总额没怎么涨说明这个人在那个月买了很多大件。要识别这种变化我会在源码里加一个diff()计算占比环比变化# 计算每个方向占比相对于上个月的增减 change monthly_cat_pct.diff().tail(3) # 找出变化率绝对值最大的方向标记为“需要关注的消费方向” notice change.abs().max(axis1).idxmax() print(f最近三个月变化最明显的方向是{notice})这段代码的思路是用diff()拿每个月所有方向占比的差分取最后三个月再按行取绝对值最大值的索引得到变化最剧烈的方向。pct_change()其实也能用但它算的是相对变化率如果上月占比接近0变化率会爆炸到几百甚至几千反而不适合占比分析。我的经验是占比变化看diff金额变化看pct_change两者结合起来才有完整判断。这里还要提醒一点monthly_cat_pct是按方向占比算的如果某个月有其他类别金额为0diff()会把0和正常值比较这反而会影响判断。所以我通常会先剔除掉占比持续低于5%的方向再求环比避免“其他”这种兜底类别干扰视线。4. 可视化分析把统计数据画成看得懂的图4.1 用matplotlib生成消费方向饼图和月度趋势折线图统计数字算完之后最终落到图上才能和人沟通。源码示例里最常用的是matplotlib的subplots一张饼图展示总消费方向结构一张折线图展示月度走势。画图前必须处理中文字体否则图上的类别名会变成一坨方块这是新手最容易踩的坑。import matplotlib.pyplot as plt # 设置中文字体Windows用SimHeimacOS用PingFang SCLinux用Noto Sans CJK plt.rcParams[font.sans-serif] [SimHei, PingFang SC, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(14, 5)) # 饼图展示消费方向占比保留一位小数 axes[0].pie( cat_ratio.values, labelscat_ratio.index, autopct%.1f%%, startangle90, counterclockFalse ) axes[0].set_title(消费方向占比) # 折线图展示月度总支出带圆点标记 axes[1].plot( monthly_total.index.astype(str), monthly_total.values, markero, linewidth2 ) axes[1].set_title(月度总支出走势) axes[1].tick_params(axisx, rotation45) plt.tight_layout() plt.savefig(consumption_analysis.png, dpi200)饼图的autopct%.1f%%控制百分比标签格式counterclockFalse让饼图按逆时针排列否则各个扇区顺序会和你印象中的排序颠倒。折线图的x轴我强制转成字符串这样每个月都显示不会因为时间索引而压缩间距。savefig的dpi200是为了放大到屏幕上还清晰实际输出到PPT可以用300。4.2 用pyecharts做交互式HTML看板如果你想在手机或浏览器上查看或者需要给朋友/同事发一个不需要Python环境就能打开的分析报告我会在源码示例里换用pyecharts生成HTML文件。pyecharts 2.x的API和0.5.x差别很大这里以2.x为例from pyecharts.charts import Pie, Bar from pyecharts import options as opts # 饼图展示近一年消费方向 pie ( Pie() .add( series_name消费方向, data_pair[list(x) for x in zip(cat_ratio.index, cat_ratio.values)], radius[30%, 75%] ) .set_global_opts(title_optsopts.TitleOpts(title个人消费方向汇总)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c}%)) ) # 柱状图展示月度总支出 bar ( Bar() .add_xaxis(monthly_total.index.astype(str).tolist()) .add_yaxis(总支出, monthly_total.values.tolist()) .set_global_opts(title_optsopts.TitleOpts(title月度总支出)) ) pie.render(消费方向.html) bar.render(月度趋势.html)data_pair需要的是一个列表每个元素是(类别, 数值)元组所以用zip转换。radius[30%, 75%]是环形饼图常用设置让中间留白方便看底图。formatter{b}: {c}%里的{b}是类别名{c}是数值这里因为我们已经把cat_ratio存成百分数所以直接显示。如果你不想生成两个HTML也可以把Pie和Bar放到一个Grid里但源码示例里分开更简单方便单独分享。4.3 图表选型饼图不是万能的堆叠面积图更适合看变化饼图直观但有一个明显缺陷当方向超过五个时小扇区很难辨认尤其是“交通”“居住”这类占比相似的时候。我的做法是总消费结构用饼图但时间维度上的变化改用堆叠面积图或堆叠柱状图。堆叠面积图能同时看到每个月各类别的占比变化和总支出水位比单独看饼图更接近“消费方向演变”这个主题。图表类型适合看的结论缺点饼图全年各类别占比大小只展示静态结果难以对比月份普通柱状图月度或类别间的金额大小比较丢失内部结构堆叠面积图/柱状图各类别占比随时间的变化类别过多时有堆积交叉需合理配色折线图单一指标的趋势如总支出、餐饮类金额不包含占比结构信息在源码示例里我实际会同时输出三张图饼图用于总结主导方向折线图用于看月度总支出趋势堆叠柱状图用于看每个方向的金额变化。堆叠图在pandas中可以用monthly_cat.div(monthly_cat.sum(axis1), axis0)得到占比后在plot.bar(stackedTrue)直接画但要注意把上节生成的monthly_cat_pct传入避免用绝对金额。颜色方面不要给每个类别都上高饱和度颜色否则堆积后视觉上很混乱。5. 常见问题与排查跑源码示例时最容易踩的5个坑5.1 中文乱码和图片里的方块字现象matplotlib画出的饼图、柱状图里所有中文标签变成一个个方框英文和大数字正常。原因matplotlib默认字体是DejaVu Sans不支持中文字形。用plt.rcParams[font.sans-serif]设置了字体但系统里没装对应字体或者设置的字体顺序不对也会失效。解决先确认你系统里有什么中文字体。Windows一般有SimHei或Microsoft YaHeimacOS用PingFang SCLinux要装Noto Sans CJK并刷新字体缓存。然后在代码里用font_manager直接指定字体文件路径效果更稳import matplotlib.pyplot as plt from matplotlib import font_manager # Windows常见路径C:/Windows/Fonts/simhei.ttf font_path C:/Windows/Fonts/simhei.ttf font_manager.fontManager.addfont(font_path) plt.rcParams[font.family] font_manager.FontProperties(fnamefont_path).get_name()用addfont添加后font.family会直接识别出新字体避免依赖系统字体列表。这个坑在远程Linux服务器跑源码时尤其明显别只在本地跑通就以为没问题。5.2 pandas版本不同导致resample频率报错现象代码里写resample(M)运行正常但收到FutureWarning: M is deprecated and will be removed in a future version.或者在pandas 2.2上写M直接报错。原因pandas 2.0以后M代表的是“Month end”被统一改成ME但一部分旧接口还能兼容。解决统一用ME和MS。另外如果你在源码示例里会拿到不同人的电脑上跑建议在读取数据后打印pd.__version__并在注释里写明版本要求。具体到代码resample(ME)在pandas 1.x也可能报错所以我的源码里会加一个兼容分支try: monthly_total expense.set_index(日期)[支出].resample(ME).sum() except ValueError: monthly_total expense.set_index(日期)[支出].resample(M).sum()这个try/except看起来笨但是实用毕竟源码zip是发给不同环境用的。5.3 金额列是字符串包含“¥”和“,”导致求和报错现象expense[支出].sum()报TypeError: unsupported operand type(s) for : float and str或者求和结果明显不对。原因导出的账单里金额列经常是文本类型包含人民币符号、千分位逗号比如1,234.50或¥12.30。直接astype(float)会把1,234.50解析失败。解决在clean_bill阶段强制清洗金额用str.replace去掉所有逗号和货币符号再做类型转换。如果你的账单里金额是1,234.50且逗号是千分位可以用.str.replace(,, )如果是英文逗号作为小数点那就要反过来处理先看地区再决定。我的源码里保留了一个CURRENCY_SYMBOL变量方便改。5.4 解压zip后ImportError: No module named pyecharts现象运行到from pyecharts.charts import Pie时直接报错或者运行pip install pyecharts后报错说只找到了pyecharts 0.5.xAPI对不上。原因源码示例里pyecharts代码是按照2.x语法写的但环境里没安装或者安装了旧版0.5.x。pyecharts 0.5和2.x的导入路径完全不同0.5是from pyecharts import Pie2.x是from pyecharts.charts import Pie。解决先指定版本安装pip install pyecharts2.0然后检查版本python -c import pyecharts; print(pyecharts.__version__)。如果版本低于2.0卸载重装。另一种情况是pip install安装到了全局环境但你的Python项目里用了虚拟环境需要先激活虚拟环境再装。这类依赖问题在源码zip里很常见我的做法是随包带一个requirements.txt至少包含pandas、matplotlib、pyecharts三行并在说明里写清安装命令。5.5 月度总支出虚高原因是没过滤退款和资金互转现象算出来的月度总支出比支付宝“月账单”里的支出高出一两百元甚至更多饼图里“其他”占比突然变大。原因账单里包含“退款”“交易关闭”等记录对应金额是负的但小于原交易另外“余额宝提现到银行卡”“信用卡还款”这类资金互转也被算进支出它们本不该出现在消费方向里。解决洗数据阶段加两个过滤备注里含“退款”的记录单独标记并在聚合时排除分类属于“资金互转”“信用卡还款”“理财”的整行过滤掉。如果退款金额需要冲减当月支出不要直接加支出而是建立一个退款表先按原交易去重再在月度堆叠图上体现。代码上我在expense df[(df[金额] 0) (~df[is_refund])]这一行里直接排除了退款但保留is_refund标记让你可以单独统计退款对账。6. 进阶技巧用双账本对比和小样本校验验证整个分析结论消费方向分析跑通之后很多人会担心一个问题这份结论可信吗我最常用的验证方法是“双账本对比”。找一个你信任的外部记录比如支付宝自带的年度账单、微信支付月账单或者你自己手工记账的App把我们的聚合结果和它做逐月对比。# 随机抽取2%的原始明细人工核对类别映射是否正确 sample expense[[日期, 备注, 方向]].sample(frac0.02, random_state42) print(sample.to_string()) # 和外部账本对比的偏差计算 external_bill {2025-01: 3456.0, 2025-02: 3890.5} # 来自支付宝月账单 actual expense.set_index(日期)[支出].resample(ME).sum().astype(float) for month, ext_amount in external_bill.items(): diff actual.get(month, 0) - ext_amount print(f{month} 偏差: {diff:.2f})只要偏差率控制在2%以内说明方向聚合基本可信。如果超过优先检查三个方面退款是否漏过滤、资金互转是否混入、关键词映射是否把某个商户分错类。我习惯每月跑一次之后把sample打印结果直接存档这也是给自己留一份“后悔药”。再往后可以加一个简单的小技巧对关键词映射表做config化把它单独放到category_map.json里每次有新商户出现时不用改主程序只改JSON就行。这个源码示例的下一步扩展方向就是它从固定规则到可配置再到用聚类自动分组是个人记账数据分析最常见的演进路线。我自己现在每月10号固定跑一遍这套流程输出三张图留着月底再和主账户余额对一下。只要偏差在50元以内我就信任这个消费方向结论超过就回头查退款和漏记。希望我的这些经验能帮你在自己的记账数据上少走几步弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网