【问题标题】:matplotlib chart - creating horizontal bar chartmatplotlib 图表 - 创建水平条形图
【发布时间】:2012-03-26 10:42:08
【问题描述】:

我偶然发现了以下 sn-p,用于使用 matplotlib 创建水平条形图:

import matplotlib
from pylab import *

val = 3+10*rand(5)    # the bar lengths
pos = arange(5)+.5    # the bar centers on the y axis
print pos
figure(1)
barh(pos,val, align='center')
yticks(pos, ('Tom', 'Dick', 'Harry', 'Slim', 'Jim'))
xlabel('Performance')
title('horizontal bar chart using matplotlib')
grid(True)
show()

我想将上面的脚本修改如下:

  1. 使绘制的条“不那么粗”(即降低绘制的水平条的高度)
  2. 在同一图上将负数和正数绘制为水平条

任何帮助我进行上述修改的帮助(代码 sn-p 或链接)都会非常有帮助。

顺便说一句,如果我想制作堆叠的水平条(假设每个标签有 3 个堆叠的水平条),我将如何修改上面的代码来绘制 3 个堆叠的水平条图?

[[编辑]]

有人可以发布两个短代码 sn-p 说明如何:

  1. 在水平条的另一侧打印标签(例如,“负”条的标签出现在第一象限,“正”条的标签出现在第二象限

  2. 绘制多个(比如 2 或 3 个)水平条(而不仅仅是一个)。很好的例子是first two images shown here

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    下面的代码 sn-p 是一个例子,使用 text 函数在左侧标注负值,右侧标注正值,gcalmettes 和zhenya 都提到过。

    from pylab import setp
    import numpy as np
    import matplotlib.pyplot as plt
    import math
    
    # creation of the data
    name_list = ['day1', 'day2', 'day3', 'day4']
    data = {name: 3+10*np.random.rand(5) for name in name_list}
    
    for name in name_list:
      data[name][0] = data[name][0]*-1
      data[name][2] = data[name][2]*-1
    
    colors_list = ['0.5', 'r', 'b', 'g'] #optional
    
    def customize_barh(data, width_bar=1, width_space=0.5, colors=None):
        n_measure = len(data)                   #number of measure per people
        n_people = data[data.keys()[0]].size    # number of people
    
        #some calculation to determine the position of Y ticks labels
        total_space = n_people*(n_measure*width_bar)+(n_people-1)*width_space
        ind_space = n_measure*width_bar
        step = ind_space/2.
        pos = np.arange(step, total_space+width_space, ind_space+width_space)
        # create the figure and the axes to plot the data 
        fig = plt.figure(figsize=(8,6))
        ax = fig.add_axes([0.15, 0.15, 0.65, 0.7])
    
        # remove top and right spines and turn ticks off if no spine
        ax.spines['right'].set_color('none')
        ax.spines['top'].set_color('none')
        ax.xaxis.set_ticks_position('bottom')
        ax.yaxis.set_ticks_position('default')    # ticks position on the right
        # postition of tick out
        ax.tick_params(axis='both', direction='out', width=3, length=6,
                       labelsize=24, pad=8)
        ax.spines['left'].set_linewidth(3)
        ax.spines['bottom'].set_linewidth(3)
    
        # plot the data
        for i,day in enumerate(data.keys()):
            if colors == None:
                ax.barh(pos-step+i*width_bar, data[day], width_bar, #facecolor='0.4',
                        edgecolor='k', linewidth=3)
            else:
                ax.barh(pos-step+i*width_bar, data[day], width_bar, facecolor=colors[i],
                        edgecolor='k', linewidth=3)
    
    
        ax.set_yticks(pos)
        # you may want to use the list of name as argument of the function to be more
        # flexible (if you have to add a people)
        setp(ax.get_yticklabels(), visible=False)         
        ax.set_ylim((-width_space, total_space+width_space))
        ax.set_xlabel('Performance', size=26, labelpad=10)
        labels_list = ['Tom', 'Dick', 'Harry', 'Slim','Jim']
    
        # creation of an array of positive/negative values (based on the values
        # of the data) that will be used as x values for adding text as side labels
        side_list = []
        for index in range(len(labels_list)):
            sum = 0
            for name in name_list:
                sum+= data[name][index]
            if math.copysign(1,sum) > 0:
                side_list.append(16)
            else:
                side_list.append(-21)
        for label in labels_list:
            plt.text(side_list[labels_list.index(label)], pos[labels_list.index(label)]-0.5, label,fontsize=26) 
    customize_barh(data, colors=colors_list)
    plt.savefig('perf.png')
    plt.show()
    

    它的工作原理是,给定人的所有条都需要是负数或正数,才能在正确的一侧注释文本。要更改此行为,只需更改 side_list 的生成即可。

    例如,如果您想要某个条形阈值来确定标签的位置,则计算超过该阈值的数据值,而不是对给定名称的值求和。

    例如,对于 3 条的阈值,无论多少条,for 循环都变为

    for index in range(len(labels_list)):
            count = 0
                for name in name_list:
                   if data[name][index] > 0:
                      count+= 1
                if count > 3:
                  side_list.append(16)
                else:
                  side_list.append(-21)
    

    side_list 的生成也需要更改以适应您的数据范围,因为给出的示例使用指定范围内的随机数据。

    例如,您需要调整 side_list.append(16)side_list.append(-21) 的标签偏移量以适合您的数据。

    【讨论】:

      【解决方案2】:

      正如珍亚所说,你将不得不调整你的情节。

      例如,下面是一个生成自定义水平条形图的函数:

      • 输入是数据,包含在字典中
      • 然后它会根据您在每个类别(人)中的度量(条)数量以及您希望在每个类别之间放置的空间来计算 Y 刻度的位置。
      • 最后它会绘制每个数据度量(如果您已指定,则使用不同的颜色)

      默认情况下,它会将类别(人)的名称绘制在右侧,但您当然可以更改它。

      import numpy as np
      import matplotlib.pyplot as plt
      
      # creation of the data
      name_list = ['day1', 'day2', 'day3', 'day4']
      data = {name: 3+10*np.random.rand(5) for name in name_list}
      
      colors_list = ['0.5', 'r', 'b', 'g'] #optional
      
      def customize_barh(data, width_bar=1, width_space=0.5, colors=None):
          n_measure = len(data)                   #number of measure per people
          n_people = data[data.keys()[0]].size    # number of people
      
          #some calculation to determine the position of Y ticks labels
          total_space = n_people*(n_measure*width_bar)+(n_people-1)*width_space
          ind_space = n_measure*width_bar
          step = ind_space/2.
          pos = np.arange(step, total_space+width_space, ind_space+width_space)
      
          # create the figure and the axes to plot the data 
          fig = plt.figure(figsize=(8,6))
          ax = fig.add_axes([0.15, 0.15, 0.65, 0.7])
      
          # remove top and right spines and turn ticks off if no spine
          ax.spines['right'].set_color('none')
          ax.spines['top'].set_color('none')
          ax.xaxis.set_ticks_position('bottom')
          ax.yaxis.set_ticks_position('right')    # ticks position on the right
          # postition of tick out
          ax.tick_params(axis='both', direction='out', width=3, length=6,
                         labelsize=24, pad=8)
          ax.spines['left'].set_linewidth(3)
          ax.spines['bottom'].set_linewidth(3)
      
          # plot the data
          for i,day in enumerate(data.keys()):
              if colors == None:
                  ax.barh(pos-step+i*width_bar, data[day], width_bar, #facecolor='0.4',
                          edgecolor='k', linewidth=3)
              else:
                  ax.barh(pos-step+i*width_bar, data[day], width_bar, facecolor=colors[i],
                          edgecolor='k', linewidth=3)
      
      
          ax.set_yticks(pos)
          # you may want to use the list of name as argument of the function to be more
          # flexible (if you have to add a people)
          ax.set_yticklabels(('Tom', 'Dick', 'Harry', 'Slim', 'Jim'))         
          ax.set_ylim((-width_space, total_space+width_space))
          ax.set_xlabel('Performance', size=26, labelpad=10)
      
      customize_barh(data, colors=colors_list)
      plt.savefig('perf.png')
      plt.show()
      

      产生:

      【讨论】:

      • 感谢代码 sn-p。比我想象的要复杂一些。仍然..,这几乎是第二部分完全完成了。我将重新阅读customize_barh 函数以确保我了解发生了什么......另外,我是否正确假设如果我想为上面的图显示一个图例,我只需添加语句 plt.legend() .最后但并非最不重要的一点是,您能否发布一个 sn-p 显示如何绘制第一部分中描述的图表? (即负条在第一象限有标签等?)。我仍然不清楚如何使用 matplotlib 实现这一目标。
      • 另外,如何在左侧而不是默认右侧绘制自定义水平条形图的标签?谢谢
      • @HomunculusReticulli,这个函数只是你可以做的一个例子。对于“一次性绘图”,只需在函数中获取您感兴趣的代码片段。 ax.yaxis.set_ticks_position('right') 是您要更改为在左侧有刻度的行(将其更改为 left)。关于您想要实现的目标,使用ax.text 应该更容易,而不是试图弄清楚如何根据值(正或负)改变刻度位置。对于图例,plt.legend(['d1', 'd2', 'd3', 'd4']) 之类的内容将起作用。
      • 如何添加带有颜色组合的图例? plt.legend(['d1', 'd2', 'd3', 'd4']) 用单色添加图例。
      • @AnushShetty 添加带有颜色组合的图例是什么意思?如果您想为每种颜色添加带有标签的图例,plt.legend(['d1', 'd2', 'd3', 'd4']) 可以工作
      【解决方案3】:
      import matplotlib
      from pylab import *
      
      val = 3-6*rand(5)    # the bar lengths        # changed your data slightly
      pos = arange(5)+.5    # the bar centers on the y axis
      print pos
      figure(1)
      barh(pos,val, align='center',height=0.1)    # notice the 'height' argument
      yticks(pos, ('Tom', 'Dick', 'Harry', 'Slim', 'Jim'))
      
      gca().axvline(0,color='k',lw=3)   # poor man's zero level
      
      xlabel('Performance')
      title('horizontal bar chart using matplotlib')
      grid(True)
      show()
      

      一般来说,我建议不要使用from pyplot import *。除非您处于交互模式,否则请使用面向对象的方法:

      import matplotlib.pyplot as plt
      from numpy.random import rand
      from numpy import arange
      
      val = 3-6*rand(5)    # the bar lengths
      pos = arange(5)+.5    # the bar centers on the y axis
      print pos
      
      fig = plt.figure()
      ax = fig.add_subplot(111)
      ax.barh(pos,val, align='center',height=0.1)
      ax.set_yticks(pos, ('Tom', 'Dick', 'Harry', 'Slim', 'Jim'))
      
      ax.axvline(0,color='k',lw=3)   # poor man's zero level
      
      ax.set_xlabel('Performance')
      ax.set_title('horizontal bar chart using matplotlib')
      ax.grid(True)
      plt.show()
      

      各种情节的一个很好的起点是matplotlib gallery

      【讨论】:

      • 谢谢,这就是我想要做的。我注意到的一件事是,图上缺少(Y 轴)标签?对于负水平条,我想要第一象限中的标签,对于正水平条,我想要第二象限中的标签。
      • 据我所知,这无法自动完成。不过,使用textannotate 自动完成并不难。
      猜你喜欢
      • 2016-07-14
      • 2018-03-11
      • 2017-11-21
      • 2013-05-15
      • 1970-01-01
      • 2016-03-08
      • 2021-11-08
      • 2023-03-11
      • 1970-01-01
      相关资源
      最近更新 更多