【问题标题】:Data value of a variable in each marker within subplots子图中每个标记中变量的数据值
【发布时间】:2021-09-17 09:55:08
【问题描述】:

我正在开发以下函数:extract_name_value(),它在 Python 中生成一个采用 pandas DataFrame 值的步骤图,现在它工作正常,但我想将变量 points_axisyvalue 或 values_list 的值添加到它在每个标记中:Script Here

我尝试使用以下示例:Data value at each markerMatplotlib scatter plot with different text at each data pointHow to put individual tags for a matplotlib scatter plot?,这将是我想要的;我什至尝试使用plt.annotate(),但值的数据并没有按照我想要的方式出现,而且我认为它会掩盖很多图表并且不能很好地理解。下面我放了我正在使用 plt.annotate() 的代码:

    # Function to extract the Name and Value attributes
    def extract_name_value(signals_df, rootXML):
        # print(signals_df)
        names_list = [name for name in signals_df['Name'].unique()]
        num_names_list = len(names_list)
        num_axisx = len(signals_df["Name"])
        values_list = [value for pos, value in enumerate(signals_df["Value"])]
        print(values_list)
        points_axisy = signals_df["Value"]
        print(len(points_axisy))

        colors = ['b', 'g', 'r', 'c', 'm', 'y']
    
        # Creation Graphic
        fig, ax = plt.subplots(nrows=num_names_list, figsize=(20, 30), sharex=True)
        plt.suptitle(f'File XML: {rootXML}', fontsize=16,         fontweight='bold', color='SteelBlue', position=(0.75, 0.95))
        plt.xticks(np.arange(-1, num_axisx), color='SteelBlue', fontweight='bold')
        labels = ['value: {0}'.format(j) for j in values_list]
        print(labels)
        i = 1
        for pos, name in enumerate(names_list):
            # get data
            data = signals_df[signals_df["Name"] == name]["Value"]
            print(data)
            # get color
            j = random.randint(0, len(colors) - 1)
            # get plots by index = pos
            x = np.hstack([-1, data.index.values, len(signals_df) - 1])
            y = np.hstack([0, data.values, data.iloc[-1]])
            ax[pos].plot(x, y, drawstyle='steps-post', marker='o', color=colors[j], linewidth=3)
            ax[pos].set_ylabel(name, fontsize=8, fontweight='bold', color='SteelBlue', rotation=30, labelpad=35)
            ax[pos].yaxis.set_major_formatter(ticker.FormatStrFormatter('%0.1f'))
             ax[pos].yaxis.set_tick_params(labelsize=6)
             ax[pos].grid(alpha=0.4)
             i += 1

             for label, x, y in zip(labels, x, y):
             plt.annotate(label, xy=(x, y), xytext=(-20, 20), textcoords='offset points', ha='right', va='bottom', bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
                    arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))

        plt.show()

我得到的是在不同位置拼接的注解。

但是,我的代码需要在每个点显示每个值吗?

我也一直在尝试使用 Matplotlib 参考中的代码,但无法完成:Marker Reference。非常感谢您,任何评论都会有所帮助。

【问题讨论】:

  • 你能举个例子说明你希望你的地块是什么样子吗?
  • 你好@Yulia,理想的是我在onenotes中的例子,我放在这里:example_freehand.png。我认为我们将不得不使用标记为黄色的变量之一或我在下图中显示的最后一个 (y):data_values_chart.png 但我无法找到如何做到这一点。非常感谢。

标签: python python-3.x matplotlib marker subplot


【解决方案1】:

我认为它应该与您所追求的非常接近。我随机生成数据,然后使用matplotlib.text 对其进行注释。它不是很漂亮,您可能想添加一些填充和更多改进,但我希望它提供一个好主意!

如果两点距离太近,您可能需要在左侧标注一个,在右侧标注另一个,就像我对第一个点所做的​​那样。在你给出的例子中我没有看到这种情况,所以没有处理。

函数place_label(label, xy, position, ax, pad=0.01) 将标签放置在您想要的位置。其余代码使用随机生成的数据演示它的工作原理。

import random
import numpy as np
import matplotlib.pyplot as plt

