销售数据可视化分析项目
一、课程目标
本资料旨在教授如何使用 Python 进行销售数据的模拟生成、加载、分析和可视化,通过一系列实际需求案例,让学员掌握 Pandas、NumPy、Matplotlib 和 Seaborn 等库的基本使用方法。
二、课程内容
1. 导入必要的库和设置
import pandas as pd # 导入 Pandas 库,用于数据处理和分析
import numpy as np # 导入 NumPy 库,用于数值计算
import matplotlib.pyplot as plt # 导入 Matplotlib 库,用于数据可视化
import os # 导入 os 库,用于操作系统相关操作,如文件和目录管理
import seaborn as sns # 导入 Seaborn 库,用于更美观的数据可视化
from datetime import datetime, timedelta # 导入 datetime 和 timedelta 类,用于日期和时间处理
# 设置中文字体,确保图表中的中文正常显示
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题
# 创建必要的目录
os.makedirs('data', exist_ok=True) # 创建 data 目录,用于保存数据文件
os.makedirs('img', exist_ok=True) # 创建 img 目录,用于保存图表文件
2. 需求 1: 模拟销售数据并保存为 CSV 文件
def generate_sales_data():
"""生成模拟销售数据并保存为 CSV 文件"""
# 设置随机种子,确保结果可重现
np.random.seed(42)
# 生成 1000 条销售记录
n_samples = 1000
# 生成日期数据(过去一年的随机日期)
start_date = datetime.now() - timedelta(days=365) # 计算一年前的日期
dates = [start_date + timedelta(days=np.random.randint(0, 365)) for _ in range(n_samples)] # 生成随机日期列表
# 生成产品类别
categories = ['电子产品', '服装', '食品', '家居', '图书']
products = {
'电子产品': ['手机', '笔记本电脑', '平板电脑', '耳机', '相机'],
'服装': ['T恤', '牛仔裤', '连衣裙', '外套', '鞋子'],
'食品': ['巧克力', '饼干', '水果', '饮料', '零食'],
'家居': ['沙发', '餐桌', '床', '台灯', '吸尘器'],
'图书': ['小说', '教材', '漫画', '传记', '工具书']
}
# 为每个类别设置基本价格范围
base_prices = {
'电子产品': (1000, 5000),
'服装': (50, 500),
'食品': (10, 100),
'家居': (500, 3000),
'图书': (20, 200)
}
# 生成销售数据
data = []
for _ in range(n_samples):
category = np.random.choice(categories) # 随机选择一个产品类别
product = np.random.choice(products[category]) # 从选定类别中随机选择一个产品
price = np.random.randint(base_prices[category][0], base_prices[category][1]) # 随机生成产品价格
quantity = np.random.randint(1, 10) # 随机生成销售数量
region = np.random.choice(['华东', '华北', '华南', '西南', '西北', '东北', '中部']) # 随机选择销售地区
customer_type = np.random.choice(['个人', '企业']) # 随机选择客户类型
# 计算销售额
revenue = price * quantity
# 计算利润(电子产品和家居利润率较高,食品较低)
if category in ['电子产品', '家居']:
profit_rate = np.random.uniform(0.2, 0.4) # 电子产品和家居的利润率在 20% - 40% 之间
elif category == '食品':
profit_rate = np.random.uniform(0.05, 0.15) # 食品的利润率在 5% - 15% 之间
else:
profit_rate = np.random.uniform(0.1, 0.3) # 其他类别的利润率在 10% - 30% 之间
profit = revenue * profit_rate
data.append({
'日期': dates[_].strftime('%Y-%m-%d'), # 将日期转换为字符串格式
'类别': category,
'产品': product,
'价格': price,
'数量': quantity,
'销售额': revenue,
'利润': profit,
'地区': region,
'客户类型': customer_type
})
# 创建 DataFrame
df = pd.DataFrame(data)
# 保存数据到 CSV 文件
file_path = 'data/sales_data.csv'
df.to_csv(file_path, index=False, encoding='utf-8-sig') # 将 DataFrame 保存为 CSV 文件
print(f"销售数据已保存到 {file_path}")
return df
generate_sales_data()
销售数据已保存到 data/sales_data.csv
日期 | 类别 | 产品 | 价格 | 数量 | 销售额 | 利润 | 地区 | 客户类型 | |
---|---|---|---|---|---|---|---|---|---|
0 | 2024-10-21 | 食品 | 水果 | 42 | 6 | 252 | 15.987250 | 华北 | 企业 |
1 | 2025-06-24 | 服装 | 外套 | 435 | 8 | 3480 | 525.229896 | 华北 | 企业 |
2 | 2025-04-07 | 图书 | 小说 | 36 | 9 | 324 | 62.958137 | 华南 | 个人 |
3 | 2024-10-25 | 食品 | 巧克力 | 78 | 2 | 156 | 19.201533 | 东北 | 个人 |
4 | 2024-09-20 | 电子产品 | 笔记本电脑 | 3635 | 3 | 10905 | 3797.384136 | 华南 | 个人 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
995 | 2024-08-17 | 电子产品 | 笔记本电脑 | 1132 | 6 | 6792 | 1398.024382 | 中部 | 个人 |
996 | 2025-04-29 | 食品 | 巧克力 | 27 | 2 | 54 | 7.623330 | 西北 | 企业 |
997 | 2024-11-26 | 图书 | 小说 | 173 | 1 | 173 | 27.368579 | 华东 | 企业 |
998 | 2025-07-01 | 服装 | 牛仔裤 | 265 | 2 | 530 | 105.190907 | 华南 | 个人 |
999 | 2025-06-08 | 服装 | T恤 | 296 | 6 | 1776 | 342.128440 | 东北 | 企业 |
1000 rows × 9 columns
3. 需求 2: 加载销售数据
def load_sales_data():
"""加载销售数据"""
try:
df = pd.read_csv('data/sales_data.csv', encoding='utf-8-sig') # 尝试读取 CSV 文件
# 转换日期列
df['日期'] = pd.to_datetime(df['日期']) # 将日期列转换为 Pandas 的日期时间类型
print("数据加载成功")
return df
except FileNotFoundError:
print("数据文件不存在,正在生成模拟数据...")
return generate_sales_data() # 如果文件不存在,调用 generate_sales_data 函数生成模拟数据
# 加载数据
df = load_sales_data()
数据加载成功
4. 需求 3: 按产品类别分析销售情况,生成透视表并保存为 CSV
def analyze_sales_by_category(df):
"""按产品类别分析销售情况"""
# 创建透视表
pivot_table = pd.pivot_table(
df,
index='类别', # 以产品类别为行索引
values=['销售额', '利润', '数量'], # 分析的数值列
aggfunc={
'销售额': 'sum', # 对销售额进行求和
'利润': 'sum', # 对利润进行求和
'数量': 'sum' # 对数量进行求和
}
)
# 计算利润率
pivot_table['利润率'] = (pivot_table['利润'] / pivot_table['销售额']).map(lambda x: f"{x:.2%}") # 计算利润率并转换为百分比格式
# 保存透视表到 CSV
file_path = 'data/category_analysis.csv'
pivot_table.to_csv(file_path, encoding='utf-8-sig') # 将透视表保存为 CSV 文件
print(f"类别分析数据已保存到 {file_path}")
# 可视化
fig, axes = plt.subplots(1, 3, figsize=(18, 6)) # 创建包含 3 个子图的图形
# 销售额柱状图
pivot_table['销售额'].plot(kind='bar', ax=axes[0], title='按类别销售额', color='skyblue') # 绘制销售额柱状图
for p in axes[0].patches:
axes[0].annotate(f'{p.get_height():,.0f}', # 在柱子上添加销售额数值注释
(p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center',
xytext=(0, 10),
textcoords='offset points')
# 利润柱状图
pivot_table['利润'].plot(kind='bar', ax=axes[1], title='按类别利润', color='lightgreen') # 绘制利润柱状图
for p in axes[1].patches:
axes[1].annotate(f'{p.get_height():,.0f}', # 在柱子上添加利润数值注释
(p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center',
xytext=(0, 10),
textcoords='offset points')
# 利润率饼图
profit_rate = pivot_table['利润'] / pivot_table['销售额']
profit_rate.plot(kind='pie', ax=axes[2], title='按类别利润率', autopct='%1.2f%%', ylabel='') # 绘制利润率饼图
plt.tight_layout() # 调整子图布局
plt.savefig('img/category_analysis.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
return pivot_table
print("\n执行需求 3: 按产品类别分析销售情况")
category_analysis = analyze_sales_by_category(df)
执行需求 3: 按产品类别分析销售情况
类别分析数据已保存到 data/category_analysis.csv
5. 需求 4: 按地区分析销售情况,生成交叉表并保存为 CSV
def analyze_sales_by_region(df):
"""按地区分析销售情况"""
# 创建交叉表 - 地区与产品类别的销售额
cross_tab = pd.crosstab(
index=df['地区'], # 以地区为行索引
columns=df['类别'], # 以产品类别为列索引
values=df['销售额'], # 分析的数值列
aggfunc='sum', # 对销售额进行求和
margins=True, # 显示总计行和列
margins_name='总计'
).fillna(0) # 将缺失值填充为 0
# 保存交叉表到 CSV
file_path = 'data/region_analysis.csv'
cross_tab.to_csv(file_path, encoding='utf-8-sig') # 将交叉表保存为 CSV 文件
print(f"地区分析数据已保存到 {file_path}")
# 可视化 - 热力图
plt.figure(figsize=(12, 8)) # 创建图形
sns.heatmap(cross_tab.iloc[:-1, :-1], annot=True, fmt='.0f', cmap='YlGnBu', cbar=True) # 绘制热力图
plt.title('地区与产品类别的销售额交叉表') # 设置图形标题
plt.tight_layout() # 调整图形布局
plt.savefig('img/region_heatmap.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
return cross_tab
print("\n执行需求 4: 按地区分析销售情况")
region_analysis = analyze_sales_by_region(df)
执行需求 4: 按地区分析销售情况
地区分析数据已保存到 data/region_analysis.csv
6. 需求 5: 按季度分析销售趋势
def analyze_sales_trend(df):
"""按季度分析销售趋势"""
# 提取季度信息
df['季度'] = df['日期'].dt.to_period('Q') # 从日期列中提取季度信息
# 创建透视表
trend_pivot = pd.pivot_table(
df,
index='季度', # 以季度为行索引
values=['销售额', '利润', '数量'], # 分析的数值列
aggfunc='sum' # 对销售额、利润和数量进行求和
).reset_index() # 重置索引
# 转换季度为字符串格式
trend_pivot['季度'] = trend_pivot['季度'].astype(str) # 将季度列转换为字符串类型
# 保存趋势数据到 CSV
file_path = 'data/sales_trend.csv'
trend_pivot.to_csv(file_path, index=False, encoding='utf-8-sig') # 将透视表保存为 CSV 文件
print(f"销售趋势数据已保存到 {file_path}")
# 可视化 - 折线图
plt.figure(figsize=(12, 6)) # 创建图形
# 销售额趋势
plt.subplot(2, 1, 1) # 创建第一个子图
plt.plot(trend_pivot['季度'], trend_pivot['销售额'], marker='o', color='skyblue', label='销售额') # 绘制销售额折线图
for x, y in zip(trend_pivot['季度'], trend_pivot['销售额']):
plt.annotate(f'{y:,.0f}', (x, y), textcoords='offset points', # 在折线上添加销售额数值注释
xytext=(0, 10), ha='center')
plt.title('季度销售趋势', loc = 'right') # 设置子图标题
plt.ylabel('销售额') # 设置 y 轴标签
plt.legend() # 显示图例
# 利润率趋势
plt.subplot(2, 1, 2) # 创建第二个子图
profit_rate = trend_pivot['利润'] / trend_pivot['销售额']
plt.plot(trend_pivot['季度'], profit_rate, marker='o', color='lightgreen', label='利润率') # 绘制利润率折线图
for x, y in zip(trend_pivot['季度'], profit_rate):
plt.annotate(f'{y:.2%}', (x, y), textcoords='offset points', # 在折线上添加利润率数值注释
xytext=(0, 10), ha='center')
plt.xlabel('季度') # 设置 x 轴标签
plt.ylabel('利润率') # 设置 y 轴标签
plt.legend() # 显示图例
plt.tight_layout() # 调整子图布局
plt.savefig('img/sales_trend.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
return trend_pivot
print("\n执行需求 5: 按季度分析销售趋势")
sales_trend = analyze_sales_trend(df)
执行需求 5: 按季度分析销售趋势
销售趋势数据已保存到 data/sales_trend.csv
7. 需求 6: 分析不同客户类型的购买偏好
def analyze_customer_preference(df):
"""分析不同客户类型的购买偏好"""
# 创建交叉表 - 客户类型与产品类别的购买数量
preference_crosstab = pd.crosstab(
index=df['客户类型'], # 以客户类型为行索引
columns=df['类别'], # 以产品类别为列索引
values=df['数量'], # 分析的数值列
aggfunc='sum', # 对购买数量进行求和
margins=True, # 显示总计行和列
margins_name='总计'
).fillna(0) # 将缺失值填充为 0
# 计算占比 - 使用 DataFrame.map 替代已弃用的 applymap
preference_percentage = preference_crosstab.div(preference_crosstab['总计'], axis=0) # 计算每个客户类型对各产品类别的购买数量占比
for col in preference_percentage.columns:
preference_percentage[col] = preference_percentage[col].map(lambda x: f"{x:.2%}") # 将占比转换为百分比格式
# 保存数据到 CSV
file_path = 'data/customer_preference.csv'
preference_percentage.to_csv(file_path, encoding='utf-8-sig') # 将数据保存为 CSV 文件
print(f"客户偏好数据已保存到 {file_path}")
# 可视化 - 堆叠柱状图
plt.figure(figsize=(12, 6)) # 创建图形
preference_crosstab.iloc[:-1, :-1].plot(kind='bar', stacked=True, ax=plt.gca()) # 绘制堆叠柱状图
plt.title('不同客户类型的购买偏好') # 设置图形标题
plt.xlabel('客户类型') # 设置 x 轴标签
plt.ylabel('购买数量') # 设置 y 轴标签
plt.legend(title='产品类别') # 显示图例
plt.tight_layout() # 调整图形布局
plt.savefig('img/customer_preference.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
return preference_percentage
print("\n执行需求 6: 分析不同客户类型的购买偏好")
customer_preference = analyze_customer_preference(df)
执行需求 6: 分析不同客户类型的购买偏好
客户偏好数据已保存到 data/customer_preference.csv
8. 需求 7: 分析销售额最高的前10个产品
def analyze_top_products(df):
"""分析销售额最高的前10个产品"""
# 按产品分组并计算总销售额
top_products = df.groupby('产品')['销售额'].sum().sort_values(ascending=False).head(10).reset_index() # 按产品分组,计算总销售额,排序并取前 10 个产品
# 保存数据到 CSV
file_path = 'data/top_products.csv'
top_products.to_csv(file_path, index=False, encoding='utf-8-sig') # 将数据保存为 CSV 文件
print(f"Top 10 产品数据已保存到 {file_path}")
# 可视化 - 水平柱状图
plt.figure(figsize=(10, 6)) # 创建图形
top_products.plot(kind='barh', x='产品', y='销售额', ax=plt.gca(), legend=False) # 绘制水平柱状图
plt.title('销售额最高的前10个产品') # 设置图形标题
plt.xlabel('销售额') # 设置 x 轴标签
plt.ylabel('产品') # 设置 y 轴标签
for i, v in enumerate(top_products['销售额']):
plt.text(v + 1000, i, f'{v:,.0f}', va='center') # 在柱子上添加销售额数值注释
plt.tight_layout() # 调整图形布局
plt.savefig('img/top_products.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
return top_products
print("\n执行需求 7: 分析销售额最高的前10个产品")
top_products = analyze_top_products(df)
执行需求 7: 分析销售额最高的前10个产品
Top 10 产品数据已保存到 data/top_products.csv
9. 需求 8: 分析各地区的客户类型分布
def analyze_region_customer_type(df):
"""分析各地区的客户类型分布"""
# 创建透视表
region_customer_pivot = pd.pivot_table(
df,
index='地区', # 以地区为行索引
columns='客户类型', # 以客户类型为列索引
values='销售额', # 分析的数值列
aggfunc='sum', # 对销售额进行求和
fill_value=0 # 将缺失值填充为 0
)
# 计算各地区客户类型占比
region_customer_percentage = region_customer_pivot.div(region_customer_pivot.sum(axis=1), axis=0) # 计算每个地区不同客户类型的销售额占比
# 保存数据到 CSV
file_path = 'data/region_customer_type.csv'
region_customer_percentage.to_csv(file_path, encoding='utf-8-sig') # 将数据保存为 CSV 文件
print(f"地区客户类型分布数据已保存到 {file_path}")
# 可视化 - 饼图
fig, axes = plt.subplots(2, 4, figsize=(16, 8)) # 创建包含 8 个子图的图形
axes = axes.flatten() # 将子图数组展平
regions = region_customer_percentage.index.tolist() # 获取地区列表
for i, region in enumerate(regions):
if i < len(axes):
region_customer_percentage.loc[region].plot(
kind='pie',
ax=axes[i],
autopct='%1.1f%%',
title=f'{region} 客户类型分布',
ylabel=''
) # 为每个地区绘制客户类型分布饼图
plt.tight_layout() # 调整子图布局
plt.savefig('img/region_customer_type.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
return region_customer_percentage
print("\n执行需求 8: 分析各地区的客户类型分布")
region_customer_type = analyze_region_customer_type(df)
执行需求 8: 分析各地区的客户类型分布
地区客户类型分布数据已保存到 data/region_customer_type.csv
10. 需求 9: 分析价格与销售额、利润的关系
def analyze_price_relationship(df):
"""分析价格与销售额、利润的关系"""
# 计算每个产品的平均价格、总销售额和总利润
product_stats = df.groupby('产品').agg({
'价格': 'mean', # 计算每个产品的平均价格
'销售额': 'sum', # 计算每个产品的总销售额
'利润': 'sum' # 计算每个产品的总利润
}).reset_index() # 重置索引
# 保存数据到 CSV
file_path = 'data/price_relationship.csv'
product_stats.to_csv(file_path, index=False, encoding='utf-8-sig') # 将数据保存为 CSV 文件
print(f"价格关系数据已保存到 {file_path}")
# 可视化 - 散点图矩阵
g = sns.pairplot(product_stats, vars=['价格', '销售额', '利润'], hue='产品', palette='husl') # 绘制散点图矩阵
plt.suptitle('价格、销售额和利润的关系', y=1.02) # 设置图形总标题
plt.tight_layout() # 调整图形布局
plt.savefig('img/price_relationship.png') # 保存图形为 PNG 文件
plt.show()
plt.close() # 关闭图形
# 计算相关系数矩阵
correlation = product_stats[['价格', '销售额', '利润']].corr() # 计算价格、销售额和利润之间的相关系数矩阵
return correlation
print("\n执行需求 9: 分析价格与销售额、利润的关系")
price_relationship = analyze_price_relationship(df)
执行需求 9: 分析价格与销售额、利润的关系
价格关系数据已保存到 data/price_relationship.csv
11. 需求 10: 综合分析报告 - 生成摘要统计信息
def generate_summary_report(df):
"""生成综合分析报告"""
# 计算关键指标
total_sales = df['销售额'].sum() # 计算总销售额
total_profit = df['利润'].sum() # 计算总利润
total_quantity = df['数量'].sum() # 计算总销售量
average_price = df['价格'].mean() # 计算平均价格
profit_rate = total_profit / total_sales # 计算利润率
# 找出最畅销的产品类别和地区
top_category = df.groupby('类别')['销售额'].sum().idxmax() # 找出销售额最高的产品类别
top_region = df.groupby('地区')['销售额'].sum().idxmax() # 找出销售额最高的地区
# 创建摘要数据
summary_data = {
'指标': ['总销售额', '总利润', '总销售量', '平均价格', '利润率', '最畅销类别', '最畅销地区'],
'数值': [
f'{total_sales:,.2f}',
f'{total_profit:,.2f}',
f'{total_quantity:,}',
f'{average_price:,.2f}',
f'{profit_rate:.2%}',
top_category,
top_region
]
}
# 创建 DataFrame
summary_df = pd.DataFrame(summary_data)
# 保存摘要报告
file_path = 'data/summary_report.csv'
summary_df.to_csv(file_path, index=False, encoding='utf-8-sig') # 将摘要报告保存为 CSV 文件
print(f"摘要报告已保存到 {file_path}")
return summary_df
print("\n执行需求 10: 生成综合分析报告")
summary_report = generate_summary_report(df)
print("\n数据分析项目完成!")
print("数据文件已保存到 data 目录")
print("图表已保存到 img 目录")
# 打印摘要报告
print("\n项目摘要报告:")
print(summary_report.to_string(index=False))
执行需求 10: 生成综合分析报告
摘要报告已保存到 data/summary_report.csv
数据分析项目完成!
数据文件已保存到 data 目录
图表已保存到 img 目录
项目摘要报告:
指标 数值
总销售额 4,839,533.00
总利润 1,377,496.93
总销售量 5,023
平均价格 978.16
利润率 28.46%
最畅销类别 电子产品
最畅销地区 西南