简介:这份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,换成xlrd==1.2.0,注意 xlrd 2.x 之后不再支持 xls,这是个高频翻车点。
2.2 数据文件的字段约定
源码默认读的是同目录下的销售明细表,字段大致是:订单编号、下单时间、商品品类、商品名称、单价、销量、销售额、店铺名。这里要提醒一句,不同平台导出的列名不一样,有的是中文,有的是英文,源码里做了一层列名映射。你拿到自己的数据后,第一件事是打开表头对一遍,别急着跑。
| 源码字段 | 常见导出名 | 类型 | 说明 |
|---|---|---|---|
| order_time | 下单时间 / order_date | datetime | 必须能解析成时间 |
| category | 品类 / category_name | str | 美妆大类,如口红、面膜 |
| price | 单价 / unit_price | float | 单位元 |
| sales | 销量 / quantity | int | 件数 |
| amount | 销售额 / total_amount | float | 元 |
如果字段对不上,改源码里的COLUMN_MAP字典即可,不用动后面的逻辑。这一步是整份代码能不能跑通的关键,字段名错一个,后面全是 NaN。
2.3 中文字体配置:不配就是一堆方块
matplotlib 默认字体不含中文,直接出图,标题和坐标轴全是方框。这是新手最容易卡住的地方,不是代码错,是字体没配。
import matplotlib.pyplot as plt # Windows 用 SimHei,macOS 用 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 :lang=zh查一下,没有就装一个思源黑体,再指定字体路径。这一步不做,后面所有图都白出。
3. 数据清洗与聚合:400 行里最值钱的部分
3.1 读数据与类型转换
源码开头用pd.read_excel读文件,然后立刻做类型转换。很多人忽略这一步,导致后面按时间分组时报TypeError。
import pandas as pd # 读 Excel,指定 sheet 名,避免读错表 df = pd.read_excel('sales.xlsx', sheet_name='明细') # 时间列转 datetime,errors='coerce' 把解析失败的置为 NaT df['order_time'] = pd.to_datetime(df['order_time'], errors='coerce') # 数值列转 float,去掉千分位逗号 df['amount'] = df['amount'].astype(str).str.replace(',', '').astype(float) # 丢掉时间或金额为空的行 df = df.dropna(subset=['order_time', 'amount']) print(df.shape, df.dtypes)逻辑说明:errors='coerce'是关键参数,遇到「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', ascending=False) # 价格带分箱: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'], bins=bins, labels=labels) 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_value=0) # 按天看销售额趋势 df['date'] = df['order_time'].dt.date day_summary = df.groupby('date').agg( total_amount=('amount', 'sum') ).reset_index()逻辑说明:reindex(range(24), fill_value=0)保证 0 到 23 点都有数据,没有下单的小时补 0,否则折线图会断。dt.date提取日期部分,比pd.Grouper(freq='D')更直观,适合按天画柱状图。这里有个细节:如果数据跨了预售和正式期,建议先按日期过滤,只留 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', dpi=150) plt.show()逻辑说明:barh是横向柱状图,invert_yaxis把 y 轴翻转,让排名第一的品类在最上方,符合阅读习惯。figsize控制画布大小,品类多的时候调宽。dpi=150保证导出图清晰,直接贴报告够用。颜色用美妆常见的粉色系,不是必须,但报告里好看一点。保存和显示的顺序要注意,先 savefig 再 show,否则某些后端会保存空白图。
4.2 价格带占比饼图
第二张图看价格带贡献,用饼图直观。
fig, ax = plt.subplots(figsize=(8, 8)) wedges, texts, autotexts = ax.pie( band_summary['total_amount'], labels=band_summary.index, autopct='%1.1f%%', startangle=90, colors=['#F48FB1', '#F06292', '#EC407A', '#C2185B'] ) ax.set_title('双十一美妆各价格带销售额占比') plt.tight_layout() plt.savefig('price_band_pie.png', dpi=150) plt.show()逻辑说明:autopct='%1.1f%%'显示百分比,保留一位小数。startangle=90让第一块从 12 点方向开始,视觉上更规整。颜色列表长度要和价格带数量一致,多一个少一个都会报错。如果某个价格带占比极小,标签会重叠,可以在pie里加pctdistance调整距离,或者干脆改用柱状图。饼图适合看占比,不适合看绝对值,这点心里要有数。
4.3 小时下单量折线图
第三张图找峰值时段,用折线图。
fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(hour_summary.index, hour_summary['order_count'], marker='o', linewidth=2, color='#AD1457') ax.set_xlabel('小时') ax.set_ylabel('订单数') ax.set_title('双十一美妆下单量时段分布') ax.set_xticks(range(0, 24, 2)) # 每两小时一个刻度 ax.grid(axis='y', linestyle='--', alpha=0.5) plt.tight_layout() plt.savefig('hour_trend.png', dpi=150) plt.show()逻辑说明:marker='o'标出每个数据点,方便定位峰值。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', marker='s', label='销量') ax1.set_ylabel('销售额(元)') ax2.set_ylabel('销量(件)') ax1.set_title('美妆品类销售额与销量对比') plt.tight_layout() plt.savefig('amount_vs_sales.png', dpi=150) 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」这种日月年格式会解析失败,errors='coerce'直接把它变 NaT。解决:读文件时加parse_dates参数,或者手动指定format,比如pd.to_datetime(df['order_time'], format='%d-%m-%Y %H:%M')。先拿几行数据单独测一下解析结果,确认没问题再全量跑。
5.3 中文显示方块,图没法用
现象:图出来了,但标题、坐标轴、图例全是方框。原因:matplotlib 默认字体不含中文字形,系统里也没配。解决:按 2.3 节配font.sans-serif,Windows 用 SimHei,macOS 用 Arial Unicode MS。如果配了还不行,用matplotlib.font_manager.findfont('SimHei')查字体路径,返回空说明系统没这个字体,需要换一个存在的。Linux 服务器上尤其常见,装字体后记得清 matplotlib 缓存。
5.4 饼图标签重叠,数字看不清
现象:价格带饼图里,小占比的标签挤在一起,百分比叠字。原因:pie默认标签距离固定,占比小于 5% 的块标签会互相压。解决:加pctdistance=0.85把百分比往里收,或者用labeldistance=1.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', required=True, help='Excel 文件路径') parser.add_argument('--output', default='./output', help='图片输出目录') return parser.parse_args() if __name__ == '__main__': args = parse_args() os.makedirs(args.output, exist_ok=True) 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_ok=True)保证输出目录不存在时自动创建,不会因为目录缺失报错。plot_all是自定义的封装函数,把 4.1 到 4.4 的四张图串起来,输出目录作为参数传进去。这样一份代码可以同时跑多个数据文件,批量出报告。
验证方法上,我习惯用一份已知结果的小数据集做回归测试:手动算一遍品类销售额,和脚本输出对一下,数字一致才说明清洗和聚合逻辑没写错。这一步很多人跳过,结果换了数据才发现分箱边界写反了。参数边界上,pd.cut的 bins 一定要覆盖数据的最小值和最大值,否则会出现 NaN 分组,图里少一块。时间过滤条件要显式写清楚,是全天还是只算 11 号,口径不同结论完全不同。
从那以后我每次拿到新的销售数据,都强制先跑一遍df.describe()和df.dtypes,确认数值范围和类型没问题再往下做,这个习惯帮我省了无数次返工。希望帮到你。
本文还有配套的精品资源,点击获取