当前位置:首页>python>Python | 手把手教你绘制3维核密度图附完整代码

Python | 手把手教你绘制3维核密度图附完整代码

  • 2026-10-01 15:45:16
Python | 手把手教你绘制3维核密度图附完整代码
在做时序数据分析时,很多朋友习惯用折线图、柱状图或者箱线图来展示变化趋势。 但如果你想进一步表达 某个指标在不同年份下的分布演化特征 ,比如整体右移、多峰分布、离散程度变化等,普通二维图往往就显得不够直观。
这时候,三维核密度图(3D Kernel Density Plot)就是一种非常实用的表达方式。
最近我也用 Python 尝试复现了这类图形,发现它不仅能用于论文插图,也很适合展示区域差异、时序演变以及分布结构变化。 今天这篇文章,就分享一下绘图代码。文末可免费无套路获取!

01

什么是三维核密度图?
三维核密度图,本质上就是把 不同时间截面上的核密度曲线 沿着时间轴堆叠起来,从而形成一个三维分布演化图。
通常它的三个坐标轴分别表示:
  • X轴 :指标值
  • Y轴 :年份或时间
  • Z轴 :核密度值
这样的图比单纯的折线图更适合表达:
  • 指标分布是否整体向高值或低值方向移动
  • 分布形态是否由单峰变成多峰
  • 区域差异是否扩大或缩小
  • 不同年份之间的分布集中度是否发生变化
也就是说,它不仅能看“均值变化”,还能看“分布变化”。

02

什么样的数据适合画三维核密度图?
这里有一个非常关键的问题:
一个年份下必须有多个样本值,才能计算核密度。
例如,下面这种数据是可以的:
year
region
sample_id
value
2014
East
1
0.31
2014
East
2
0.28
2014
East
3
0.35
2015
East
1
0.32
因为在同一个年份下,你有一组样本值,可以估计这一年的分布。
但如果你的数据是下面这样:
year
region
value
2014
East
0.31
2015
East
0.32
2016
East
0.34
那么每年只有一个值,这种情况下就 不能严格计算核密度 。 它更适合画折线图或趋势图,而不是核密度图。
所以,在正式绘图之前,一定要先检查你的数据结构是否满足要求。

03

Python绘制三维核密度图需要哪些库?
这类图的绘制并不复杂,主要会用到以下几个常见库:
  • pandas:用来读取和整理表格数据
  • numpy:用来生成绘图网格
  • scipy.stats:用来计算核密度
  • matplotlib:用来绘制三维图形

04

