Python数据分析实战:双十一美妆销售数据清洗与可视化

发布时间:2026/10/12 17:53:23
Python数据分析实战:双十一美妆销售数据清洗与可视化
简介这份Python示例源码面向数据分析初学者与高校学生以天猫双十一美妆销售数据为对象演示从数据清洗、转换到可视化呈现的完整分析流程适合用作课程大作业或数据分析入门练手项目。压缩包共17个文件、约5.08MB包含py主程序、ipynb笔记本与html导出报告、csv与xlsx原始及清洗后数据另有xml配置、png图表、url快捷方式等辅助文件结构清晰便于对照运行。源码约400行围绕pandas完成分组、排序、筛选与聚合并借助matplotlib绘制直方图、折线图、散点图等展示品牌、类别、价格区间等维度的销售变化同时涉及数据预处理与自动化分析思路。目前已有149人学习下载读者可据此掌握电商数据清洗、指标拆解与图表表达方法并参考其目录组织与代码结构快速搭建自己的分析脚本或完成大作业报告。1. 双十一美妆数据怎么拆一份 400 行 Python 源码能跑出什么结论每年双十一结束后台都会堆一批美妆销售明细字段散、口径乱用 Excel 透视表拉几下就卡死。这份约 400 行的 Python 示例源码就是冲着这个场景写的读一份电商美妆销售数据用 pandas 做清洗聚合再用 matplotlib 出图把「哪个品类卖得最好、哪个价格带贡献最大、哪个时段下单最猛」这几件事讲清楚。它属于典型的 Python 语言大作业级别项目代码量不大但数据分析自动化的完整链路都在读文件、清洗、分组、可视化、导出。适合正在找数据分析类大作业参考的学生也适合想拿一份能直接跑通的模板改自己业务数据的从业者。下面按「先跑通、再拆解、最后避坑」的顺序走一遍。2. 环境与数据准备把源码跑起来前的三件事2.1 依赖清单与版本选择这份源码只依赖三个库pandas 做数据处理matplotlib 做可视化openpyxl 或 xlrd 负责读 Excel。常见做法是建一个独立虚拟环境避免和系统里其他项目的 pandas 版本打架。我一般用 venv不折腾 conda因为这份代码没有复杂的科学计算依赖。# 创建虚拟环境Python 3.8 以上都行 python -m venv venv # 激活Windows venv\Scripts\activate # 激活macOS / Linux source venv/bin/activate # 安装依赖版本不用锁太死 pip install pandas matplotlib openpyxl逻辑说明venv 隔离环境是为了防止 pandas 版本冲突导致groupby行为差异。参数上pandas 建议 1.3 以上因为源码里用到了pd.Grouper做时间分组老版本对频率字符串的解析不一致。matplotlib 用 3.4 以上即可主要是为了中文字体配置接口稳定。openpyxl 负责.xlsx如果你的数据是老的.xls换成xlrd1.2.0注意 xlrd 2.x 之后不再支持 xls这是个高频翻车点。2.2 数据文件的字段约定源码默认读的是同目录下的销售明细表字段大致是订单编号、下单时间、商品品类、商品名称、单价、销量、销售额、店铺名。这里要提醒一句不同平台导出的列名不一样有的是中文有的是英文源码里做了一层列名映射。你拿到自己的数据后第一件事是打开表头对一遍别急着跑。源码字段常见导出名类型说明order_time下单时间 / order_datedatetime必须能解析成时间category品类 / category_namestr美妆大类如口红、面膜price单价 / unit_pricefloat单位元sales销量 / quantityint件数amount销售额 / total_amountfloat元如果字段对不上改源码里的COLUMN_MAP字典即可不用动后面的逻辑。这一步是整份代码能不能跑通的关键字段名错一个后面全是 NaN。2.3 中文字体配置不配就是一堆方块matplotlib 默认字体不含中文直接出图标题和坐标轴全是方框。这是新手最容易卡住的地方不是代码错是字体没配。import matplotlib.pyplot as plt # Windows 用 SimHeimacOS 用 Arial Unicode MS 或 PingFang SC plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 负号正常显示 # 验证字体是否生效 print(plt.rcParams[font.sans-serif])逻辑说明font.sans-serif是字体候选列表matplotlib 会按顺序找系统里存在的字体。axes.unicode_minus设 False 是为了让负号用 ASCII 减号渲染否则负值坐标轴也会变方块。如果你在 Linux 服务器上跑系统可能没装中文字体需要先fc-list :langzh查一下没有就装一个思源黑体再指定字体路径。这一步不做后面所有图都白出。3. 数据清洗与聚合400 行里最值钱的部分3.1 读数据与类型转换源码开头用pd.read_excel读文件然后立刻做类型转换。很多人忽略这一步导致后面按时间分组时报TypeError。import pandas as pd # 读 Excel指定 sheet 名避免读错表 df pd.read_excel(sales.xlsx, sheet_name明细) # 时间列转 datetimeerrorscoerce 把解析失败的置为 NaT df[order_time] pd.to_datetime(df[order_time], errorscoerce) # 数值列转 float去掉千分位逗号 df[amount] df[amount].astype(str).str.replace(,, ).astype(float) # 丢掉时间或金额为空的行 df df.dropna(subset[order_time, amount]) print(df.shape, df.dtypes)逻辑说明errorscoerce是关键参数遇到「2023-11-11 00:00:00」这种能解析遇到「双十一当天」这种脏数据直接变 NaT不会让整个脚本崩掉。金额列先转字符串再替换逗号是因为有些导出文件把 1,299.00 当文本存直接 astype(float) 会报错。dropna的 subset 只检查这两列其他列缺失不影响分析。跑完打印 shape 和 dtypes确认行数和类型符合预期再往下走。3.2 按品类和价格带分组清洗完就是聚合。源码里做了两个维度的分组品类维度和价格带维度。价格带是自定义分箱用pd.cut实现。# 品类维度销售额和销量汇总 cat_summary df.groupby(category).agg( total_amount(amount, sum), total_sales(sales, sum), order_count(order_id, nunique) ).sort_values(total_amount, ascendingFalse) # 价格带分箱0-100, 100-300, 300-500, 500 bins [0, 100, 300, 500, float(inf)] labels [0-100, 100-300, 300-500, 500] df[price_band] pd.cut(df[price], binsbins, labelslabels) band_summary df.groupby(price_band).agg( total_amount(amount, sum) ).reindex(labels) # 保证顺序逻辑说明agg里用字典指定每列的计算方式nunique统计去重订单数比 count 更准因为一个订单可能有多行商品。pd.cut的 bins 左开右闭float(inf)兜住高价商品。reindex(labels)是为了让价格带按业务顺序排列不然 groupby 会按字符串排序0-100 排在 100-300 后面图就乱了。这一步的参数改动点在于 bins 边界不同品类价格分布不同美妆可以按 100/300/500 分数码就得按 1000/3000/5000 分。3.3 时间维度按小时和按天聚合双十一的核心看点是时段。源码用dt.hour提取小时用pd.Grouper按天聚合。# 按小时看下单量找峰值时段 df[hour] df[order_time].dt.hour hour_summary df.groupby(hour).agg( order_count(order_id, nunique) ).reindex(range(24), fill_value0) # 按天看销售额趋势 df[date] df[order_time].dt.date day_summary df.groupby(date).agg( total_amount(amount, sum) ).reset_index()逻辑说明reindex(range(24), fill_value0)保证 0 到 23 点都有数据没有下单的小时补 0否则折线图会断。dt.date提取日期部分比pd.Grouper(freqD)更直观适合按天画柱状图。这里有个细节如果数据跨了预售和正式期建议先按日期过滤只留 11 月 11 日当天否则趋势图会被预售期拉平看不出峰值。这个过滤条件在源码里是注释掉的需要你自己按业务打开。4. 可视化落地四张图讲清美妆销售结构4.1 品类销售额横向柱状图第一张图是品类销售额排名用横向柱状图品类名放得下不会挤在一起。fig, ax plt.subplots(figsize(10, 6)) ax.barh(cat_summary.index, cat_summary[total_amount], color#E91E63) ax.set_xlabel(销售额元) ax.set_title(双十一美妆各品类销售额排名) ax.invert_yaxis() # 销售额最高的排最上面 plt.tight_layout() plt.savefig(category_amount.png, dpi150) plt.show()逻辑说明barh是横向柱状图invert_yaxis把 y 轴翻转让排名第一的品类在最上方符合阅读习惯。figsize控制画布大小品类多的时候调宽。dpi150保证导出图清晰直接贴报告够用。颜色用美妆常见的粉色系不是必须但报告里好看一点。保存和显示的顺序要注意先 savefig 再 show否则某些后端会保存空白图。4.2 价格带占比饼图第二张图看价格带贡献用饼图直观。fig, ax plt.subplots(figsize(8, 8)) wedges, texts, autotexts ax.pie( band_summary[total_amount], labelsband_summary.index, autopct%1.1f%%, startangle90, colors[#F48FB1, #F06292, #EC407A, #C2185B] ) ax.set_title(双十一美妆各价格带销售额占比) plt.tight_layout() plt.savefig(price_band_pie.png, dpi150) plt.show()逻辑说明autopct%1.1f%%显示百分比保留一位小数。startangle90让第一块从 12 点方向开始视觉上更规整。颜色列表长度要和价格带数量一致多一个少一个都会报错。如果某个价格带占比极小标签会重叠可以在pie里加pctdistance调整距离或者干脆改用柱状图。饼图适合看占比不适合看绝对值这点心里要有数。4.3 小时下单量折线图第三张图找峰值时段用折线图。fig, ax plt.subplots(figsize(12, 5)) ax.plot(hour_summary.index, hour_summary[order_count], markero, linewidth2, color#AD1457) ax.set_xlabel(小时) ax.set_ylabel(订单数) ax.set_title(双十一美妆下单量时段分布) ax.set_xticks(range(0, 24, 2)) # 每两小时一个刻度 ax.grid(axisy, linestyle--, alpha0.5) plt.tight_layout() plt.savefig(hour_trend.png, dpi150) plt.show()逻辑说明markero标出每个数据点方便定位峰值。set_xticks控制刻度密度24 个刻度太挤隔一个显示。grid只开 y 轴虚线辅助读数又不抢眼。这张图通常能看到两个峰零点预售尾款和晚上八点直播场具体峰值取决于数据口径。如果折线锯齿太厉害可以做一次移动平均源码里没做属于可扩展点。4.4 销售额与销量双轴对比第四张图把销售额和销量放一起看「卖得多」和「卖得贵」是不是一回事。fig, ax1 plt.subplots(figsize(10, 6)) ax2 ax1.twinx() # 双轴 ax1.bar(cat_summary.index, cat_summary[total_amount], color#F8BBD0, label销售额) ax2.plot(cat_summary.index, cat_summary[total_sales], color#880E4F, markers, label销量) ax1.set_ylabel(销售额元) ax2.set_ylabel(销量件) ax1.set_title(美妆品类销售额与销量对比) plt.tight_layout() plt.savefig(amount_vs_sales.png, dpi150) plt.show()逻辑说明twinx()创建共享 x 轴的第二个 y 轴柱状图看销售额折线看销量。两个轴的量级差很多所以不能共用一个轴。图例要分别从两个轴取源码里简化了实际用的时候建议合并图例避免读者分不清。这张图的价值在于发现「高销量低销售额」的品类通常是引流款和「低销量高销售额」的利润款形成对比是运营最关心的结论。5. 避坑与排查跑这份源码最容易翻车的五个地方5.1 报错 KeyError: xxx列名对不上现象脚本跑到df[order_time]就报 KeyError提示列不存在。原因你的 Excel 表头和源码里的字段名不一致可能是英文、可能是别名也可能多了空格。解决先print(df.columns.tolist())把真实列名打出来然后改源码顶部的COLUMN_MAP字典或者在读文件后加一句df df.rename(columns{你的列名: order_time})。别硬改后面的代码改映射最省事。5.2 时间列全是 NaT聚合结果为空现象dropna之后 df 变成 0 行或者按小时分组全是空。原因时间列格式特殊比如「2023/11/11 00:00」用默认解析没问题但「11-11-2023」这种日月年格式会解析失败errorscoerce直接把它变 NaT。解决读文件时加parse_dates参数或者手动指定format比如pd.to_datetime(df[order_time], format%d-%m-%Y %H:%M)。先拿几行数据单独测一下解析结果确认没问题再全量跑。5.3 中文显示方块图没法用现象图出来了但标题、坐标轴、图例全是方框。原因matplotlib 默认字体不含中文字形系统里也没配。解决按 2.3 节配font.sans-serifWindows 用 SimHeimacOS 用 Arial Unicode MS。如果配了还不行用matplotlib.font_manager.findfont(SimHei)查字体路径返回空说明系统没这个字体需要换一个存在的。Linux 服务器上尤其常见装字体后记得清 matplotlib 缓存。5.4 饼图标签重叠数字看不清现象价格带饼图里小占比的标签挤在一起百分比叠字。原因pie默认标签距离固定占比小于 5% 的块标签会互相压。解决加pctdistance0.85把百分比往里收或者用labeldistance1.1把标签往外推。更彻底的办法是占比小于 3% 的合并成「其他」或者直接换柱状图。饼图超过 5 个分类就不建议用了这是可视化常识。5.5 导出图片空白savefig 没生效现象plt.savefig执行了但打开图片是白的。原因savefig写在plt.show()之后某些后端 show 会清空画布。解决把savefig挪到show之前这是铁律。另外确认路径有写权限相对路径是相对当前工作目录不是脚本目录用os.path.dirname(__file__)拼绝对路径最稳。如果还空白检查figsize和dpi是否合理极端值可能导致渲染异常。6. 进阶玩法把这份源码改成能复用的分析模板跑通之后这份代码最大的价值不是那四张图而是它给了一个可复用的骨架。我一般会做三件事把它变成自己的工具。第一把字段映射和分箱参数抽到配置文件里用 JSON 或 YAML 存换数据只改配置不改代码。第二把四张图封装成函数主流程只负责读数据和调函数这样加图减图不影响核心逻辑。第三加一个命令行参数用argparse接收文件路径和输出目录变成python analyze.py --input sales.xlsx --output ./reports这种用法脱离 IDE 也能跑。import argparse import os def parse_args(): parser argparse.ArgumentParser(description美妆销售数据分析) parser.add_argument(--input, requiredTrue, helpExcel 文件路径) parser.add_argument(--output, default./output, help图片输出目录) return parser.parse_args() if __name__ __main__: args parse_args() os.makedirs(args.output, exist_okTrue) df load_data(args.input) df clean_data(df) cat_summary, band_summary, hour_summary aggregate(df) plot_all(cat_summary, band_summary, hour_summary, args.output) print(f分析完成图片已输出到 {args.output})逻辑说明argparse让脚本从「改代码跑」变成「传参跑」这是自动化分析的基本功。os.makedirs(exist_okTrue)保证输出目录不存在时自动创建不会因为目录缺失报错。plot_all是自定义的封装函数把 4.1 到 4.4 的四张图串起来输出目录作为参数传进去。这样一份代码可以同时跑多个数据文件批量出报告。验证方法上我习惯用一份已知结果的小数据集做回归测试手动算一遍品类销售额和脚本输出对一下数字一致才说明清洗和聚合逻辑没写错。这一步很多人跳过结果换了数据才发现分箱边界写反了。参数边界上pd.cut的 bins 一定要覆盖数据的最小值和最大值否则会出现 NaN 分组图里少一块。时间过滤条件要显式写清楚是全天还是只算 11 号口径不同结论完全不同。从那以后我每次拿到新的销售数据都强制先跑一遍df.describe()和df.dtypes确认数值范围和类型没问题再往下做这个习惯帮我省了无数次返工。希望帮到你。本文还有配套的精品资源点击获取