如何在Seaborn FacetGrid中通过拾取事件显示散点图点的全部信息
问题解决:Seaborn FacetGrid散点点击显示完整行数据
问题说明
已通过Seaborn的FacetGrid和scatterplot实现数据可视化:以Epsilon1为no的函数,通过子图区分A类别、颜色区分B类别,绘图正常。需求是点击任意散点时,在IPython控制台和Matplotlib图的状态栏显示对应DataFrame行的全部信息(如'no':5, 'Date':1997-12-15 03:50:41, 'A':A6, 'B':B4, 'Epsilon1':0.670635, 'Epsilon2':0.756461, 'Epsilon3':0.530825)。尝试使用onpick事件未成功,无法理解event.ind返回整数列表的原因。
关键解释:event.ind的含义
event.ind返回的是当前子图中被点击散点在该子图数据集中的位置索引。因为FacetGrid会将原始DataFrame按A分组后分别绘制每个子图,每个子图的数据集是原始数据的子集,所以这个索引是子集内的相对位置,不是原始DataFrame的全局索引。
解决方案
- 在绘制散点时,将原始DataFrame的全局索引绑定到每个散点的Artist对象上
- 重写Matplotlib画布的
format_coord方法,实现状态栏显示点击数据 - 在
onpick事件中,通过绑定的全局索引提取完整行数据,输出到控制台
修改后的完整代码
import pandas as pd import numpy as np import seaborn as sns import random import matplotlib.pyplot as plt # 数据库规模 n = 1000 nA = 6 nB = 5 no = np.arange(n) date = np.random.randint(1e9, size=n).astype('datetime64[s]') A = [''.join(['A',str(random.randint(1, nA))]) for j in range(n)] B = [''.join(['B',str(random.randint(1, nB))]) for j in range(n)] Epsilon1 = np.random.random_sample((n,)) Epsilon2 = np.random.random_sample((n,)) Epsilon3 = np.random.random_sample((n,)) data = pd.DataFrame({'no':no, 'Date':date, 'A':A, 'B':B, 'Epsilon1':Epsilon1, 'Epsilon2':Epsilon2, 'Epsilon3':Epsilon3}) # 存储当前点击的行数据,供状态栏显示 current_row = None def onpick(event): global current_row # 获取点击散点对应的原始DataFrame全局索引 selected_indices = event.artist.custom_indices[event.ind] # 提取完整行数据 current_row = data.loc[selected_indices].iloc[0] # 输出到IPython控制台 print(current_row.to_dict()) def plot_Epsilon1_seaborn(): global current_row sns.set_theme() g = sns.FacetGrid(data, col="A", col_wrap=4, hue='B', hue_order=data['B'].sort_values().drop_duplicates().tolist(), palette="viridis", col_order=data['A'].sort_values().drop_duplicates().tolist()) # 自定义绘图函数,绑定原始索引到散点对象 def scatter_with_indices(x, y, data, **kwargs): # 获取当前分组数据对应的原始全局索引 indices = data.index # 绘制散点 scatter = sns.scatterplot(x=x, y=y, data=data, **kwargs) # 将全局索引绑定到散点对象上 scatter.custom_indices = indices g.map(scatter_with_indices, 'no', 'Epsilon1', picker=True) g.add_legend() # 重写format_coord方法,修改状态栏显示内容 def format_coord(x, y): if current_row is not None: # 将行数据格式化为字符串 row_str = ', '.join([f"'{k}':{repr(v)}" for k, v in current_row.to_dict().items()]) return row_str # 无点击时显示默认坐标 return f'x={x:.2f}, y={y:.2f}' g.fig.canvas.format_coord = format_coord g.fig.canvas.mpl_connect("pick_event", onpick) if __name__ == '__main__': plot_Epsilon1_seaborn() plt.show()
代码说明
- 绑定全局索引:通过自定义的
scatter_with_indices函数,在绘制每个子图的散点时,将该子图数据对应的原始DataFrame全局索引存储到散点对象的custom_indices属性中 - onpick事件处理:点击散点时,通过
event.ind获取子图内的相对索引,再结合custom_indices得到原始全局索引,从而提取完整行数据并打印到控制台 - 状态栏显示:重写
format_coord方法,当有点击事件时,在状态栏显示当前点击行的所有数据,无点击时显示默认坐标
内容的提问来源于stack exchange,提问作者Julien M.
相关产品推荐
相关产品推荐

