销售数据可视化分析项目
销售数据可视化分析项目
一、课程目标
本资料旨在教授如何使用 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_ratedata.append({'日期': dates[_].strftime('%Y-%m-%d'), # 将日期转换为字符串格式'类别': category,'产品': product,'价格': price,'数量': quantity,'销售额': revenue,'利润': profit,'地区': region,'客户类型': customer_type})# 创建 DataFramedf = 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 dfexcept 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%}") # 计算利润率并转换为百分比格式# 保存透视表到 CSVfile_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# 保存交叉表到 CSVfile_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) # 将季度列转换为字符串类型# 保存趋势数据到 CSVfile_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 替代已弃用的 applymappreference_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%}") # 将占比转换为百分比格式# 保存数据到 CSVfile_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 个产品# 保存数据到 CSVfile_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) # 计算每个地区不同客户类型的销售额占比# 保存数据到 CSVfile_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() # 重置索引# 保存数据到 CSVfile_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]}# 创建 DataFramesummary_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%
最畅销类别 电子产品
最畅销地区 西南