📚 引言
在上一篇Matplotlib教程中,我们学习了基础图表的绘制。今天,我们将深入探索Seaborn统计可视化库和Plotly交互式图表库,让你的数据可视化作品更加专业、美观和具有交互性。本文涵盖Seaborn的高级统计图表、Plotly的动态交互功能以及实际项目案例。
1. Seaborn:统计可视化利器
1.1 Seaborn风格设置
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 设置Seaborn风格
sns.set_style('whitegrid') # 可选: darkgrid, whitegrid, dark, white, ticks
sns.set_context('notebook') # 可选: paper, notebook, talk, poster
# 设置调色板
sns.set_palette('husl') # 可选: deep, muted, bright, pastel, dark, colorblind
# 更精细的样式控制
sns.set_theme(
style='whitegrid',
palette='viridis',
font='SimHei',
font_scale=1.2,
rc={'figure.figsize': (12, 6)}
)
# 加载示例数据
tips = sns.load_dataset('tips')
iris = sns.load_dataset('iris')
titanic = sns.load_dataset('titanic')
print("数据预览:")
print(tips.head())
1.2 分布图:直方图、KDE、箱线图
# 创建子图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 直方图 + KDE
sns.histplot(data=tips, x='total_bill', kde=True, color='#2980b9', ax=axes[0, 0])
axes[0, 0].set_title('总消费分布(直方图+KDE)', fontsize=12)
# 2. 核密度估计图
sns.kdeplot(data=tips, x='total_bill', hue='time', fill=True, alpha=0.5, ax=axes[0, 1])
axes[0, 1].set_title('KDE密度曲线(按用餐时间)', fontsize=12)
# 3. 箱线图
sns.boxplot(data=tips, x='day', y='total_bill', palette='Set2', ax=axes[0, 2])
axes[0, 2].set_title('箱线图(按星期分组)', fontsize=12)
# 4. 小提琴图
sns.violinplot(data=tips, x='day', y='total_bill', hue='sex', split=True, ax=axes[1, 0])
axes[1, 0].set_title('小提琴图(含性别对比)', fontsize=12)
# 5. 蜂群图
sns.swarmplot(data=tips, x='day', y='total_bill', hue='time', size=4, ax=axes[1, 1])
axes[1, 1].set_title('蜂群图', fontsize=12)
# 6. 累积分布图
sns.ecdfplot(data=tips, x='total_bill', hue='sex', ax=axes[1, 2])
axes[1, 2].set_title('累积分布图', fontsize=12)
plt.tight_layout()
plt.show()
1.3 关系图:散点图、联合分布、热力图
# 创建图形
fig = plt.figure(figsize=(15, 10))
# 1. 散点图 + 回归线
ax1 = fig.add_subplot(2, 2, 1)
sns.regplot(data=tips, x='total_bill', y='tip',
scatter_kws={'alpha': 0.6, 's': 50},
line_kws={'color': 'red', 'linewidth': 2},
ax=ax1)
ax1.set_title('消费金额 vs 小费金额(含回归线)', fontsize=12)
# 2. 联合分布图
ax2 = fig.add_subplot(2, 2, 2)
sns.jointplot(data=tips, x='total_bill', y='tip', kind='hex', height=5)
# 注意: jointplot 创建的是独立图形,这里仅作示例
plt.close()
# 单独绘制联合分布
g = sns.JointGrid(data=tips, x='total_bill', y='tip', height=5)
g.plot_joint(sns.scatterplot, alpha=0.5)
g.plot_marginals(sns.histplot, kde=True)
g.fig.suptitle('联合分布图', y=1.02)
plt.show()
# 3. 相关性热力图
ax3 = fig.add_subplot(2, 2, 3)
correlation = tips.select_dtypes(include=[np.number]).corr()
sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=1, cbar_kws={'shrink': 0.8}, ax=ax3)
ax3.set_title('特征相关性热力图', fontsize=12)
# 4. 成对关系图
ax4 = fig.add_subplot(2, 2, 4)
sns.pairplot(iris, hue='species', diag_kind='kde', height=2)
plt.suptitle('鸢尾花数据集成对关系图', y=1.02)
plt.show()
plt.tight_layout()
plt.show()
1.4 分类图:条形图、计数图、点图
# 创建图形
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 条形图(带置信区间)
sns.barplot(data=titanic.dropna(), x='class', y='fare', hue='sex',
ci=95, palette='Set1', ax=axes[0, 0])
axes[0, 0].set_title('不同舱位票价对比(含置信区间)', fontsize=12)
# 2. 计数图
sns.countplot(data=titanic, x='class', hue='survived',
palette='Set2', ax=axes[0, 1])
axes[0, 1].set_title('不同舱位幸存人数统计', fontsize=12)
# 3. 点图
sns.pointplot(data=titanic.dropna(), x='class', y='age', hue='sex',
markers=['o', 's'], linestyles=['-', '--'], ax=axes[1, 0])
axes[1, 0].set_title('不同舱位年龄分布(按性别)', fontsize=12)
# 4. 分类散点图(Strip Plot)
sns.stripplot(data=titanic.dropna(), x='class', y='age', hue='survived',
dodge=True, jitter=True, alpha=0.7, ax=axes[1, 1])
axes[1, 1].set_title('年龄分布与幸存情况', fontsize=12)
plt.tight_layout()
plt.show()
2. 高级统计图表
2.1 矩阵图与聚类图
# 聚类热力图(Clustermap)
from scipy.cluster import hierarchy
# 准备数据
correlation_matrix = iris.iloc[:, :-1].corr()
# 创建聚类热力图
g = sns.clustermap(correlation_matrix, annot=True, cmap='coolwarm',
center=0, square=True, linewidths=1,
figsize=(10, 8), dendrogram_ratio=0.2,
cbar_pos=(0.02, 0.8, 0.05, 0.18))
g.fig.suptitle('特征聚类热力图', y=1.02, fontsize=14)
plt.show()
# 分层聚类树状图
from scipy.cluster.hierarchy import dendrogram, linkage
# 计算链接矩阵
linked = linkage(iris.iloc[:, :-1], method='ward')
plt.figure(figsize=(12, 6))
dendrogram(linked, orientation='top', labels=iris['species'].values,
distance_sort='descending', show_leaf_counts=True)
plt.title('鸢尾花数据分层聚类树状图', fontsize=14)
plt.xlabel('样本')
plt.ylabel('距离')
plt.show()
2.2 线性回归诊断图
from sklearn.linear_model import LinearRegression
import statsmodels.api as sm
# 线性回归模型
X = tips[['total_bill']]
y = tips['tip']
model = LinearRegression()
model.fit(X, y)
y_pred = model.predict(X)
residuals = y - y_pred
# 创建回归诊断图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 1. 残差 vs 拟合值
axes[0, 0].scatter(y_pred, residuals, alpha=0.5)
axes[0, 0].axhline(y=0, color='red', linestyle='--')
axes[0, 0].set_xlabel('拟合值')
axes[0, 0].set_ylabel('残差')
axes[0, 0].set_title('残差图', fontsize=12)
# 2. Q-Q图(正态性检验)
import scipy.stats as stats
stats.probplot(residuals, dist="norm", plot=axes[0, 1])
axes[0, 1].set_title('Q-Q图', fontsize=12)
# 3. 残差直方图 + KDE
sns.histplot(residuals, kde=True, ax=axes[1, 0])
axes[1, 0].set_title('残差分布', fontsize=12)
# 4. 残差 vs 顺序
axes[1, 1].plot(residuals, 'o-', alpha=0.5)
axes[1, 1].axhline(y=0, color='red', linestyle='--')
axes[1, 1].set_xlabel('观测顺序')
axes[1, 1].set_ylabel('残差')
axes[1, 1].set_title('残差顺序图', fontsize=12)
plt.tight_layout()
plt.show()
# 输出诊断统计
print(f"残差均值: {residuals.mean():.4f}")
print(f"残差标准差: {residuals.std():.4f}")
print(f"残差偏度: {stats.skew(residuals):.4f}")
print(f"残差峰度: {stats.kurtosis(residuals):.4f}")
3. Plotly:交互式可视化
3.1 基础交互图表
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# 3.1 交互式散点图
fig_scatter = px.scatter(tips, x='total_bill', y='tip', color='time',
size='size', hover_data=['day', 'sex'],
title='消费金额与小费关系(交互式)',
labels={'total_bill': '消费金额($)', 'tip': '小费($)'})
fig_scatter.show()
# 3.2 交互式折线图
# 准备时间序列数据
date_rng = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
ts_data = pd.DataFrame(date_rng, columns=['date'])
ts_data['value'] = np.cumsum(np.random.randn(len(date_rng))) + 100
fig_line = px.line(ts_data, x='date', y='value',
title='2024年趋势图',
labels={'date': '日期', 'value': '数值'})
fig_line.update_traces(line=dict(color='#2980b9', width=2))
fig_line.show()
# 3.3 交互式柱状图
fig_bar = px.bar(titanic.groupby('class').size().reset_index(name='count'),
x='class', y='count', color='class',
title='泰坦尼克号乘客舱位分布',
labels={'class': '舱位等级', 'count': '人数'})
fig_bar.show()
# 3.4 交互式饼图
fig_pie = px.pie(tips, names='day', values='total_bill',
title='各天消费总额占比',
hole=0.3) # 环形图
fig_pie.show()
3.2 高级交互图表
# 3.5 3D散点图
fig_3d = px.scatter_3d(iris, x='sepal_length', y='sepal_width', z='petal_length',
color='species', size='petal_width',
title='鸢尾花3D可视化',
labels={'sepal_length': '花萼长度',
'sepal_width': '花萼宽度',
'petal_length': '花瓣长度'})
fig_3d.show()
# 3.6 气泡图(带动画)
# 准备动画数据
np.random.seed(42)
animation_data = pd.DataFrame({
'year': np.repeat(range(2010, 2024), 50),
'gdp': np.random.randn(14*50) * 10 + 50,
'life_exp': np.random.randn(14*50) * 5 + 70,
'population': np.random.rand(14*50) * 100,
'country': np.random.choice(['中国', '美国', '日本', '德国', '英国'], 14*50)
})
fig_bubble = px.scatter(animation_data, x='gdp', y='life_exp',
size='population', color='country',
animation_frame='year', hover_name='country',
title='全球发展动态气泡图',
labels={'gdp': 'GDP', 'life_exp': '预期寿命'})
fig_bubble.show()
# 3.7 旭日图(层级数据)
# 准备层级数据
hierarchy_data = pd.DataFrame({
'category': ['电子产品', '电子产品', '服装', '服装', '食品', '食品'],
'subcategory': ['手机', '电脑', '男装', '女装', '零食', '饮料'],
'sales': [100, 80, 60, 70, 90, 50]
})
fig_sunburst = px.sunburst(hierarchy_data, path=['category', 'subcategory'],
values='sales', title='销售额层级分布')
fig_sunburst.show()
# 3.8 等高线图
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig_contour = go.Figure(data=
go.Contour(z=Z, x=x, y=y, colorscale='Viridis',
contours=dict(coloring='heatmap'))
)
fig_contour.update_layout(title='等高线图', width=600, height=500)
fig_contour.show()
3.3 多子图布局
# 创建多子图
fig_subplots = make_subplots(
rows=2, cols=2,
subplot_titles=('散点图', '柱状图', '箱线图', '折线图'),
specs=[[{'type': 'scatter'}, {'type': 'bar'}],
[{'type': 'box'}, {'type': 'scatter'}]]
)
# 添加散点图
fig_subplots.add_trace(
go.Scatter(x=tips['total_bill'], y=tips['tip'], mode='markers',
marker=dict(color='#2980b9', size=8)),
row=1, col=1
)
# 添加柱状图
day_counts = tips['day'].value_counts()
fig_subplots.add_trace(
go.Bar(x=day_counts.index, y=day_counts.values, marker_color='#e74c3c'),
row=1, col=2
)
# 添加箱线图
fig_subplots.add_trace(
go.Box(y=tips['total_bill'], name='总消费', marker_color='#27ae60'),
row=2, col=1
)
# 添加折线图
ts_data_monthly = ts_data.resample('M', on='date').mean()
fig_subplots.add_trace(
go.Scatter(x=ts_data_monthly.index, y=ts_data_monthly['value'],
mode='lines+markers', line=dict(color='#9b59b6', width=2)),
row=2, col=2
)
fig_subplots.update_layout(height=800, title_text='多图表综合分析看板', showlegend=False)
fig_subplots.show()
4. 地理数据可视化
# 4.1 地图散点图
# 准备地理数据(示例)
geo_data = pd.DataFrame({
'city': ['北京', '上海', '广州', '深圳', '成都', '武汉', '西安', '杭州'],
'lat': [39.9042, 31.2304, 23.1291, 22.5431, 30.5728, 30.5928, 34.3416, 30.2741],
'lon': [116.4074, 121.4737, 113.2644, 114.0579, 104.0668, 114.3055, 108.9402, 120.1551],
'value': [100, 95, 85, 90, 70, 75, 65, 80]
})
fig_map = px.scatter_geo(geo_data, lat='lat', lon='lon', text='city',
size='value', color='value',
title='中国主要城市数据分布',
projection='natural earth')
fig_map.show()
# 4.2 中国地图(需要安装 china-map 插件)
# pip install china-map
try:
import china_map
fig_china = px.choropleth(geo_data, locations='city', locationmode='country names',
color='value', scope='asia',
title='中国城市数据热力图')
fig_china.show()
except:
print("需要安装 china-map 包以显示中国地图")
5. 实战案例:电商销售数据分析看板
# 生成模拟电商数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', '2024-12-31', freq='D')
sales_data = pd.DataFrame({
'date': np.tile(dates, 5),
'category': np.repeat(['电子产品', '服装', '食品', '家居', '美妆'], len(dates)),
'sales': np.random.randint(1000, 10000, len(dates)*5),
'quantity': np.random.randint(10, 500, len(dates)*5),
'customer_score': np.random.uniform(3, 5, len(dates)*5)
})
# 按日期和类别聚合
daily_sales = sales_data.groupby(['date', 'category'])['sales'].sum().reset_index()
category_sales = sales_data.groupby('category')['sales'].sum().reset_index()
# 创建综合看板
fig_dashboard = make_subplots(
rows=3, cols=2,
subplot_titles=('销售趋势(按类别)', '类别销售额占比',
'销量分布箱线图', '客户评分分布',
'日销售额趋势', '销售额与销量关系'),
specs=[[{'type': 'scatter'}, {'type': 'pie'}],
[{'type': 'box'}, {'type': 'histogram'}],
[{'type': 'scatter'}, {'type': 'scatter'}]]
)
# 1. 销售趋势(按类别)
for category in daily_sales['category'].unique():
cat_data = daily_sales[daily_sales['category'] == category]
fig_dashboard.add_trace(
go.Scatter(x=cat_data['date'], y=cat_data['sales'],
name=category, mode='lines'),
row=1, col=1
)
# 2. 类别销售额占比
fig_dashboard.add_trace(
go.Pie(labels=category_sales['category'], values=category_sales['sales']),
row=1, col=2
)
# 3. 销量分布箱线图
fig_dashboard.add_trace(
go.Box(x=sales_data['category'], y=sales_data['quantity'],
name='销量分布', boxmean='sd'),
row=2, col=1
)
# 4. 客户评分分布
fig_dashboard.add_trace(
go.Histogram(x=sales_data['customer_score'], nbinsx=20,
marker_color='#27ae60'),
row=2, col=2
)
# 5. 日销售额趋势(总计)
daily_total = sales_data.groupby('date')['sales'].sum().reset_index()
fig_dashboard.add_trace(
go.Scatter(x=daily_total['date'], y=daily_total['sales'],
mode='lines', name='总销售额', line=dict(color='#e74c3c', width=2)),
row=3, col=1
)
# 6. 销售额与销量关系
fig_dashboard.add_trace(
go.Scatter(x=sales_data['quantity'], y=sales_data['sales'],
mode='markers', marker=dict(color=sales_data['customer_score'],
colorscale='Viridis', showscale=True),
text=sales_data['category'],
hovertemplate='销量: %{x}
销售额: %{y}
类别: %{text}'),
row=3, col=2
)
# 更新布局
fig_dashboard.update_layout(
height=1000,
title_text='电商销售数据分析看板',
showlegend=True,
hovermode='closest'
)
# 更新轴标签
fig_dashboard.update_xaxes(title_text='日期', row=1, col=1)
fig_dashboard.update_yaxes(title_text='销售额', row=1, col=1)
fig_dashboard.update_xaxes(title_text='类别', row=2, col=1)
fig_dashboard.update_yaxes(title_text='销量', row=2, col=1)
fig_dashboard.update_xaxes(title_text='客户评分', row=2, col=2)
fig_dashboard.update_yaxes(title_text='频次', row=2, col=2)
fig_dashboard.update_xaxes(title_text='日期', row=3, col=1)
fig_dashboard.update_yaxes(title_text='总销售额', row=3, col=1)
fig_dashboard.update_xaxes(title_text='销量', row=3, col=2)
fig_dashboard.update_yaxes(title_text='销售额', row=3, col=2)
fig_dashboard.show()
# 输出关键指标
print("=" * 50)
print("电商销售数据分析报告")
print("=" * 50)
print(f"总销售额: {sales_data['sales'].sum():,.0f}")
print(f"总销量: {sales_data['quantity'].sum():,}")
print(f"平均客单价: {sales_data['sales'].sum() / sales_data['quantity'].sum():.2f}")
print(f"平均客户评分: {sales_data['customer_score'].mean():.2f}")
print("\n各类别销售占比:")
for _, row in category_sales.sort_values('sales', ascending=False).iterrows():
pct = row['sales'] / category_sales['sales'].sum() * 100
print(f" {row['category']}: {pct:.1f}%")
6. 图表导出与部署
# 6.1 Seaborn图表保存
fig, ax = plt.subplots(figsize=(12, 6))
sns.lineplot(data=daily_sales, x='date', y='sales', hue='category')
plt.title('销售趋势图')
plt.tight_layout()
# 保存为不同格式
plt.savefig('sales_trend.png', dpi=300, bbox_inches='tight') # PNG
plt.savefig('sales_trend.pdf', bbox_inches='tight') # PDF
plt.savefig('sales_trend.svg', bbox_inches='tight') # SVG矢量图
plt.close()
# 6.2 Plotly图表保存
fig_scatter.write_html('scatter_plot.html') # 保存为HTML
fig_scatter.write_image('scatter_plot.png') # 保存为PNG(需要安装kaleido)
fig_scatter.write_image('scatter_plot.pdf') # 保存为PDF
# 6.3 批量导出报告
import os
from datetime import datetime
def export_dashboard(fig, name):
"""导出仪表盘为多种格式"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# 创建导出目录
os.makedirs('exports', exist_ok=True)
# 导出HTML
fig.write_html(f'exports/{name}_{timestamp}.html')
# 导出图片(如果安装了kaleido)
try:
fig.write_image(f'exports/{name}_{timestamp}.png', width=1200, height=800)
fig.write_image(f'exports/{name}_{timestamp}.pdf')
print(f"已导出: {name}_{timestamp}")
except:
print("需要安装 kaleido 包以导出图片: pip install -U kaleido")
# 导出看板
# export_dashboard(fig_dashboard, 'sales_dashboard')
可视化工具对比
| 工具 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Matplotlib | 功能全面、高度可定制 | 代码繁琐、默认样式一般 | 基础图表、论文配图 |
| Seaborn | 统计图表丰富、美观、简洁 | 灵活性略低 | 统计分析、数据探索 |
| Plotly | 交互式、3D支持、Web友好 | 学习曲线较陡 | Web应用、仪表盘 |
| Bokeh | 交互式、大规模数据 | 文档较少 | 实时数据可视化 |
| Altair | 声明式语法、语法简洁 | 大数据集性能差 | 快速原型开发 |
总结
本文深入讲解了数据可视化的高级技巧:
- ✅ Seaborn统计图表:分布图、关系图、分类图、矩阵图
- ✅ Plotly交互图表:散点图、3D图、动画图、旭日图
- ✅ 多子图布局:综合看板创建
- ✅ 地理可视化:地图散点图、热力图
- ✅ 实战案例:完整的电商数据分析看板
- ✅ 图表导出:多种格式保存与部署
📌 进阶建议:根据场景选择合适的工具 - 探索性分析用Seaborn,Web展示用Plotly,学术论文用Matplotlib。
🔗 相关文章推荐
如果这篇文章对你有帮助,欢迎点赞、收藏、转发!