
上周接了一个朋友转过来的需求他手里有一份某电商平台的订单导出表几万行数据想让我帮他看清几个问题卖得最好的品类是什么、用户的复购情况怎么样、哪些用户值得重点运营。正好手头比较空就花了半天时间用Python整套流程跑了一遍。这活儿不复杂但流程比较完整——从数据清洗、指标体系搭建、RFM用户分层到可视化输出和关键结论提炼基本覆盖了日常业务数据分析的大部分场景。我觉得这套流程挺有代表性的适合刚开始接触Python数据分析的同学参考也适合已经在用Excel做报表、但想切换到代码化处理的朋友。你只要有一份类似的销售明细表不管几百行还是几万行把下面这套流程跑通就能得到一份可以拿去汇报的分析结果。1. 数据准备与理解先摸清家底再动手1.1 拿到表之后的第一件事不是写代码很多人拿到数据第一反应是赶紧写代码跑统计我建议先别急。先用文本编辑器、Excel或者数据库客户端打开看前几十行搞清楚几件事有哪些字段、每个字段大概是什么格式、日期字段长什么样、金额字段有没有单位或逗号、是否存在明显缺失。这一步看起来不起眼但能帮你避开后面一大堆坑。以这份电商订单表为例常见的核心字段大概有这些字段名含义常见格式order_id订单号字符串如 OD202411150001order_date下单日期字符串如 2024-11-15 或 yyyy/mm/ddcustomer_id用户ID字符串或数字amount订单金额数字可能带千分位逗号quantity商品数量整数product_category商品品类字符串如 数码、服饰、家居payment_status支付状态枚举值如 已支付、待支付、已取消我把这份表加载到pandas里之后第一轮探查常用这几行代码import pandas as pd df pd.read_excel(订单数据.xlsx, sheet_name0) # 看一眼行列规模和数据类型 print(df.shape) print(df.dtypes) # 查看前5行确认读取结果是否符合预期 print(df.head()) # 检查缺失值数量和唯一值情况 print(df.isnull().sum()) for col in df.columns: print(col, df[col].nunique())注意df.shape会输出一个元组第一个值是行数、第二个是列数比如(52000, 8)就代表5.2万行、8个字段。dtypes能帮你看清每个字段被pandas识别成了什么类型这一步非常关键——如果日期字段显示为object而不是datetime64后面做时间序列分析就要先转换。1.2 探查阶段最容易忽略的三个细节第一是金额字段里的千分位逗号。Excel导出经常会把金额显示成12,345.67这种字段读进来是object类型直接astype(float)必报错。解决办法是用str.replace顺带把货币符号一起清掉df[amount] df[amount].astype(str).str.replace(,, ).str.replace(¥, ).astype(float)第二是订单号重复问题。一个订单可能对应多个商品行所以order_id重复不一定是脏数据也可能是正常的明细展开。判断方法很简单看同一订单号的金额是否每行都相同。如果相同说明是整单金额被复制到了每一行做订单级分析时必须先按order_id去重如果不同说明每行是商品子项需要聚合到订单级再做用户分析。第三是日期字段的格式混乱。有的行是2024/11/15有的行是2024-11-15还有的是Excel序列号数字。统一处理的通用办法是用pd.to_datetime搭配多种格式尝试df[order_date] pd.to_datetime(df[order_date], errorscoerce, formatmixed)errorscoerce会把无法解析的日期变成NaTNot a Time后面回头统计有多少NaT就知道哪些行需要人工洗一下。formatmixed是让pandas自动猜测各行的日期格式在pandas 2.0以上版本实测挺好用。2. 数据清洗脏数据不洗掉分析就是白做2.1 清洗优先级先删明显无效再补合理缺失数据清洗是整个流程里最琐碎、但也是投入产出比最高的一步。我的清洗原则只有一句能不改业务含义就尽量不改能明确判断无效的直接剔除并做好记录。具体到这个电商项目我按下面的顺序操作删除支付状态为已取消的订单因为这部分订单从未形成真实收入。删除金额等于0或小于0的异常行。小于0可能是退款单混在销售明细里如果要做GMV口径分析退款单应该单独拆出去看而不是混在一起算销售额。处理空值用户ID缺失的行直接删因为后面做用户分析完全用不上商品品类缺失的行按未知品类填充不删除保持销售金额统计的完整性。日期为空的行删除因为无法进入时间序列分析。对应代码# 只保留有效订单 df df[df[payment_status] 已支付].copy() # 过滤金额异常 df df[df[amount] 0].copy() # 删除用户ID缺失 df df.dropna(subset[customer_id]) # 品类缺失填充 df[product_category] df[product_category].fillna(未知品类) # 删除日期无法解析的行 df df.dropna(subset[order_date])每次清洗之后我都建议保存一份df_cleaned.to_csv(订单数据_清洗后.csv, indexFalse)到磁盘。后面一旦发现分析结果不对劲至少能回到清洗后的中间态排查不用从头再来。2.2 一个非常容易踩的坑退款单混入销售我处理这份数据时就发现明细表里混了一批金额为负数的行字段内容跟正常订单几乎一样只是金额前带负号备注里写着退款。如果直接求和销售额会被严重低估如果直接删除退款率这个指标就没法算了。正确的做法是拆出退款单单独统计# 退款单 refund df[df[amount] 0].copy() # 正常销售单 sales df[df[amount] 0].copy() refund_total round(refund[amount].sum(), 2) print(f退款总金额: {refund_total}) sales_total round(sales[amount].sum(), 2) print(f销售总金额: {sales_total})这样做的好处是既能算出净销售额销售总额-退款总额又能保留退款率的口径汇报的时候直接多一个业务洞察。我见过不少新手把退款行直接删掉后面老板问退款率怎么没算就傻眼了。2.3 清洗后的数据质量校验清洗不是删完就算完一定要做校验不然删过头自己都不知道。我一般会对比清洗前后的几个核心数字总行数、销售总额、订单数、用户数。如果销售总额的下降幅度超过一定比例比如20%就要回头查是不是过滤条件太苛刻把正常数据误删了。print(清洗前记录数:, len(df_raw)) print(清洗后记录数:, len(df)) print(清洗前后订单数对比:, df_raw[order_id].nunique(), df[order_id].nunique())这个对比看起来简单但能救命。有一回我处理另一份数据清洗后订单数少了一半一查发现是按行去重的时候把同一天同一用户的多笔订单也当成重复给删了。教训就是去重前先想清楚重复的粒度到底是什么——按订单号还是按订单商品3. 指标计算销售分析离不开的几个核心量3.1 核心指标定义要在一开始就定死业务数据最怕口径不一致。同一个销售额有的人算含运费有的人算不含运费同一个订单量有的按支付成功算有的按下单算。项目一开始我就和需求方确认好了口径下面是这次用的指标体系指标定义计算方式总销售额成交订单金额合计按已支付订单的amount求和总订单量有效支付订单数按order_id去重后计数客单价平均每单金额总销售额 / 总订单量付费用户数至少成交一单的用户数customer_id去重计数人均订单数平均每个用户下单数总订单量 / 付费用户数复购率购买≥2次的用户占比多次购买用户数 / 付费用户数品类销售额占比各品类销售贡献品类销售额 / 总销售额在代码里同时输出这些数比一个个单独查方便得多order_level df.groupby(order_id).agg( order_date(order_date, first), customer_id(customer_id, first), amount(amount, sum), quantity(quantity, sum), product_category(product_category, first) ).reset_index() total_sales round(order_level[amount].sum(), 2) total_orders order_level[order_id].nunique() aov round(total_sales / total_orders, 2) paying_users order_level[customer_id].nunique() repeat_users order_level.groupby(customer_id).size() repeat_rate round((repeat_users[repeat_users 1].count() / paying_users) * 100, 2) print(总销售额:, total_sales) print(总订单量:, total_orders) print(客单价:, aov) print(付费用户数:, paying_users) print(复购率:, repeat_rate, %)注意我先把订单明细聚合到了订单级order_level再来算用户级指标。这个细节很重要——如果不聚合同一订单里的多行商品会被当成多个订单导致订单量虚高客单价被低估。3.2 用RFM模型给用户分群找出真正值得运营的人算完整体指标只是第一步业务方更想知道的是哪些用户更值钱。RFM模型是经典做法它只看三个维度最近一次购买时间Recency、购买频率Frequency、购买金额Monetary。从订单数据构造每个用户的RFM数值import datetime as dt # 设置一个基准日一般是数据记录截止日期的次日 ref_date df[order_date].max() pd.Timedelta(days1) rfm order_level.groupby(customer_id).agg( recency(order_date, lambda x: (ref_date - x.max()).days), frequency(order_id, count), monetary(amount, sum) ).reset_index()RFM分群的核心逻辑是给三个维度分别打分。我给每个维度按四分位数分成四档前25%算最高分4逐次递减。实际处理中我一般用中位数作为切分标准因为均值很容易被大额订单拉偏而中位数能更真实反映整体分布。def rfm_score(rfm, col): q rfm[col].quantile([0.25, 0.5, 0.75]) if col recency: # 间隔越小越好所以分档顺序要反过来 rfm[score_ col] pd.cut(rfm[col], bins[0, q[0.25], q[0.5], q[0.75], float(inf)], labels[4,3,2,1]) else: rfm[score_ col] pd.cut(rfm[col], bins[0, q[0.25], q[0.5], q[0.75], float(inf)], labels[1,2,3,4]) return rfm分好之后把三位分数拼接起来得到每个用户的RFM组合。比如某用户score_recency4, score_frequency3, score_monetary4代码就是434。然后按组合划分人群类别重要价值客户R高F高M高比如组合[4,4,4][4,3,4]这类。重要发展客户R高但F或M偏低比如刚下单但金额还不高的用户。重要保持客户R低但F或M高以前买得多但现在有一阵子没来了。一般挽留客户三项都低的用户虽然消费潜力有限但仍可以通过优惠券唤醒。我这里直接用分数组合加业务规则做聚合分类比直接套用理论模型更灵活type_map { 444: 重要价值客户, 434: 重要价值客户, 344: 重要价值客户, 334: 重要保持客户, 443: 重要保持客户, 433: 重要保持客户, # 其他组合按类似规则继续映射 } rfm[user_type] rfm[score_recency].astype(str) \ rfm[score_frequency].astype(str) \ rfm[score_monetary].astype(str) rfm[user_type] rfm[user_type].map(type_map).fillna(一般挽留客户)我特别想提醒的是RFM的切分阈值完全依赖这份数据的内部分布所以只能用于内部用户对比不能直接拿去跟别的店铺比绝对值。举个例子A店铺的老用户可能每月买两次B店铺的老用户每月买五次两边RFM的F维度4分对应的实际购买次数完全不同。4. 可视化和报告输出让数据自己会说话4.1 用Python生态快速出图不用羡慕BI工具这个阶段我用到的还是经典的matplotlib加seaborn组合再加上plotly做简单交互图。数据量不算大几万行这两个库完全扛得住。先看整体趋势。把每日销售额聚合出来直接画折线图import matplotlib.pyplot as plt import matplotlib.dates as mdates daily_sales sales.groupby(sales[order_date].dt.date)[amount].sum().reset_index() daily_sales.columns [order_date, amount] plt.figure(figsize(14, 6)) plt.plot(daily_sales[order_date], daily_sales[amount], color#2E86AB, linewidth1.5) plt.gca().xaxis.set_major_formatter(mdates.DateFormatter(%m-%d)) plt.title(每日销售额趋势) plt.xlabel(日期) plt.ylabel(销售额) plt.grid(alpha0.3) plt.show()画图并不是为了好看而是为了发现藏在数字里的异常。比如我看到趋势图上某一天出现一个明显尖峰就知道当天可能有大促或爆款商品推动销售这样可以提醒业务方去核对活动效果。品类结构用柱状图或饼图展示category_summary sales.groupby(product_category)[amount].sum().sort_values(ascendingFalse) plt.figure(figsize(10, 6)) category_summary.plot(kindbar, color#F18F01) plt.title(品类销售额分布) plt.ylabel(销售额) plt.show()4.2 核心结论不要只丢图表要给文字解读跑完图之后我把关键数字整理成一张汇总表下面这种格式指标数值解读总销售额213.5万过去90天全部已支付订单总额总订单量1.62万单去重后的订单数客单价131.8元平均每单金额付费用户数0.92万人至少购买一次的用户复购率18.6%购买≥2次的用户占比TOP1品类数码配件占比31.2%是绝对主力写解读的时候有个技巧不要停在数码配件占比31.2%这种描述层面要往前推一步得出业务动作层面的结论比如数码配件用户以男性为主且换机周期平均在1-2年建议针对老用户推送新品配件。如果你手头没有图形化BI环境还可以直接用pandas输出一个可交互的Excel报告with pd.ExcelWriter(电商分析报告.xlsx, engineopenpyxl) as writer: sales.to_excel(writer, sheet_name销售明细, indexFalse) daily_sales.to_excel(writer, sheet_name每日趋势, indexFalse) category_summary.to_excel(writer, sheet_name品类汇总, indexFalse) rfm.to_excel(writer, sheet_name用户分层, indexFalse)Excel报告的好处是业务同事可以直接用筛选器和数据透视表自己看不用每次改动都来找你。5. 常见问题与排查数据干活路上的五个深坑5.1 订单号重复导致指标虚高这个坑在第2部分提到过但值得再强调一次。如果是整单金额重复出现在多行直接用订单明细做除法就会把销售额放大好几倍。排查方法非常直接dup sales.groupby(order_id).size().reset_index(namecnt) dup[dup[cnt] 1]看到有重复值时先搞清楚到底是订单多商品还是脏数据再决定要不要聚合去重。多商品订单是正常的但每个商品行的金额可能都是整单金额必须用聚合之后再算指标。5.2 日期格式混乱导致匹配失败Excel里日期经常被存成序列号。比如2024年11月15日excel内部其实是45391从1900年1月1日起算的天数。这类数据会被pandas误判成int类型需要手动还原# 如果读出来是数字说明是Excel序列号 df[order_date] pd.to_datetime(df[order_date], unitD, origin1899-12-30)排查思路很简单先print(df[order_date].dtypes)如果是int64或object就必须走日期转换。origin1899-12-30是Excel日期的基准mac和Windows有细微差异但大多数场景这个基准都能对上。5.3 复购率口径不统一复购率有三种常见算法按用户算购买次数≥2的用户在统计期内占比、按订单算老客订单占总订单比例、按时间维度算某月首购用户后面再购的比例。不同口径算出来的数字可以差一倍以上。我做任何数据报告都会在指标表下面注明口径。落到代码里就是先确定统计期内的全部下单用户到底怎么算# 口径统计期内下单次数2的付费用户数 / 统计期内的付费用户总数 first_buy order_level.groupby(customer_id)[order_id].count() repeat_rate (first_buy[first_buy 2].count() / len(first_buy)) * 1005.4 客单价被异常大单拉偏如果几个几百万元的企业采购单混在零售数据里客单价会被瞬间拉高。这时候平均值不是个好指标最好同时看中位数print(客单价均值:, round(order_level[amount].mean(), 2)) print(客单价中位数:, round(order_level[amount].median(), 2))均值和中位数一对比就能判断数据是否偏态严重。如果均值远大于中位数说明存在高额长尾订单汇报时建议两个数一起给避免被质疑数据失真。5.5 时间范围不一致导致环比计算错误计算环比本月和上月比时如果上月数据只统计到月中或者本月遇到多一天直接对比就会得出错误结论。正确处理是用日平均销售额去对标或者保证对比的两段区间天数一致monthly sales.groupby(sales[order_date].dt.to_period(M))[amount].sum() print(monthly)然后再用monthly.pct_change()算环比就准确多了。更稳妥的做法是再细分到日平均销售额 月度总额 / 当月天数对销售有时间周期性的行业尤其适用。最后再分享一个实用小技巧整个项目跑完之后我习惯把所有处理代码按顺序整理成一份analysis.py文件保存下来数据清洗、指标计算、可视化、RFM分层各放一个函数。这样下次遇到新的数据导出一份只要能对上字段名直接改路径重跑就出来了。后续做月度跟踪分析的时候这个方法帮我省了大量重复劳动。另外如果在分析过程中对某些结论没有把握宁可少写一个推断也不要硬编一个解释。数据分析的价值在小范围内精确可复现不在大而全的模糊判断。就这点来说Python基于pandas的这套处理流程确实比纯Excel手工操作可靠得多——至少每次跑完留下的那几行代码就是你分析逻辑的全部证据。