# Python 数据分析实战

**学习目标**：使用 pandas 和 matplotlib 进行数据清洗、分析与可视化，完成一个完整的数据分析项目。

---

## 1. 数据分析工具链

### 1.1 核心库介绍

```
pandas      → 数据处理与分析（DataFrame）
numpy       → 数值计算
matplotlib  → 基础绘图
seaborn     → 统计可视化（基于 matplotlib）
```

### 1.2 安装依赖

```bash
pip install pandas numpy matplotlib seaborn
```

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 中文显示设置
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False
```

---

## 2. 数据加载与概览

### 2.1 读取数据

```python
import pandas as pd

# 读取 CSV
df = pd.read_csv('sales_data.csv', encoding='utf-8')

# 读取 Excel（需安装 openpyxl）
# df = pd.read_excel('sales_data.xlsx', sheet_name='Sheet1')

# 读取 JSON
# df = pd.read_json('data.json')

print(df.shape)        # (行数, 列数)
print(df.columns)      # 列名
print(df.dtypes)       # 数据类型
```

### 2.2 查看数据

```python
# 前 5 行
print(df.head())

# 后 5 行
print(df.tail())

# 随机 3 行
print(df.sample(3))

# 基本信息
df.info()

# 数值列统计描述
print(df.describe())
```

---

## 3. 数据清洗

### 3.1 生成示例数据

```python
import pandas as pd
import numpy as np

# 构造销售数据（含缺失值和异常值）
data = {
    'date': pd.date_range('2024-01-01', periods=100, freq='D'),
    'product': np.random.choice(['手机', '电脑', '耳机', '平板'], 100),
    'region': np.random.choice(['华东', '华北', '华南', '西南'], 100),
    'quantity': np.random.randint(1, 50, 100),
    'price': np.random.uniform(100, 5000, 100).round(2),
}
df = pd.DataFrame(data)
df['amount'] = df['quantity'] * df['price']

# 人为制造缺失值和异常值
df.loc[10:15, 'price'] = np.nan          # 缺失值
df.loc[20, 'amount'] = -999              # 异常值（负数）
df.loc[30, 'quantity'] = 0               # 零值

print(df.info())
```

### 3.2 处理缺失值

```python
# 检查缺失值
print(df.isnull().sum())

# 按产品分组，用组内均值填充价格
df['price'] = df.groupby('product')['price'].transform(
    lambda x: x.fillna(x.mean())
)

# 仍有缺失则用全局均值
df['price'].fillna(df['price'].mean(), inplace=True)
print(f"填充后缺失值: {df['price'].isnull().sum()}")
```

### 3.3 处理异常值

```python
# 识别异常值（负金额）
print(df[df['amount'] < 0])

# 修正：重新计算
df.loc[df['amount'] < 0, 'amount'] = df['quantity'] * df['price']

# 用 IQR 检测价格异常
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

outliers = df[(df['price'] < lower) | (df['price'] > upper)]
print(f"价格异常值数量: {len(outliers)}")

# 截断处理
df['price'] = df['price'].clip(lower, upper)
```

### 3.4 去重与类型转换

```python
# 去除完全重复行
df.drop_duplicates(inplace=True)

# 类型转换
df['quantity'] = df['quantity'].astype(int)
df['date'] = pd.to_datetime(df['date'])

# 添加派生列
df['month'] = df['date'].dt.month
df['weekday'] = df['date'].dt.day_name()
```

---

## 4. 数据分析

### 4.1 分组聚合

```python
# 按产品统计
product_stats = df.groupby('product').agg(
    总销量=('quantity', 'sum'),
    总金额=('amount', 'sum'),
    平均单价=('price', 'mean'),
    订单数=('amount', 'count'),
).round(2)

print(product_stats)

# 按地区和产品交叉统计
pivot = pd.pivot_table(
    df,
    values='amount',
    index='region',
    columns='product',
    aggfunc='sum',
    fill_value=0,
)
print(pivot)
```

### 4.2 时间序列分析

```python
# 设置日期索引
df.set_index('date', inplace=True)

# 按月汇总
monthly = df.resample('M')['amount'].sum()
print(monthly)

# 7 日滑动平均
df['amount_7d_ma'] = df['amount'].rolling(window=7).mean()

# 累计销售额
df['cumsum'] = df['amount'].cumsum()
```

### 4.3 排序与筛选

```python
# 销量 TOP 5
top5 = df.nlargest(5, 'amount')[['product', 'region', 'amount']]
print(top5)

# 筛选条件
high_value = df[(df['amount'] > 10000) & (df['region'] == '华东')]
print(f"华东高价值订单: {len(high_value)} 笔")
```

---

## 5. 数据可视化

### 5.1 柱状图

```python
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))

product_stats['总金额'].plot(kind='bar', ax=ax, color='steelblue')
ax.set_title('各产品销售总额')
ax.set_xlabel('产品')
ax.set_ylabel('金额（元）')
ax.tick_params(axis='x', rotation=0)

# 添加数值标签
for i, v in enumerate(product_stats['总金额']):
    ax.text(i, v + 500, f'{v:,.0f}', ha='center', fontsize=9)

plt.tight_layout()
plt.savefig('product_sales.png', dpi=150)
plt.show()
```

### 5.2 饼图

```python
# 地区销售占比
region_sales = df.groupby('region')['amount'].sum()

