如何获取Matplotlib中hexbin每个六边形内的对应数据点?
获取Matplotlib Hexbin分箱对应的原始数据点
问题背景
我正在开发一款用于交互式分析百万级2D散点图的应用,采用Matplotlib的2D六边形分箱(hexbin)功能来减少绘图点数。但需要获取每个分箱对应的原始数据点,目前用shapely判断点是否在六边形内的方案存在冗余——Matplotlib内部应该已经完成了点到分箱的分配计算,希望直接提取这些信息。
无需修改源码的高效解决方案
Matplotlib的hexbin返回的PolyCollection对象,其内部计算逻辑已经完成了点到分箱的索引映射,我们可以直接复用这部分结果,无需重复做几何判断:
步骤1:调用Matplotlib内部分箱计算逻辑
import numpy as np from matplotlib import pyplot as plt from matplotlib.axes._axes import _hexbin # 生成百万级测试数据 x = np.random.normal(loc=100, scale=20, size=1000000) y = np.random.normal(loc=1000, scale=20, size=1000000) # 绘制hexbin并获取对象 fig, ax = plt.subplots(figsize=(10, 8)) hexbin = ax.hexbin(x, y, gridsize=25, cmap='jet', picker=True) # 调用Matplotlib内部的核心分箱计算函数 xlim = ax.get_xlim() ylim = ax.get_ylim() counts, xedges, yedges, nx, ny, binx, biny = _hexbin(x, y, hexbin.gridsize, xlim, ylim)
这里的binx和biny是每个原始数据点对应的分箱在x、y方向的网格索引,nx/ny是x/y方向的总分箱数。
步骤2:建立分箱到原始点的映射
# 生成每个点的唯一分箱ID bin_ids = binx + biny * nx # 过滤掉超出绘图范围的点(这类点的binx/biny为-1) valid_mask = binx != -1 valid_bin_ids = bin_ids[valid_mask] valid_point_indices = np.where(valid_mask)[0] # 分组获取每个分箱对应的原始点索引 unique_bin_ids = np.unique(valid_bin_ids) bin_to_points = {} for bin_id in unique_bin_ids: point_mask = valid_bin_ids == bin_id bin_to_points[bin_id] = valid_point_indices[point_mask] # 关联分箱ID与hexbin的offsets(即分箱中心坐标) # 非空分箱的顺序与hexbin.get_offsets()完全一致 non_empty_mask = counts.flatten() > 0 all_bin_ids = np.arange(nx * ny).reshape(ny, nx).flatten() non_empty_bin_ids = all_bin_ids[non_empty_mask] # 最终得到:offsets索引 → 对应原始点的索引列表 offset_to_points = {i: bin_to_points[bid] for i, bid in enumerate(non_empty_bin_ids)}
现在offset_to_points中的键对应hexbin.get_offsets()的索引,值就是该分箱下所有原始数据点的索引,完全复用Matplotlib的计算结果,效率比shapely方案提升几个数量级。
为什么这个方案可行?
Matplotlib的_hexbin是ax.hexbin的核心计算函数,它会直接对原始坐标做网格映射,计算出每个点所属的分箱索引,这一步是绘制hexbin的必经流程。我们只是把它内部计算好的索引结果提取出来,避免了重复的几何判断操作。
修改Matplotlib源码的思路(可选)
如果希望直接在Matplotlib中集成该功能,可以修改axes/_axes.py中的hexbin相关代码:
- 在
_hexbin函数末尾添加点到分箱ID的计算:
bin_ids = binx + biny * nx point_to_bin = bin_ids
- 在
ax.hexbin函数中,将point_to_bin赋值给返回的PolyCollection对象:
hexbin = PolyCollection(...) hexbin.point_to_bin = point_to_bin
后续就可以直接通过hexbin.point_to_bin获取每个点对应的分箱ID,再自行分组。不过这种方案需要修改源码,存在版本兼容性风险,建议优先使用非侵入式方案。
内容的提问来源于stack exchange,提问作者sweetdream
相关产品推荐
相关产品推荐

