More efficient matplotlib stacked bar chart - how to calculate bottom values
Asked Answered
B

4

21

I need some help making a set of stacked bar charts in python with matlibplot. My basic code is below but my problems is how to generate the value for bottom for any element beyond the 2nd one efficiently. I can get the example graph to stack correctly (always a,b,c,d from bottom to top)

import numpy as np
import matplotlib.pyplot as plt

ind = np.arange(3)

a = [3,6,9]
b = [2,7,1]
c = [0,3,1]
d = [4,0,3]

p1 = plt.bar(ind, a, 1, color='#ff3333')
p2 = plt.bar(ind, b, 1, color='#33ff33', bottom=a)
p3 = plt.bar(ind, c, 1, color='#3333ff', bottom=[a[j] +b[j] for j in range(len(a))])
p4 = plt.bar(ind, d, 1, color='#33ffff', bottom=[a[j] +b[j] +c[j] for j in range(len(a))])

plt.show()

My final code could have very large number of bars and the ever expanding function bottom = [...] cannot be the best solution. It would be great if you could also explain how I need to derive the value. Is there a numpy function.

Thank you very much!!! PS I have searched for an answer but I did not understand what I could find.

Botvinnik answered 27/9, 2013 at 21:12 Comment(0)
M
30

I have just recently faced the same problem. Afterwards I decided to wrap it all up in a nice class. For anyone interested you get an implementation of a stacked bar graph class here:

https://github.com/minillinim/stackedBarGraph

It allows scaled stacked graphs as well as setting bar widths and set heights (with scaled inners).

Given a data set like this:

    d = np.array([[101.,0.,0.,0.,0.,0.,0.],
                  [92.,3.,0.,4.,5.,6.,0.],
                  [56.,7.,8.,9.,23.,4.,5.],
                  [81.,2.,4.,5.,32.,33.,4.],
                  [0.,45.,2.,3.,45.,67.,8.],
                  [99.,5.,0.,0.,0.,43.,56.]])

    d_heights = [1.,2.,3.,4.,5.,6.]
    d_widths = [.5,1.,3.,2.,1.,2.]
    d_labels = ["fred","julie","sam","peter","rob","baz"]
    d_colors = ['#2166ac',
                '#fee090',
                '#fdbb84',
                '#fc8d59',
                '#e34a33',
                '#b30000',
                '#777777']

It can make images like this:

stacked bar graph

GPLv3 with love.

Mcilwain answered 7/1, 2014 at 22:12 Comment(3)
Thanks - how can I get spaces between the bars?Junia
I updated the code to allow for gaps. It's actually pretty simple, if you deduct a fixed amount from the widths of the bars then it effectively shrinks them. After that it's just a matter of playing with the xlims. The main function call now has two new paramters, gap and endGaps, The bottom two pictures show examples of these in use.Mcilwain
Love @minillinim's package. It felt too easy. To add a legend, if you set the colors with an array such as stacked_colors = ['#2166ac', '#fee090', '#fdbb84'] and cols=stacked_colors, then it's easy to add a legend to a plot made from a pandas DataFrame: legends = [] i = 0 for column in df.columns: legends.append(mpatches.Patch(color=stacked_colors[i], label=column)) i+=1 plt.legend(handles=legends) Wooton
C
16

Converting your values to numpy arrays will make your life easier:

data = np.array([a, b, c, d])
bottom = np.cumsum(data, axis=0)
colors = ('#ff3333', '#33ff33', '#3333ff', '#33ffff')

plt.bar(ind, data[0], color=colors[0])
for j in xrange(1, data.shape[0]):
    plt.bar(ind, data[1], color=colors[j], bottom=bottom[i-1])

Alternatively, to get rid of the nasty particular case for the first bar:

data = np.array([a, b, c, d])
bottom = np.vstack((np.zeros((data.shape[1],), dtype=data.dtype),
                    np.cumsum(data, axis=0)[:-1]))
colors = ('#ff3333', '#33ff33', '#3333ff', '#33ffff')
for dat, col, bot in zip(data, colors, bottom):
    plt.bar(ind, dat, color=col, bottom=bot)
Copolymer answered 27/9, 2013 at 21:29 Comment(2)
If you are using matplotlib, everything ends up as a ndarray underneath anyway. Might as well make your life pleasant as well ;)Indorse
thanks, how can I add labels into that? I have a list of label/names for each of the series I am stacking up, but although I have tried I cannot get them to come out properly. I have also tried to run a simple legend like the below but it has not really worked.: codeplt.legend((pl[0], pm[0],ph[0],pa[0]),('L','M','H','At'),bbox_to_anchor=[1.05, 0.5], loc='center')Botvinnik
S
7
[sum(values) for values in zip(a, b, c)]

In Python 2 you can also do

map(sum, zip(a, b, c))

but Python 3 would need

list(map(sum, zip(a, b, c)))

which is less nice.


You could encapsulate this:

def sumzip(*items):
    return [sum(values) for values in zip(*items)]

and then do

p1 = plt.bar(ind, a, 1, color='#ff3333')
p2 = plt.bar(ind, b, 1, color='#33ff33', bottom=sumzip(a))
p3 = plt.bar(ind, c, 1, color='#3333ff', bottom=sumzip(a, b))
p4 = plt.bar(ind, d, 1, color='#33ffff', bottom=sumzip(a, b, c))

too.


If a, b, c and d are numpy arrays you can also do sum([a, b, c]):

a = np.array([3,6,9])
b = np.array([2,7,1])
c = np.array([0,3,1])
d = np.array([4,0,3])

p1 = plt.bar(ind, a, 1, color='#ff3333')
p2 = plt.bar(ind, b, 1, color='#33ff33', bottom=sum([a]))
p3 = plt.bar(ind, c, 1, color='#3333ff', bottom=sum([a, b]))
p4 = plt.bar(ind, d, 1, color='#33ffff', bottom=sum([a, b, c]))
Scorper answered 27/9, 2013 at 21:18 Comment(0)
T
2

I solved it like this:

import numpy as np

dates = # somehow get a list of dates
labels = # a list of various labels
colors = # somehow get a list of colors

margin_bottom = np.zeros(dates)

for index, label in enumerate(labels):
    values = # get your values for the label at index-th position from somewhere
    ax.bar(
        dates, values, 
        align='center', label=label, color=colors[index], bottom=margin_bottom
    )
    margin_bottom += values # here you simply add it to the previous margin
    # margin_bottom is a numpy array, adding a list will not change that

It's similar to some other solutions, but it doesn't require all of the margins being stored at all time. Instead it "builds" the stacks from bottom up, adding more and more margin with each iteration.

Thatcher answered 19/3, 2016 at 1:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.