科研论文配图的绘制与配色基础-- 密度图

Seaborn 的 kdeplot() 函数是 Python 中绘制密度图的方式之一,Matplotlib 在现阶段则没有具体的绘制密度图的函数,一般是结合 Scipy 库中的 gaussian_kde() 函数结果进行绘制。

Python 的 scikit-learn 库中 neighbors.KernelDensity() 模块提供Gaussian、Tophat、Epanechnikov、Exponential、Linear 和 Cosine 6 种核函数来进行核密度估计计算。Python 的 KDEpy 库更是提供了多达 9 种核函数,包括 Gaussian、Exponential、Box、Tri、Epa、Biweight、Triweight、Tricube、
Cosine。
在这里插入图片描述

3种绘制密度图的方法对比

  • 使用 Seaborn 中的 kdeplot() 函数绘制密度图较为简单,结合 rugplot() 函数可
    以绘制沿 X 轴的数据分布情况。
  • gaussian_kde() 和KDEpy 库的计算结果结合 Matplotlib 中的 axes.Axes.plot()、axes.Axes.fill() 函数 -这两个方法相对麻烦一些
    在这里插入图片描述
# 图3-2-5(a)核心绘制代码
import Seaborn as sns
fig,ax = plt.subplots(figsize=(4,3.5),dpi=100,)
kde_01 = sns.kdeplot(x="data_01",data=data_df,
color="#1180D5",alpha=1,shade=True,ax=ax)
sns.rugplot(data=data_df, x="data_01",color='k',
height=.05,ax=ax)

# 图3-2-5(b)核心绘制代码
from scipy import stats
density = stats.kde.gaussian_kde(data_01)
x = np.linspace(-2,25,500)
y = density(x)
fig,ax = plt.subplots(figsize=(4,3.5),dpi=100)
ax.plot(x,y, lw=1,color="k")
ax.fill(x,y,color="#07A6C5")

# 添加单独的数据
ax.plot(data_01, [0.005]*len(data_01), '|',color='k',lw=1)

# 图3-2-5(c)核心绘制代码
from KDEpy import FFTKDE
x, y = FFTKDE(bw=2).fit(data_01).evaluate()
fig,ax = plt.subplots(figsize=(4,3.5),dpi=100)
ax.plot(x,y, lw=1,color="k",)
ax.fill(x,y,color="#FBCD2D")

# 添加单独的数据
ax.plot(data_01, [0.005]*len(data_01), '|', color='k',lw=1)

多组数据、同一个 核函数

对于具有不同数值分布情况的多组样本数据,我们经常使用同一个核函数对它进行拟合并将结果绘制成密度图。这种情况一般发生在数据探索阶段,上述方法常用于查看每个维度数据的分布情况或对不同数据间的差异进行对比。

下图为 Matplotlib 绘制的多组样本数据使用同一个核函数的核密度图,展示了不同数据的分布情况,涉及多子图绘制、X 轴Y 轴共享、子图间布局等绘图技巧。
在这里插入图片描述

代码如下:

import numpy as np
import pandas as pd
from KDEpy import NaiveKDE
import matplotlib.pyplot as plt
nrow = 2
ncol = 2
ax_label = ["a.","b.","c.","d."]
titles = ["Type One","Type Two","Type Three","Type Four"]
indexs = [i for i in data_df.columns] 
fig,axs = plt.subplots(nrow,ncol,figsize=(5,4),dpi=100,
sharey=True,sharex=True,constrained_layout=True)
for ax, index,label in zip(axs.flat, indexs,ax_label):
x,y = NaiveKDE(kernel="Gaussian",bw=2)\ 
fit(data_df[index].values).evaluate()
ax.plot(x,y, lw=1,color="#1BB71B")
ax.fill(x,y,color="#1BB71B",alpha=.6)

