当前位置:首页>python>Python 零基础100天—Day78 项目:电商销售数据分析

Python 零基础100天—Day78 项目:电商销售数据分析

  • 2026-10-11 06:54:22
Python 零基础100天—Day78 项目:电商销售数据分析

🐍 Python Day78:电商销售数据分析 — 数据科学综合项目

🕐 预计用时:3-4 小时 | 🎯 目标:用 NumPy + Pandas + Matplotlib + Seaborn 完成完整的数据分析报告


📖 今日目录

  1. 项目背景
  2. 数据生成
  3. 数据清洗
  4. 整体概览
  5. 销售趋势分析
  6. 品类分析
  7. 用户分析
  8. 关联分析
  9. 完整报告
  10. 今日小结

1. 项目背景

假设你是一家电商公司的数据分析师,老板给你一份销售数据,要求你回答以下问题:

问题
分析方向
整体销售情况如何?
总销售额、订单数、客单价
哪些品类卖得最好?
品类销售额排名、占比
销售趋势是上升还是下降?
月度/周度趋势、同比环比
用户有什么特征?
性别、年龄、地区分布
什么时间段订单最多?
小时/星期分布
高价值用户有什么特征?
RFM 分析

2. 数据生成

我们先用代码生成一份模拟电商数据,方便后续分析。

import numpy as np
import pandas as pd
from datetime import datetime, timedelta

np.random.seed(42)

# 生成 10000 条订单数据
n_orders = 10000

# 用户信息(500 个用户)
n_users = 500
user_ids = np.random.randint(1001, 1001 + n_users, n_orders)
genders = np.random.choice(['男', '女'], n_orders, p=[0.55, 0.45])
ages = np.random.normal(30, 8, n_orders).astype(int)
ages = np.clip(ages, 18, 65)

# 商品品类和价格
categories = ['电子产品', '服装', '食品', '家居', '美妆', '图书']
cat_probs = [0.25, 0.20, 0.15, 0.15, 0.15, 0.10]
order_categories = np.random.choice(categories, n_orders, p=cat_probs)

# 品类价格范围
price_ranges = {
    '电子产品': (200, 8000),
    '服装': (50, 2000),
    '食品': (10, 300),
    '家居': (30, 3000),
    '美妆': (30, 1500),
    '图书': (15, 200),
}
amounts = []
for cat in order_categories:
    low, high = price_ranges[cat]
    amounts.append(np.random.uniform(low, high))
amounts = np.round(amounts, 2)

# 数量
quantities = np.random.choice([1, 1, 1, 2, 2, 3], n_orders)

# 日期(2025年全年,含随机下单小时)
start_date = datetime(2025, 1, 1)
end_date = datetime(2025, 12, 31)
days_range = (end_date - start_date).days
order_dates = [start_date + timedelta(days=np.random.randint(0, days_range + 1),
                                       hours=np.random.randint(0, 24)) for _ in range(n_orders)]

# 地区
regions = ['华东', '华南', '华北', '华中', '西南', '西北', '东北']
region_probs = [0.30, 0.20, 0.18, 0.12, 0.08, 0.07, 0.05]
order_regions = np.random.choice(regions, n_orders, p=region_probs)

# 创建 DataFrame
df = pd.DataFrame({
    '订单号': range(100001, 100001 + n_orders),
    '用户ID': user_ids,
    '日期': order_dates,
    '品类': order_categories,
    '单价': amounts,
    '数量': quantities,
    '性别': genders,
    '年龄': ages,
    '地区': order_regions,
})

# 计算总金额
df['总金额'] = df['单价'] * df['数量']

# 保存为 CSV
df.to_csv('ecommerce_sales.csv', index=False, encoding='utf-8-sig')
print(f'数据生成完成!共 {len(df)} 条订单')
print(df.head(10))

3. 数据清洗

import pandas as pd
import numpy as np

df = pd.read_csv('ecommerce_sales.csv', encoding='utf-8-sig')

# === 1. 基本信息 ===
print(df.shape)          # (10000, 10)
print(df.dtypes)         # 查看数据类型
print(df.info())         # 概览

