密度图是一种用于可视化数据分布的图表类型,特别适用于连续变量的分布展示。它通过平滑地估计数据的概率密度函数来显示数据的分布情况。密度图可以帮助我们更清晰地理解数据的分布形态、集中趋势和离散程度,以及检测潜在的模式和异常值。
| 密度图的特点和优势:
连续性展示:密度图提供了数据分布的连续性展示,相较于直方图更加平滑。
无需预先分组:与直方图不同,密度图无需预先将数据分组,而是通过核密度估计(Kernel Density Estimation,KDE)等方法在整个数据范围内平滑地估计概率密度函数。
相对尺度比较:密度图可以方便地比较不同分布的形状和相对尺度,因为它们都在相同的概率密度轴上显示。
异常值识别:由于密度图能够平滑地显示数据分布,因此可以更容易地识别异常值,这些异常值在直方图中可能会被过于粗糙的分组遮盖。
| 密度图的绘制方法:
密度图的绘制通常通过核密度估计方法来实现。主要步骤包括:
选择核函数:核函数用于在每个数据点周围生成一定宽度的函数,通常选择的核函数有高斯核(Gaussian kernel)、矩形核(Rectangular kernel)等。
计算核密度估计(Kernel Density Estimation, KDE):对每个数据点应用核函数,并将结果加总,得到整个数据集的概率密度估计。
绘制密度图:将计算得到的概率密度曲线绘制在图表上,通常使用平滑的曲线表示。
密度图基本绘制
假设我们有一组假想的药物半衰期数据,假设这个数据集名为drug_half_lives.xlsx
。本例中我们将模拟这些数据,然后绘制密度图来展示它们的分布情况。
首先,我们需要安装并导入必要的Python库。确保你已经安装了以下库:Pandas, Matplotlib, KDEpy 和 Seaborn。
# 安装必要的库
pip install pandas matplotlib KDEpy seaborn
图 1 为使用python中的 KDEpy
库进行核密度估计并结合 matplotlib
绘制的密度图的实例,使用 KDEpy
进行了核密度估计,然后使用 matplotlib
绘制密度图,绘制核密度估计曲线,并填充曲线下的区域以显示数据分布的相对密度。可以根据需要添加其它的参数来控制绘制的曲线外观和行为。如 color
用于指定曲线的颜色;linestyle
用于指定曲线的线型,常用的线型包括实线('-')、虚线('--')、点线(':')、点划线('-.')等;linewidth
或 lw
用于指定曲线的线宽;marker
用于指定曲线上数据点的标记类型,常用的标记类型包括圆圈('o')、方形('s')、三角形('^')等;markersize
或 ms
用于指定标记的大小;alpha
指定曲线的透明度。取值范围为0到1之间,0表示完全透明,1表示完全不透明;label
:为曲线添加标签,用于图例中的显示;zorder
用于指定曲线的绘制顺序。值越大,曲线越靠前绘制(即覆盖其他曲线)。默认情况下,所有曲线的 zorder
值为2。
图 1. 密度图绘制实例
图 1 的绘图代码如下所示:
import os
import matplotlib.pyplot as plt
import pandas as pd
from KDEpy import FFTKDE
# 从 Excel 文件中读取数据,确保路径和文件名正确
df = pd.read_excel('drug_half_lives.xlsx', sheet_name='Sheet1')
# 设置字体和图形参数
plt.rcParams["font.family"] = "Arial"
plt.rcParams["axes.linewidth"] = 1
plt.rcParams["axes.labelsize"] = 14
plt.rcParams["xtick.labelsize"] = 12
plt.rcParams["ytick.labelsize"] = 12
# 提取药物半衰期数据
half_lives = df['Half-life (hours)'].values.reshape(-1, 1)
# 使用 KDEpy 进行核密度估计
kde = FFTKDE(kernel='gaussian', bw='silverman').fit(half_lives)
x, y = kde.evaluate()
# 绘制密度图
plt.figure(figsize=(8, 6))
plt.plot(x, y, color='#F8AC8C', linewidth=2)
plt.fill_between(x, y, color='#F8AC8C', alpha=0.3)
plt.xlabel('Half-life (hours)')
plt.ylabel('Density')
# 调整图像布局,确保所有元素居中
plt.tight_layout()
# 确保保存路径存在
output_dir = '图像实例'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 保存图像
plt.savefig(os.path.join(output_dir, '图1 密度图绘制实例.png'), dpi=600)
plt.show()
2. 单一数据,多个核函数
在图 1 的代码中,我们在使用 KDEpy
进行核密度估计时,创建了一个 FFTKDE
对象,并通过 kernel='gaussian', bw='silverman'
指定了核函数和带宽方法。KDEpy 支持多种核函数和带宽选择方法。
图 2 展示了同一组数据使用 9 种核函数进行核密度估计的图像。绘图时涉及了多子图的布局与绘制、坐标轴共享等绘图技巧,详见绘图的核心代码。
图 2. 不同核函数绘制的图像实例
图 2 的核心绘图代码如下所示:
# 核函数列表
kernels = ['gaussian', 'epa', 'box', 'tri', 'triweight',
'tricube', 'cosine', 'exponential', 'biweight']
# 创建3x3的子图网格
fig, axes = plt.subplots(3, 3, figsize=(6, 4), sharex=True, sharey=True, constrained_layout=True)
axes = axes.flatten()
# 遍历每种核函数并进行绘制
for i, kernel in enumerate(kernels):
kde = FFTKDE(kernel=kernel, bw='silverman').fit(half_lives)
x, y = kde.evaluate()
# 在子图中绘制核密度估计曲线
axes[i].plot(x, y, color='#2878B5', linewidth=2)
axes[i].fill_between(x, y, color='#2878B5', alpha=0.3) # 填充曲线下方的区域
axes[i].set_title(kernel.capitalize(), fontsize=10, fontweight='bold', fontname='Arial')
# 添加全局的 X 轴和 Y 轴标签
fig.supxlabel('Half-life (hours)', fontsize=14, fontname='Arial')
fig.supylabel('Density', fontsize=14, fontname='Arial')
为直观地了解数据的分布情况,包括数据的集中程度和离散程度以及增强图表的可视化效果,可以在图表中展示数据沿 X 轴的分布情况,即添加一个额外的图形元素来显示数据点。常见的方法是使用 matplotlib
的 plt.plot
或 seaborn
的 rugplot
等方法,在 X 轴上绘制竖线 "|"
来表示数据点的分布。如图 3 所示。
图 3. X轴数据分布密度图绘制实例
图 3 的核心绘图代码如下所示:
# 使用 KDEpy 进行核密度估计
# 这里我们可以选择不同的核函数和带宽方法
# 核函数示例:Gaussian、Epanechnikov、Linear、Cosine 等
# 带宽方法示例:Silverman、Scott、ISJ(插件估计)
kde = FFTKDE(kernel='gaussian', bw='silverman').fit(half_lives)
x, y = kde.evaluate()
# 图3.a 的核心绘图代码
# 绘制密度图
plt.figure(figsize=(6, 4))
plt.plot(x, y, color='#F8AC8C', linewidth=2)
plt.fill_between(x, y, color='#F8AC8C', alpha=0.3)
# 添加数据沿X轴的分布情况
plt.plot(df['Half-life (hours)'], [0.01] * len(df), '|', color='black', markersize=8)
plt.xlabel('Half-life (hours)')
plt.ylabel('Density')
# 图3.b 的核心绘图代码
import seaborn as sns
# 绘制密度图
plt.figure(figsize=(8, 6))
plt.plot(x, y, color='#F8AC8C', linewidth=2)
plt.fill_between(x, y, color='#F8AC8C', alpha=0.3)
# 添加数据沿X轴的分布情况
sns.rugplot(df['Half-life (hours)'], color='black', height=0.03)
plt.xlabel('Half-life (hours)')
plt.ylabel('Density')
4. 渐变颜色填充
传统的密度图通常使用单一颜色填充曲线下方的区域。然而,为了更加直观地展示数据分布的细微变化和趋势,可以使用渐变颜色填充技术。这种方法通过颜色渐变来表示数据密度或其他变量的变化,使得图形不仅美观,而且更具信息量。图 4-1-7 中使用Matlab的经典颜色映射方案 plasma
渐变色对密度曲线下方的区域进行了填充。通过循环遍历密度曲线的每个区间,根据X轴的值选择对应的颜色,逐段进行填充。
图 4. 数据分布颜色密度图绘制实例
图 4 的核心绘图代码如下:
from matplotlib.colors import Normalize
from matplotlib.cm import ScalarMappable
# 使用 KDEpy 进行核密度估计
kde = FFTKDE(kernel='gaussian', bw='silverman').fit(half_lives)
x, y = kde.evaluate()
# 创建颜色映射对象
norm = Normalize(vmin=min(x), vmax=max(x))
cmap = plt.get_cmap('plasma')
# 绘制密度图
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, y, color='black', linewidth=1)
# 添加数据沿X轴的分布情况
ax.plot(half_lives, [0.01] * len(half_lives,), '|', color='black', lw=1, markersize=8)
# 填充密度曲线下方的区域,并应用基于 X 轴数值的颜色映射
for i in range(len(x) - 1):
ax.fill_between(x[i:i+2], y[i:i+2], color=cmap(norm(x[i])), alpha=0.3)
# 设置标签
ax.set_xlabel('Half-life (hours)')
ax.set_ylabel('Density')
# 添加颜色条
sm = ScalarMappable(norm=norm, cmap=cmap)
cbar = fig.colorbar(sm, ax=ax, aspect=12, label='Half-life (hours)')
选择适合的渐变色取决于数据的特点和可视化的需求。例如,如果需要确保色盲友好,可以选择 viridis
或 cividis
;如果需要强调高密度区域,可以选择 inferno
或 magma
。通过试验不同的渐变色,可以找到最适合展示特定数据模式的颜色方案。图 5 展示了不同渐变色绘制的密度图。
图 5 . 不同颜色映射密度图绘制实例
图 5 的核心绘图代码如下:
# KDE 估计
kde = FFTKDE(kernel='gaussian', bw='silverman').fit(half_lives)
x, y = kde.evaluate()
# 定义色彩映射
color_maps = ['viridis', 'inferno', 'magma', 'cividis', 'plasma', 'coolwarm', 'Spectral', 'rainbow', 'turbo']
# 创建子图
fig, axes = plt.subplots(3, 3, figsize=(10, 6), sharex=True, sharey=True, constrained_layout=True)
axes = axes.flatten()
# 为了保持一致的色彩映射,对数据进行归一化
norm = Normalize(vmin=min(x), vmax=max(x))
# 定义函数用于绘制给定色彩映射的 KDE 图
def plot_kde(ax, cmap_name):
cmap = plt.get_cmap(cmap_name)
ax.plot(x, y, color='black', linewidth=1)
ax.plot(half_lives, [0.001] * len(half_lives), '|', color='black', lw=1, markersize=4)
for i in range(len(x) - 1):
ax.fill_between(x[i:i+2], y[i:i+2], color=cmap(norm(x[i])), alpha=0.3)
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
cbar = fig.colorbar(sm, ax=ax, aspect=40, pad=0.02)
cbar.set_label('Half-life', fontsize=10)
# 对每种色彩映射绘制 KDE 图
for ax, cmap_name in zip(axes, color_maps):
plot_kde(ax, cmap_name)
ax.set_title(cmap_name, fontsize=10, fontweight='bold', fontname='Arial')
# 添加全局标签
fig.supxlabel('Half-life (hours)', fontsize=14)
fig.supylabel('Density', fontsize=14)
5. 多组数据共享颜色映射
在某些情况下,可能希望同时比较多组数据的分布情况,例如,对于药物半衰期数据,可能有不同类型或不同来源的药物。共享颜色映射意味着不同的数据列使用相同的颜色映射方案,这使得比较更容易,因为相似的颜色对应于相似的数值范围。绘制多组数据列的共享颜色映射密度图可以使比较更加直观和易于理解。图 6 为针对多组数据列的共享颜色映射密度图绘制实例。
图 6. 不同数据列的共享颜色映射密度图绘制实例
图 6 的核心绘图代码如下:
# 读取数据
df = pd.read_excel('Multiple_density_data.xlsx', sheet_name='Sheet1')
# 定义颜色映射
cmap_name = 'plasma'
# 创建子图
fig, axes = plt.subplots(3, 3, figsize=(10, 6), sharex=True, sharey=True, constrained_layout=True)
axes = axes.flatten()
# 针对每个子图,绘制不同数据列的 KDE 图,并共享色卡
for i, (col, ax) in enumerate(zip(df.columns[0:], axes)):
density_data = df[col].values.reshape(-1, 1)
# KDE 估计
kde = FFTKDE(kernel='gaussian', bw='silverman').fit(density_data)
x, y = kde.evaluate()
# 绘制 KDE 图
ax.plot(x, y, color='black', linewidth=1)
ax.plot(density_data, [0.001] * len(density_data), '|', color='black', lw=1, markersize=4)
# 使用相同的颜色映射填充密度曲线下方的区域
cmap = plt.get_cmap(cmap_name)
norm = Normalize(vmin=min(x), vmax=max(x))
for j in range(len(x) - 1):
ax.fill_between(x[j:j+2], y[j:j+2], color=cmap(norm(x[j])), alpha=0.3)
ax.set_title(col, fontsize=10, fontweight='bold', fontname='Arial')
# 添加全局标签
fig.supxlabel('Value', fontsize=14)
fig.supylabel('Density', fontsize=14)
# 添加共享色卡
cmap = plt.get_cmap(cmap_name)
norm = Normalize(vmin=0, vmax=1)
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
cbar = fig.colorbar(sm, ax=axes, aspect=40, pad=0.02)
cbar.set_label('Value', fontsize=10)
如果您喜欢我们的文章,欢迎关注