seaborn,一个优雅的 Python 库!
一、库的简介
Seaborn是基于Matplotlib构建的高级数据可视化库,专为统计图形设计,提供了一系列精美的图表样式和颜色主题,让数据科学家能够用更少的代码创建更具洞察力的可视化结果。在实际生活中,Seaborn的应用场景非常广泛:市场分析师用它快速探索消费者行为模式,生物学家用它可视化实验数据的分布特征,金融分析师用它呈现复杂的相关性分析。当你在学术期刊中看到配色协调、信息丰富的统计图表,在商业报告中看到直观的多变量关系可视化,或是在数据科学竞赛中看到优雅的探索性数据分析时,Seaborn往往都在其中扮演着重要角色。它不仅简化了复杂统计图表的创建过程,更通过精心设计的默认美学,让数据分析结果自然而然地呈现出专业和优雅的气质。
二、安装库
安装Seaborn及其依赖非常简单:
python
# 基础安装
pip install seaborn
# 安装完整版本(包含所有可选依赖)
pip install seaborn[all]
# 使用conda安装
conda install seaborn
# 验证安装
import seaborn as sns
print(f"Seaborn版本: {sns.__version__}")
# 检查重要依赖版本
import matplotlib as mpl
import pandas as pd
import numpy as np
print(f"Matplotlib版本: {mpl.__version__}")
print(f"Pandas版本: {pd.__version__}")
print(f"NumPy版本: {np.__version__}")
# 安装可选依赖以支持更多功能
pip install statsmodels # 支持更复杂的统计功能
pip install scipy # 支持统计检验和分布拟合
对于需要生成出版质量图表的用户,建议安装完整依赖:
bash
pip install seaborn[all] matplotlib pandas numpy scipy statsmodels
三、基本用法
1. 样式和颜色主题设置
python
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 设置Seaborn默认样式
sns.set_theme() # 这是Seaborn 0.11+推荐的方式
# 或者使用传统方式设置样式
sns.set_style("whitegrid") # 设置样式为白色网格
sns.set_palette("husl") # 设置颜色调色板
# 查看可用的样式
print("可用样式:", sns.style.available)
# ['darkgrid', 'whitegrid', 'dark', 'white', 'ticks']
# 查看可用的调色板
print("定性调色板:", sns.color_palette("qualitative").as_hex()[:5])
print("连续调色板:", sns.color_palette("Blues").as_hex()[:5])
print("发散调色板:", sns.color_palette("RdBu").as_hex()[:5])
# 自定义样式
custom_style = {
"axes.facecolor": "#f5f5f5", # 背景色
"grid.color": "white", # 网格颜色
"grid.linewidth": 1.5, # 网格线宽
"axes.grid": True, # 显示网格
"font.family": "DejaVu Sans", # 字体
"axes.labelsize": 12, # 坐标轴标签大小
"axes.titlesize": 14, # 标题大小
}
sns.set_theme(style=custom_style)
# 创建示例数据
np.random.seed(42)
data = pd.DataFrame({
'类别': np.repeat(['A', 'B', 'C', 'D'], 25),
'数值': np.random.randn(100).cumsum() + np.random.randint(0, 50, 100),
'分组': np.tile(['X', 'Y'], 50)
})
# 应用不同样式进行对比
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 不同样式展示
styles = ['darkgrid', 'whitegrid', 'dark', 'white', 'ticks']
for idx, style in enumerate(styles):
sns.set_style(style)
row, col = divmod(idx, 3)
ax = axes[row, col]
sns.histplot(data=data, x='数值', ax=ax, kde=True)
ax.set_title(f'样式: {style}')
ax.set_xlabel('')
ax.set_ylabel('')
# 恢复默认样式
sns.set_theme()
plt.suptitle('Seaborn不同样式对比', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
2. 单变量分布可视化
python
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
# 创建多种分布的示例数据
np.random.seed(42)
n_samples = 1000
# 正态分布
normal_data = np.random.normal(loc=0, scale=1, size=n_samples)
# 偏态分布(右偏)
skewed_data = np.random.exponential(scale=2, size=n_samples)
# 双峰分布
bimodal_data = np.concatenate([
np.random.normal(loc=-2, scale=0.5, size=n_samples//2),
np.random.normal(loc=2, scale=0.8, size=n_samples//2)
])
# 创建DataFrame
dist_df = pd.DataFrame({
'正态分布': normal_data,
'右偏分布': skewed_data,
'双峰分布': bimodal_data
}).melt(var_name='分布类型', value_name='数值')
# 创建子图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 直方图 + 核密度估计
ax1 = axes[0, 0]
sns.histplot(data=normal_data, kde=True, ax=ax1,
color='skyblue', edgecolor='black', linewidth=1)
ax1.set_title('直方图 + KDE', fontsize=12, fontweight='bold')
ax1.set_xlabel('数值')
ax1.set_ylabel('频数')
# 添加理论正态分布曲线
x = np.linspace(normal_data.min(), normal_data.max(), 100)
pdf = stats.norm.pdf(x, loc=0, scale=1)
ax1.plot(x, pdf * n_samples * 0.1, 'r--', linewidth=2, label='理论分布')
ax1.legend()
# 2. 核密度估计图
ax2 = axes[0, 1]
sns.kdeplot(data=skewed_data, ax=ax2, color='lightgreen',
fill=True, alpha=0.5, linewidth=2)
ax2.set_title('核密度估计图', fontsize=12, fontweight='bold')
ax2.set_xlabel('数值')
ax2.set_ylabel('密度')
# 添加中位数和均值线
median = np.median(skewed_data)
mean = np.mean(skewed_data)
ax2.axvline(median, color='blue', linestyle='--', linewidth=2, label=f'中位数: {median:.2f}')
ax2.axvline(mean, color='red', linestyle='--', linewidth=2, label=f'均值: {mean:.2f}')
ax2.legend()
# 3. 箱线图
ax3 = axes[0, 2]
sns.boxplot(x=normal_data, ax=ax3, color='lightcoral')
ax3.set_title('箱线图', fontsize=12, fontweight='bold')
ax3.set_xlabel('数值')
# 添加统计信息
stats_text = f"""
Q1: {np.percentile(normal_data, 25):.2f}
中位数: {np.median(normal_data):.2f}
Q3: {np.percentile(normal_data, 75):.2f}
IQR: {np.percentile(normal_data, 75) - np.percentile(normal_data, 25):.2f}
"""
ax3.text(0.05, 0.95, stats_text, transform=ax3.transAxes, fontsize=10,
verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
# 4. 小提琴图
ax4 = axes[1, 0]
sns.violinplot(data=dist_df, x='分布类型', y='数值', ax=ax4,
palette='Set2', inner='quartile')
ax4.set_title('小提琴图(多分布比较)', fontsize=12, fontweight='bold')
ax4.set_xlabel('分布类型')
ax4.set_ylabel('数值')
# 5. 经验累积分布函数图
ax5 = axes[1, 1]
for dist_type in dist_df['分布类型'].unique():
subset = dist_df[dist_df['分布类型'] == dist_type]
sns.ecdfplot(data=subset, x='数值', ax=ax5, label=dist_type)
ax5.set_title('经验累积分布函数', fontsize=12, fontweight='bold')
ax5.set_xlabel('数值')
ax5.set_ylabel('累积概率')
ax5.legend()
# 6. 带状图 + 蜂群图
ax6 = axes[1, 2]
# 创建小型数据用于演示
sample_data = pd.DataFrame({
'组别': np.repeat(['A', 'B', 'C'], 20),
'数值': np.random.randn(60) + np.repeat([0, 2, -1], 20)
})
# 绘制带状图
sns.stripplot(data=sample_data, x='组别', y='数值', ax=ax6,
palette='tab10', alpha=0.7, size=8, jitter=True)
# 添加蜂群图
sns.swarmplot(data=sample_data, x='组别', y='数值', ax=ax6,
palette='dark:black', size=5, alpha=0.8)
ax6.set_title('带状图 + 蜂群图', fontsize=12, fontweight='bold')
ax6.set_xlabel('组别')
ax6.set_ylabel('数值')
plt.suptitle('单变量分布可视化方法大全', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 高级统计信息展示
print("分布统计摘要:")
for dist_name in ['正态分布', '右偏分布', '双峰分布']:
dist_data = dist_df[dist_df['分布类型'] == dist_name]['数值']
print(f"\n{dist_name}:")
print(f" 样本数: {len(dist_data)}")
print(f" 均值: {dist_data.mean():.4f}")
print(f" 标准差: {dist_data.std():.4f}")
print(f" 偏度: {stats.skew(dist_data):.4f}")
print(f" 峰度: {stats.kurtosis(dist_data):.4f}")
print(f" Shapiro-Wilk正态性检验p值: {stats.shapiro(dist_data)[1]:.4e}")
3. 双变量关系分析
python
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
# 创建包含多种关系的示例数据
np.random.seed(42)
n_samples = 200
# 线性正相关
x1 = np.random.uniform(0, 10, n_samples)
y1 = 2 * x1 + np.random.normal(0, 2, n_samples)
# 线性负相关
x2 = np.random.uniform(0, 10, n_samples)
y2 = -1.5 * x2 + np.random.normal(0, 3, n_samples)
# 非线性关系(二次)
x3 = np.random.uniform(-5, 5, n_samples)
y3 = 0.5 * x3**2 + np.random.normal(0, 2, n_samples)
# 无关系
x4 = np.random.uniform(0, 10, n_samples)
y4 = np.random.uniform(0, 10, n_samples)
# 创建分组数据
group_data = pd.DataFrame({
'X': np.concatenate([x1, x2, x3, x4]),
'Y': np.concatenate([y1, y2, y3, y4]),
'关系类型': (['线性正相关'] * n_samples +
['线性负相关'] * n_samples +
['非线性关系'] * n_samples +
['无关系'] * n_samples)
})
# 计算相关系数
print("各关系类型的相关系数:")
for rel_type in group_data['关系类型'].unique():
subset = group_data[group_data['关系类型'] == rel_type]
corr = subset['X'].corr(subset['Y'])
print(f" {rel_type}: Pearson r = {corr:.4f}")
# 创建可视化
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 散点图
ax1 = axes[0, 0]
sns.scatterplot(data=group_data, x='X', y='Y', hue='关系类型',
palette='Set2', s=50, alpha=0.7, ax=ax1)
ax1.set_title('散点图(按关系类型着色)', fontsize=12, fontweight='bold')
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
ax1.legend(title='关系类型')
# 2. 回归图
ax2 = axes[0, 1]
sns.regplot(data=group_data[group_data['关系类型'] == '线性正相关'],
x='X', y='Y', ax=ax2, color='blue',
scatter_kws={'s': 50, 'alpha': 0.6},
line_kws={'color': 'red', 'linewidth': 2})
ax2.set_title('线性回归图(带置信区间)', fontsize=12, fontweight='bold')
ax2.set_xlabel('X')
ax2.set_ylabel('Y')
# 添加回归方程
from sklearn.linear_model import LinearRegression
subset = group_data[group_data['关系类型'] == '线性正相关']
X = subset[['X']]
y = subset['Y']
model = LinearRegression()
model.fit(X, y)
equation = f'y = {model.coef_[0]:.2f}x + {model.intercept_:.2f}'
ax2.text(0.05, 0.95, equation, transform=ax2.transAxes, fontsize=11,
verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
# 3. 残差图
ax3 = axes[0, 2]
sns.residplot(data=group_data[group_data['关系类型'] == '线性正相关'],
x='X', y='Y', ax=ax3, color='green',
scatter_kws={'s': 50, 'alpha': 0.6},
line_kws={'color': 'red', 'linewidth': 1})
ax3.axhline(y=0, color='black', linestyle='--', linewidth=1)
ax3.set_title('回归残差图', fontsize=12, fontweight='bold')
ax3.set_xlabel('X')
ax3.set_ylabel('残差')
# 4. 联合分布图
ax4 = axes[1, 0]
g = sns.jointplot(data=group_data[group_data['关系类型'] == '线性正相关'],
x='X', y='Y', kind='reg',
color='purple', height=5, ratio=3,
marginal_kws={'bins': 20, 'kde': True})
g.ax_joint.set_title('联合分布图', fontsize=12, fontweight='bold')
g.ax_joint.set_xlabel('X')
g.ax_joint.set_ylabel('Y')
# 5. 热图(相关性矩阵)
ax5 = axes[1, 1]
# 创建相关性矩阵
corr_data = group_data.pivot_table(index=None, columns='关系类型',
values=['X', 'Y'], aggfunc='mean')
corr_matrix = corr_data.corr()
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm',
center=0, square=True, linewidths=1, ax=ax5,
cbar_kws={'label': '相关系数'})
ax5.set_title('相关性热图', fontsize=12, fontweight='bold')
# 6. 成对关系图
# 创建新数据用于成对关系图
pair_data = pd.DataFrame({
'特征1': np.random.randn(100),
'特征2': np.random.randn(100) * 0.5 + 1,
'特征3': np.random.randn(100) * 1.5 - 1,
'类别': np.random.choice(['A', 'B', 'C'], 100)
})
ax6 = axes[1, 2]
# 使用pairplot可能需要单独的子图,这里简化处理
sns.scatterplot(data=pair_data, x='特征1', y='特征2',
hue='类别', style='类别', s=100, ax=ax6)
ax6.set_title('多特征散点图示例', fontsize=12, fontweight='bold')
ax6.set_xlabel('特征1')
ax6.set_ylabel('特征2')
ax6.legend(title='类别')
plt.suptitle('双变量关系分析方法大全', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 高级分析:成对关系网格
print("\n生成成对关系网格图...")
pair_grid = sns.pairplot(data=pair_data, hue='类别',
palette='Set2', diag_kind='kde',
plot_kws={'alpha': 0.7, 's': 50},
diag_kws={'fill': True})
pair_grid.fig.suptitle('成对关系网格图', fontsize=16, fontweight='bold', y=1.02)
plt.show()
4. 分类数据可视化
python
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
# 创建复杂的分类数据
np.random.seed(42)
n_categories = 4
n_subcategories = 3
n_samples = 300
# 生成数据
data = []
for cat in ['电子产品', '服装', '家居', '食品']:
for subcat in ['低端', '中端', '高端']:
base_price = {'电子产品': [100, 500, 1500],
'服装': [50, 200, 800],
'家居': [80, 400, 1200],
'食品': [10, 50, 200]}[cat][['低端', '中端', '高端'].index(subcat)]
price_variation = np.random.uniform(0.7, 1.3, n_samples // (n_categories * n_subcategories))
sales_variation = np.random.uniform(0.5, 2.0, n_samples // (n_categories * n_subcategories))
for i in range(n_samples // (n_categories * n_subcategories)):
data.append({
'产品类别': cat,
'价格档次': subcat,
'价格': base_price * price_variation[i],
'销量': 1000 * sales_variation[i] / (base_price * price_variation[i]) ** 0.5,
'评分': np.random.beta(a=2, b=2) * 4 + 1, # 1-5分
'退货率': np.random.beta(a=1, b=9) * 10, # 0-10%
'月份': np.random.choice(['1月', '2月', '3月', '4月', '5月', '6月'])
})
df = pd.DataFrame(data)
# 添加衍生特征
df['收入'] = df['价格'] * df['销量']
df['利润'] = df['收入'] * np.random.uniform(0.1, 0.3, len(df))
print("数据概览:")
print(f"总记录数: {len(df)}")
print(f"产品类别: {df['产品类别'].unique().tolist()}")
print(f"价格档次: {df['价格档次'].unique().tolist()}")
print(f"数值特征: {df.select_dtypes(include=[np.number]).columns.tolist()}")
# 创建分类数据可视化
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
# 1. 分类柱状图(带误差条)
ax1 = axes[0, 0]
sns.barplot(data=df, x='产品类别', y='收入', hue='价格档次',
palette='Blues', ax=ax1, ci='sd', capsize=0.1)
ax1.set_title('各品类收入分布(按价格档次)', fontsize=12, fontweight='bold')
ax1.set_xlabel('产品类别')
ax1.set_ylabel('平均收入')
ax1.tick_params(axis='x', rotation=45)
ax1.legend(title='价格档次')
# 添加数值标签
for container in ax1.containers:
ax1.bar_label(container, fmt='%.0f', padding=3, fontsize=9)
# 2. 箱线图 + 带状图
ax2 = axes[0, 1]
sns.boxplot(data=df, x='产品类别', y='价格', hue='价格档次',
palette='Set2', ax=ax2, showfliers=False)
sns.stripplot(data=df, x='产品类别', y='价格', hue='价格档次',
palette='dark:black', ax=ax2, dodge=True,
size=3, alpha=0.5, jitter=0.2)
ax2.set_title('价格分布箱线图(带原始数据点)', fontsize=12, fontweight='bold')
ax2.set_xlabel('产品类别')
ax2.set_ylabel('价格')
ax2.tick_params(axis='x', rotation=45)
# 处理图例(避免重复)
handles, labels = ax2.get_legend_handles_labels()
ax2.legend(handles[:3], labels[:3], title='价格档次')
# 3. 小提琴图
ax3 = axes[0, 2]
sns.violinplot(data=df, x='产品类别', y='评分', hue='价格档次',
palette='viridis', ax=ax3, split=True, inner='quartile')
ax3.set_title('评分分布小提琴图', fontsize=12, fontweight='bold')
ax3.set_xlabel('产品类别')
ax3.set_ylabel('评分')
ax3.tick_params(axis='x', rotation=45)
ax3.legend(title='价格档次')
# 4. 点图(Point Plot)
ax4 = axes[1, 0]
sns.pointplot(data=df, x='月份', y='销量', hue='产品类别',
palette='tab10', ax=ax4, ci='sd', dodge=0.3,
markers=['o', 's', '^', 'D'], linestyles=['-', '--', '-.', ':'])
ax4.set_title('月度销量趋势(按产品类别)', fontsize=12, fontweight='bold')
ax4.set_xlabel('月份')
ax4.set_ylabel('平均销量')
ax4.legend(title='产品类别', bbox_to_anchor=(1.05, 1), loc='upper left')
# 5. 热图(分类数据聚合)
ax5 = axes[1, 1]
# 创建透视表
heatmap_data = df.pivot_table(values='退货率',
index='产品类别',
columns='价格档次',
aggfunc='mean')
sns.heatmap(heatmap_data, annot=True, fmt='.1f', cmap='YlOrRd',
square=True, linewidths=1, ax=ax5, cbar_kws={'label': '平均退货率 (%)'})
ax5.set_title('各品类退货率热图', fontsize=12, fontweight='bold')
ax5.set_xlabel('价格档次')
ax5.set_ylabel('产品类别')
# 6. 分类散点图
ax6 = axes[1, 2]
sns.scatterplot(data=df, x='价格', y='销量', hue='产品类别',
style='价格档次', size='评分', sizes=(50, 200),
palette='Set2', alpha=0.7, ax=ax6)
ax6.set_title('价格 vs 销量(多维度编码)', fontsize=12, fontweight='bold')
ax6.set_xlabel('价格')
ax6.set_ylabel('销量')
ax6.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.suptitle('分类数据可视化方法大全', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 高级可视化:分类数据关系网格
print("\n生成分类数据关系网格...")
g = sns.FacetGrid(df, col='产品类别', row='价格档次',
margin_titles=True, height=3, aspect=1.2)
g.map_dataframe(sns.scatterplot, x='价格', y='销量',
hue='月份', palette='tab10', alpha=0.7)
g.add_legend()
g.set_titles(row_template='{row_name}档次', col_template='{col_name}')
g.fig.suptitle('多维度分类数据关系网格', fontsize=16, fontweight='bold', y=1.02)
plt.show()
四、高级用法
1. 多面板复杂图形
python
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
# 设置专业样式
sns.set_theme(style="whitegrid", font_scale=1.2)
plt.rcParams['figure.dpi'] = 150
# 创建综合数据集
np.random.seed(42)
n_samples = 500
# 生成复杂数据
data = pd.DataFrame({
'基因表达量_A': np.random.exponential(2, n_samples),
'基因表达量_B': np.random.normal(5, 1.5, n_samples),
'基因表达量_C': np.random.lognormal(1, 0.5, n_samples),
'细胞直径': np.random.uniform(10, 30, n_samples),
'细胞复杂度': np.random.beta(2, 5, n_samples) * 100,
'处理组': np.random.choice(['对照组', '药物A', '药物B', '药物C'], n_samples),
'细胞类型': np.random.choice(['神经元', '胶质细胞', '干细胞'], n_samples),
'时间点': np.random.choice(['0h', '6h', '12h', '24h'], n_samples),
'重复实验': np.random.choice(['实验1', '实验2', '实验3'], n_samples)
})
# 添加相关性(创建一些特征之间的关系)
data['基因表达量_B'] = data['基因表达量_B'] + 0.3 * data['基因表达量_A'] + np.random.normal(0, 1, n_samples)
data['细胞直径'] = data['细胞直径'] + 0.5 * data['基因表达量_C'] + np.random.normal(0, 3, n_samples)
data['细胞复杂度'] = 50 + 10 * data['基因表达量_A'] - 5 * data['基因表达量_B'] + np.random.normal(0, 15, n_samples)
# 创建多面板图形
fig = plt.figure(figsize=(20, 16))
# 使用GridSpec创建复杂布局
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3, figure=fig,
width_ratios=[1, 1, 0.8],
height_ratios=[1, 1, 1.2],
wspace=0.3, hspace=0.4)
# 1. 主散点图(左上角,占两行两列)
ax1 = fig.add_subplot(gs[:2, :2])
scatter = sns.scatterplot(data=data, x='基因表达量_A', y='基因表达量_B',
hue='处理组', style='细胞类型', size='细胞直径',
sizes=(30, 300), alpha=0.7, palette='Set2', ax=ax1)
# 添加回归线
for treatment in data['处理组'].unique():
subset = data[data['处理组'] == treatment]
sns.regplot(data=subset, x='基因表达量_A', y='基因表达量_B',
scatter=False, ci=None,
line_kws={'alpha': 0.5, 'linewidth': 2}, ax=ax1)
ax1.set_title('基因表达关系网络(多维度编码)', fontsize=14, fontweight='bold')
ax1.set_xlabel('基因A表达量', fontsize=12)
ax1.set_ylabel('基因B表达量', fontsize=12)
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
# 2. 小提琴图(右上角)
ax2 = fig.add_subplot(gs[0, 2])
sns.violinplot(data=data, x='处理组', y='基因表达量_C',
hue='时间点', split=True, inner='quartile',
palette='husl', ax=ax2)
ax2.set_title('基因C表达分布', fontsize=12, fontweight='bold')
ax2.set_xlabel('处理组')
ax2.set_ylabel('基因C表达量')
ax2.tick_params(axis='x', rotation=45)
ax2.legend(title='时间点', bbox_to_anchor=(1.05, 1), loc='upper left')
# 3. 热图(中右)
ax3 = fig.add_subplot(gs[1, 2])
# 计算相关性矩阵
corr_cols = ['基因表达量_A', '基因表达量_B', '基因表达量_C',
'细胞直径', '细胞复杂度']
corr_matrix = data[corr_cols].corr()
# 创建带星号注释的热图
def annotate_heatmap(data, fmt, **kwargs):
"""自定义热图注释函数,添加统计显著性"""
annotations = []
p_values = np.zeros_like(data)
# 计算p值
n = len(data)
for i in range(n):
for j in range(n):
if i != j:
corr, p_value = stats.pearsonr(data[corr_cols[i]], data[corr_cols[j]])
p_values[i, j] = p_value
# 创建带星号的注释
for i in range(n):
row = []
for j in range(n):
if i == j:
row.append(f'{data.iloc[i, j]:.2f}')
else:
p = p_values[i, j]
if p < 0.001:
star = '***'
elif p < 0.01:
star = '**'
elif p < 0.05:
star = '*'
else:
star = ''
row.append(f'{data.iloc[i, j]:.2f}{star}')
annotations.append(row)
return annotations
annotations = annotate_heatmap(data[corr_cols], '.2f')
sns.heatmap(corr_matrix, annot=annotations, fmt='', cmap='coolwarm',
center=0, square=True, linewidths=1, ax=ax3,
cbar_kws={'label': '相关系数'}, annot_kws={'size': 9})
ax3.set_title('特征相关性热图\n(*p<0.05, **p<0.01, ***p<0.001)',
fontsize=12, fontweight='bold')
# 4. 箱线图网格(左下角)
ax4 = fig.add_subplot(gs[2, 0])
# 重塑数据用于箱线图
melted_data = data.melt(id_vars=['处理组', '细胞类型'],
value_vars=['基因表达量_A', '基因表达量_B', '基因表达量_C'],
var_name='基因', value_name='表达量')
sns.boxplot(data=melted_data, x='基因', y='表达量', hue='处理组',
palette='pastel', ax=ax4, showfliers=False)
sns.stripplot(data=melted_data, x='基因', y='表达量', hue='处理组',
palette='dark:black', ax=ax4, dodge=True,
size=2, alpha=0.5, jitter=0.2)
ax4.set_title('多基因表达箱线图', fontsize=12, fontweight='bold')
ax4.set_xlabel('基因')
ax4.set_ylabel('表达量')
ax4.tick_params(axis='x', rotation=45)
# 简化图例
handles, labels = ax4.get_legend_handles_labels()
ax4.legend(handles[:4], labels[:4], title='处理组')
# 5. 分布图网格(中下)
ax5 = fig.add_subplot(gs[2, 1])
# 绘制多变量分布
variables = ['细胞直径', '细胞复杂度']
treatments = data['处理组'].unique()
for i, var in enumerate(variables):
for j, treatment in enumerate(treatments):
subset = data[data['处理组'] == treatment]
sns.kdeplot(data=subset, x=var, ax=ax5,
label=f'{treatment} - {var}',
fill=True, alpha=0.3)
ax5.set_title('细胞特征分布(按处理组)', fontsize=12, fontweight='bold')
ax5.set_xlabel('特征值')
ax5.set_ylabel('密度')
ax5.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
# 6. 点图(右下角)
ax6 = fig.add_subplot(gs[2, 2])
# 计算每个组的统计量
summary_data = data.groupby(['处理组', '时间点']).agg({
'基因表达量_A': 'mean',
'基因表达量_B': 'mean',
'细胞复杂度': 'mean'
}).reset_index()
# 绘制点图
sns.pointplot(data=summary_data, x='时间点', y='基因表达量_A',
hue='处理组', palette='Set2', ax=ax6,
markers=['o', 's', '^', 'D'], dodge=0.3,
errwidth=1.5, capsize=0.1)
ax6.set_title('时间序列基因表达趋势', fontsize=12, fontweight='bold')
ax6.set_xlabel('时间点')
ax6.set_ylabel('基因A平均表达量')
ax6.legend(title='处理组', bbox_to_anchor=(1.05, 1), loc='upper left')
# 添加全局标题
plt.suptitle('生物医学研究数据综合可视化面板',
fontsize=18, fontweight='bold', y=0.98)
# 调整布局
plt.tight_layout()
plt.show()
# 统计摘要
print("数据集统计摘要:")
print(f"总样本数: {len(data)}")
print(f"处理组分布:")
print(data['处理组'].value_counts())
print(f"\n细胞类型分布:")
print(data['细胞类型'].value_counts())
print(f"\n数值特征统计:")
print(data[['基因表达量_A', '基因表达量_B', '基因表达量_C',
'细胞直径', '细胞复杂度']].describe().round(2))
2. 时间序列可视化
python
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats, signal
# 设置专业样式
sns.set_theme(style="darkgrid", palette="husl", font_scale=1.1)
# 创建复杂的时间序列数据
np.random.seed(42)
n_days = 365 * 3 # 3年数据
dates = pd.date_range(start='2021-01-01', periods=n_days, freq='D')
# 生成基础时间序列(带趋势、季节性和噪声)
def generate_time_series(base_value=100, trend=0.02, seasonal_amplitude=20,
noise_level=5, freq='D', n_periods=n_days):
"""生成模拟时间序列"""
# 时间索引
t = np.arange(n_periods)
# 趋势成分(线性趋势 + 轻微非线性)
trend_component = base_value + trend * t + 0.001 * t**2
# 季节性成分(多重季节性)
# 年季节性
seasonal_yearly = seasonal_amplitude * np.sin(2 * np.pi * t / 365)
# 月季节性
seasonal_monthly = 0.3 * seasonal_amplitude * np.sin(2 * np.pi * t / 30.5)
# 周季节性
seasonal_weekly = 0.1 * seasonal_amplitude * np.sin(2 * np.pi * t / 7)
seasonal_component = seasonal_yearly + seasonal_monthly + seasonal_weekly
# 噪声成分(异方差噪声)
noise_component = np.random.normal(0, noise_level, n_periods) * (1 + 0.01 * t)
# 异常值
outlier_prob = 0.005 # 0.5%的概率出现异常值
outliers = np.random.choice([0, 1], size=n_periods, p=[1-outlier_prob, outlier_prob])
outlier_component = outliers * np.random.choice([-1, 1], n_periods) * seasonal_amplitude * 3
# 合成时间序列
ts = trend_component + seasonal_component + noise_component + outlier_component
return ts
# 生成多个相关的时间序列
metrics = {
'销售额': generate_time_series(100, 0.03, 25, 8),
'用户数': generate_time_series(1000, 0.01, 200, 50),
'转化率': generate_time_series(0.05, 0.0001, 0.01, 0.005),
'客单价': generate_time_series(200, 0.1, 30, 10),
'营销费用': generate_time_series(50, 0.02, 15, 5)
}
# 添加序列间的相关性
metrics['用户数'] = metrics['用户数'] + 0.5 * metrics['销售额'] + np.random.normal(0, 20, n_days)
metrics['转化率'] = 0.05 + 0.0002 * metrics['营销费用'] + np.random.normal(0, 0.003, n_days)
metrics['客单价'] = 200 + 0.1 * metrics['销售额'] - 0.05 * metrics['用户数'] + np.random.normal(0, 8, n_days)
# 创建DataFrame
ts_data = pd.DataFrame(metrics, index=dates)
ts_data.index.name = '日期'
# 添加衍生特征
ts_data['收入'] = ts_data['销售额'] * ts_data['客单价']
ts_data['获客成本'] = ts_data['营销费用'] / ts_data['用户数']
ts_data['月'] = ts_data.index.month
ts_data['季度'] = ts_data.index.quarter
ts_data['年份'] = ts_data.index.year
ts_data['星期'] = ts_data.index.dayofweek
ts_data['是否周末'] = ts_data['星期'].isin([5, 6]).astype(int)
print("时间序列数据概览:")
print(f"时间范围: {ts_data.index.min()} 到 {ts_data.index.max()}")
print(f"总天数: {len(ts_data)}")
print(f"指标数量: {len(ts_data.columns)}")
# 创建时间序列可视化面板
fig = plt.figure(figsize=(18, 14))
# 使用GridSpec创建布局
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3, figure=fig,
width_ratios=[1.5, 1, 1],
height_ratios=[1, 1, 1.2],
wspace=0.3, hspace=0.4)
# 1. 多时间序列趋势图(左上,占两行一列)
ax1 = fig.add_subplot(gs[:2, 0])
# 标准化以便在同一图中比较
normalized_data = (ts_data[['销售额', '用户数', '营销费用']] -
ts_data[['销售额', '用户数', '营销费用']].mean()) / \
ts_data[['销售额', '用户数', '营销费用']].std()
for column in normalized_data.columns:
sns.lineplot(data=normalized_data, x=normalized_data.index, y=column,
label=column, ax=ax1, linewidth=1.5, alpha=0.8)
# 添加移动平均
window = 30
for column in normalized_data.columns:
moving_avg = normalized_data[column].rolling(window=window).mean()
ax1.plot(normalized_data.index, moving_avg, linewidth=2.5,
label=f'{column} ({window}天移动平均)')
ax1.set_title('核心指标时间序列(标准化)', fontsize=14, fontweight='bold')
ax1.set_xlabel('日期')
ax1.set_ylabel('标准化值')
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.tick_params(axis='x', rotation=45)
# 2. 季节分解(右上)
ax2 = fig.add_subplot(gs[0, 1])
# 使用Statsmodels进行季节分解
from statsmodels.tsa.seasonal import seasonal_decompose
# 选择销售额进行分析
sales_series = ts_data['销售额']
decomposition = seasonal_decompose(sales_series.resample('M').mean(),
model='additive', period=12)
# 绘制趋势成分
ax2.plot(decomposition.trend.index, decomposition.trend,
color='red', linewidth=2, label='趋势')
ax2.fill_between(decomposition.trend.index,
decomposition.trend - decomposition.trend.std(),
decomposition.trend + decomposition.trend.std(),
alpha=0.2, color='red')
ax2.set_title('销售额趋势分解', fontsize=12, fontweight='bold')
ax2.set_xlabel('日期')
ax2.set_ylabel('销售额')
ax2.legend()
ax2.tick_params(axis='x', rotation=45)
# 3. 季节性分析(右中)
ax3 = fig.add_subplot(gs[1, 1])
# 计算月度平均值
monthly_avg = ts_data.groupby('月')['销售额'].agg(['mean', 'std']).reset_index()
# 绘制月度模式
sns.barplot(data=monthly_avg, x='月', y='mean',
palette='viridis', ax=ax3, alpha=0.7)
# 添加误差条
x_positions = np.arange(len(monthly_avg))
ax3.errorbar(x_positions, monthly_avg['mean'], yerr=monthly_avg['std'],
fmt='none', color='black', capsize=3, capthick=1)
ax3.set_title('销售额月度季节性模式', fontsize=12, fontweight='bold')
ax3.set_xlabel('月份')
ax3.set_ylabel('平均销售额')
ax3.set_xticklabels(['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月'])
# 4. 自相关和偏自相关图(左中下)
ax4 = fig.add_subplot(gs[2, 0])
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# 绘制自相关函数
plot_acf(sales_series.diff().dropna(), lags=50, ax=ax4, alpha=0.05)
ax4.set_title('销售额一阶差分自相关函数', fontsize=12, fontweight='bold')
ax4.set_xlabel('滞后')
ax4.set_ylabel('自相关系数')
# 5. 热力图:星期vs月份(中下)
ax5 = fig.add_subplot(gs[2, 1])
# 创建透视表:星期 vs 月份 的平均销售额
heatmap_data = ts_data.pivot_table(values='销售额',
index='星期',
columns='月',
aggfunc='mean')
# 重新索引使星期从周一开始
heatmap_data = heatmap_data.reindex([1, 2, 3, 4, 5, 6, 0])
sns.heatmap(heatmap_data, annot=True, fmt='.0f', cmap='YlOrRd',
linewidths=0.5, ax=ax5, cbar_kws={'label': '平均销售额'})
ax5.set_title('销售额:星期 vs 月份热图', fontsize=12, fontweight='bold')
ax5.set_xlabel('月份')
ax5.set_ylabel('星期')
ax5.set_yticklabels(['周一', '周二', '周三', '周四', '周五', '周六', '周日'])
ax5.set_xticklabels(['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月'])
# 6. 多指标关系散点图(右下)
ax6 = fig.add_subplot(gs[:2, 2])
# 按季度着色
scatter = sns.scatterplot(data=ts_data, x='用户数', y='销售额',
hue='季度', size='客单价', style='年份',
palette='Set2', sizes=(30, 200), alpha=0.7, ax=ax6)
# 添加回归线
sns.regplot(data=ts_data, x='用户数', y='销售额',
scatter=False, ci=95,
line_kws={'color': 'red', 'linewidth': 2, 'alpha': 0.5}, ax=ax6)
ax6.set_title('用户数 vs 销售额(多维度编码)', fontsize=12, fontweight='bold')
ax6.set_xlabel('用户数')
ax6.set_ylabel('销售额')
ax6.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
# 7. 滚动统计图(左下角)
ax7 = fig.add_subplot(gs[2, 2])
# 计算滚动统计量
window_sizes = [7, 30, 90]
colors = ['blue', 'green', 'red']
for window, color in zip(window_sizes, colors):
rolling_mean = ts_data['销售额'].rolling(window=window).mean()
rolling_std = ts_data['销售额'].rolling(window=window).std()
# 绘制滚动平均值
ax7.plot(ts_data.index, rolling_mean, color=color, linewidth=1.5,
label=f'{window}天移动平均', alpha=0.8)
# 填充滚动标准差区域
ax7.fill_between(ts_data.index,
rolling_mean - rolling_std,
rolling_mean + rolling_std,
color=color, alpha=0.1)
ax7.set_title('销售额滚动统计量', fontsize=12, fontweight='bold')
ax7.set_xlabel('日期')
ax7.set_ylabel('销售额')
ax7.legend()
ax7.tick_params(axis='x', rotation=45)
# 添加全局标题
plt.suptitle('业务时间序列数据综合分析面板',
fontsize=18, fontweight='bold', y=0.98)
# 调整布局
plt.tight_layout()
plt.show()
# 高级分析:时间序列统计检验
print("\n时间序列统计检验:")
print("=" * 50)
# ADF单位根检验(检验平稳性)
from statsmodels.tsa.stattools import adfuller
adf_result = adfuller(sales_series.dropna())
print(f"ADF单位根检验(销售额):")
print(f" ADF统计量: {adf_result[0]:.4f}")
print(f" p值: {adf_result[1]:.4e}")
print(f" 是否平稳: {'是' if adf_result[1] < 0.05 else '否'}")
# 季节性检验
print(f"\n季节性分析(销售额):")
monthly_effects = ts_data.groupby('月')['销售额'].mean()
print(f" 月度变异系数: {monthly_effects.std() / monthly_effects.mean():.3f}")
print(f" 最强月份: {monthly_effects.idxmax()}月 ({monthly_effects.max():.1f})")
print(f" 最弱月份: {monthly_effects.idxmin()}月 ({monthly_effects.min():.1f})")
# 趋势分析
print(f"\n趋势分析(销售额):")
# 计算年度增长
yearly_growth = ts_data.groupby('年份')['销售额'].mean()
growth_rates = yearly_growth.pct_change().dropna()
print(f" 年均增长率: {growth_rates.mean():.2%}")
print(f" 增长稳定性(变异系数): {growth_rates.std() / growth_rates.mean():.3f}")
# 预测区间
print(f"\n预测分析:")
last_value = sales_series.iloc[-1]
trend_growth = 0.03 # 假设3%的日趋势增长
forecast_days = 30
forecast = last_value * (1 + trend_growth) ** forecast_days
print(f" {forecast_days}天预测值: {forecast:.1f}")
print(f" 95%预测区间: [{forecast * 0.9:.1f}, {forecast * 1.1:.1f}]")
五、实际应用场景
1. 用户行为分析系统
python
class UserBehaviorAnalyzer:
def __init__(self):
self.data = None
self.fig = None
def generate_sample_data(self, n_users=1000, n_days=90):
"""生成模拟用户行为数据"""
np.random.seed(42)
# 生成用户基本属性
user_ids = np.arange(1, n_users + 1)
ages = np.random.randint(18, 65, n_users)
genders = np.random.choice(['男', '女'], n_users, p=[0.55, 0.45])
cities = np.random.choice(['北京', '上海', '广州', '深圳', '杭州', '成都',
'武汉', '南京', '西安', '重庆'], n_users)
registration_dates = pd.date_range(
start='2024-01-01',
end='2024-03-31',
periods=n_users
)
# 用户行为数据
user_data = []
for user_id, age, gender, city, reg_date in zip(user_ids, ages, genders, cities, registration_dates):
# 每个用户生成多个行为记录
n_sessions = np.random.poisson(30) # 平均30次会话
for _ in range(n_sessions):
# 会话时间(注册后随机时间)
days_after_reg = np.random.randint(0, (pd.Timestamp('2024-06-30') - reg_date).days)
session_date = reg_date + pd.Timedelta(days=days_after_reg)
# 会话特征
session_duration = np.random.exponential(300) # 平均5分钟
page_views = np.random.randint(1, 50)
clicks = np.random.binomial(page_views, 0.3) # 30%点击率
purchases = 1 if np.random.random() < 0.05 else 0 # 5%购买率
purchase_amount = purchases * np.random.uniform(10, 1000) if purchases else 0
# 设备类型
device = np.random.choice(['手机', '电脑', '平板'],
p=[0.6, 0.3, 0.1])
# 流量来源
traffic_source = np.random.choice(['直接访问', '搜索引擎', '社交媒体', '广告'],
p=[0.2, 0.4, 0.2, 0.2])
user_data.append({
'用户ID': user_id,
'年龄': age,
'性别': gender,
'城市': city,
'注册日期': reg_date,
'会话日期': session_date,
'会话时长': session_duration,
'页面浏览数': page_views,
'点击次数': clicks,
'是否购买': purchases,
'购买金额': purchase_amount,
'设备类型': device,
'流量来源': traffic_source
})
self.data = pd.DataFrame(user_data)
# 添加衍生特征
self.data['会话日期'] = pd.to_datetime(self.data['会话日期'])
self.data['注册月份'] = self.data['注册日期'].dt.to_period('M')
self.data['会话月份'] = self.data['会话日期'].dt.to_period('M')
self.data['注册天数'] = (self.data['会话日期'] - self.data['注册日期']).dt.days
self.data['点击率'] = self.data['点击次数'] / self.data['页面浏览数']
self.data['会话时长分钟'] = self.data['会话时长'] / 60
# 用户级别聚合
user_agg = self.data.groupby('用户ID').agg({
'会话日期': 'count',
'是否购买': 'sum',
'购买金额': 'sum',
'会话时长': 'mean',
'页面浏览数': 'mean'
}).rename(columns={
'会话日期': '总会话数',
'是否购买': '购买次数',
'购买金额': '总消费金额',
'会话时长': '平均会话时长',
'页面浏览数': '平均页面浏览数'
})
self.user_summary = user_agg.reset_index()
print(f"数据生成完成:")
print(f" 总用户数: {n_users}")
print(f" 总会话数: {len(self.data)}")
print(f" 总购买次数: {self.data['是否购买'].sum()}")
print(f" 总消费金额: ${self.data['购买金额'].sum():,.2f}")
return self.data
def create_user_segmentation_dashboard(self):
"""创建用户分群分析仪表板"""
if self.data is None:
print("请先生成数据")
return
# 设置样式
sns.set_theme(style="whitegrid", palette="husl", font_scale=1.1)
# 创建图形
fig = plt.figure(figsize=(20, 16))
# 使用GridSpec创建布局
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3, figure=fig,
width_ratios=[1, 1, 1],
height_ratios=[1, 1, 1.2],
wspace=0.3, hspace=0.4)
# 1. RFM分析(左上)
ax1 = fig.add_subplot(gs[0, 0])
# 计算RFM值
current_date = self.data['会话日期'].max()
rfm_data = self.data.groupby('用户ID').agg({
'会话日期': lambda x: (current_date - x.max()).days, # 最近访问
'用户ID': 'count', # 访问频率
'购买金额': 'sum' # 消费金额
}).rename(columns={
'会话日期': 'Recency',
'用户ID': 'Frequency',
'购买金额': 'Monetary'
})
# RFM分箱
rfm_data['R_Score'] = pd.qcut(rfm_data['Recency'], 4, labels=[4, 3, 2, 1])
rfm_data['F_Score'] = pd.qcut(rfm_data['Frequency'], 4, labels=[1, 2, 3, 4])
rfm_data['M_Score'] = pd.qcut(rfm_data['Monetary'], 4, labels=[1, 2, 3, 4])
rfm_data['RFM_Score'] = rfm_data['R_Score'].astype(str) + \
rfm_data['F_Score'].astype(str) + \
rfm_data['M_Score'].astype(str)
# 用户分群
def segment_user(row):
if row['R_Score'] >= 3 and row['F_Score'] >= 3 and row['M_Score'] >= 3:
return '高价值用户'
elif row['R_Score'] >= 3:
return '活跃用户'
elif row['R_Score'] < 2:
return '流失用户'
else:
return '一般用户'
rfm_data['Segment'] = rfm_data.apply(segment_user, axis=1)
# 绘制分群分布
segment_counts = rfm_data['Segment'].value_counts()
colors = sns.color_palette('Set2', len(segment_counts))
wedges, texts, autotexts = ax1.pie(segment_counts.values,
labels=segment_counts.index,
autopct='%1.1f%%',
colors=colors,
startangle=90,
wedgeprops=dict(width=0.3, edgecolor='w'))
# 美化饼图
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
ax1.set_title('用户RFM分群分布', fontsize=12, fontweight='bold')
# 2. 用户生命周期价值分布(右上)
ax2 = fig.add_subplot(gs[0, 1])
# 计算用户LTV(简化版)
ltv_data = self.user_summary.copy()
ltv_data['LTV'] = ltv_data['总消费金额']
# 绘制LTV分布
sns.histplot(data=ltv_data, x='LTV', kde=True, ax=ax2,
bins=30, color='skyblue', edgecolor='black')
# 添加统计线
mean_ltv = ltv_data['LTV'].mean()
median_ltv = ltv_data['LTV'].median()
ax2.axvline(mean_ltv, color='red', linestyle='--',
linewidth=2, label=f'均值: ${mean_ltv:.2f}')
ax2.axvline(median_ltv, color='green', linestyle='--',
linewidth=2, label=f'中位数: ${median_ltv:.2f}')
ax2.set_title('用户生命周期价值分布', fontsize=12, fontweight='bold')
ax2.set_xlabel('LTV (美元)')
ax2.set_ylabel('用户数')
ax2.legend()
ax2.set_xlim([0, ltv_data['LTV'].quantile(0.95)]) # 排除极端值
# 3. 转化漏斗分析(左中)
ax3 = fig.add_subplot(gs[1, 0])
# 定义漏斗阶段
funnel_stages = ['访问', '浏览', '点击', '购买']
funnel_counts = [
len(self.data),
self.data['页面浏览数'].sum(),
self.data['点击次数'].sum(),
self.data['是否购买'].sum()
]
# 计算转化率
conversion_rates = []
for i in range(1, len(funnel_counts)):
rate = funnel_counts[i] / funnel_counts[i-1] * 100
conversion_rates.append(f'{rate:.1f}%')
# 绘制漏斗图
y_positions = np.arange(len(funnel_stages))
bar_width = 0.6
# 归一化数据用于绘图
max_count = max(funnel_counts)
normalized_counts = [count / max_count for count in funnel_counts]
bars = ax3.barh(y_positions, normalized_counts,
height=bar_width, color=sns.color_palette('Blues', len(funnel_stages)))
# 添加数值标签
for i, (bar, count, rate) in enumerate(zip(bars, funnel_counts, [None] + conversion_rates)):
ax3.text(bar.get_width() + 0.02, bar.get_y() + bar.get_height()/2,
f'{count:,}\n{rate if rate else ""}',
va='center', fontsize=10)
ax3.set_yticks(y_positions)
ax3.set_yticklabels(funnel_stages)
ax3.set_xlabel('归一化用户数')
ax3.set_title('用户转化漏斗分析', fontsize=12, fontweight='bold')
ax3.set_xlim([0, 1.1])
# 4. 用户行为热力图(中中)
ax4 = fig.add_subplot(gs[1, 1])
# 创建用户行为矩阵(用户 vs 行为指标)
behavior_matrix = self.user_summary[['总会话数', '购买次数', '总消费金额',
'平均会话时长', '平均页面浏览数']]
# 标准化
behavior_matrix_normalized = (behavior_matrix - behavior_matrix.mean()) / behavior_matrix.std()
# 绘制热力图
sns.heatmap(behavior_matrix_normalized.corr(), annot=True, fmt='.2f',
cmap='coolwarm', center=0, square=True, ax=ax4,
cbar_kws={'label': '相关系数'})
ax4.set_title('用户行为指标相关性热图', fontsize=12, fontweight='bold')
# 5. 用户留存分析(右下)
ax5 = fig.add_subplot(gs[0, 2])
# 计算用户留存率
# 简化计算:以月为单位
user_cohorts = self.data.groupby(['注册月份', '会话月份'])['用户ID'].nunique().unstack()
# 计算留存率(每行除以其第一个非空值)
retention_matrix = user_cohorts.div(user_cohorts.iloc[:, 0], axis=0) * 100
# 绘制留存曲线
for cohort in retention_matrix.index[:5]: # 只显示前5个同期群
ax5.plot(retention_matrix.columns.astype(str),
retention_matrix.loc[cohort].values,
marker='o', label=str(cohort))
ax5.set_title('用户月留存率曲线', fontsize=12, fontweight='bold')
ax5.set_xlabel('注册后月份')
ax5.set_ylabel('留存率 (%)')
ax5.legend(title='注册月份')
ax5.grid(True, alpha=0.3)
# 6. 用户特征多维度分析(右中)
ax6 = fig.add_subplot(gs[1, 2])
# 选择关键特征
features = ['年龄', '总会话数', '平均会话时长', '购买次数']
# 创建平行坐标图数据
parallel_data = self.user_summary[features].copy()
# 标准化
parallel_data_normalized = (parallel_data - parallel_data.mean()) / parallel_data.std()
# 添加分群信息
parallel_data_normalized['Segment'] = rfm_data['Segment'].values
# 绘制平行坐标图(简化版)
# 由于Seaborn没有原生平行坐标图,我们用多子图替代
segment_colors = {'高价值用户': 'red', '活跃用户': 'green',
'一般用户': 'blue', '流失用户': 'gray'}
for segment, color in segment_colors.items():
subset = parallel_data_normalized[parallel_data_normalized['Segment'] == segment]
if len(subset) > 0:
for i in range(len(subset)):
ax6.plot(features, subset[features].iloc[i].values,
color=color, alpha=0.1, linewidth=0.5)
# 添加每个特征的均值线
for feature in features:
mean_val = parallel_data_normalized[feature].mean()
ax6.axhline(y=mean_val, color='black', linestyle='--',
linewidth=1, alpha=0.5)
ax6.set_title('用户特征多维度分析(按分群)', fontsize=12, fontweight='bold')
ax6.set_xlabel('用户特征')
ax6.set_ylabel('标准化值')
ax6.set_xticks(range(len(features)))
ax6.set_xticklabels(features, rotation=45)
ax6.grid(True, alpha=0.3)
# 7. 设备与流量来源分析(左下)
ax7 = fig.add_subplot(gs[2, 0])
# 创建分组数据
device_source_data = self.data.groupby(['设备类型', '流量来源']).agg({
'用户ID': 'count',
'是否购买': 'mean',
'购买金额': 'mean'
}).reset_index()
# 绘制分组柱状图
sns.barplot(data=device_source_data, x='设备类型', y='是否购买',
hue='流量来源', palette='Set3', ax=ax7)
ax7.set_title('设备类型与流量来源的购买转化率', fontsize=12, fontweight='bold')
ax7.set_xlabel('设备类型')
ax7.set_ylabel('购买转化率')
ax7.legend(title='流量来源', bbox_to_anchor=(1.05, 1), loc='upper left')
# 8. 时间序列行为分析(中下)
ax8 = fig.add_subplot(gs[2, 1])
# 按日期聚合
daily_data = self.data.groupby('会话日期').agg({
'用户ID': 'nunique',
'是否购买': 'sum',
'会话时长': 'mean'
}).rename(columns={
'用户ID': '日活跃用户数',
'是否购买': '日购买次数',
'会话时长': '平均会话时长'
})
# 绘制双轴图
color1 = 'tab:blue'
ax8.plot(daily_data.index, daily_data['日活跃用户数'],
color=color1, linewidth=2, label='日活跃用户')
ax8.set_xlabel('日期')
ax8.set_ylabel('日活跃用户数', color=color1)
ax8.tick_params(axis='y', labelcolor=color1)
ax8.tick_params(axis='x', rotation=45)
# 第二个y轴
ax8_2 = ax8.twinx()
color2 = 'tab:red'
ax8_2.plot(daily_data.index, daily_data['日购买次数'],
color=color2, linewidth=2, label='日购买次数', linestyle='--')
ax8_2.set_ylabel('日购买次数', color=color2)
ax8_2.tick_params(axis='y', labelcolor=color2)
ax8.set_title('用户行为时间序列趋势', fontsize=12, fontweight='bold')
# 合并图例
lines1, labels1 = ax8.get_legend_handles_labels()
lines2, labels2 = ax8_2.get_legend_handles_labels()
ax8.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
# 9. 用户聚类分析(右下)
ax9 = fig.add_subplot(gs[2, 2])
# 使用PCA进行降维
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# 准备数据
cluster_features = ['总会话数', '购买次数', '总消费金额',
'平均会话时长', '平均页面浏览数']
cluster_data = self.user_summary[cluster_features].fillna(0)
# 标准化
scaler = StandardScaler()
scaled_data = scaler.fit_transform(cluster_data)
# PCA降维到2维
pca = PCA(n_components=2)
pca_result = pca.fit_transform(scaled_data)
# 添加到数据框
self.user_summary['PC1'] = pca_result[:, 0]
self.user_summary['PC2'] = pca_result[:, 1]
# 按分群着色
scatter = sns.scatterplot(data=self.user_summary, x='PC1', y='PC2',
hue=rfm_data['Segment'], palette='Set2',
alpha=0.6, s=50, ax=ax9)
# 添加聚类中心(按分群)
for segment in rfm_data['Segment'].unique():
subset = self.user_summary[rfm_data['Segment'] == segment]
if len(subset) > 0:
center_x = subset['PC1'].mean()
center_y = subset['PC2'].mean()
ax9.scatter(center_x, center_y, color='black',
s=200, marker='X', edgecolors='white', linewidth=2)
ax9.text(center_x, center_y, segment, fontsize=10,
fontweight='bold', ha='center', va='center',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
ax9.set_title('用户行为PCA降维与聚类分析', fontsize=12, fontweight='bold')
ax9.set_xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.1%}方差)')
ax9.set_ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.1%}方差)')
ax9.legend(title='用户分群', bbox_to_anchor=(1.05, 1), loc='upper left')
# 添加全局标题
plt.suptitle('用户行为分析综合仪表板',
fontsize=18, fontweight='bold', y=0.98)
# 调整布局
plt.tight_layout()
self.fig = fig
return fig
def calculate_key_metrics(self):
"""计算关键业务指标"""
if self.data is None:
print("请先生成数据")
return
metrics = {}
# 用户指标
total_users = self.user_summary['用户ID'].nunique()
active_users = self.user_summary[self.user_summary['总会话数'] > 0]['用户ID'].nunique()
paying_users = self.user_summary[self.user_summary['购买次数'] > 0]['用户ID'].nunique()
metrics['用户指标'] = {
'总用户数': total_users,
'活跃用户数': active_users,
'付费用户数': paying_users,
'活跃用户占比': f'{active_users/total_users*100:.1f}%',
'付费用户占比': f'{paying_users/total_users*100:.1f}%'
}
# 行为指标
total_sessions = len(self.data)
avg_session_duration = self.data['会话时长分钟'].mean()
avg_pageviews = self.data['页面浏览数'].mean()
avg_ctr = self.data['点击率'].mean() * 100
metrics['行为指标'] = {
'总会话数': total_sessions,
'平均会话时长': f'{avg_session_duration:.1f}分钟',
'平均页面浏览数': f'{avg_pageviews:.1f}',
'平均点击率': f'{avg_ctr:.1f}%'
}
# 转化指标
total_purchases = self.data['是否购买'].sum()
conversion_rate = total_purchases / total_sessions * 100
avg_purchase_value = self.data[self.data['是否购买'] == 1]['购买金额'].mean()
total_revenue = self.data['购买金额'].sum()
metrics['转化指标'] = {
'总购买次数': total_purchases,
'转化率': f'{conversion_rate:.2f}%',
'平均订单价值': f'${avg_purchase_value:.2f}',
'总收入': f'${total_revenue:,.2f}'
}
# RFM分群指标
if hasattr(self, 'user_summary'):
# 计算每个分群的关键指标
segment_metrics = {}
for segment in ['高价值用户', '活跃用户', '一般用户', '流失用户']:
segment_users = self.user_summary[self.user_summary['用户ID'].isin(
self.user_summary['用户ID'] # 这里需要实际的RFM分群数据
)]
if len(segment_users) > 0:
segment_metrics[segment] = {
'用户数': len(segment_users),
'占比': f'{len(segment_users)/total_users*100:.1f}%',
'平均LTV': f'${segment_users["总消费金额"].mean():.2f}'
}
metrics['分群指标'] = segment_metrics
return metrics
# 使用示例
analyzer = UserBehaviorAnalyzer()
data = analyzer.generate_sample_data(n_users=1000, n_days=90)
dashboard = analyzer.create_user_segmentation_dashboard()
# 计算关键指标
metrics = analyzer.calculate_key_metrics()
print("\n关键业务指标:")
print("=" * 50)
for category, category_metrics in metrics.items():
print(f"\n{category}:")
for metric_name, metric_value in category_metrics.items():
print(f" {metric_name}: {metric_value}")
plt.show()
Seaborn通过其优雅的设计哲学和精心考虑的默认设置,真正实现了"美就是生产力"的理念。在数据科学工作中,我们常常发现:一个清晰、美观的可视化不仅能更好地传达信息,还能激发更深层次的洞察。Seaborn的成功不仅在于它提供了丰富的统计图表类型,更在于它建立了一套完整的美学体系,让数据分析师能够专注于数据本身,而不是图表的格式调整。
随着数据可视化需求的不断增长,Seaborn也在持续进化。新的版本提供了更好的颜色主题、更强的定制能力,以及与现代数据科学工作流的深度集成。同时,Seaborn与Matplotlib的完美兼容性意味着你既可以享受Seaborn的简洁优雅,又可以在需要时使用Matplotlib的强大功能进行深度定制。
你在使用Seaborn进行数据可视化时有什么特别的技巧或遇到过什么有趣的挑战?或者你有关于统计可视化的独特应用场景想要分享吗?欢迎在评论区交流你的经验和想法!
更多推荐



所有评论(0)