# === 2. 缺失值检查 ===
print(df.isnull().sum())
# 本数据无缺失值(模拟数据很干净)
# 如果有缺失值:
# df['年龄'].fillna(df['年龄'].median(), inplace=True)
# df['地区'].fillna('未知', inplace=True)

# === 3. 重复值检查 ===
print(f'重复行数: {df.duplicated().sum()}')
df.drop_duplicates(inplace=True)

# === 4. 异常值检查 ===
print(f'单价范围: {df["单价"].min():.2f} ~ {df["单价"].max():.2f}')
print(f'数量范围: {df["数量"].min()} ~ {df["数量"].max()}')
print(f'年龄范围: {df["年龄"].min()} ~ {df["年龄"].max()}')

# 删除异常值
df = df[df['单价'] > 0]
df = df[df['数量'] > 0]

# === 5. 数据类型转换 ===
df['日期'] = pd.to_datetime(df['日期'])
df['年'] = df['日期'].dt.year
df['月'] = df['日期'].dt.month
df['星期'] = df['日期'].dt.day_name()
df['小时'] = df['日期'].dt.hour

# === 6. 最终确认 ===
print(f'清洗后数据: {df.shape[0]} 行, {df.shape[1]} 列')
print(df.dtypes)

4. 整体概览

import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
sns.set_theme(style='whitegrid', palette='Set2')

# === 核心指标 ===
total_sales = df['总金额'].sum()
total_orders = len(df)
total_users = df['用户ID'].nunique()
avg_order = total_sales / total_orders

print('=' * 50)
print('📊 电商销售数据 — 整体概览')
print('=' * 50)
print(f'总销售额: ¥{total_sales:,.2f}')
print(f'总订单数: {total_orders:,}')
print(f'独立用户: {total_users:,}')
print(f'客单价:   ¥{avg_order:,.2f}')
print('=' * 50)

# === KPI 卡片图 ===
fig, axes = plt.subplots(1, 4, figsize=(20, 5))

kpis = [
    ('总销售额', f'¥{total_sales/10000:,.1f}万', '
#07c160'),
    ('总订单数', f'{total_orders:,}', '#4d96ff'),
    ('独立用户', f'{total_users}', '#ffd93d'),
    ('客单价', f'¥{avg_order:,.0f}', '#ff6b6b'),
]

for ax, (title, value, color) in zip(axes, kpis):
    ax.text(0.5, 0.5, value, ha='center', va='center',
            fontsize=28, fontweight='bold', color=color,
            transform=ax.transAxes)
    ax.text(0.5, 0.15, title, ha='center', va='center',
            fontsize=14, color='#666', transform=ax.transAxes)
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1)
    ax.axis('off')
    ax.set_facecolor('#f8f9fa')
    for spine in ax.spines.values():
        spine.set_visible(True)
        spine.set_color('#e0e0e0')

fig.suptitle('📊 电商销售 KPI 看板', fontsize=20, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()

5. 销售趋势分析

# === 月度销售趋势 ===
monthly = df.groupby('月')['总金额'].agg(['sum', 'count', 'mean']).reset_index()
monthly.columns = ['月份', '销售额', '订单数', '客单价']

fig, axes = plt.subplots(1, 3, figsize=(20, 6))

# 销售额趋势
axes[0].plot(monthly['月份'], monthly['销售额'], marker='o',
             color='#07c160', linewidth=2, markersize=8)
axes[0].fill_between(monthly['月份'], monthly['销售额'], alpha=0.1, color='#07c160')
axes[0].set_title('月度销售额趋势', fontsize=14)
axes[0].set_xlabel('月份')
axes[0].set_ylabel('销售额(元)')
for i, row in monthly.iterrows():
    axes[0].annotate(f'¥{row["销售额"]/10000:.1f}万',
                     (row['月份'], row['销售额']),
                     textcoords="offset points", xytext=(0, 10), ha='center', fontsize=9)

# 订单数趋势
axes[1].bar(monthly['月份'], monthly['订单数'], color='#4d96ff', alpha=0.8)
axes[1].set_title('月度订单数趋势', fontsize=14)
axes[1].set_xlabel('月份')
axes[1].set_ylabel('订单数')

# 客单价趋势
axes[2].plot(monthly['月份'], monthly['客单价'], marker='s',
             color='#ff6b6b', linewidth=2, markersize=8)
axes[2].set_title('月度客单价趋势', fontsize=14)
axes[2].set_xlabel('月份')
axes[2].set_ylabel('客单价(元)')

for ax in axes:
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# === 星期分布 ===
weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
weekday_labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
weekday_sales = df.groupby('星期')['总金额'].sum().reindex(weekday_order)

fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.bar(weekday_labels, weekday_sales.values,
              color=['#4d96ff' if i < 5 else '#ff6b6b' for i in range(7)])
ax.set_title('一周销售额分布(工作日 vs 周末)', fontsize=14)
ax.set_ylabel('销售额(元)')
for bar, val in zip(bars, weekday_sales.values):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 500,
            f'¥{val/10000:.1f}万', ha='center', fontsize=10)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