绘图思路是什么?
整体思路其实很清晰,可以概括以下几步:
1、导入需要的库,这一部分主要是导入数据处理、核密度估计和三维绘图所需的 Python 库。
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltfrom scipy.stats import gaussian_kdefrom mpl_toolkits.mplot3d import Axes3D
2、读取数据并设置字体。这一步是读取 CSV 数据,同时设置字体为 Times New Roman,避免图中英文字体不统一。
file_path = r"G:\Maping\demo\agri_green_kde_sample.csv"df = pd.read_csv(file_path)plt.rcParams["font.family"] = "Times New Roman"plt.rcParams["axes.unicode_minus"] = False
3、数据预处理。这里主要是统一字段类型,并且可选地减少每个“地区-年份”分组中的样本数,从而让三维网格面不要过密。
df["year"] = df["year"].astype(int)df["region"] = df["region"].astype(str)df["sample_id"] = df["sample_id"].astype(str)df["value"] = df["value"].astype(float)sample_n_per_group = 10if sample_n_per_group isnotNone:    df = (        df.groupby(["region", "year"], group_keys=False)          .apply(lambda x: x.sample(              n=min(sample_n_per_group, len(x)),              random_state=42          ))          .reset_index(drop=True)    )regions = sorted(df["region"].unique())years = sorted(df["year"].unique())
4、设置画布和子图布局。由于这里要展示 8 个地区,因此把整体图形设置为 2 行 4 列,并准备好每个子图对应的编号 (a)~(h)。
nrows, ncols = 2, 4fig = plt.figure(figsize=(24, 10))panel_labels = list("abcdefgh")
5、设置横轴范围和绘图网格。这一步是生成核密度计算所需的横轴网格。把横轴网格点数量适当减少,可以让曲面网格不那么密集,整体看起来更清爽。
x_min = df["value"].min() - 0.03x_max = df["value"].max() + 0.03x_grid = np.linspace(x_min, x_max, 120)
6、预先计算各地区各年份的核密度这一部分是整个绘图的核心:对每个地区、每个年份的数据分别进行核密度估计,并保存结果,方便后面统一绘制三维曲面。
global_zmax = 0kde_dict = {}for region in regions:    Z_list = []    for year in years:        vals = df.loc[            (df["region"] == region) & (df["year"] == year),            "value"        ].dropna().values        iflen(vals) < 2or np.std(vals) == 0:            z = np.zeros_like(x_grid)        else:            kde = gaussian_kde(vals, bw_method=0.35)            z = kde(x_grid)        Z_list.append(z)        global_zmax = max(global_zmax, z.max())    kde_dict[region] = np.array(Z_list)
7、绘制 2×4 的三维核密度图。这一步就是正式作图。每个地区对应一个子图,使用  绘制三维核密度曲面,并叠加年份轮廓线增强层次感。同时,把子图标题改为 ,并手动把 Z 轴标题放到左侧。
for i, region inenumerate(regions[:8], start=1):    ax = fig.add_subplot(nrows, ncols, i, projection="3d")    Z = kde_dict[region]    X = np.tile(x_grid, (len(years), 1))    Y = np.tile(np.array(years).reshape(-1, 1), (1, len(x_grid)))    surf = ax.plot_surface(        X, Y, Z,        cmap="viridis",        edgecolor="white",        linewidth=0.25,        antialiased=True,        alpha=0.95,        rstride=1,        cstride=1    )    for j, year inenumerate(years):        ax.plot(            x_grid,            np.full_like(x_grid, year),            Z[j, :],            color="black",            linewidth=0.45,            alpha=0.6        )    ax.set_title(f"({panel_labels[i-1]})", fontsize=14, pad=8)    ax.set_xlabel("Agricultural Green Development", fontsize=10, labelpad=8)    ax.set_ylabel("Year", fontsize=10, labelpad=8)    ax.set_zlabel("")    ax.text2D(        -0.10, 0.52, "Kernel Density",        transform=ax.transAxes,        rotation=90,        va="center",        ha="center",        fontsize=10    )    ax.set_xlim(x_min, x_max)    ax.set_ylim(min(years), max(years))    ax.set_zlim(0, global_zmax * 1.05)    ax.set_yticks(years[::2] iflen(years) > 6else years)    ax.tick_params(axis="both", which="major", labelsize=8)    ax.tick_params(axis="z", which="major", labelsize=8)    ax.view_init(elev=24, azim=-125)    ax.xaxis.pane.fill = False    ax.yaxis.pane.fill = False    ax.zaxis.pane.fill = False    ax.grid(True, linestyle="--", alpha=0.2)

05

成图效果有哪些细节值得注意?
在实际绘图过程中,我发现有几个细节非常重要:
  1. 子图布局
    如果是多个地区,建议用统一布局,比如 2行4列 或 2×2 ,这样对比更清晰。
  2. 标题简化
    如果每个子图都写完整地区名,画面容易拥挤。 更推荐直接用 (a)、(b)、(c) 这种方式标识,然后在图注中说明各子图对应区域。
  3. 网格不要太密
    三维曲面如果网格过密,看起来会比较乱。 可以通过减少横轴插值点数量,或者减少每个年份的样本数量,让图面更清爽。
  4. Z轴标题位置
    默认的三维坐标轴有时会把 Z 轴标题放到右边,看起来不太协调。 这时候可以用手动文本标注的方式,把标题放到左侧,更接近论文制图习惯。
  5. 带宽参数很关键
核密度图的平滑程度很大程度上取决于带宽。 带宽太小,曲线会很尖锐;带宽太大,曲线又会过于平滑。 实际应用中需要根据样本数量和数据分布适当调整。

06

结语
最后
这次整理的是 Python绘制三维核密度图 的思路与方法。 如果你也在做科研绘图、论文配图或时序分布分析,这类图确实值得收藏。
如果你需要:
  • 完整 Python 代码
  • 完整样例数据
可以在我们的“梧桐GIS助手”小程序中获取,点击链接即可:三维核密度图代码分享
或者点击关注后在后台私信“ 三维核密度 ”获取。

最新文章

随机文章