# function that places the label give the desired position
def place_label(label, xy, position, ax, pad=0.01):

  # annotate in the initial position, xy is the top right corner of the bounding box
  t_ = ax.text(x=xy[0], y=xy[1], s=label, fontsize=16)

  # find useful values
  tbb = t_.get_window_extent(renderer=rend)
  abb = ax.get_window_extent(renderer=rend)
  a_xlim, a_ylim = ax.get_xlim(), a_.get_ylim()

  # now adjust the position if needed
  new_xy = [xy[0], xy[1]]

  relative_width = tbb.width/abb.width * (a_xlim[1] - a_xlim[0])
  pad_x = pad * (a_xlim[1] - a_xlim[0])
  assert(position[0] in ['l', 'c', 'r'])
  if position[0] == 'c':
    new_xy[0] -= relative_width/2
  elif position[0] == 'l':
    new_xy[0] -= relative_width + pad_x
  else:
    new_xy[0] += pad_x

  relative_height =  tbb.height/abb.height * (a_ylim[1] - a_ylim[0])
  pad_y = pad * (a_ylim[1] - a_ylim[0])
  assert(position[1] in ['b', 'c', 't'])
  if position[1] == 'c':
    new_xy[1] -= relative_height/2
  elif position[1] == 'b':
    new_xy[1] -= relative_height + pad_y
  else:
    new_xy[1] += pad_y

  t_.set_position(new_xy)

  return t_

# generate data, plot it and annotate it!
axes_qty = 9
axes_gap = 0.035

fig = plt.figure(figsize=(10, 8))
ax = [plt.axes([axes_gap, axes_gap/2 + i*(1/axes_qty), 1-2*axes_gap, 1/axes_qty-axes_gap]) for i in range(axes_qty)]
rend = fig.canvas.get_renderer()

for a_ in ax:
  x_ = [random.randint(0, 10) for _ in range(5)]
  x_ = np.unique(x_)
  y_ = [random.randint(0, 12) for _ in x_]
  # as x is shared, we set the limits in advance, otherwise the adjustments won't be accurate
  a_.set_xlim([-0.5, 10.5])
  
  # plotting the data
  data_ = [[x_[0], y_[0]]]
  for i in range(1, len(x_)):
    data_ += [[x_[i-1], y_[i]], [x_[i], y_[i]]]
  a_.plot([d[0] for d in data_], [d[1] for d in data_])

  mid_y = 0.5 * (a_.get_ylim()[0] + a_.get_ylim()[1])

  # now let's label it
  for i in range(len(x_)):
    # decide what point we annotate
    if i == 0:
      xy = [x_  [0], y_[0]]
    else:
      xy = [x_[i-1], y_[i]]

    # decide its position
    position_0 = 'l' if i == 0 else 'r'
    position_1 = 'b' if xy[1] > mid_y else 't'

    place_label(label=str(xy[1]), xy=xy, position=position_0+position_1, ax=a_)

plt.show()

【讨论】:

  • 我一直在查看您的代码,将函数 place_label (label, xy, position, ax, pad = 0.01) 与 extract_name_value (signals_df, rootXML) 集成在一起,但结果并不完全正确正如我想要的check image,每个标记中都有一个值,有些值甚至没有出现;顺便说一句,职位无关紧要。要查看代码,您可以直接查看脚本here。我怎样才能使每个值都在标记中?谢谢。
  • 其中一个原因是您在绘制轴上的任何内容之前使用get_ylim - 这显然行不通。
  • 尝试将脚本中的第 127 行替换为 0.5 * (min(y) + max(y)),看看会发生什么。
  • @MarshRangel 对您来说最简单的方法是从我的示例开始-它应该可以工作,然后用您的数据替换我的虚拟数据-我想它仍然可以工作,然后添加您的轴标签,然后其他花里胡哨。老实说,我认为这将是完成任务的最快方法:)
  • 另外,如果在添加花里胡哨时,您的代码不再有效,您知道这是因为花里胡哨,您需要就 SO 提出不同的问题。
【解决方案2】:

您可以在循环中使用plt.annotate 函数来解决您的问题。

我随机生成了一些数据并将其绘制为单个图。你可以在子图中做同样的事情,功能是一样的。

# sample data points for the plot
x=np.arange(1,10)
y=np.linspace(20,40,9)

