Matplotlib chart - создание горизонтальной гистограммы

Я наткнулся на следующий фрагмент, создав горизонтальную гистограмму с использованием 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()

Я хочу изменить приведенный выше script следующим образом:

  • Сделать стробированные полосы "менее короткими" (т.е. уменьшить высоту построенных горизонтальных баров)
  • Поместите как отрицательные, так и положительные числа в виде горизонтальных полос на одном и том же участке

любая помощь (фрагмент кода или ссылки), чтобы помочь мне сделать вышеуказанные изменения очень полезными.

как в сторону, если бы я хотел сделать штабелированные горизонтальные бары (скажем, каждая метка имела 3 уложенных горизонтальных полос), как бы я мог изменить вышеприведенный код, чтобы построить график из трех штабелированных горизонтальных полос?

[[Edit]]

Может ли кто-нибудь опубликовать два коротких фрагмента кода, которые показывают, как:

  • Печать меток на противоположной стороне горизонтальных полос (так, например, метка для "отрицательных" баров появляется в первом квартете, а метки для "положительных" баров отображаются во втором квадранте

  • График (например, 2 или 3) горизонтальных бара (вместо одного). Хорошими примерами являются первые два изображения, показанные здесь

Ответ 1

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 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 zero level

ax.set_xlabel('Performance')
ax.set_title('horizontal bar chart using matplotlib')
ax.grid(True)
plt.show()

Хорошей отправной точкой для различных видов графиков является matplotlib галерея

Ответ 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()

который производит: this

Ответ 3

Следующий фрагмент кода - это пример использования текстовой функции для комментирования текстовой метки в левой части для отрицательных значений, а справа - для положительных значений, как указано как 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) в соответствии с вашими данными.