Python 数据分析实战
目录
学习目标:使用 pandas 和 matplotlib 进行数据清洗、分析与可视化,完成一个完整的数据分析项目。
1. 数据分析工具链
1.1 核心库介绍
pandas → 数据处理与分析(DataFrame)
numpy → 数值计算
matplotlib → 基础绘图
seaborn → 统计可视化(基于 matplotlib)1.2 安装依赖
pip install pandas numpy matplotlib seabornimport 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'] = False2. 数据加载与概览
2.1 读取数据
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 查看数据
# 前 5 行
print(df.head())
# 后 5 行
print(df.tail())
# 随机 3 行
print(df.sample(3))
# 基本信息
df.info()
# 数值列统计描述
print(df.describe())3. 数据清洗
3.1 生成示例数据
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 处理缺失值
# 检查缺失值
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 处理异常值
# 识别异常值(负金额)
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 去重与类型转换
# 去除完全重复行
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 分组聚合
# 按产品统计
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 时间序列分析
# 设置日期索引
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 排序与筛选
# 销量 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 柱状图
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 饼图
# 地区销售占比
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 折线图(趋势)
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 绘制高级图表
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. 完整分析项目
"""
销售数据分析报告
- 读取数据 → 清洗 → 分析 → 可视化 → 导出报告
"""
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. 导出数据
# 导出 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. 练习题
- 读取一份真实 CSV 数据(如股票、天气),完成缺失值处理和异常值检测。
- 使用
pivot_table分析不同月份各地区的销售情况并绘制热力图。 - 实现一个数据分析类,接收任意 CSV 文件,自动生成统计报告和图表。
- 使用滑动平均平滑一条时间序列数据,对比平滑前后的折线图。
下节预告:我们将使用 FastAPI 开发一个完整的 RESTful API 服务。