plt.figure(figsize=[15,5],dpi=200)
plt.plot(x,y,drawstyle='steps-post', marker='o')
# using annotate function to show the changepoints in a loop 
for i in range(len(x)):
    # I rounded the y values as string and used the same x and y coords as the locations
    # next we can give a constant offset points to offset the annotation from each value
    # here I used (-20,20) as the offset values
    plt.annotate(f"{str(round((y[i])))}",(x[i],y[i]),xycoords='data',
                 xytext=(-20,20), textcoords='offset points',color="r",fontsize=12,
                 arrowprops=dict(arrowstyle="->", color='black'))

如果您不想要箭头,可以移除箭头道具。

已编辑

我在您的 GitHub 存储库中使用了 example1.xml 文件并稍微编辑了该函数。我所做的只是在你的函数中添加一个循环和一个 if-else 条件。

# Initial part is same as yours
names_list = [name for name in signals_df['Name'].unique()]
num_names_list = len(names_list)
num_axisx = len(signals_df["Name"])
values_list = [value for pos, value in enumerate(signals_df["Value"])]
points_axisy = signals_df["Value"]
colors = ['b', 'g', 'r', 'c', 'm', 'y']
# start new figure
plt.figure(figsize=[20,28],dpi=200)
#start a loop with the subplots
for i in range(len(names_list)):
    # subplot has 14 rows, 1 column and the i+1 represents the i'th plot
    plt.subplot(num_names_list,1,i+1)
    # choose color
    col=np.random.randint(0, len(colors) - 1)
    # get the locations of the values with the similar name in your list
    locs=signals_df['Name']==names_list[i]
    # get the values in those locations
    data=signals_df['Value'][locs]
    # arrange the x and y coordinates
    x = np.hstack([-1, data.index.values, len(signals_df) - 1])
    y = np.hstack([0, data.values, data.iloc[-1]])
    # plot the values as usual
    plt.plot(x, y, drawstyle='steps-post', marker='o', color=colors[col], linewidth=3)
    plt.ylabel(names_list[i], fontsize=8, fontweight='bold', color='SteelBlue', rotation=30, labelpad=35)
    plt.grid(alpha=0.4)
    # this loop is for annotating the values 
    for j in range(len(x)):
        # I found it is better to alternate the position of the annotations 
        # so that they wont overlap for the adjacent values
        if j%2==0:
            # In this condition the xytext position is (-20,20)
            # this posts the annotation box over the plot value
            plt.annotate(f"Val={round((y[j]))}",(x[j],y[j]),xycoords='data',
                         xytext=(-20,20), textcoords='offset points',color="r",fontsize=8,
                         arrowprops=dict(arrowstyle="->", color='black'),
                        bbox=dict(boxstyle='round', pad=0.5, fc='yellow', alpha=0.5))
        else:
            # In this condition the xytext position is (-20,-20)
            # this posts the annotation box under the plot value
            plt.annotate(f"Val={round((y[j]))}",(x[j],y[j]),xycoords='data',
                 xytext=(-20,-20), textcoords='offset points',color="r",fontsize=8,
                 arrowprops=dict(arrowstyle="->", color='black'),
                         bbox=dict(boxstyle='round', pad=0.5, fc='yellow', alpha=0.5))

新建函数结果

希望对你有用。

【讨论】:

  • 谢谢@ArunRaj131。这就像我在我的问题中分享的最后一张图片中尝试的那样,但是如果我使用子图会是什么样子,以便它保留信号的名称、值和 len?请参阅我分享的代码的x = np.hstack ([- 1, data.index.values, len (signals_df) - 1])y = np.hstack ([0, data.values, data.iloc [-1]])
  • 嗨,我试着稍微编辑一下你的代码。请参阅编辑后的答案和代码。希望这会有所帮助。
  • 谢谢@ArunRaj131。一个问题... plt.ylabel 的 name 变量是从哪里得到的?
  • 你是如何上传到 tkinter 的?
  • 我已经使用第一个变量修复了它,索引在 plt.ylabel 中,输入:name_list [i]。我只是想知道你是如何将它上传到 tkinter 的?提前感谢您的帮助。
猜你喜欢
  • 2017-01-10
  • 2011-09-18
  • 1970-01-01
  • 2017-08-24
  • 2019-12-06
  • 1970-01-01
  • 2021-12-26
  • 2023-04-04
  • 1970-01-01
相关资源
最近更新 更多