资讯动态

电商销售数据分析实战:从数据清洗到业务洞察的完整流程

发布时间:2026/9/30 3:32:54 来源:尧图企业网站定制
做电商数据分析这个事我前前后后折腾了不少项目从最早拿Excel透视表硬扛几十万行数据卡到死机到后来用Python一套流程跑下来效率完全不是一个量级。今天想跟你聊的就是一次完整的实战用Python分析某电商平台的销售数据。这篇文章不是教科书是我自己踩坑、试错、最后沉淀下来的整套流程包括数据怎么洗、指标怎么定、图表怎么画才能让老板一眼看懂以及那些文档里不会写但你一定会遇到的坑。这个项目适合谁如果你刚入门Python数据分析或者正在准备自己的数据分析作品集又或者工作中经常要处理销售报表但总觉得效率太低都可以参考这条路径。整篇文章按照我的实际操作顺序来写从拿到原始数据那一刻开始到最终输出分析结论结束每一步都有代码、有参数说明、有取舍理由。1. 项目背景与需求拆解1.1 先搞清楚业务问题再谈技术方案我接手这份销售数据的时候第一件事不是打开Python环境而是先想清楚一个问题这份数据到底要回答什么很多新手容易犯的毛病就是拿到数据就开跑pandas读进来print几个head()然后就开始画图画完发现不知道要表达什么。这是本末倒置。当时的业务背景是某电商平台的一个品类店铺经营了大概一年半积累了十几万条订单记录。店主的原始诉求听起来很简单帮我看看为什么最近三个月销售额下滑了。但销售额下滑这个现象背后可能有无数种原因流量少了、转化率低了、客单价跌了、退款变多了、爆款商品生命周期到了、竞争对手降价了……如果不在分析前把问题拆解清楚后面做的所有分析都会变成无头苍蝇。我习惯用的方法是把模糊的业务问题翻译成可量化的分析指标。这个翻译过程有个很实用的框架叫OSM模型也就是先明确业务目标Objective再拆解达成这个目标的关键策略Strategy最后定义衡量策略效果的指标Measurement。落在我们这个场景里目标就是找出销售额下滑的原因并给出可执行的改进建议那么我就需要同时关注流量指标、转化指标、商品指标、用户指标四个层面这样销售下滑这个现象才能被层层解剖找到真正的问题层级。1.2 指标口径必须先统一否则后面全是坑做销售数据分析最烦的就是指标口径不统一。什么叫口径不统一举个例子同样说销售额到底是按下单时间统计还是按支付时间统计是含运费还是不含运费退款订单算不算进去这些如果不提前定死你分析的时候会发现数字怎么都对不上尤其当你最后要向业务方汇报的时候对方随口问一句这个数跟后台对不上啊整个分析的可信度就崩塌了。我当时和业务方确认的统计口径是这样的销售额按支付成功时间统计剔除已退款订单金额以实际支付金额为准不含优惠券抵扣前的原价。同时约定如果订单状态为已关闭或已退款则这一单不参与任何销售统计但退款金额要单独成一个指标叫退款额用来分析售后质量。还有一个容易被忽略的问题时间字段的粒度。销售数据里会有下单时间、支付时间、发货时间、完成时间不同分析场景要用不同的时间字段。比如分析销售趋势应该用支付时间因为钱真正到账才算销售成立分析履约效率才用发货时间和完成时间。这个规则我在项目开始前就写进了自己的分析笔记里后面所有代码都围绕这套统一口径来写才避免了返工。2. 环境准备与数据清洗2.1 工具选型为什么是Python而不是Excel或BI工具先说个现实问题这份数据大概有13万行、23列Excel处理起来已经很吃力了做一次透视表要转好几圈彩虹圈而且一旦涉及多表关联订单表、商品表、用户表Excel的VLOOKUP写得人头大。BI工具比如Power BI、Tableau可视化能力确实强但对于这种需要灵活做数据清洗、自定义复杂逻辑的任务反而没有Python这么顺手。Python在这个场景下的核心优势有三个。第一是pandas库的数据处理能力处理几十万行数据几乎是秒级响应清洗、筛选、分组、关联这些操作写得非常直观。第二是整个分析链路的可复现性——我今天处理完这波数据三个月后同样的数据再来一波把脚本重新跑一遍就能得到同样的结果不用每次手动点来点去。第三是生态完整pandas做清洗、matplotlib和seaborn做可视化、scipy做统计分析一条链路走到底中间不用切换工具。我用的环境是Anaconda自带的Python 3.9配合pandas 1.4.0、matplotlib 3.5.1、seaborn 0.11.2。Anaconda的好处就是帮你把常用的数据分析库一次性装好了不用自己一个个pip install踩依赖的坑。如果你已经装了原生Python那就记得手动装这三个库命令很简单pip install pandas matplotlib seaborn2.2 拿到原始数据后的第一件事摸清底细数据加载进来之前我强烈建议你先用文本编辑器或者Excel看一眼原始文件的长相不要急着写代码。因为实际拿到的数据往往比你想的脏得多。我这次拿到的是一份CSV文件编码是GBK这在国产电商系统导出的数据里非常常见。直接用pandas默认的UTF-8编码读取上来就是一个UnicodeDecodeError很多新手在这里就被卡住了。正确做法是读取的时候显式指定编码同时先只读前几百行做探查import pandas as pd # 先用nrows参数只读一部分速度更快方便快速探查结构 df_preview pd.read_csv(sales_data_raw.csv, encodinggbk, nrows500) print(df_preview.shape) print(df_preview.columns.tolist()) print(df_preview.head(10))这一步能看到什么列名是否符合预期、哪些列明显是空的、日期字段长什么样、金额字段有没有¥符号或者千分位逗号。记住一个原则在你没有彻底搞清楚数据长什么样之前永远不要用df pd.read_csv(...)把全量数据一次读进来。先探查再全量这是数据分析的基本素养。我探查完发现的问题有列名是中文且带空格和括号、日期字段是字符串格式混合了2023/1/5和2023-01-05两种写法、金额字段是字符串类型且带人民币符号、有大约3%的行存在空值、还有一部分重复订单记录。这些问题的处理方案下面一节展开讲。2.3 数据清洗的标准操作流程数据清洗是整个分析流程里最枯燥但最重要的一环行业里有句话叫Garbage in, garbage out数据不干净后面分析得再漂亮也是自欺欺人。我梳理了一份清洗清单按顺序执行每步都有明确目的。第一步是列名规范化。把中文列名改成英文小写加下划线的风格去除空格和括号。这一步看起来无关紧要但能避免后面写代码时反复切换输入法、防止因为全角字符导致的各种诡异报错。我当时的映射规则是这样的column_mapping { 订单编号: order_id, 下单时间: order_time, 支付时间: pay_time, 商品名称: product_name, 商品类目: category, 商品数量: quantity, 商品单价: unit_price, 实付金额: actual_amount, 订单状态: order_status, 买家账号: user_id, 省份: province, 支付方式: payment_method } df df.rename(columnscolumn_mapping)第二步是类型转换。日期字段要转成datetime64类型金额和数量要转成数值类型。这一年里最常见的坑就是你以为它是数字其实它是字符串甚至里面混着None或者空字符串。转换的时候用pd.to_datetime和pd.to_numeric配合errorscoerce参数让无法解析的值变成NaN这样后面统一处理空值就行。df[pay_time] pd.to_datetime(df[pay_time], errorscoerce) df[actual_amount] pd.to_numeric(df[actual_amount].str.replace(¥, ).str.replace(,, ), errorscoerce) df[quantity] pd.to_numeric(df[quantity], errorscoerce)第三步是去重。电商订单数据最常见的重复来源是系统重放或者数据导出时的冗余。我按order_id去重保留支付时间最新的那条记录逻辑是如果同一订单号出现多次说明某次导出或同步出了问题取最新状态最保险。df df.sort_values(pay_time, ascendingFalse).drop_duplicates(subsetorder_id, keepfirst)第四步是空值处理。注意不是所有空值都直接删除要看这个字段在后续分析里是否用到。比如province为空不影响销售金额分析可以先留着但actual_amount为空那这一行销售统计里就完全没法用只能删掉。我当时统计了一下空值比例不高所以对关键字段直接删行对非关键字段暂时保留。处理完以后记得重置索引df df.dropna(subset[actual_amount, pay_time]) df df.reset_index(dropTrue)这里我想多说一句清洗逻辑一定要记录清楚。我通常在代码里写注释说明每个清洗步骤的原因同时单独开一个Markdown文件记录数据质量报告——原始多少行、清洗后多少行、删掉了什么原因的行、空值比例是多少。这些信息在最后汇报时反而能体现你分析的专业度因为业务方最怕的就是数据被偷偷处理了。3. 核心分析维度与指标体系搭建3.1 销售趋势分析先看整体再看结构数据干净了正式分析开始。我的分析习惯是先整体后局部先粗后细。第一件事永远是看整体销售趋势也就是按天、按周、按月汇总销售额和订单量画一条时间序列曲线。这一步能快速判断几个关键问题全年销售走势有没有明显的季节性波动最近三个月的下滑是断崖式还是缓慢型是不是去年同期也在跌关于时间粒度怎么选我的经验是如果数据跨度超过一年优先看月度趋势和周度趋势因为日粒度噪声太大周末和工作日天然有波动直接看日数据容易误判。如果只看最近三个月再切换到周粒度必要时看日粒度。代码如下# 按月汇总销售额和订单量 df[year_month] df[pay_time].dt.to_period(M) monthly df.groupby(year_month).agg( total_sales(actual_amount, sum), total_orders(order_id, count) ).reset_index() # 转成字符串方便画图 monthly[year_month_str] monthly[year_month].astype(str) # 画双轴图柱状图看销售额折线看订单量 import matplotlib.pyplot as plt import matplotlib.ticker as mtick fig, ax1 plt.subplots(figsize(14, 6)) ax1.bar(monthly[year_month_str], monthly[total_sales], color#4C72B0, alpha0.8) ax1.set_ylabel(销售额元) ax1.yaxis.set_major_formatter(mtick.FuncFormatter(lambda x, p: f{x/10000:.0f}万)) ax2 ax1.twinx() ax2.plot(monthly[year_month_str], monthly[total_orders], color#C44E52, markero, linewidth2) ax2.set_ylabel(订单量单) plt.title(月度销售额与订单量趋势) plt.xticks(rotation45) plt.tight_layout() plt.show()画完这个图后结论很快浮出水面全年确实有两个高峰年中大促和年底大促但最近三个月的销售额同比去年同期的确是明显下降的。不过订单量下降幅度没那么大这意味着问题可能出在客单价上——这正好引出了下一层分析。3.2 客单价与转化漏斗拆解销售额这个指标可以拆成两个核心因子的乘积销售额 访客数 × 转化率 × 客单价。这是电商分析里最经典、也是最基础的拆解公式。我手上这份数据虽然只有订单维度的数据没有完整的访客数据但至少可以算出客单价也就是每笔订单的平均金额。我当时做了两个维度的客单价分析。第一个是按月的客单价趋势看它是不是真的在下降。第二个是按商品类目的客单价分布看不同品类的价格带变化。代码实现很简单# 按月计算客单价 monthly[avg_order_value] monthly[total_sales] / monthly[total_orders] # 查看最近6个月客单价 print(monthly[[year_month_str, total_sales, total_orders, avg_order_value]].tail(6))分析结果显示客单价从峰值时期的平均280元一路降到了最近三个月的210元左右降幅大概25%。再结合订单量只跌了5%左右这个事实基本可以锁定销售额下滑的主要矛盾在客单价而不是流量和转化率。这就是层层拆解的好处不用找业务方要一堆后台权限先用手上的订单数据把能拆的都拆了。当然光知道客单价跌了还不够还得知道为什么跌。这时候就要看商品结构了——是不是便宜的品类卖得更多了还是所有品类都在降价这引出下一节。3.3 商品结构与品类分析找到罪魁祸首商品维度分析的核心是用帕累托法则也就是二八定律来找重点商品。我的做法是先按商品汇总销量和销售额排序后计算累计占比找出贡献了80%销售额的那些头部商品。然后重点观察这些头部商品最近三个月的表现是否在恶化。先看整体品类结构# 按类目汇总销售额 category_sales df.groupby(category).agg( sales(actual_amount, sum), orders(order_id, count), quantity(quantity, sum) ).sort_values(sales, ascendingFalse) # 计算销售额占比和累计占比 category_sales[sales_pct] category_sales[sales] / category_sales[sales].sum() category_sales[cum_pct] category_sales[sales_pct].cumsum() print(category_sales)我当时跑出来的结果很有意思店铺主要做三个类目——家居日用、厨房用品、收纳整理。其中家居日用贡献了大概55%的销售额是绝对的现金牛。但对比最近三个月和历史数据发现家居日用类目的客单价明显下降主要原因是这个类目里的一款主力商品的售价被下调了而且促销频率变高了。再往下钻取到单品维度定位到具体是哪一个SKU的变化导致了客单价下滑。这一步我用了前后对比的思路把今年最近三个月和去年同期同三个月的数据做对比# 筛选今年和去年同期的数据做对比 current_period df[(df[pay_time] 2024-01-01) (df[pay_time] 2024-04-01)] last_year_period df[(df[pay_time] 2023-01-01) (df[pay_time] 2023-04-01)] # 分别按商品汇总销售额取TOP5对比 top_current current_period.groupby(product_name)[actual_amount].sum().nlargest(5) top_last_year last_year_period.groupby(product_name)[actual_amount].sum().nlargest(5)这个对比表做出来结论就非常直观了去年同期的TOP3爆款中有两款今年已经不在TOP5里了取而代之的是两款低客单价的引流款。这说明店铺的爆款迭代出了问题老爆款生命周期到了尾声新品没能接上。到这里销售额下滑的问题已经拆解到了商品层面后面给业务方的建议也就有了着力点。3.4 用户维度分析复购与RFM模型订单数据里还有买家账号这个字段所以按用户维度分析也是顺手的事。我主要做了两块复购率分析和RFM用户分层。复购率的定义要先说清楚——我采用的是在某段时间内下单两次及以上的用户数 / 总下单用户数统计周期按季度来切。代码思路是这样# 给每个订单标记是用户的第几单 df[order_rank] df.groupby(user_id)[pay_time].rank(methodfirst, ascendingTrue) # 一单用户 vs 多单用户 user_stats df.groupby(user_id).agg( order_count(order_id, nunique), total_spend(actual_amount, sum), first_order_time(pay_time, min), last_order_time(pay_time, max) ).reset_index() # 按季度计算复购率 user_stats[first_quarter] user_stats[first_order_time].dt.to_period(Q) repurchase user_stats[user_stats[order_count] 2].groupby(first_quarter).size() / user_stats.groupby(first_quarter).size() print(repurchase)复购率算出来之后发现一个规律新客的季度复购率大约在18%左右低于行业优秀水平一般20%以上就算不错了这说明用户黏性不够买过一次就走了。RFM模型这块我简化了一下因为手上没有每个用户的访问频率数据所以只用了三个字段R最近一次购买距今天数、F购买频次、M累计消费金额。用pandas算出来之后按三分位数把用户分成8类重点观察重要价值用户和重要保持用户这两类人占比的变化。# 计算RFM三个指标 rfm user_stats.copy() rfm[R] (pd.Timestamp(2024-04-01) - rfm[last_order_time]).dt.days rfm[F] rfm[order_count] rfm[M] rfm[total_spend] # 按三分位数打分 rfm[R_score] pd.qcut(rfm[R], 3, labels[3, 2, 1]) rfm[F_score] pd.qcut(rfm[F].rank(methodfirst), 3, labels[1, 2, 3]) rfm[M_score] pd.qcut(rfm[M].rank(methodfirst), 3, labels[1, 2, 3]) # 合并RFM总分并打标签 rfm[RFM_score] rfm[R_score].astype(str) rfm[F_score].astype(str) rfm[M_score].astype(str)RFM分层的意义在于它能直接指导运营动作。比如重要价值用户最近买过、买得多、买得频应该重点维护给他们发新品通知和专属优惠重要保持用户买得多买得频但最近没来应该做召回通过短信或者优惠券刺激回访。我把这个分层结果导出了一份名单后面给业务方的时候可以直接用来做定向运营。4. 可视化呈现与结论输出4.1 图表设计的原则一图一事结论前置很多人在可视化这步翻车不是因为不会用matplotlib而是因为不知道画图是为了什么。数据分析报告里的每一张图都应该回答一个具体的业务问题。一张图里又是折线又是柱状又是散点塞得满满当当看起来很厉害实际上信息密度过高老板根本不知道你想说什么。我的原则是一图一事每张图只表达一个核心结论并且图表的标题直接用结论性的语言而不是描述性的语言。举个例子如果图表标题写各品类销售额对比这就是描述性标题说了等于没说如果写成家居日用品类贡献超五成销售额是绝对主力品类这才是有信息量的标题。读者一眼扫过去就知道这张图想表达什么。具体实现上我用matplotlib和seaborn搭配matplotlib负责底层控制坐标轴、刻度、布局seaborn负责快速画统计图形箱线图、分布图、热力图。几行代码就能出一张可用的图import seaborn as sns # 各品类的销售额分布箱线图观察价格离散程度 sns.boxplot(datadf, xcategory, yactual_amount) plt.xticks(rotation45) plt.title(各品类订单金额分布箱线图) plt.tight_layout() plt.show()画图的时候别忘了处理中文显示问题——matplotlib默认字体不支持中文会显示方块。这个坑几乎每个新手都会踩解决办法是显式指定中文字体plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False # 解决负号显示问题4.2 关键图表一览从趋势到结构到相关我这次最终交付的分析报告里精选了六张图每张都对应一个关键结论。第一张是月度销售额与订单量趋势图说明大盘走势和最近三个月的问题定位第二张是客单价月度变化图说明客单价是主要矛盾的证据第三张是品类销售额占比环形图说明品类结构第四张是TOP10商品对比条形图说明爆款迭代出了问题第五张是用户复购率季度趋势图说明用户黏性下滑第六张是RFM用户分层的堆叠条形图说明用户资产结构。这里我想特别说下第五张图也就是复购率趋势。很多人会忽视复购率总觉得销售分析就是销售额、订单量、客单价这三个数字但复购率实际上是判断一个电商业务健康度的核心指标。销售额可以靠砸钱投广告拉起来但复购率低说明用户留不住全靠持续拉新维持规模这就像接水的水桶一直在漏水前面的水龙头开到最大也填不满。我把这个比喻写在了报告里业务方一看就明白了。画相关矩阵热力图也是我很喜欢用的一步虽然它不能直接回答业务问题但能帮你快速发现变量之间的关联性比如是不是订单量越大的商品退款率也越高是不是促销折扣力度越大客单价反而越低# 计算数值型字段的相关矩阵 corr_matrix df[[quantity, unit_price, actual_amount]].corr() sns.heatmap(corr_matrix, annotTrue, cmapcoolwarm, fmt.2f) plt.title(核心数值字段相关性热力图) plt.show()4.3 从数据洞察到业务建议输出可执行的结论分析做得再深入最后不能落地就是白做。我给业务方的报告最后一部分直接列了五条建议每条都对应前面分析中的一个发现。第一条针对客单价下滑建议调整商品组合把高客单价的搭配套餐作为主推而不是单纯依赖低价引流款。具体操作是在详情页做组合购买的推荐位把爆款配件和高毛利主商品绑定。第二条针对爆款断档建议加快新品测试节奏用老爆款的用户数据进行相似商品推荐同时把去年同期的TOP商品重新做一轮推广测试。第三条针对复购率偏低建议搭建会员积分体系对RFM模型识别出的重要价值用户做定向召回。第四条针对促销频率数据显示今年前三个月的促销活动密度是去年同期的1.7倍但促销带来的销售额增量却在递减建议控制促销频次把资源集中在关键节点。第五条是针对数据层面的建议建议店铺日常就做好数据埋点和口径规范避免每次分析都要花大量时间清洗数据。这些建议不是空话每一条都指向具体的执行动作业务方拿到之后可以直接排期去做。这也是数据分析报告和交作业式报告最大的区别——前者是决策工具后者是事后总结。5. 常见问题与排查技巧实录5.1 编码问题GBK和UTF-8的相爱相杀国产电商系统导出的CSV文件十有八九是GBK编码。你直接用pandas读取大概率报UnicodeDecodeError。解决方案是读取时指定encodinggbk如果还报错就试试encodinggb18030这是GBK的超集兼容性更好。还有一个更稳妥的办法用Python的chardet库先检测文件编码import chardet with open(sales_data_raw.csv, rb) as f: result chardet.detect(f.read(10000)) print(result[encoding])这个库会根据字节特征推断编码类型识别准确率还挺高的。不过注意它读取的是字节流不能直接拿来做数据分析只能用来获取编码名然后再传给pandas。5.2 日期解析失败errorscoerce保护你的流程pd.to_datetime遇到无法解析的日期格式时会直接抛异常导致整个脚本中断。我处理这个问题有两个办法。第一个是加errorscoerce参数无法解析的值会变成NaT脚本不会中断后面再统一处理这些空值。第二个是先探查一下日期字段都有哪些格式用df[pay_time].astype(str).str.contains(/)之类的布尔筛选把不同格式分开处理再统一格式。我个人更推荐第一种因为电商数据的日期格式再乱用errorscoerce把异常值揪出来之后再单独查看这些异常值的原始样子往往就发现是因为日期里混入了0000-00-00这种占位符或者文本说明。处理掉就好了不用追求把所有格式都完美解析。5.3 数据量大时内存爆掉分块读取和类型优化虽然这次项目数据量只有十几万行但我之前接过几百万行的订单数据在这里分享两个实用技巧。第一个是分块读取pd.read_csv支持chunksize参数可以分批把数据读进来处理避免一次加载全部导致内存不足。chunk_iter pd.read_csv(big_sales_data.csv, encodinggbk, chunksize100000) processed_chunks [] for chunk in chunk_iter: # 对每个chunk做同样的清洗 chunk clean_function(chunk) processed_chunks.append(chunk) df pd.concat(processed_chunks, ignore_indexTrue)第二个技巧是类型优化pandas默认会把字符串列存成object类型很吃内存。对于像order_status这种取值有限的列可以转成category类型内存占用能少一大截。同样能转成int32的不要用默认的int64。df[order_status] df[order_status].astype(category) df[quantity] df[quantity].astype(int32)5.4 指标对不上账永远保留一份清洗前的原始备份这是我最想强调的一个习惯。我在每次分析项目开始前都会把原始文件做一份只读备份放在单独目录里任何清洗操作都在副本上进行绝不在原始文件上改。这个习惯救过我很多次最典型的情况就是分析做到一半发现清洗逻辑写错了比如去重的时候误删了正常数据如果没有原始备份那整个分析就只能推倒重来。另外每次清洗步骤完成后我都输出一份数据质量报告记录当前数据的行数、列数、缺失值数量、去重后的数量变化。这样万一后面发现数据有问题可以快速定位是哪一步搞错了而不是在几千行代码里大海捞针。6. 写在最后几点实操心得这个项目做下来我最大的感受是数据分析的瓶颈往往不在技术而在业务理解。同样的数据不同的人能挖出完全不同的东西差别就在于你是否愿意在写代码之前先花时间想清楚这个数字背后代表什么业务动作。还有一个经验是别指望一次分析就能把所有问题解决掉。数据分析是一个迭代的过程第一次分析找到客单价的问题那下一步就要深入分析为什么客单价会跌是商品结构变了还是促销策略变了这可能需要补充更多数据比如活动数据、流量数据。分析做完一轮新的问题又会浮现这个循环本身就是业务增长的过程。最后分享一个小技巧每次分析项目结束我都会把代码整理成一个可复用的模板把清洗逻辑、常用分析函数、画图样式都封装好。下次再接到类似的数据分析需求我只需要改改列名映射和业务字段两个小时就能出初版报告。这个习惯帮我节省了大量重复劳动的时间而且随着模板越来越完善分析的质量和一致性也在不断提升。数据分析这条路做的项目和踩的坑越多越会觉得它是一门手艺活。希望这篇实战记录能帮你少走一些弯路如果你在实际操作中遇到什么新的坑也欢迎在实践中慢慢摸索出自己的解法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