# 添加单独的数据
ax.plot(data_df[index].values,
[0.005]*len(data_df[index].values), '|', lw=1,
color='k')
ax.text(0.05, 0.95, label, transform=ax.transAxes,
fontsize=16, fontweight='bold', va='top')
ax.text(0.65, 0.95, index,transform=ax.transAxes,
fontsize=14, fontweight='bold', va='top')
# 添加子图共用坐标轴标签
fig.supxlabel('Values')
fig.supylabel('Density')
plt.show()

渐变颜色填充

用一个连续渐变颜色条表示具体的绘图数值,且对应颜色填充在密度图曲线范围内。
在 Python 绘图体系中,我们需要通过自定义绘制方式实现该效果。图 3-2-8 展示了利用 Matplotlib、ProPlot、SciencePlots 库分别绘制的带渐变颜色(gradient color)填充的密度图。需要注意的是,这里的连续填充颜色系为自定义的 parula 颜色系(MATLAB 经典的颜色系),只需将 colormaps.py 文件添加到当前绘制环境中即可导入定义好的 parula 颜色系。
在这里插入图片描述

代码如下:

import numpy as np
import pandas as pd
from KDEpy import FFTKDE
from colormaps import parula
from matplotlib.axes import Axes 
import matplotlib.pyplot as plt 
 
cmap = parula
x, y = FFTKDE(kernel="gaussian", bw=2).fit(data_01).
evaluate()
img_data = x.reshape(1, -1)
 
fig,ax = plt.subplots(figsize=(4,3.5),dpi=100)
ax.plot(x,y, lw=1,color="k")

# 添加单独的数据
ax.plot(data_01, [0.005]*len(data_01), '|', color='k',lw=1)
extent=[*ax.get_xlim(), *ax.get_ylim()]
im = Axes.imshow(ax, img_data, aspect='auto',cmap=cmap,extent=extent)

# 注意,这是使用了","符号
fill_line,= ax.fill(x, y, facecolor='none')
# 利用set_clip_path()方法进行裁剪
23. im.set_clip_path(fill_line)
24. colorbar = fig.colorbar(im,ax=ax,aspect=12,label="Values")

山脊图

在对多组数据进行密度图绘制时,除上述介绍的使用子图对每组数据进行绘制以外,我们还可以将多组数据绘制结果进行堆叠摆放,即使用“山脊”图(ridgeline chart)进行表示。“山脊”图通常用来表示不同类别的数据在同一因素的分布差异情况。在 Matplotlib 中,我们可以使用 Matplotlib 的“原生”方法绘制“山脊”图,也可以使用 JoyPy 库绘制。图 3-2-10 为利用Matplotlib 库原生方法和 JoyPy 库分别绘制的“山脊”图。
在这里插入图片描述

sord_index = [i for i in group_data.color.unique()]
sord_index = sorted(sord_index, key=str.lower)
fig,ax = plt.subplots(figsize=(5.5,4.5), dpi=100)
for i,index in zip(range(len(sord_index)), sord_index):
data = group_data.loc[group_data["color"]==index,
"depth"].values
x,y = NaiveKDE(kernel="Gaussian", bw=.8).
fit(data).evaluate()
ax.plot(x, 6*y+i, lw=.6, color="k", zorder=100-i)
ax.fill(x, 6*y+i, lw=1, color="gray", alpha=.6,
zorder=100-i)
ax.grid(which="major", axis="y", ls="--", lw=.7,
color="gray", zorder=-1)
ax.yaxis.set_tick_params(labelleft=True)
ax.set_yticks(np.arange(len(sord_index)))
ax.set_yticklabels(sord_index)

如果想使用连续渐变颜色对“山脊”图中的每组数据进行填充,并且用连续渐变颜色值表示数据大小,那么可以参考渐变颜色填充密度图的绘制方法。需要注意的是,由于绘制脚本中涉及循环绘制语句,因此,在保存成矢量文件(如 PDF 文件)时,会出现裁剪失败问题。想要解决这一问题,我们只需要在编写脚本前添加如下代码。

