解决不同数量级坐标轴下点击事件的最近点选取偏差问题
我正在编写Python脚本,实现交互式让用户选择并移除数据点以排除后续计算。但我的y轴数据范围为e-9至e-14(单次分析的数据仅覆盖一个数量级,但该数量级可能在e-9到e-14之间),x轴数据范围为10-100,数量级差异导致y轴主导最近点的距离计算。例如,有两个用*表示的数据点,以及用x表示的点击点:
| | * | | | | | x * |__________________
点击点明显更靠近下方的数据点,但由于y轴主导距离计算,实际选中的却是上方的数据点。
我尝试过将x轴的时间数据缩放到y轴原始数据的数量级,或反之,代码如下:
# this finds the closest datum to the click point def find_closest_index(analysis, click_coords, mass): y_magnitude = np.floor(np.log10(np.max(analysis.raw_data[mass]))) scale_factor = 10**abs(y_magnitude - 1) scaled_data = analysis.raw_data[mass] * scale_factor distances = np.sqrt( (analysis.time_sec - click_coords[0])**2 + (scaled_data - click_coords[1])**2 ) # find the closest index closest_index = distances.argmin() return closest_index
将scale_factor设置为y_magnitude - 1可将y轴数据缩放到10-100量级,理论上是正确的,但上述问题仍存在。将-1改为-3或+3会错误选中最大或最小数据点,我也未找到能改变该核心问题的中间值。
请问我该如何解决此问题,确保下方的数据点被正确选中?
核心问题是没有基于数据在可视化界面上的实际像素位置计算距离,而是用了原始数据的数值范围。不管怎么缩放原始数据,都不如直接映射到绘图的像素坐标系准确。
推荐两种可行方法:
方法1:利用绘图库的坐标转换功能
如果使用Matplotlib这类绘图库,直接将数据坐标转换为屏幕像素坐标计算距离,完全匹配用户视觉上的远近判断:
import matplotlib.pyplot as plt import numpy as np def find_closest_index(analysis, click_coords, mass, ax): # 获取坐标轴的数据转像素的转换对象 trans = ax.transData # 把点击点的数据坐标转为像素坐标 click_pixel = trans.transform(click_coords) # 将所有数据点批量转为像素坐标 data_points = np.column_stack((analysis.time_sec, analysis.raw_data[mass])) data_pixels = trans.transform(data_points) # 计算欧氏距离并返回最近点索引 distances = np.sqrt( (data_pixels[:,0] - click_pixel[0])**2 + (data_pixels[:,1] - click_pixel[1])**2 ) return distances.argmin()
调用时需要传入当前绘图的Axes对象ax,这样就能基于界面上的真实像素位置计算距离,彻底解决数量级差异的问题。
方法2:归一化处理数据
如果不想依赖绘图库的转换功能,可将x、y轴数据分别归一到[0,1]区间,让两个维度的权重一致:
import numpy as np def find_closest_index(analysis, click_coords, mass): # 归一化x轴数据 x_data = analysis.time_sec x_min, x_max = x_data.min(), x_data.max() norm_x = (x_data - x_min) / (x_max - x_min) norm_click_x = (click_coords[0] - x_min) / (x_max - x_min) # 归一化y轴数据 y_data = analysis.raw_data[mass] y_min, y_max = y_data.min(), y_data.max() norm_y = (y_data - y_min) / (y_max - y_min) norm_click_y = (click_coords[1] - y_min) / (y_max - y_min) # 计算归一化后的距离并返回最近点索引 distances = np.sqrt( (norm_x - norm_click_x)**2 + (norm_y - norm_click_y)**2 ) return distances.argmin()
这种方法让x、y轴的数值范围统一到相同区间,避免了某一轴数量级过大主导距离计算的问题,同样能匹配用户视觉感知。
内容的提问来源于stack exchange,提问作者ohshitgorillas

