首页 / 资讯中心 / 文章详情

三个Python数据分析项目:从数据清洗到自动化报表实战

三个Python数据分析项目:从数据清洗到自动化报表实战 ★ FEATURED ARTICLE
我经常在交流群里看到类似的问题Python基础语法刷完了pandas的教程也跟了一遍但一拿到真实的数据表还是不知道从哪里下手。我自己也经历过这个阶段——教程看得懂项目做不出来。后来回头看问题不在于学习材料而是缺了完整的项目训练。Python数据分析这条路真正把人拉开差距的不是多记住了几个函数而是有没有亲手处理过一份脏数据、跑通一个完整分析流程、再用结论去回答一个真实的业务问题。这篇文章我梳理了三个反复推荐给身边人的数据分析项目分别对应入门、进阶和工程化三个层次会把每个项目的核心步骤、设计理由和容易踩的坑都拆开讲清楚。这三个项目都不是什么高深算法而是数据分析日常工作里真正会碰到的场景销售数据的清洗与报表、用户行为的分析与分层、重复取数工作的自动化。它们覆盖了从“拿到数据”到“产出结论”的完整链路做完之后你再看招聘要求里写的那些技能点会觉得它们突然变得具体了。1. 为什么我建议你把精力放在项目而不是教程上1.1 教程教的是“招式”项目练的是“拆招”教程和项目最本质的区别在于问题的上下文是否完整。教程里每一道练习题都已经帮你把数据准备好了字段含义是确定的答案也是确定的。你跟着敲一遍感觉学会了但其实只是把“招式”记住了。真实项目是完全不同的几万行数据摆在那里没有说明书告诉你“Discount”列里的空值意味着什么也没有人告诉你哪个字段是业务上真正关心的你只能通过观察、提问、尝试去判断。我见过很多人在这一步卡住明明函数都会用一面对真实数据就手足无措。这不是能力问题是训练方式问题。项目训练的是“拆招”能力——拿到一个模糊的问题先拆成几个小问题再逐个击破。这个能力只能靠完整地做项目练出来看多少教程都替代不了。1.2 一个合格的数据分析项目应该包含哪些环节判断一个项目值不值得做我一般会看它是否覆盖了下面的完整流程明确分析目标搞清楚业务方到底想知道什么而不是拿到数据就开始跑代码数据获取与抽取从数据库、文件或接口里把数据取出来必要时做字段选择数据清洗与预处理处理缺失值、重复值、异常值统一格式探索性分析通过统计描述和可视化观察数据分布和潜在规律建模或统计验证如果业务问题需要做分层、漏斗或者简单的建模可视化与结论呈现用图和表格把结论讲清楚落到业务行动上很多半途而废的项目问题都出在跳过了前两步直接进入写代码阶段。一旦数据的情况和预期不符后面全部白做。一个完整的项目会让你养成“先想清楚再做”的习惯这种东西靠碎片化练习是练不出来的。1.3 选择项目时的三条原则市面上的项目一堆但不是每一个都值得做。我的筛选标准有三条第一数据规模不能太小。至少要有几千行最好有多个表这样才有“合并”和“筛选”的操作空间。第二要有明确的业务场景。比如“销售下降了分析原因”“用户流失了找出特征”纯技术演示没有业务载体做出来也讲不出价值。第三最好能走完“清洗—分析—结论”全过程。有的项目偏重可视化有的偏重建模但作为练手项目一定要覆盖完整链路。这三条原则其实也在回答一个常见问题“为什么别人做过的项目我还要做”因为项目内容可以相同但你在过程中走通的分析思维方式是自己的这是抄不走的。2. 入门项目某连锁书店销售数据的清洗与业务报表2.1 项目背景与数据情况这个项目是我最常推荐给入门者的场景设定成某连锁书店的销售数据分析。数据是我自己构造的模拟数据路径上分了三个表订单明细表、图书信息表、门店信息表。订单明细表大概有几万行字段包括订单编号、销售日期、门店编号、图书编号、销售数量、销售金额、折扣比例图书信息表包含图书编号、书名、分类、定价门店信息表包含门店编号、门店名称、所在城市、开业日期。为了让数据更接近真实情况我在里面有意埋了一些脏数据日期格式不统一一部分是“2024/01/05”一部分是“2024-01-05”销售金额列里有少量明显不合理的极端值订单明细和图书信息之间有一小部分匹配不上的记录折扣列大量为空但这个空值在业务上其实代表“无折扣”。这些情况在真实的数据分析里太常见了教程不会给你这样的数据但实际工作里几乎天天遇到。2.2 第一步先建数据字典不要急着写代码拿到数据我建议先做一件事给每个表每个字段建立一个数据字典记录字段名、类型、业务含义、取值样例、是否可能为空。这个步骤看起来不产生任何“成果”但它能避免后面大量的返工。有一次我就是没查清楚字段含义把一个表示折扣比例的列当成折扣金额直接做汇总结果报表数字大得离谱翻了一小时才找到原因。你可以用下面这段代码快速检查每个表的字段概况import pandas as pd orders pd.read_csv(orders.csv, encodingutf-8) books pd.read_csv(books.csv, encodingutf-8) stores pd.read_csv(stores.csv, encodingutf-8) for name, df in [(orders, orders), (books, books), (stores, stores)]: print(f表名: {name}, 行数: {len(df)}, 列数: {len(df.columns)}) print(df.dtypes) print(df.head(3)) print(---)这一步的产出是一张数据字典表形式可以参考下面这样表名字段名类型业务含义备注订单明细order_idobject订单编号同一订单可能包含多本书订单明细sale_dateobject销售日期格式不统一需要清洗订单明细discountfloat折扣比例空值代表无折扣图书信息book_idobject图书编号与订单明细的book_id对应建完数据字典之后你再看这份数据时的思路会清晰很多哪些字段需要清洗、哪些字段之间存在关联一目了然。2.3 清洗的关键操作与背后的理由清洗的第一步是处理缺失值。这里要区分“真缺失”和“有意义缺失”。订单明细里的折扣列为空不代表数据丢失而是这笔订单本来就没有折扣。如果把空值统一填充成0反而会把这部分订单的折扣率错误地标记为“打0折”。所以正确的做法是保留空值在后续分析中单独处理。第二步是处理重复值。判断重复不能只看整行是否完全相同因为一个订单可能包含多种图书订单编号相同但图书编号不同这是正常的数据结构。如果按整行判重就会把合法的数据删掉。正确的方法是结合业务逻辑指定关键字段来判重# 完全重复的行才删除 orders orders.drop_duplicates() # 如果担心同订单同图书的重复记录可以这样判断 orders orders.drop_duplicates(subset[order_id, book_id])第三步是统一日期格式。原始数据里日期有两种写法pandas在读取时会把它们统一识别成object类型。可以这样处理orders[sale_date] pd.to_datetime(orders[sale_date], format%Y-%m-%d)这里我建议显式指定format而不是直接让pandas自动推断。真实数据里可能出现“2024/1/5”“2024-01-05 10:30:00”等各种格式自动推断在这种场景下很容易出错指定format之后如果数据里出现了不符合格式的值会直接报错提醒你后续处理起来也方便。第四步是处理金额异常。先对销售金额做描述性统计print(orders[sales_amount].describe())如果最小值是负数或者最大值比99分位数高出好几个数量级基本可以断定数据里混入了异常值。我建议用箱线图来判断而不是拍脑袋定一个阈值。因为不同品类的价格区间差异很大固定阈值对图书类可能不适用但箱线图的IQR方法是基于数据本身分布计算的相对客观。import matplotlib.pyplot as plt plt.figure(figsize(8, 4)) orders.boxplot(columnsales_amount) plt.title(Sales Amount Distribution) plt.show()对箱线图里标记出来的异常点先不要急着删除。我一般是先查这些订单对应的是什么图书、什么门店再决定是保留还是剔除。比如一套高价精装书或者一个企业团购订单销售金额本来就高这类值不算异常是有业务含义的。只有确认是录入错误比如把123录成了1230才做修正或删除。2.4 多表合并与汇总报表的写法清洗完之后进入分析阶段。这里的关键操作是把订单明细、图书信息、门店信息三张表合并起来。我见过有人用concat来做这个事但concat是纵向拼接和这里的需求完全不同。多表合并要用merge因为它按指定键把行横向连接起来。# 先合并订单和图书信息 df orders.merge(books, onbook_id, howleft) # 再合并门店信息 df df.merge(stores, onstore_id, howleft)代码里我用了howleft意思是保留订单明细里的全部记录图书或门店信息匹配不上就留空。这样做的原因是我要的是订单的完整清单不希望合并之后行数变少。合并前后最好检查一下行数如果行数突然增多或减少多半是合并键存在重复导致的问题。合并之后就可以做汇总了。一个常用的组合是按门店、按月份、按图书分类做销售额汇总df[sale_month] df[sale_date].dt.to_period(M) monthly_summary df.pivot_table( indexsale_month, columnscategory, valuessales_amount, aggfuncsum )pivot_table和groupby都能完成这类聚合我个人的习惯是如果只做一个分组列一个聚合值groupby更直观如果要做行、列两个维度的交叉汇总pivot_table更方便。这个例子就是典型的行列交叉汇总场景。2.5 把结果变成“能看”的图表和结论分析的最后一步是可视化出图。这一步不是画完图就完事了而是要保证每张图都附带一个业务结论。比如画完月度销售趋势图如果发现8月和9月出现明显下滑就要去数据里找原因是门店数量减少还是某个月缺了大促画完品类占比的饼图如果发现“社科类”只占5%的销售额但占了15%的库存那这个结论对采购部门就是有行动价值的——库存结构可能需要调整。我个人习惯用matplotlib和seaborn这类Python绘图库画出来之后再统一导出成图片放进报告里。入门阶段不推荐花太多时间打磨交互式大屏先把静态图表做到“有标题、有注释、有结论”就行了。3. 进阶项目某商城订单数据搭建用户价值分层与转化漏斗3.1 这个项目要解决的业务问题做完销售数据清洗与报表之后下一个值得做的是用户行为分析类的项目。我自己用过这样的模拟场景某商城有完整的用户访问日志和订单数据业务方最关心两个问题——用户到底分几类钱该往哪类用户身上倾斜以及用户从进入页面到完成支付到底在哪一个环节流失最多。这两个问题分别对应两个分析手段RFM用户价值分层和转化漏斗分析。它们不涉及复杂的机器学习算法但对数据处理的功底和业务理解的要求都比较高做出来之后在简历上也很能说明问题。3.2 用户价值分层RFM模型的工程实现RFM是三个维度的缩写R代表Recency用户最近一次购买距离现在多久F代表Frequency用户的购买频次M代表Monetary用户的累计消费金额。它的核心思想是判断一个用户的价值不能只看他花了多少钱还要看他有多久没来了、来过几次。直接用订单表就能算出这三个指标rfm df.groupby(user_id).agg( recency(order_date, lambda x: (today - x.max()).days), frequency(order_id, nunique), monetary(order_amount, sum) )这里有几个细节要注意recency要用“今天”减去“最近一次购买日期”如果直接用最大日期减去最小日期就等于把新用户错当成熟客frequency用唯一订单数而不是行数因为同一张订单可能包含多个商品直接数行数会虚高monetary直接求和没问题但最好先确认金额字段里没有异常值。算完三个维度之后需要给每个维度打分。最常见的做法是按分位数打1到4分比如recency越短分越高frequency和monetary越大分越高然后组合成分层标签rfm[R_score] pd.qcut(rfm[recency], 4, labels[4, 3, 2, 1]) rfm[F_score] pd.qcut(rfm[frequency], 4, labels[1, 2, 3, 4]) rfm[M_score] pd.qcut(rfm[monetary], 4, labels[1, 2, 3, 4])为什么推荐用分位数而不是固定阈值因为不同业务体量差异很大电商平台的“高消费”和线下零售的“高消费”完全不是一个量级。分位数是基于当前用户群分布来划分的自动适配业务量级。pd.qcut返回的是category类型后面直接比较大小很方便。按3个维度的组合可以把用户分成“重要价值用户”“重要保持用户”“重要发展用户”“一般价值用户”等好几类。这里要特别提醒一个RFM的坑高频低价用户和低频高价用户可能落在同一个M区间甚至F区间但他们的运营策略完全不同。前者适合做复购引导和会员关怀后者适合做高客单商品的精准推荐。所以分完之后不要只贴标签要想清楚每个标签背后的用户画像和行动建议。3.3 转化漏斗从曝光到支付的环节拆解转化漏斗分析要回答的问题是用户从进页面到下单每一步有多少人留下来。假设环节是曝光→浏览→加购→下单→支付那每个环节的统计口径必须统一用户先去重再判断是否发生过对应动作。实现思路大致是exposed_users set(logs[logs[event] expose][user_id]) viewed_users set(logs[logs[event] view][user_id]) added_users set(logs[logs[event] add_cart][user_id]) ordered_users set(logs[logs[event] order][user_id]) paid_users set(logs[logs[event] pay][user_id]) print(len(exposed_users), len(viewed_users), len(added_users), len(ordered_users), len(paid_users))为什么一定要去重因为一个用户一天可能曝光10次、浏览3次如果按事件流水去数数字会严重虚高而且每一步的统计口径不一致算出来的转化率根本没有可比性。模拟数据算出来的结果很典型曝光到浏览70%浏览到加购30%加购到下单50%下单到支付90%。最明显的流失发生在“浏览到加购”这一环。你作为分析师不能只说“这里流失严重”还要结合用户行为去猜原因——是否商品详情页缺乏用户评价是否价格比竞品高是否加购入口不明显再找运营或产品同事去验证假设。这个项目做下来你会发现自己从“会跑代码”进步到了“会设计分析逻辑”。同样一份数据懂业务的人看到的是流失环节不懂业务的人只看到几张报表这个差距在面试里非常明显。3.4 除了RFM和漏斗这个项目还能延伸什么RFM和漏斗是这个项目我已经推荐过的两个模块其实还可以往外延伸一个模块留存分析。计算方法是把用户按首次购买月份分组看每个月之后的一个月、三个月、六个月各自留存的百分比。留存高的产品通常说明复购驱动稳定留存低则说明可能是“一次性生意”。延伸模块的意义在于同一个项目代码量不变但它让你在面试中能讲的业务场景变多了。你讲一个项目时说“我做了用户分层、漏斗分析、留存分析”比只说“我用了pandas处理订单数据”要具体得多。所以我一直认为项目数量不用贪多把一个数据集分析透比泛泛做三个半成品有价值得多。4. 工程化项目用自动化数据管道替换每天的“查数”工作4.1 场景周报月报为什么这么耗人第三个项目的场景来自我自己工作里经常看到的重复劳动。某业务团队每周一早上需要一份上一周的销售数据之前的流程是分析师手动去后台系统导出Excel然后用透视表做汇总再复制到邮件里发出去。整套流程要花一两个小时而且每周都一样。过程里只要有一次手抖选错日期范围发出去的数据就是错的等别人发现的时候可能已经影响了一个会议。这类工作几乎每个数据分析师都做过它是典型的不需要多少“分析”但必须保证准确的任务。把它变成自动化脚本是我觉得每一个做数据分析的人都应该掌握的基础工程能力。4.2 管道设计抽取、清洗、存储、输出这个项目我推荐的技术栈是Python pandas SQLite Excel/邮件定时发送。数据文件可以来源于模拟的系统导出csv放在固定目录下脚本每天或每周定时运行。管道分四层抽取层读取指定目录下所有csv文件统一schema清洗层复用前面项目里写好的清洗函数处理缺失值、异常值、日期字段存储层把清洗后的数据写入本地数据库方便回溯和查询输出层按模板生成Excel报表或直接写入邮件正文四层各干各的事每一层都能单独测试。比如清洗层发现新数据出现了之前没见过的脏格式你只需要改清洗函数不用动其他层的代码。4.3 关键代码函数式封装与异常监控清洗逻辑最好封装成独立函数主脚本只负责调度。下面是一个简化的示例def clean_sales_data(raw_df): df raw_df.copy() df[sale_date] pd.to_datetime(df[sale_date], format%Y-%m-%d) df df.drop_duplicates(subset[order_id, book_id]) df[sales_amount] df[sales_amount].astype(float) return df def load_all_files(data_dir): all_files pathlib.Path(data_dir).glob(sales_*.csv) lst [pd.read_csv(f, encodingutf-8) for f in all_files] return pd.concat(lst, ignore_indexTrue) if __name__ __main__: today pd.Timestamp.now().date().isoformat() raw load_all_files(/data/raw) clean clean_sales_data(raw) clean.to_sql(sales, engine, indexFalse, if_existsreplace) report build_weekly_report(clean) send_report(report, receivers[opsexample.com])这个项目最大的价值不在代码本身而在异常监控意识。最简单的做法是统计最近一次运行的数据量和上一期做对比如果下降幅度超过20%脚本就发一封标记“数据异常”的邮件而不是默不作声地生成一份看起来正常的报表。我在这个项目里还会把脚本运行日志写入文件哪一步出了问题、原始文件缺了哪个、清洗掉了多少行全部能回溯。4.4 定时任务的配置与运行保障清洗和报表脚本写完之后还需要一个定时机制来触发它。如果你在Linux服务器上用crontab一行配置就能实现0 9 * * 1 cd /path/to/project python weekly_report.py这条cron表达式表示每周一早上9点运行一次。如果是在Windows环境也可以使用系统自带的任务计划程序来定时运行。定时任务跑起来之后还要注意几个问题数据文件路径是否写死、运行环境是否一致、脚本对异常是否有兜底。我的经验是凡是涉及定时任务的脚本运行时环境一定要干净可复现。把项目依赖列出统一记录在另一台机器上能一行命令复现环境这样才能保证换一台电脑或者部署到服务器上不会崩。这个习惯和算法模型调参一样重要但很多人完全没有意识。5. 这些项目里反复出现的五个坑5.1 索引没重置数据整体“串位”这是初学者最常见却不自知的错误。比如你对orders做了筛选只保留数量大于1的行然后直接执行orders[new_col] some_series。这个some_series可能是按另一个顺序排列的索引对不上pandas在赋值的时候就会按索引对齐产生错位数据。我的习惯是只要对DataFrame做了筛选、排序、分组聚合之类的操作下一步需要和别人合并或赋值之前一律先reset_index(dropTrue)。这个习惯救了我不止一次。5.2 merge之后行数暴增或减少的排查用merge合并两个表合并之后行数如果比左表还多说明左表的合并键在右表里存在重复值。比如你拿订单表和图书信息表按book_id合并如果图书信息表里同一个book_id出现了两条记录合并结果的行数就会翻倍。排查方法很简单合并之前先判断键的唯一性。print(books[book_id].duplicated().sum()) print(stores[store_id].duplicated().sum())如果重复值不为0就要先厘清为什么重复、保留哪条更合理。这个检查步骤我建议写成一个固定习惯能省掉很多后续对数字对不上的排查时间。5.3 日期类型是object排序和筛选全乱我见过有人在sort_values之后发现日期排序结果完全不对比如“2024-10-01”并没有排在“2024-09-30”后面反而夹在中间。原因是日期列还是字符串object类型字符串排序是按字符一位一位比的年、月、日每一位都要对齐才能排对。解决办法就是统一转成datetime类型转完之后还要验证一下orders[sale_date] pd.to_datetime(orders[sale_date], format%Y-%m-%d) print(orders[sale_date].dtype) # datetime64[ns]顺手把dtype打印出来确认不要转完就直接用。5.4 get_dummies之后直接跑模型的共线性陷阱特征工程里做独热编码时pandas的get_dummies默认会对每个类别生成一个0/1列。如果某个字段有5个类别就会生成5列直接放进回归模型时会因为完全共线性导致系数无法解释甚至报错。解决办法是加一个参数pd.get_dummies(df, columns[category], drop_firstTrue)drop_firstTrue会删掉第一列剩下的N-1列足以表达全部信息同时避免了共线性问题。这个细节在面试里也经常被问到属于“看着简单但没多少人真正注意”的点。5.5 读大文件内存溢出数据文件稍微大一点直接用pd.read_csv(large.csv)很容易内存报警。我的做法是先别急着读先看文件大小和行数规模再决定怎么读。有几个参数很实用df pd.read_csv(large.csv, usecols[order_id, sale_date, amount], dtype{order_id: str, amount: float32}, nrows10000)usecols只读需要的列dtype指定列类型比如int64改float32能省一半内存分块读取用nrows或chunksize。这几招组合起来单机处理几百万行的数据通常没有问题。6. 三个项目做完后怎么整理成自己的作品集6.1 代码仓库的组织方式项目做完不是终点把它展示出去才是闭环。我建议每个项目单独建一个目录里面至少包含README.md、数据说明文档、脚本文件、输出图表、结论报告。README要写清楚项目背景、数据来源、分析思路和核心结论。一个简单的项目目录结构长这样store_sales_analysis/ ├── README.md ├── data/ │ └── data_dictionary.md ├── scripts/ │ ├── clean_data.py │ └── analysis.py ├── output/ │ ├── monthly_sales.png │ └── summary_report.md └── requirements.txtREADME的写作要克制不要写流水账重点写分析和结论。读者打开你的仓库第一眼要能看出“你做了什么”“为什么做”“结论是什么”。代码注释也要写清楚但不要每行都注释核心函数和关键逻辑处注释就够了。6.2 简历和面试中如何描述项目简历里的项目描述我见过两种写法。普通写法是“熟练使用pandas进行数据处理”这种话面试官一天能看几十遍等于什么都没说。更有信息量的写法是在某个项目中用pandas处理了几万行销售数据完成了缺失值处理和多表合并产出了按月、按门店、按品类的销售报表发现某品类库存占比与销售占比严重不匹配给采购部门提供了调整建议。普通写法有信息量的写法熟悉Python和数据分析用Python处理数万行销售数据独立完成清洗、合并、汇总分析会做数据可视化产出月度销售趋势图和品类结构图并基于图表提出库存调整建议使用SQL进行查询联表查询订单和用户数据支撑RFM用户分层和漏斗分析面试的时候讲项目要尽量避免“我用了什么工具”多讲“我解决了什么问题”。面试官真正想确认的是你的分析思路和落地能力工具只是手段。多准备几个“为什么这么选”的回答比如为什么用分位数做RFM打分、为什么清洗时保留折扣空值、为什么合并后要检查行数这些都是能体现思考深度的地方。6.3 面试问答的准备方向针对这几个项目面试常被追问的问题我整理过几个方向数据量如果翻十倍你的脚本哪里会成瓶颈怎么优化清洗时为什么保留一部分异常值而不是全部剔除RFM分数用什么方法划分的换成别的划分方式结果会有多大差异漏斗分析中每一层的用户口径是什么为什么不能直接用事件流水数自动化报表的脚本如果哪天数据源文件缺失系统会有什么表现这些问题不一定有标准答案但每一个回答都能反映你项目做得细不细。做项目的时候多留一个心眼把每个决策背后的原因归档在文档里面试的时候会从容很多。最后再分享一个我自己的习惯做完一个项目把中间过程的所有临时脚本、中间结果、报错记录都保留一段时间不要急着删。很多当时看起来没用的探索代码后来写复盘文章或者做下一个项目时会突然再次用到。我认为数据分析的能力不是体现在你背了多少个API而是体现在你面对一份具体数据时能不能迅速判断“该洗哪里、该看哪里、该怎么说”。这三个项目恰好就是练这三件事的。
阅读完成 · 觉得有帮助?
咨询建站