简介:基于Python的天猫双十一美妆销售数据分析实战资源,聚焦数据清洗、探索分析与matplotlib可视化,适合希望快速上手Python数据分析的学生、运营及电商从业者。压缩包共含6个文件,包括2个Jupyter Notebook交互式分析文档、原始CSV数据、清洗后的Excel表格、可复现的Python脚本及一份HTML结果报告,整体大小约4.93MB;Notebook便于分步运行,脚本适合直接复用,HTML方便展示结论,足以支撑从数据读取到图表输出的完整演示。已有835人学习下载,是入门数据可视化的高频参考案例。通过约400行代码可完整走通销售数据的处理链路,包括缺失值处理、字段探索、品牌与品类销售排行,以及用折线图、柱状图呈现双十一销售趋势;同时能理解从数据清洗到结果呈现的完整思考路径,便于模仿搭建自己的分析项目。整套流程清晰,适合作为毕业设计或课堂练习的参考蓝本。
1. 双十一美妆数据拆解:销售额最高那天不是当天
把天猫双十一美妆销售数据拉出来跑一遍,最先看到的可能和你预期相反:销量最高的单品、销售额最大的品牌、客单价最高的人群,往往不是集中在双十一当天,而是分布在预售期和返场期。这背后是促销节奏、价格锚点和用户决策周期的综合结果,也正是这份约 400 行代码项目最有价值的地方——它把 csv 和 xlsx 两套数据源、清洗流程、matplotlib 可视化和 Pandas 聚合分析串成了一条完整可复现的链路。项目里同时包含.ipynb、.py和.html三种形态,既能在 Jupyter 里逐格调试,也能直接跑脚本出报告。对想通过真实业务数据练手 Python 数据分析、又不想只用鸢尾花和房价数据集的人来说,这是一个贴近电商场景、能直接改参数复用的实战样本。
2. 数据读入与清洗:CSV 和 XLSX 双源头的字段统一
2.1 为什么需要同时处理两种数据格式
电商数据分析的常态是数据散落在多个系统里:运营导出的 csv、财务提供的 Excel、甚至还有手工维护的副本。这个项目里恰好同时出现了双十一淘宝美妆数据.csv和clean_beautymakeup.xlsx,一个偏原始记录、一个是清洗后的版本。直接把两个文件读进来做对比分析,能发现很多隐藏问题,比如同一品牌在不同文件里的名称写法不一致、价格字段一个有单位一个没有、日期格式一个带时分秒一个只有年月日。
常见做法是先各自读入,再统一列名和类型,最后按业务主键合并。我一般会先打印两个文件的列名和dtypes,确认差异再动手,避免后面聚合时踩类型不一致的坑。
import pandas as pd df_raw = pd.read_csv('双十一淘宝美妆数据.csv', encoding='utf-8') df_clean = pd.read_excel('clean_beautymakeup.xlsx') print(df_raw.columns.tolist()) print(df_clean.columns.tolist()) print(df_raw.dtypes) print(df_clean.dtypes)这段代码的逻辑分三步:先分别读取原始 csv 和清洗后的 Excel,然后打印列名确认字段命名差异——原始文件里可能是中文列名、清洗文件里可能是英文列名,最后打印数据类型,重点看日期、价格、销量这三类字段是否已经是数值型或时间型。encoding='utf-8'是处理 csv 时的必填项,如果源文件是用 Excel 另存为的,经常会遇到gbk编码,这时就要改成encoding='gbk',否则读进来就是乱码。
2.2 缺失值和异常值的处理策略
这类销售数据里最常见的脏数据有三类:价格字段为 0 或负数、销量为空、日期格式异常。价格为 0 的极端情况多数是缺货标记、赠品记录或者数据录入错误,直接参与聚合会严重拉低客单价;销量为空的行在按品牌或品类求和时会丢数据,需要先决定是删除还是填充。
# 统一列名映射,方便后续操作 df_raw.columns = ['date', 'brand', 'category', 'price', 'sales_volume'] # 删除价格无效的记录(0 和负数) df_raw = df_raw[df_raw['price'] > 0] # 删除销量缺失的行 df_raw = df_raw.dropna(subset=['sales_volume']) # 时间字段统一为 datetime 类型,并只保留日期部分 df_raw['date'] = pd.to_datetime(df_raw['date']).dt.normalize() # 新增销售额计算列 df_raw['sales_amount'] = df_raw['price'] * df_raw['sales_volume'] print(df_raw.shape) print(df_raw.isnull().sum())逻辑说明:先重命名列名让后续代码好写,然后用布尔索引把price <= 0的行过滤掉——这里用的是df_raw[df_raw['price'] > 0]而不是dropna,因为价格这类数值字段里的异常值和缺失值的处理逻辑不同。dropna(subset=['sales_volume'])代表只关注销量列是否有缺失,其他列的缺失暂时不影响本阶段分析。dt.normalize()是把2024-11-11 08:30:00归一化成2024-11-11,这样按天聚合时不会把同一天的数据拆成多个桶。最后新增的sales_amount是后续所有销售额分析的基础字段。
参数的调整空间在于:如果业务上需要保留价格异常的行做单独研究,可以把过滤条件改成df_raw['price'] >= 0并在聚合前用中位数填充;销量缺失时也可以用fillna(df_raw['sales_volume'].median())替代直接删除,具体取舍看分析目标是趋势还是精确值。这一步结束后应该用df_raw['brand'].nunique()看一下唯一品牌数量,如果比预期少很多,大概率是品牌名里有空格或全半角符号差异。
3. matplotlib 可视化:从折线图到多子图组合
3.1 中文乱码和负号显示的根治方案
matplotlib 默认字体不含中文字符,直接plt.title('销售额趋势')画出来就是一堆方块。这是每个用 matplotlib 做中文可视化的人都会遇到的第一道坎。根治的办法是显式指定系统中文字体,而不是靠rcParams默认值瞎猜。Windows 下用SimHei或Microsoft YaHei,macOS 下用PingFang SC或Heiti TC,Linux 服务器上要看有没有安装wqy-microhei或Noto Sans CJK SC。
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC'] plt.rcParams['axes.unicode_minus'] = False # 验证是否生效 fig, ax = plt.subplots(figsize=(6, 3)) ax.set_title('中文标题测试') fig.savefig('test_cn.png', dpi=100)参数说明:font.sans-serif填的是一个字体列表,matplotlib 会从左到右逐个查找系统里存在的字体,这比只写一个字体名更保险,因为不同环境下同一字体名称可能不可用。axes.unicode_minus必须设为False,否则坐标轴上的负号会显示成方块。确认字体生效后,后续所有图表函数都应该复用同一套rcParams配置,不要在每个绘图函数里重复设置。
3.2 销售额趋势折线图:时间序列在多日促销中的应用
双十一的销售不是单日爆发,而是从预售期就开始爬坡,到当天冲顶,再进入返场期回落。用折线图看全周期的日销售额曲线,能明显看到几个关键拐点:预售定金支付日的小高峰、11 月 10 日晚的预热冲刺、11 月 11 日 0 点后的脉冲式增长。
import pandas as pd import matplotlib.pyplot as plt daily = df_raw.groupby('date')['sales_amount'].sum().reset_index() fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(daily['date'], daily['sales_amount'], color='#c0392b', linewidth=2, marker='o', markersize=4) ax.set_title('双十一周期美妆日销售额趋势', fontsize=14) ax.set_xlabel('日期') ax.set_ylabel('销售额(元)') ax.grid(True, linestyle='--', alpha=0.6) fig.autofmt_xdate(rotation=30) plt.tight_layout() plt.show()这里先用groupby('date')按天聚合销售额总和,生成一个两列的 DataFrame,然后绘图。linewidth=2控制线条粗细,太细在多日跨度下不明显;marker='o'在每个数据点上画圆点,方便精确读数;linestyle='--'的网格线是为了不对主线条造成视觉干扰,alpha=0.6进一步弱化网格存在感。fig.autofmt_xdate(rotation=30)是时间序列绘图的常用处理——日期标签如果横着排很容易重叠,旋转 30 度后既能看清又不挤。
3.3 品牌销量 TOP10 柱状图:横向还是竖向的关键取舍
品牌销量排名用柱状图表达最直观,但当品牌名超过 6 个字符时,竖向柱状图的 x 轴标签必然倾斜甚至重叠。电商数据里品牌名基本都是中英文组合,比如「巴黎欧莱雅」「完美日记」,横向柱状图是更好的选择。
brand_sales = df_raw.groupby('brand')['sales_amount'].sum().nlargest(10) fig, ax = plt.subplots(figsize=(10, 6)) bars = ax.barh(brand_sales.index[::-1], brand_sales.values[::-1], color='#2980b9', alpha=0.85) ax.set_title('销售额 TOP10 品牌', fontsize=14) ax.set_xlabel('总销售额(元)') # 在柱子右侧标注具体数值 for bar in bars: width = bar.get_width() ax.text(width * 1.02, bar.get_y() + bar.get_height() / 2, f'{width:,.0f}', va='center', fontsize=9) plt.tight_layout() plt.show()逻辑说明:nlargest(10)从聚合结果里取出销售额最高的 10 个品牌,[::-1]是 Python 的负步长切片,把顺序反转让最大的品牌排在图表顶部,这是横向柱状图的通用阅读习惯。ax.barh是横向柱状图接口,参数顺序和bar不一样,第一个参数是 y 轴坐标、第二个是长度。遍历bars用bar.get_width()获取每个柱子的数值,然后通过ax.text在柱子末端 1.02 倍的位置标注带千分位格式的文本——f'{width:,.0f}'里的,是千分位分隔符,0f表示不保留小数。
表格:柱状图常用参数速查
| 参数 | 取值示例 | 作用 | 使用场景 |
|---|---|---|---|
alpha | 0.8 | 柱子整体透明度 | 多系列重叠时需要透视 |
edgecolor | 'white' | 柱子描边颜色 | 深色填充时让边界清晰 |
bottom | 0.5 | y 轴起始偏移 | 对数坐标或突出非零基线 |
width | 0.7 | 柱子宽度占比 | 品牌数多时收窄避免粘连 |
3.4 多子图组合:一张图看完四个维度的布局技巧
项目中约 400 行代码里,最值得复用的部分是plt.subplots的多子图组合。用 2x2 的网格同时展示日销售额趋势、品牌 TOP10、品类分布和价格带散点,可以在单屏内完成数据概览。
fig, axes = plt.subplots(2, 2, figsize=(16, 10)) fig.suptitle('天猫双十一美妆销售数据总览', fontsize=16, y=0.98) # 左上:日销售额折线图 axes[0, 0].plot(daily['date'], daily['sales_amount'], color='#c0392b') axes[0, 0].set_title('日销售额趋势') axes[0, 0].tick_params(axis='x', rotation=30) # 右上:品牌 TOP10 横向柱状图 axes[0, 1].barh(brand_sales.index, brand_sales.values, color='#2980b9') # 左下:品类销量占比 category_counts = df_raw['category'].value_counts() axes[1, 0].pie(category_counts.values, labels=category_counts.index, autopct='%.1f%%', startangle=90) # 右下:价格与销量散点图 axes[1, 1].scatter(df_raw['price'], df_raw['sales_volume'], alpha=0.4, s=8, color='#27ae60') axes[1, 1].set_xscale('log') axes[1, 1].set_title('价格 vs 销量') plt.tight_layout(rect=[0, 0, 1, 0.95]) plt.show()这里的核心是axes二维数组的索引方式:axes[0, 0]是左上、axes[0, 1]是右上、axes[1, 0]是左下、axes[1, 1]是右下。fig.suptitle是整个大图的全局标题,y=0.98把标题位置稍微抬高,避免和子图标题重叠。左下角用饼图展示品类占比,autopct='%.1f%%'控制饼图上的百分比显示格式,startangle=90让第一个扇区从 12 点方向开始排。右下角散点的s=8是散点面积参数,数据点较多时调小避免糊成一团;set_xscale('log')是因为美妆价格跨度从 9.9 到 1000 以上,线性坐标会把低价区数据完全压扁。
4. 深入分析:品牌结构、品类关联与价格带分布
4.1 用 Pandas 交叉表分析品牌与品类的销售结构
可视化之前需要先把分析指标算出来。品牌维度的聚合只能回答「谁卖得好」,但要进一步看「卖得好是因为品类布局广还是单品爆款强」,需要用pivot_table或crosstab做品牌 x 品类的透视矩阵。这个矩阵既能看每个品牌在各个品类的铺货情况,也能横向比较不同品牌的品类集中度。
pivot = pd.pivot_table( df_raw, index='brand', columns='category', values='sales_amount', aggfunc='sum', fill_value=0 ) # 计算每个品牌的品类集中度:最大品类销售额 / 品牌总销售额 brand_total = pivot.sum(axis=1) brand_max_cat = pivot.max(axis=1) pivot['concentration'] = (brand_max_cat / brand_total).round(3) # 取出销售额 TOP5 品牌查看品类结构 top5 = pivot.sort_values('total_sales', ascending=False).head(5) print(top5[['category_A', 'category_B', 'category_C', 'concentration']])参数说明:pivot_table的index是行索引维度,columns是列维度,values是需要聚合的指标,aggfunc='sum'决定汇总方式,fill_value=0把没有记录的交叉单元格填成 0,否则会出现大量 NaN,后续计算集中度时会被 Pandas 跳过。concentration集中度是一个 0 到 1 之间的指标,越接近 1 说明品牌越依赖某个单一品类,越接近 0 说明品类结构越均衡。sort_values('total_sales')前需要先把各品牌的合计列加入 DataFrame,否则排序没有依据。
提示:
pivot_table的aggfunc也可以传多个函数,比如['sum', 'mean', 'count'],同时看总销售额、平均客单价和成交笔数三项指标。品牌太多时先按总销售额排序再取前 20 个做热力图,否则矩阵过宽无法阅读。
4.2 价格带分桶与销量分布:离散化揭示的真实需求区间
电商数据分析里,价格不是连续变量直接看,而是要先分桶看区间效应。美妆品类尤其明显:50 元以下往往是眉笔、唇釉等小物件,冲动消费为主;100 到 300 元是护肤品的主力价格带;500 元以上则是高端精华和礼盒的天下。用pd.cut把价格离散化成 5 个区间,再按区间聚合销量和销售额,能直接看出哪个价格带的转化效率最高。
bins = [0, 50, 150, 300, 500, float('inf')] labels = ['0-50', '50-150', '150-300', '300-500', '500+'] df_raw['price_band'] = pd.cut(df_raw['price'], bins=bins, labels=labels, right=False) band_stats = df_raw.groupby('price_band', observed=True).agg( total_sales=( 'sales_amount', 'sum'), total_volume=('sales_volume', 'sum'), order_count=('sales_volume', 'count') ) band_stats['avg_price'] = band_stats['total_sales'] / band_stats['total_volume'] print(band_stats)pd.cut的三个核心参数:bins是区间边界列表,right=False意味着区间是左闭右开,即[0, 50)包含价格等于 0 的记录但不包含等于 50 的,这样避免区间重叠。labels给每个区间起一个可读性强的名称。groupby(... , observed=True)是 Pandas 2.x 里的要求,当分组列是 Categorical 类型时,observed=True只统计实际出现的类别,否则会输出空类别导致结果里出现一堆 0 行。agg里用named aggregation语法,每个指标以新列名=(原列名, 聚合函数)的形式传给agg,比字典方式更清晰,也支持对同一列做不同聚合。
从输出结果看行,order_count在这里用的是count,它统计的是每个区间内的记录条数,代表成交笔数;如果sales_volume本身就是含多件购买的累计数值,成交笔数应该单独用订单号列去重统计,而不能用count代替。
4.3 相关系数矩阵:找出销售数据中的隐藏关联
价格、销量、销售额三个原始指标之间必然高度相关,但引入「单价 x 折扣力度」「销量 x 评价数」这类衍生字段后,相关矩阵会揭示更有意思的关系。美妆品类常见的结论是:价格与销量呈中度负相关,但个别高端品牌因为品牌溢价反而出现正相关——这正是异常值和分层分析的价值所在。
import numpy as np df_raw['is_high_end'] = (df_raw['price'] >= 300).astype(int) corr_df = df_raw[['price', 'sales_volume', 'sales_amount', 'is_high_end']].corr() fig, ax = plt.subplots(figsize=(6, 5)) im = ax.imshow(corr_df, cmap='coolwarm', vmin=-1, vmax=1) ax.set_xticks(range(len(corr_df.columns))) ax.set_yticks(range(len(corr_df.columns))) ax.set_xticklabels(corr_df.columns, rotation=45, ha='right') ax.set_yticklabels(corr_df.columns) for i in range(len(corr_df.columns)): for j in range(len(corr_df.columns)): ax.text(j, i, f'{corr_df.iloc[i, j]:.2f}', ha='center', va='center', fontsize=10, color='black') plt.colorbar(im, shrink=0.8) plt.title('核心指标相关系数矩阵') plt.tight_layout() plt.show()逻辑说明:is_high_end是价格是否大于等于 300 的哑变量,把它放进相关矩阵是为了验证「高端产品是否真的卖得少」这个假设。corr()默认计算皮尔逊相关系数,对线性关系敏感。热力图的颜色映射用coolwarm,蓝色代表负相关、红色代表正相关,vmin=-1, vmax=1把色彩范围固定住,不然系数都在 0.9 左右时颜色会糊成一片。双重for循环在每个单元格中心标注具体数值,f'{corr_df.iloc[i, j]:.2f}'保留两位小数。
如果只想看某个品牌的数据,在计算相关矩阵前先加一行df_brand = df_raw[df_raw['brand'] == '完美日记'],对比整体和单品牌的相关性差异,这是分群分析的起点。
5. 用函数封装把可视化流程变成可复用模块
5.1 从脚本到函数:消除重复代码的通用绘图模板
400 行代码如果全部平铺在.ipynb里,换一个数据文件重跑时改动量会非常大。把数据清洗和可视化拆成独立函数,是把这个项目从「跑通一次」升级为「换个数据也能用」的关键一步。下面是一个通用的销售趋势绘图函数,参数留好了日期列、金额列和数据源的接口。
def plot_sales_trend(df, date_col, value_col, title='销售趋势', figsize=(12, 5)): """按指定时间列聚合数值列并绘制折线图 Args: df: 含日期和数值列的 DataFrame date_col: 日期列名,需为 datetime 类型 value_col: 需要聚合的数值列名 title: 图表标题 figsize: 画布尺寸 """ daily = df.groupby(date_col)[value_col].sum().reset_index() fig, ax = plt.subplots(figsize=figsize) ax.plot(daily[date_col], daily[value_col], color='#c0392b', linewidth=2, marker='o', markersize=4) ax.set_title(title) ax.set_xlabel(date_col) ax.set_ylabel(value_col) ax.grid(True, linestyle='--', alpha=0.6) fig.autofmt_xdate(rotation=30) plt.tight_layout() return fig, ax函数设计的关键在参数抽象:date_col和value_col分开传,换一个数据集时不需要改函数体,只需要改调用时的列名。返回值是(fig, ax)而不是直接plt.show(),这样调用方可以决定是保存图片、叠加更多图层还是嵌入报告。默认参数figsize=(12, 5)满足大多数场景,高频使用的颜色、线宽也写成默认值而不是每处硬编码。
调用方式:
fig, ax = plot_sales_trend(df_raw, 'date', 'sales_amount') fig.savefig('daily_sales.png', dpi=200, bbox_inches='tight')bbox_inches='tight'会把多余的空白边裁掉,避免图表周围出现大片白边。dpi=200是适合 PPT 或公众号配图的分辨率,如果只是屏幕看 100 dpi 就够了,文件体积也更小。
5.2 主题风格统一切换与商业图表样式
matplotlib 默认样式偏学术,直接用在业务汇报里显得朴素。好的做法是在程序入口一次性加载自定义样式,而不是在每个绘图函数里逐项修改rcParams。先创建mystyle.mplstyle文件:
axes.spines.top: False axes.spines.right: False axes.grid: True grid.linestyle: -- grid.alpha: 0.5 axes.titlesize: 16 axes.labelsize: 12 legend.frameon: False figure.dpi: 120 savefig.dpi: 200然后在主程序里用一行代码加载:
import matplotlib as mpl mpl.rc_file('mystyle.mplstyle') # 局部覆盖样式 with mpl.rc_context({'lines.linewidth': 1.5}): fig, ax = plot_sales_trend(df_raw, 'date', 'sales_amount')rc_file的好处是样式集中在单独的配置文件里,业务逻辑和图表样式彻底分离。rc_context用于局部覆盖,比如某个图表需要突出粗线条时,在with代码块里临时修改lines.linewidth,退出代码块后自动恢复。
5.3 用 DataFrame.style 快速生成简化版 HTML 报告
项目里包含.html报告文件,除了用nbconvert转换 Jupyter Notebook,Pandas 自带的to_html也能快速把分析结果输出为可分享的网页。加上style模块的格式化后,表格的可读性能达到基础汇报标准。
brand_summary = df_raw.groupby('brand').agg( total_sales=('sales_amount', 'sum'), total_volume=('sales_volume', 'sum'), avg_price=('sales_amount', lambda x: x.sum() / df_raw.loc[x.index, 'sales_volume'].sum()) ).sort_values('total_sales', ascending=False).head(10) html_out = (brand_summary.style .format({'total_sales': '¥{:,.0f}', 'total_volume': '{:,.0f}', 'avg_price': '¥{:,.1f}'}) .background_gradient(subset=['total_sales'], cmap='YlOrRd') .set_table_attributes('class="table table-striped"') .to_html()) with open('brand_report.html', 'w', encoding='utf-8') as f: f.write(f'<meta charset="utf-8">{html_out}')这里用lambda计算加权平均价格,因为直接用mean会把高价低量品牌抬得太高,sales_amount 总和 / sales_volume 总和才是真实的加权客单价。background_gradient按销售额值从浅黄到深红渐变填充单元格,方便快速定位头部品牌。set_table_attributes写入 Bootstrap 的表格样式类名,生成的 HTML 放进任何带 Bootstrap 的页面里就能直接渲染出美化后的表格。写入文件时手动加<meta charset="utf-8">是防止浏览器用错误编码解析中文内容,不写的话 Chrome 可能默认按Windows-1252解码,品牌名全部乱码。
本文还有配套的精品资源,点击获取