6. 品类分析

# === 品类销售排名 ===
cat_sales = df.groupby('品类')['总金额'].agg(['sum', 'count', 'mean']).reset_index()
cat_sales.columns = ['品类', '销售额', '订单数', '客单价']
cat_sales = cat_sales.sort_values('销售额', ascending=False)
cat_sales['占比'] = cat_sales['销售额'] / cat_sales['销售额'].sum() * 100

fig, axes = plt.subplots(1, 3, figsize=(20, 6))

# 品类销售额柱状图
colors = ['#07c160', '#4d96ff', '#ffd93d', '#ff6b6b', '#6bcb77', '#cccccc']
axes[0].barh(cat_sales['品类'], cat_sales['销售额'], color=colors[:len(cat_sales)])
axes[0].set_xlabel('销售额(元)')
axes[0].set_title('品类销售额排名', fontsize=14)
for i, row in cat_sales.iterrows():
    axes[0].text(row['销售额'] + 1000, i, f'¥{row["销售额"]/10000:.1f}万',
                 va='center', fontsize=10)

# 品类占比饼图
axes[1].pie(cat_sales['占比'], labels=cat_sales['品类'], autopct='%1.1f%%',
            colors=colors[:len(cat_sales)], startangle=90, pctdistance=0.85)
axes[1].set_title('品类销售占比', fontsize=14)

# 品类订单数 vs 客单价散点图
scatter = axes[2].scatter(cat_sales['订单数'], cat_sales['客单价'],
                          s=cat_sales['销售额']/100, alpha=0.7,
                          c=colors[:len(cat_sales)], edgecolors='gray')
for i, row in cat_sales.iterrows():
    axes[2].annotate(row['品类'], (row['订单数'], row['客单价']),
                     textcoords="offset points", xytext=(5, 5), fontsize=10)
axes[2].set_xlabel('订单数')
axes[2].set_ylabel('客单价(元)')
axes[2].set_title('品类订单数 vs 客单价(气泡=销售额)', fontsize=14)
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# === 品类×月份热力图 ===
cat_month = df.pivot_table(values='总金额', index='品类', columns='月', aggfunc='sum', fill_value=0)

fig, ax = plt.subplots(figsize=(14, 6))
sns.heatmap(cat_month, annot=True, fmt='.0f', cmap='YlOrRd',
            linewidths=0.5, ax=ax)
ax.set_title('品类 × 月份 销售额热力图', fontsize=14)
ax.set_xlabel('月份')
ax.set_ylabel('品类')
plt.tight_layout()
plt.show()

7. 用户分析

# === 性别分布 ===
gender_sales = df.groupby('性别')['总金额'].agg(['sum', 'count']).reset_index()
gender_sales.columns = ['性别', '销售额', '订单数']

fig, axes = plt.subplots(1, 2, figsize=(14, 6))

axes[0].pie(gender_sales['销售额'], labels=gender_sales['性别'],
            autopct='%1.1f%%', colors=['#4d96ff', '#ff6b6b'], startangle=90)
axes[0].set_title('男女销售额占比', fontsize=14)

# 年龄分布
axes[1].hist(df['年龄'], bins=20, color='#07c160', edgecolor='white', alpha=0.8)
axes[1].axvline(df['年龄'].mean(), color='red', linestyle='--', linewidth=2,
                label=f'平均年龄: {df["年龄"].mean():.1f}岁')