# 在将多个绘图对象保存为PDF文件时,需要进行如下设置
plt.rcParams["image.composite_image"] = False

图 3-2-12 为利用 Matplotlib 绘制的渐变颜色填充“山脊”图,其中,图 3-2-12(a)使用了颜色映射样式“plasma”,图 3-2-12(b)使用了颜色映射样式“parula”,不同颜色代表不同的变量(Depth)数值大小。
在这里插入图片描述

# 在将多个绘图对象保存为PDF文件时,需要进行如下设置
plt.rcParams["image.composite_image"] = False
fig,ax = plt.subplots(figsize=(5.5, 4.5), dpi=100)
for i,index in zip(range(len(sord_index)), sord_index):
	data = group_data.loc[group_data["color"]==index,"depth"].values
	x,y = NaiveKDE(kernel="Gaussian", bw=.8).fit(data).evaluate()
	img_data = x.reshape(1, -1)
	ax.plot(x,6*y+i, lw=1, color="k", zorder=100-i)
	fill_line, = ax.fill(x, 6*y+i, facecolor="none")
	ax.grid(which="major", axis="y", ls="--",
	lw=.7, color="gray", zorder=-1)
	ax.set_xlim(50,72)
	ax.yaxis.set_tick_params(labelleft=True)
	ax.set_yticks(np.arange(len(sord_index)))
	ax.set_yticklabels(sord_index)
	ax.set_xlabel("Depth")
	ax.set_ylabel("Color")
	ax.tick_params(which ="both",top=False,right=False)
	ax.tick_params(which = "minor", axis="both", left=False,bottom=False)
	for spin in ["top", "right", "bottom", "left"]:
		ax.spines[spin].set_visible(False)
	extent=[*ax.get_xlim(), *ax.get_ylim()]
	im = Axes.imshow(ax, img_data, aspect='auto',cmap="plasma", extent=extent)
	im.set_clip_path(fill_line)
colorbar = fig.colorbar(im, aspect=10, shrink=0.5)
colorbar.ax.set_title("Values", fontsize=10)

同一坐标系中多个密度图的绘制

在将多个密度图绘制在同一坐标系时,除了使用 Matplotlib 库进行循环绘制以外,还可以使用 Seaborn 库进行快速绘制。图 3-2-14 为使用 Matplotlib 和 Seaborn 分别绘制的“同一坐标系中的多个密度图”。注意,使用 Seaborn 绘制的同一坐标系中的多个密度图的默认顺序与Matplotlib 绘制结果不同。
在这里插入图片描述

# 利用Matplotlib 绘制
from scipy import stats
palette = ["#352A87", "#108ED2", "#65BE86", "#FFC337"]
fig,ax = plt.subplots(figsize=(4, 3.5), dpi=100)
for i, index, color in zip(range(len(palette)), data_df.columns, palette):
	data = data_df[index].values
	density = stats.kde.gaussian_kde(data)
	x = np.linspace(-2, 25, 500)
	y = density(x)
ax.plot(x, y, lw=.5, color="k", zorder=5-i)
ax.fill(x, y, color=color, label=index, alpha=.7)
ax.set_xlabel("Values")
ax.set_ylabel("Density")
ax.legend()
 
# 利用Seaborn绘制
fig,ax = plt.subplots(figsize=(4, 3.5), dpi=100)
sns.kdeplot(data=data_df, shade=True, palette= palette, alpha=.6, ax=ax)
ax.set_xlabel("Values")

在 ProPlot 库的编辑环境中绘制 Seaborn 的绘图对象时,两者虽然都是基于 Matplotlib 开发的高级封装库,但二者之间还存在较大的差异,无法较好地在特定图形绘制中形成统一的语法标准,导致 ProPlot 库在绘制 Seaborn 图形对象时的绘图定制化操作较弱。图 3-2-15 为使用ProPlot、SciencePlots 库分别绘制的“同一坐标系中的多个密度图”。
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值