Python 数据可视化实战:Matplotlib、Seaborn 与 Plotly 完全指南
数据可视化是数据分析中最直观、最有力的工具。本文将带你深入掌握 Python 三大主流可视化库:Matplotlib、Seaborn 和 Plotly,从基础图表到高级交互式可视化。
📊 三大可视化库对比
核心特性对比
| 特性 | Matplotlib | Seaborn | Plotly |
|---|---|---|---|
| 定位 | 基础绘图库 | 统计可视化 | 交互式可视化 |
| 学习曲线 | 中等 | 较低 | 中等 |
| 图表类型 | 最丰富 | 专注统计图表 | 交互式图表 |
| 美观度 | 需手动调整 | 默认美观 | 现代化设计 |
| 交互性 | 静态 | 静态 | 强交互 |
| 适用场景 | 基础图表、定制化 | 统计分析、快速出图 | 仪表板、演示展示 |
| 性能 | 快 | 快(基于Matplotlib) | 较慢(交互开销) |
选择建议
python
# 使用 Matplotlib 当:
# - 需要完全控制每个细节
# - 创建科学出版物级别的图表
# - 需要最大的灵活性
# 使用 Seaborn 当:
# - 进行统计分析
# - 需要快速生成美观的图表
# - 处理分类数据和统计关系
# 使用 Plotly 当:
# - 需要交互式图表
# - 构建仪表板或 Web 应用
# - 演示和分享可视化结果🎨 Matplotlib:基础绘图库
1. 基础图表
python
import matplotlib.pyplot as plt
import numpy as np
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 创建数据
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
# 创建画布
fig, ax = plt.subplots(figsize=(10, 6))
# 绘制线条
ax.plot(x, y1, label='Sin(x)', linewidth=2, linestyle='-', color='blue')
ax.plot(x, y2, label='Cos(x)', linewidth=2, linestyle='--', color='red')
# 添加标签和标题
ax.set_xlabel('X 轴', fontsize=12)
ax.set_ylabel('Y 轴', fontsize=12)
ax.set_title('三角函数图', fontsize=14, pad=20)
# 添加图例
ax.legend(fontsize=10)
# 添加网格
ax.grid(True, alpha=0.3, linestyle='--')
# 设置坐标轴范围
ax.set_xlim(0, 10)
ax.set_ylim(-1.5, 1.5)
plt.tight_layout()
plt.show()2. 多子图布局
python
# 2x2 子图布局
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 子图1:折线图
axes[0, 0].plot(x, y1, 'b-', linewidth=2)
axes[0, 0].set_title('折线图')
axes[0, 0].grid(True, alpha=0.3)
# 子图2:散点图
axes[0, 1].scatter(x, y2, c='red', alpha=0.6, s=50)
axes[0, 1].set_title('散点图')
# 子图3:柱状图
categories = ['A', 'B', 'C', 'D']
values = [25, 30, 15, 20]
axes[1, 0].bar(categories, values, color=['red', 'green', 'blue', 'yellow'])
axes[1, 0].set_title('柱状图')
# 子图4:直方图
data = np.random.randn(1000)
axes[1, 1].hist(data, bins=30, color='purple', alpha=0.7, edgecolor='black')
axes[1, 1].set_title('直方图')
plt.tight_layout()
plt.show()3. 自定义样式
python
# 使用预设样式
plt.style.use('seaborn-v0_8') # 使用 Seaborn 风格
# 或者创建自定义样式
plt.style.use('default')
plt.rcParams.update({
'figure.facecolor': 'white',
'axes.facecolor': '#f0f0f0',
'axes.grid': True,
'grid.color': '#e0e0e0',
'axes.linewidth': 1.2,
'xtick.labelsize': 10,
'ytick.labelsize': 10,
'font.size': 11
})
# 绘制图表
fig, ax = plt.subplots(figsize=(10, 6))
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), linewidth=2, color='#2E86AB')
ax.fill_between(x, 0, np.sin(x), alpha=0.3, color='#A23B72')
ax.set_title('自定义样式图表', pad=20)
plt.tight_layout()
plt.show()4. 高级图表
python
# 饼图
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 简单饼图
labels = ['A', 'B', 'C', 'D', 'E']
sizes = [25, 30, 15, 20, 10]
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
explode = (0, 0.1, 0, 0, 0) # 突出显示 B
axes[0].pie(sizes, explode=explode, labels=labels, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=90)
axes[0].set_title('饼图')
# 箱线图
data = [np.random.normal(0, std, 100) for std in range(1, 4)]
axes[1].boxplot(data, labels=['Group 1', 'Group 2', 'Group 3'])
axes[1].set_title('箱线图')
axes[1].set_ylabel('Value')
plt.tight_layout()
plt.show()🎭 Seaborn:统计可视化
1. 统计关系图
python
import seaborn as sns
import pandas as pd
# 加载示例数据集
tips = sns.load_dataset('tips')
# 创建画布
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 散点图
sns.scatterplot(data=tips, x='total_bill', y='tip', hue='day', style='time', ax=axes[0, 0])
axes[0, 0].set_title('散点图:消费与小费关系')
# 线性回归图
sns.regplot(data=tips, x='total_bill', y='tip', ax=axes[0, 1],
scatter_kws={'alpha':0.5}, line_kws={'color':'red'})
axes[0, 1].set_title('线性回归图')
# 热力图
numeric_tips = tips.select_dtypes(include=[np.number])
correlation = numeric_tips.corr()
sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0,
square=True, ax=axes[1, 0])
axes[1, 0].set_title('相关系数热力图')
# 聚合图
sns.barplot(data=tips, x='day', y='total_bill', hue='sex', ax=axes[1, 1])
axes[1, 1].set_title('柱状图:每日消费')
plt.tight_layout()
plt.show()2. 分布图
python
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
# 直方图
sns.histplot(data=tips, x='total_bill', kde=True, ax=axes[0, 0])
axes[0, 0].set_title('直方图 + KDE')
# 密度图
sns.kdeplot(data=tips, x='total_bill', hue='day', ax=axes[0, 1])
axes[0, 1].set_title('核密度估计图')
# 累积分布
sns.ecdfplot(data=tips, x='total_bill', ax=axes[0, 2])
axes[0, 2].set_title('累积分布函数')
# 箱线图
sns.boxplot(data=tips, x='day', y='total_bill', ax=axes[1, 0])
axes[1, 0].set_title('箱线图')
# 小提琴图
sns.violinplot(data=tips, x='day', y='total_bill', ax=axes[1, 1])
axes[1, 1].set_title('小提琴图')
# 箱形图 + 散点图
sns.boxenplot(data=tips, x='day', y='total_bill', ax=axes[1, 2])
axes[1, 2].set_title('增强箱形图')
plt.tight_layout()
plt.show()3. 分类数据图
python
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 箱线图(按分类)
sns.boxplot(data=tips, x='day', y='total_bill', hue='time', ax=axes[0, 0])
axes[0, 0].set_title('按时间分类的箱线图')
# 小提琴图
sns.violinplot(data=tips, x='day', y='total_bill', hue='sex', split=True, ax=axes[0, 1])
axes[0, 1].set_title('按性别分类的小提琴图')
# 点图
sns.pointplot(data=tips, x='day', y='total_bill', hue='sex', ax=axes[1, 0])
axes[1, 0].set_title('点图:平均值比较')
# 条形图
sns.barplot(data=tips, x='day', y='total_bill', hue='smoker', ax=axes[1, 1])
axes[1, 1].set_title('条形图:按吸烟者分类')
plt.tight_layout()
plt.show()4. 多变量关系图
python
# pairplot:展示所有变量两两关系
sns.pairplot(tips, hue='day', palette='husl', markers=['o', 's', 'D', '^'])
plt.suptitle('多变量关系图', y=1.02)
plt.show()
# jointplot:双变量关系 + 边缘分布
sns.jointplot(data=tips, x='total_bill', y='tip', kind='reg',
height=8, ratio=5, marginal_kws=dict(bins=25, fill=True))
plt.show()✨ Plotly:交互式可视化
1. 基础交互图表
python
import plotly.express as px
import pandas as pd
# 创建示例数据
df = pd.DataFrame({
'x': np.linspace(0, 10, 100),
'y': np.sin(x),
'category': np.random.choice(['A', 'B', 'C'], 100)
})
# 折线图
fig = px.line(df, x='x', y='y', title='交互式折线图',
labels={'x': 'X 轴', 'y': 'Y 轴'})
fig.update_layout(
template='plotly_white',
hovermode='x unified',
xaxis_showgrid=True,
yaxis_showgrid=True,
gridcolor='lightgray'
)
fig.show()
# 散点图
fig = px.scatter(df, x='x', y='y', color='category',
size=df['y'].abs(),
title='交互式散点图',
labels={'category': '分类'})
fig.update_traces(marker=dict(line=dict(width=1, color='DarkSlateGrey')))
fig.show()2. 高级交互图表
python
# 3D 散点图
fig = px.scatter_3d(
df, x='x', y='y', z=np.cos(x),
color='category', size=df['y'].abs(),
title='3D 交互式散点图'
)
fig.update_layout(scene=dict(
xaxis_title='X 轴',
yaxis_title='Y 轴',
zaxis_title='Z 轴'
))
fig.show()
# 动画图表
df_animated = pd.DataFrame({
'year': np.tile(range(2000, 2010), 10),
'value': np.random.randn(100).cumsum(),
'category': np.repeat(['A', 'B', 'C', 'D', 'E'], 20)
})
fig = px.bar(df_animated, x='category', y='value',
color='category', animation_frame='year',
title='动画柱状图',
range_y=[df_animated['value'].min(), df_animated['value'].max()])
fig.show()3. 统计图表
python
# 箱线图
fig = px.box(tips, x='day', y='total_bill', color='time',
title='交互式箱线图',
points='all') # 显示所有数据点
fig.show()
# 小提琴图
fig = px.violin(tips, x='day', y='total_bill', color='sex',
title='交互式小提琴图',
box=True, # 显示箱线图
points='all') # 显示所有数据点
fig.show()
# 直方图
fig = px.histogram(tips, x='total_bill', color='day',
title='交互式直方图',
marginal='box', # 边缘显示箱线图
nbins=30)
fig.show()4. 地理数据可视化
python
# 创建示例地理数据
geo_df = pd.DataFrame({
'lat': np.random.uniform(30, 40, 50),
'lon': np.random.uniform(110, 120, 50),
'value': np.random.randint(1, 100, 50),
'category': np.random.choice(['A', 'B', 'C'], 50)
})
# 地图散点图
fig = px.scatter_geo(geo_df, lat='lat', lon='lon',
color='category', size='value',
title='地理数据可视化',
projection='natural earth')
fig.update_geos(projection_type="equirectangular")
fig.show()
# 等值线图(需要 geojson 文件)
# fig = px.choropleth(...)🎯 实战案例:销售数据分析
python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 创建模拟销售数据
np.random.seed(42)
dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')
df = pd.DataFrame({
'date': dates,
'sales': np.random.randint(100, 500, len(dates)),
'visitors': np.random.randint(1000, 5000, len(dates)),
'conversion_rate': np.random.uniform(0.02, 0.08, len(dates)),
'product': np.random.choice(['A', 'B', 'C', 'D'], len(dates)),
'region': np.random.choice(['East', 'West', 'North', 'South'], len(dates))
})
df['month'] = df['date'].dt.month
df['weekday'] = df['date'].dt.day_name()
# 创建综合分析仪表板
fig = plt.figure(figsize=(16, 12))
gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)
# 1. 销售趋势图
ax1 = fig.add_subplot(gs[0, :])
monthly_sales = df.groupby('month')['sales'].sum()
ax1.plot(monthly_sales.index, monthly_sales.values,
marker='o', linewidth=2, markersize=8, color='#2E86AB')
ax1.fill_between(monthly_sales.index, 0, monthly_sales.values,
alpha=0.3, color='#A23B72')
ax1.set_title('月度销售趋势', fontsize=14, pad=15)
ax1.set_xlabel('月份')
ax1.set_ylabel('销售额')
ax1.set_xticks(range(1, 13))
ax1.set_xticklabels(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
ax1.grid(True, alpha=0.3)
# 2. 产品销售分布
ax2 = fig.add_subplot(gs[1, 0])
product_sales = df.groupby('product')['sales'].sum()
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99']
ax2.pie(product_sales.values, labels=product_sales.index,
autopct='%1.1f%%', colors=colors,
startangle=90, shadow=True)
ax2.set_title('产品销售占比', pad=15)
# 3. 区域销售对比
ax3 = fig.add_subplot(gs[1, 1])
region_sales = df.groupby('region')['sales'].sum().sort_values(ascending=False)
bars = ax3.bar(region_sales.index, region_sales.values,
color=['#2E86AB', '#A23B72', '#F18F01', '#C73E1D'])
ax3.set_title('区域销售对比', pad=15)
ax3.set_ylabel('销售额')
ax3.grid(axis='y', alpha=0.3)
# 4. 工作日销售分布
ax4 = fig.add_subplot(gs[1, 2])
weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
weekday_sales = df.groupby('weekday')['sales'].mean().reindex(weekday_order)
ax4.plot(weekday_sales.index, weekday_sales.values,
marker='o', linewidth=2, color='#F18F01')
ax4.set_title('工作日平均销售', pad=15)
ax4.set_ylabel('平均销售额')
ax4.tick_params(axis='x', rotation=45)
ax4.grid(True, alpha=0.3)
# 5. 销售与访客量散点图
ax5 = fig.add_subplot(gs[2, 0])
scatter = ax5.scatter(df['visitors'], df['sales'],
c=df['conversion_rate'], cmap='viridis',
alpha=0.6, s=50)
ax5.set_xlabel('访客量')
ax5.set_ylabel('销售额')
ax5.set_title('销售 vs 访客量(颜色:转化率)', pad=15)
ax5.grid(True, alpha=0.3)
plt.colorbar(scatter, ax=ax5, label='转化率')
# 6. 转化率分布
ax6 = fig.add_subplot(gs[2, 1])
ax6.hist(df['conversion_rate'], bins=30,
color='#C73E1D', alpha=0.7, edgecolor='black')
ax6.set_xlabel('转化率')
ax6.set_ylabel('频数')
ax6.set_title('转化率分布', pad=15)
ax6.grid(True, alpha=0.3, axis='y')
# 7. 箱线图
ax7 = fig.add_subplot(gs[2, 2])
sns.boxplot(data=df, x='product', y='sales', ax=ax7, palette='Set2')
ax7.set_title('产品销售分布(箱线图)', pad=15)
ax7.set_ylabel('销售额')
ax7.set_xlabel('产品')
fig.suptitle('2023年销售数据分析仪表板', fontsize=16, y=0.995)
plt.show()📚 最佳实践
1. 图表设计原则
python
# 好的图表设计
fig, ax = plt.subplots(figsize=(10, 6))
# 清晰的标题
ax.set_title('2023年月度销售趋势', fontsize=14, pad=20)
# 有意义的轴标签
ax.set_xlabel('月份', fontsize=12)
ax.set_ylabel('销售额(万元)', fontsize=12)
# 合适的刻度和网格
ax.set_xticks(range(1, 13))
ax.grid(True, alpha=0.3, linestyle='--')
# 清晰的图例
ax.legend(['2023年'], loc='upper left')
# 高对比度的颜色
ax.plot(monthly_sales.index, monthly_sales.values,
color='#2E86AB', linewidth=2, marker='o')
plt.tight_layout()
plt.show()2. 性能优化
python
# 大数据集可视化
import matplotlib as mpl
mpl.use('Agg') # 使用非交互式后端
# 使用散点图而不是密集的点图
plt.scatter(x, y, alpha=0.1) # 使用透明度
# 使用 hexbin 处理密集数据
plt.hexbin(x, y, gridsize=30, cmap='viridis')
plt.colorbar()
# 使用抽样数据
sample = df.sample(frac=0.1)
plt.scatter(sample['x'], sample['y'])3. 保存图表
python
# 保存为不同格式
fig.savefig('chart.png', dpi=300, bbox_inches='tight', transparent=True)
fig.savefig('chart.pdf', bbox_inches='tight')
fig.savefig('chart.svg', bbox_inches='tight')
# Plotly 保存
fig.write_html('chart.html')
fig.write_image('chart.png', width=1200, height=800, scale=2)🎓 学习路径
Matplotlib(2-3周)
- 基础图表(折线、散点、柱状)
- 多子图布局
- 自定义样式
Seaborn(2-3周)
- 统计关系图
- 分布图
- 分类数据图
Plotly(2-3周)
- 交互式基础图表
- 高级交互功能
- 仪表板构建
综合应用(3-4周)
- 数据分析项目
- 仪表板设计
- 实战案例
📖 推荐资源
官方文档
推荐书籍
- 《Storytelling with Data》- Cole Nussbaumer Knaflic
- 《Fundamentals of Data Visualization》- Claus O. Wilke
在线画廊
掌握数据可视化,让你的数据分析结果更加生动有力!