axes[1].set_title('用户年龄分布', fontsize=14)
axes[1].set_xlabel('年龄')
axes[1].set_ylabel('订单数')
axes[1].legend()

plt.tight_layout()
plt.show()

# === 地区分析 ===
region_sales = df.groupby('地区')['总金额'].sum().sort_values(ascending=False)

fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.bar(region_sales.index, region_sales.values, color=colors[:len(region_sales)])
ax.set_title('各地区销售额', fontsize=14)
ax.set_ylabel('销售额(元)')
for bar, val in zip(bars, region_sales.values):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 500,
            f'¥{val/10000:.1f}万', ha='center', fontsize=10)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

# === 年龄段分析 ===
df['年龄段'] = pd.cut(df['年龄'], bins=[0, 20, 25, 30, 35, 40, 50, 100],
                      labels=['20以下', '20-25', '25-30', '30-35', '35-40', '40-50', '50以上'])

age_sales = df.groupby('年龄段', observed=False)['总金额'].agg(['sum', 'mean', 'count']).reset_index()
age_sales.columns = ['年龄段', '总消费', '平均消费', '订单数']

fig, axes = plt.subplots(1, 3, figsize=(18, 6))

axes[0].bar(age_sales['年龄段'], age_sales['总消费'], color='#07c160')
axes[0].set_title('各年龄段总消费', fontsize=14)
axes[0].set_ylabel('总消费(元)')

axes[1].bar(age_sales['年龄段'], age_sales['平均消费'], color='#4d96ff')
axes[1].set_title('各年龄段平均消费', fontsize=14)
axes[1].set_ylabel('平均消费(元)')

axes[2].bar(age_sales['年龄段'], age_sales['订单数'], color='#ffd93d')
axes[2].set_title('各年龄段订单数', fontsize=14)
axes[2].set_ylabel('订单数')

for ax in axes:
    ax.tick_params(axis='x', rotation=45)
    ax.grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

8. 关联分析

# === 品类×性别的交叉分析 ===
cross = pd.crosstab(df['品类'], df['性别'], values=df['总金额'], aggfunc='sum')

fig, ax = plt.subplots(figsize=(10, 6))
cross.plot(kind='bar', ax=ax, color=['#ff6b6b', '#4d96ff'], alpha=0.8)
ax.set_title('品类 × 性别 销售额对比', fontsize=14)
ax.set_ylabel('销售额(元)')
ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
ax.legend(title='性别')
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

# === 价格区间分析 ===
df['价格区间'] = pd.cut(df['单价'], bins=[0, 50, 100, 300, 500, 1000, 5000, 10000],
                        labels=['0-50', '50-100', '100-300', '300-500',
                                '500-1000', '1000-5000', '5000+'])

price_dist = df['价格区间'].value_counts().sort_index()

fig, ax = plt.subplots(figsize=(10, 6))
ax.bar(price_dist.index, price_dist.values, color='#07c160', alpha=0.8)
ax.set_title('订单价格区间分布', fontsize=14)
ax.set_xlabel('价格区间(元)')
ax.set_ylabel('订单数')
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

# === RFM 分析(简化版)===
# R (Recency): 最近一次购买距今天数
# F (Frequency): 购买频次
# M (Monetary): 消费总额
max_date = df['日期'].max()

rfm = df.groupby('用户ID').agg({
    '日期': lambda x: (max_date - x.max()).days,  # R
    '订单号': 'count',                              # F
    '总金额': 'sum'                                 # M
}).reset_index()
rfm.columns = ['用户ID', 'R', 'F', 'M']

# 打分(简化版:按分位数打 1-5 分)
# 注意:用 rank(method='first') 避免重复值导致 qcut 报错
rfm['R_score'] = pd.qcut(rfm['R'].rank(method='first'), 5, labels=[5, 4, 3, 2, 1]).astype(int)
rfm['F_score'] = pd.qcut(rfm['F'].rank(method='first'), 5, labels=[1, 2, 3, 4, 5]).astype(int)
rfm['M_score'] = pd.qcut(rfm['M'].rank(method='first'), 5, labels=[1, 2, 3, 4, 5]).astype(int)

rfm['RFM_score'] = rfm['R_score'] + rfm['F_score'] + rfm['M_score']

