当初作图时想往 sns.pairplot 里加些内容,搜索了解到 sns.PairGrid 可以更灵活地绘制多因素(多变量)关系图,遂在网上找相关教程,最后发现 CSDN 上的内容竟然都要付费,无奈转向 ChatGPT 求助,最后在 C 老师帮助下成功完成了自己的想法。

以上既是这篇文章的来源也是动机,这篇文章就来分享一下如何使用 sns.PairGrid 绘制更加复杂的多变量图,本文示例将是 hexbin + 拟合直线,但只要知道如何将图架构起来那么绘制其他类型的图也不会有问题。

sns.pairplot 的详尽用法:

https://seaborn.pydata.org/generated/seaborn.pairplot.html#seaborn-pairplot

一般的多变量图绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 模拟三个变量
np.random.seed(114514)
a = np.random.randn(100)
b = a*0.7+0.5+np.random.rand(100)
c = b**2-0.1+np.random.rand(100)

data = {
'a': a,
'b': b,
'c': c
}

df = pd.DataFrame(data)
sns.pairplot(df)

1
2
# 添加一些参数,使对角变成核密度图
sns.pairplot(df, diag_kind="kde")

虽然说这个功能确实非常方便,但是它有个不足就在于其框架已经是设置好的,也就是说图形的变化都只能在已设置好的范围里进行。如果我想要进行其他的操作比如拟合直线或者给每一个图添加相关性标注,这时就只能寻找一个更加 “泛用” 的框架了。

sns.PairGrid 就是理想之选,它允许我们自定义绘图参数并应用于对角线下、对角线和对角线上的部分,因此假如这里我们想要拟合直线,那么可以自定义绘图函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from scipy.stats import spearmanr
from statsmodels.nonparametric.smoothers_lowess import lowess

# 下方的图,此处计算了 spearman 相关系数并拟合了一次项的直线
# **kwargs 是为了让绘图函数能接受任意关键字参数避免 sns 传入其他参数时报错。
def regres_scatter(x, y, nhc='black', **kwargs):

m, b = np.polyfit(x, y, 1)
y_fit = m*x + b
rho, p_value = spearmanr(x, y)

plt.scatter(x, y, color='#C9DCEB', edgecolors='black')
plt.plot(x, y_fit, color=nhc, lw=2)
ax = plt.gca()
ax.text(1.5*x.min(), 0.9*y.max(),
f"Spearman R: {rho:.2f}\np-value: {p_value:.3f}",
)

# 上方的图,此处使用 hexbin 而非 scatter 并且拟合了 lowess 曲线
def lowess_scatter(x, y, gridsize=30, **kwargs):

result = lowess(y, x)
x_fit = result[:, 0]
y_fit = result[:, 1]

plt.hexbin(x, y, gridsize=gridsize, cmap='Blues')
plt.plot(x_fit, y_fit, color='red', lw=2)

自定义好函数以后,使用 sns.PairGrid 进行构造:

1
2
3
4
5
6
# 传入数据
pgp = sns.PairGrid(df, diag_sharey=False)
# 对不同部分进行绘图,可以传入参数也可以选择不传入
pgp.map_lower(lambda x, y, **kwargs: regres_scatter(x, y, nhc='red'))
pgp.map_diag(sns.kdeplot, shade=True, shade_lowest=False)
pgp.map_upper(lowess_scatter)

能看到每一部分的图都依据自己定义或选择的绘图函数进行了绘制,可以看出在绘制多变量图时 sns.PairGridsns.pairplot 会更加灵活,上限也能拉的更高。

课后问题(bushi):

  1. 如何使用相对坐标放置标签(ax.text)?相较于绝对坐标使用相对坐标的好处是什么?
  2. hexbin plot 中如何让没有数据点的位置不进行着色?如何让着色根据数据点数量的 log 值进行?
  3. 如何将 spearman r 换成 pearson r?两者有什么区别?文中的数据更适用于哪个(亦或是都适用)?