fig, ax = plt.subplots(figsize=(6, 6))
ax.pie(
    region_sales,
    labels=region_sales.index,
    autopct='%1.1f%%',
    startangle=90,
    colors=['#4C72B0', '#DD8452', '#55A467', '#C44E52'],
)
ax.set_title('各地区销售占比')
plt.tight_layout()
plt.savefig('region_pie.png', dpi=150)
plt.show()
```

### 5.3 折线图（趋势）

```python
fig, ax = plt.subplots(figsize=(10, 5))
monthly.plot(ax=ax, marker='o', color='steelblue')
ax.set_title('月度销售趋势')
ax.set_xlabel('月份')
ax.set_ylabel('金额（元）')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('monthly_trend.png', dpi=150)
plt.show()
```

### 5.4 使用 seaborn 绘制高级图表

```python
import seaborn as sns

# 箱线图：各产品价格分布
fig, ax = plt.subplots(figsize=(8, 5))
sns.boxplot(data=df.reset_index(), x='product', y='price', ax=ax)
ax.set_title('各产品价格分布')
plt.tight_layout()
plt.savefig('price_boxplot.png', dpi=150)
plt.show()

# 热力图：地区×产品销售额
fig, ax = plt.subplots(figsize=(7, 5))
sns.heatmap(pivot, annot=True, fmt='.0f', cmap='YlOrRd', ax=ax)
ax.set_title('地区×产品销售额热力图')
plt.tight_layout()
plt.savefig('sales_heatmap.png', dpi=150)
plt.show()
```

---

## 6. 完整分析项目

```python
"""
销售数据分析报告
- 读取数据 → 清洗 → 分析 → 可视化 → 导出报告
"""

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from pathlib import Path

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False


class SalesAnalyzer:
    """销售数据分析器"""

    def __init__(self, filepath: str):
        self.df = pd.read_csv(filepath, parse_dates=['date'])
        self.report = {}

    def clean(self) -> 'SalesAnalyzer':
        """数据清洗"""
        # 填充缺失
        self.df['price'] = self.df.groupby('product')['price'].transform(
            lambda x: x.fillna(x.mean())
        )
        # 去除异常
        self.df = self.df[self.df['amount'] > 0]
        # 派生字段
        self.df['month'] = self.df['date'].dt.month
        return self

    def analyze(self) -> 'SalesAnalyzer':
        """数据分析"""
        self.report['总销售额'] = self.df['amount'].sum()
        self.report['总订单数'] = len(self.df)
        self.report['平均客单价'] = self.df['amount'].mean()

        self.product_summary = self.df.groupby('product').agg(
            销售额=('amount', 'sum'),
            销量=('quantity', 'sum'),
        ).sort_values('销售额', ascending=False)

        self.monthly = self.df.resample('M', on='date')['amount'].sum()
        return self

    def visualize(self, outdir: str = '.'):
        """生成图表"""
        Path(outdir).mkdir(exist_ok=True)

        # 产品销售柱状图
        fig, ax = plt.subplots(figsize=(8, 5))
        self.product_summary['销售额'].plot(kind='bar', ax=ax, color='steelblue')
        ax.set_title('各产品销售额')
        ax.set_ylabel('金额（元）')
        plt.tight_layout()
        plt.savefig(f'{outdir}/product_sales.png', dpi=150)
        plt.close()

        # 月度趋势
        fig, ax = plt.subplots(figsize=(10, 5))
        self.monthly.plot(ax=ax, marker='o')
        ax.set_title('月度销售趋势')
        ax.set_ylabel('金额（元）')
        ax.grid(True, alpha=0.3)
        plt.tight_layout()
        plt.savefig(f'{outdir}/monthly_trend.png', dpi=150)
        plt.close()

    def export(self, outpath: str = 'analysis_report.csv'):
        """导出分析结果"""
        self.product_summary.to_csv(outpath, encoding='utf-8-sig')
        print(f"报告已导出: {outpath}")
        for k, v in self.report.items():
            print(f"  {k}: {v:,.2f}")


# 使用
# analyzer = SalesAnalyzer('sales_data.csv')
# analyzer.clean().analyze().visualize('output').export()
```

---

## 7. 导出数据

```python
# 导出 CSV（utf-8-sig 兼容 Excel）
df.to_csv('cleaned_data.csv', index=False, encoding='utf-8-sig')

# 导出 Excel（多个 sheet）
with pd.ExcelWriter('report.xlsx') as writer:
    df.to_excel(writer, sheet_name='明细数据', index=False)
    product_stats.to_excel(writer, sheet_name='产品汇总')
    pivot.to_excel(writer, sheet_name='地区透视')

# 导出 JSON
df.reset_index().to_json('data.json', orient='records', force_ascii=False, indent=2)
```

---

## 8. 小结

| 操作 | 方法 |
|------|------|
| 读取数据 | `read_csv` / `read_excel` / `read_json` |
| 数据清洗 | `fillna` / `dropna` / `drop_duplicates` |
| 分组聚合 | `groupby` / `pivot_table` / `agg` |
| 时间序列 | `resample` / `rolling` / `cumsum` |
| 可视化 | `plot` / `seaborn` / `matplotlib` |
| 导出 | `to_csv` / `to_excel` / `to_json` |

---

## 9. 练习题

1. 读取一份真实 CSV 数据（如股票、天气），完成缺失值处理和异常值检测。
2. 使用 `pivot_table` 分析不同月份各地区的销售情况并绘制热力图。
3. 实现一个数据分析类，接收任意 CSV 文件，自动生成统计报告和图表。
4. 使用滑动平均平滑一条时间序列数据，对比平滑前后的折线图。

---

> **下节预告**：我们将使用 FastAPI 开发一个完整的 RESTful API 服务。