# 用户分层
def rfm_label(row):
    if row['RFM_score'] >= 12:
        return '高价值用户'
    elif row['RFM_score'] >= 9:
        return '中价值用户'
    elif row['RFM_score'] >= 6:
        return '低价值用户'
    else:
        return '流失风险用户'

rfm['用户等级'] = rfm.apply(rfm_label, axis=1)

fig, axes = plt.subplots(1, 3, figsize=(18, 6))

# 用户等级分布
level_counts = rfm['用户等级'].value_counts()
axes[0].pie(level_counts, labels=level_counts.index, autopct='%1.1f%%',
            colors=['#07c160', '#4d96ff', '#ffd93d', '#ff6b6b'])
axes[0].set_title('RFM 用户分层', fontsize=14)

# RFM 散点图
scatter = axes[1].scatter(rfm['F'], rfm['M'], c=rfm['R_score'],
                          cmap='RdYlGn', s=30, alpha=0.6)
axes[1].set_xlabel('购买频次 (F)')
axes[1].set_ylabel('消费总额 (M)')
axes[1].set_title('RFM 散点图(颜色=R_score)', fontsize=14)
plt.colorbar(scatter, ax=axes[1], label='R_score')

# 各等级平均消费
level_monetary = rfm.groupby('用户等级', observed=False)['M'].mean()
axes[2].bar(level_monetary.index, level_monetary.values,
            color=['#07c160', '#4d96ff', '#ffd93d', '#ff6b6b'])
axes[2].set_title('各等级用户平均消费', fontsize=14)
axes[2].set_ylabel('平均消费(元)')
axes[2].grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

print('\n📊 RFM 用户分层统计:')
print(rfm['用户等级'].value_counts())

9. 完整报告

# 将所有分析整合为一份完整的 HTML 报告
# 以下为报告框架,实际项目中可生成 HTML 或 PDF

print("""
╔══════════════════════════════════════════════════╗
║         📊 2025年度电商销售分析报告               ║
╠══════════════════════════════════════════════════╣
║                                                  ║
║  一、整体概览                                     ║
║  • 总销售额:¥XXX 万                              ║
║  • 总订单数:XX,XXX                               ║
║  • 独立用户:XXX                                  ║
║  • 客单价:¥XXX                                   ║
║                                                  ║
║  二、销售趋势                                     ║
║  • 月度趋势:X月最高,X月最低                      ║
║  • 周度规律:周X销售最好                           ║
║  • 同比增长:XX%                                  ║
║                                                  ║
║  三、品类分析                                     ║
║  • Top1品类:XXX(占比XX%)                       ║
║  • 客单价最高:XXX(¥XXXX)                       ║
║  • 订单数最多:XXX                                ║
║                                                  ║
║  四、用户画像                                     ║
║  • 男女比例:X:X                                  ║
║  • 核心年龄段:XX-XX岁                            ║
║  • Top地区:XX(占比XX%)                         ║
║                                                  ║
║  五、RFM 分析                                     ║
║  • 高价值用户:XX人(XX%)                        ║
║  • 流失风险用户:XX人                             ║
║  • 建议:针对XX用户群做XX活动                     ║
║                                                  ║
╚══════════════════════════════════════════════════╝
""")

10. 今日小结

分析模块
核心方法
使用库
数据生成
np.random + pd.DataFrame
NumPy, Pandas
数据清洗
缺失值/重复值/异常值/类型转换
Pandas
整体概览
聚合统计 + KPI 卡片
Pandas, Matplotlib
趋势分析
groupby + 折线图 + 柱状图
Pandas, Matplotlib
品类分析
分组聚合 + 饼图 + 热力图
Pandas, Seaborn
用户分析
分组 + 直方图 + 交叉表
Pandas, Seaborn
RFM 分析
分位数打分 + 用户分层
Pandas

🎉 数据科学综合项目完成!

今天你走完了一个完整的数据分析流程:
数据生成 → 清洗 → 概览 → 趋势 → 品类 → 用户 → 关联 → RFM → 报告

这就是数据分析师的日常工作!接下来进入 自动化 阶段(Day 79-84)—— Selenium 自动化、Excel 处理、邮件发送、定时任务。

最新文章

随机文章