Homunculus Reticulli
Homunculus Reticulli

Reputation: 68366

matplotlib chart - creating horizontal bar chart

I have stumbled accross the following snippet, for creating horizontal bar chart using 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()

I want to modify the above script as follows:

  1. Make the plotted bars 'less chunky' (i.e. reduce the height of the plotted horiz bars)
  2. Plot both negative and positive numbers as horizontal bars on the same plot

any help (code snippet or links) to help me make the above modifications would be very helpful.

as an aside, if I wanted to make stacked horizontal bars (say each label had 3 stacked horizontal bars), how would I modify the code above to do plot a 3 stacked horizontal bar plot?

[[Edit]]

Could someone post two short code snippet that shows how to:

  1. Print labels on the opposite side of the horizontal bars (so that for example, the label for 'negative' bars appears in the 1st quarant, and the labels for 'positive' bars appears in the 2nd quadrant

  2. Plot multiple (say 2 or 3) horizontal bars (instead of just one). Good examples are the first two images shown here

Upvotes: 18

Views: 40013

Answers (3)

Appleman1234
Appleman1234

Reputation: 16076

The following codes snippet is an example of using the text function to annotate text label on the left hand side for negative values and the right hand side for positive values as mentioned by both gcalmettes and 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()

It works on the basis that the all the bars for a given person need to be negative or positive for the text to be annotated on the correct side. To change this behaviour just change the generation of side_list.

E.g If you want a certain bar threshold to determine the position of the label, then count the data values over that threshold instead of summing the values for a given name.

E.g For threshold of 3 bars out of however many, the for loop becomes

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)

The generation of side_list will also need to be changed to adjust to the range of your data, as the examples given use random data in a specified range.

E.g You will need to adjust the label offsets of side_list.append(16) and side_list.append(-21) to suit your data.

Upvotes: 1

gcalmettes
gcalmettes

Reputation: 8704

As said by Zhenya, you will have to tweek your plot.

As an example, below is a function which produces a customize horizontal bar plot:

  • the input is the data, enclosed in a dictionary
  • it then calculates the position of the Y ticks according of the number of measures (bar) you have in each categories (people), and the space you want to put between each categories.
  • finally it plots each of the data measure (with a different color if you have specified it)

By default, it will plot the name of the categories (people) to the right, but you can of course change that.

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()

which produces: this

Upvotes: 11

ev-br
ev-br

Reputation: 26030

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()

In general, I'd suggest not using from pyplot import *. Unless you're in the interactive mode, use the object-oriented approach:

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()

A good starting point for various sorts of plots is the matplotlib gallery

Upvotes: 25

Related Questions