matplotlib 使用mplcursors访问“本地数据”

92vpleto  于 2023-01-13  发布在  其他
关注(0)|答案(1)|浏览(141)

我不太理解mplcursors游标的工作原理,让我举一个例子。

import pandas as pd
import matplotlib.pyplot as plt
import mplcursors
%matplotlib qt5

def random_data(rows_count):
    data = []
    for i in range(rows_count):
        row = {}
        row["x"] = np.random.uniform()
        row["y"] = np.random.uniform()
        if (i%2 == 0):
            row["type"] = "sith"
            row["color"] = "red"
        else:
            row["type"] = "jedi"
            row["color"] = "blue"
        data.append(row)
    return pd.DataFrame(data)

data_df = random_data(30)

fig, ax = plt.subplots(figsize=(8,8))
ax = plt.gca()

types = ["jedi","sith"]

for scat_type in types:
    local_data_df = data_df.loc[data_df["type"] == scat_type]
    scat = ax.scatter(local_data_df["x"],
               local_data_df["y"],
               c=local_data_df["color"],
               label=scat_type)
    cursor = mplcursors.cursor(scat, hover=mplcursors.HoverMode.Transient)
    @cursor.connect("add")
    def on_add(sel):
        annotation = (local_data_df.iloc[sel.index]["type"]+
                      "\n"+str(local_data_df.iloc[sel.index]["x"])+
                      "\n"+str(local_data_df.iloc[sel.index]["y"]))
        sel.annotation.set(text=annotation)

ax.legend()
plt.title("a battle of Force users")
plt.xlabel("x")
plt.ylabel("y")
plt.xlim(-1, 2)
plt.ylim(-1, 2)
ax.set_aspect('equal', adjustable='box')
plt.show()

此代码应生成一个 Dataframe ,使每行具有随机属性xytypejedisith)以及colorbluered),具体取决于该行是jedi还是sith,然后以其颜色散点绘制jedi,给它们附加一个光标,然后用它们的颜色散点图,给它们附加另一个光标,并显示一个图例框,告诉读者蓝色点对应jedi行,红色点对应sith行。但是,当悬停点时,注解说所有点都是sith,坐标看起来不好。

    • 我希望了解代码为什么不能执行我希望它执行的操作。**

只是澄清一下:我为每种类型(jedisith)调用.scatter(),然后尝试将光标附加到每个图上,因为我已经尝试对整个data_df调用scatter,但.legend()没有显示我想要的内容。

    • 我希望您给我的答案足以让我编写一个代码,显示jedisith点,显示正确的注解和图例框。**
eni9jsuy

eni9jsuy1#

有很多奇怪的事情发生。
其中一个困惑是,将变量local_data_df放在for循环中会创建一个只对循环的一个循环是局部变量的变量,而它只是一个在每个循环中被覆盖的全局变量。在for循环中定义函数on_add不会使其成为局部函数。同样,on_add将成为全局函数,并被for循环的每个循环覆盖。
另一个困惑是,连接的函数可以从另一个函数或循环中访问局部变量,而这些局部变量在函数或循环结束后就不可访问了。
此外,并不是说sel.index不是 Dataframe 的索引,而是散点图的索引,您可以重置“本地df”的索引,使其与sel.index的排序方式相似。
要模拟本地变量,可以向scat对象添加额外的数据。例如,scat.my_data = local_df将向包含scatter元素的全局对象添加该变量(包含所有信息的PathCollection matplotlib需要表示散点).虽然变量scat被覆盖,每个对ax.scatter的调用都有一个PathCollection。(您也可以通过ax.collections访问它们)。
下面是代码的重写,尽量与原始代码保持一致:

import pandas as pd
import matplotlib.pyplot as plt
import mplcursors

def random_data(rows_count):
    df = pd.DataFrame({'x': np.random.uniform(0, 1, rows_count),
                       'y': np.random.uniform(0, 1, rows_count),
                       'type': np.random.choice(['sith', 'jedi'], rows_count)})
    df['color'] = df['type'].replace({'sith': 'red', 'jedi': 'blue'})
    return df

def on_add(sel):
    local_data_df = sel.artist.my_data
    annotation = (local_data_df.iloc[sel.index]["type"] +
                  "\n" + str(local_data_df.iloc[sel.index]["x"]) +
                  "\n" + str(local_data_df.iloc[sel.index]["y"]))
    sel.annotation.set(text=annotation)

data_df = random_data(30)

fig, ax = plt.subplots(figsize=(8, 8))

types = ["jedi", "sith"]

for scat_type in types:
    local_data_df = data_df.loc[data_df["type"] == scat_type].reset_index() # resetting the index is necessary
    scat = ax.scatter(local_data_df["x"],
                      local_data_df["y"],
                      c=local_data_df["color"],
                      label=scat_type)
    scat.my_data = local_data_df # store the data into the scat object
    cursor = mplcursors.cursor(scat, hover=mplcursors.HoverMode.Transient)
    cursor.connect("add", on_add)

ax.legend()
ax.set_title("a battle of Force users")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_xlim(-1, 2)
ax.set_ylim(-1, 2)
ax.set_aspect('equal', adjustable='box')
plt.show()

相关问题