2013-09-27 16 views
15

Ho bisogno di aiuto per creare una serie di grafici a barre in pila in python con matlibplot. Il mio codice di base è sotto ma i miei problemi è come generare il valore per in basso per qualsiasi elemento oltre il 2o in modo efficiente. Posso ottenere il grafico esempio di impilare correttamente (sempre a, b, c, d dal basso verso l'alto)Grafico a barre matplotlib più efficiente - come calcolare i valori inferiori

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

mio codice finale potrebbe avere gran numero di barre e la funzione continua espansione sotto = [.. .] non può essere la soluzione migliore. Sarebbe bello se potessi anche spiegare come ho bisogno di ricavarne il valore. C'è una funzione numpy.

Grazie mille !!! PS Ho cercato una risposta ma non ho capito cosa potrei trovare.

risposta

28

Ho appena affrontato lo stesso problema. In seguito ho deciso di concludere tutto in una bella lezione. Per chiunque interessato si ottiene un'implementazione di una barra classe grafico impilato qui:

https://github.com/minillinim/stackedBarGraph

Permette grafici scalati accatastati nonché impostazione larghezze barre e le altezze impostate (con ampolle in scala).

Dato un insieme di dati come questo:

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'] 

Si può rendere le immagini in questo modo:

stacked bar graph

GPLv3 con amore.

+0

Grazie - come posso ottenere spazi tra le barre? – Matt

+0

Ho aggiornato il codice per consentire gli spazi vuoti. In realtà è piuttosto semplice, se si deduce una quantità fissa dalle larghezze delle barre, quindi le riduce efficacemente. Dopodiché è solo questione di giocare con gli xlim. La chiamata alla funzione principale ora ha due nuovi paramter, gap e endGaps, le due immagini in basso mostrano esempi di questi in uso. – minillinim

+0

Pacchetto di Love @ minillinim. Mi è sembrato troppo facile. Per aggiungere una legenda, se imposti i colori con una matrice come 'stacked_colors = ['# 2166ac', '# fee090', '# fdbb84']' e 'cols = stacked_colors', allora è facile aggiungere una legenda a una trama fatta da un dataframe panda: 'legende = [] i = 0 per colonna df.columns: legends.append (mpatches.Patch (colore = stacked_colors [i], label = colonna)) i + = 1 plt.legend (handles = legende) ' –

5
[sum(values) for values in zip(a, b, c)] 

In Python 2 si può anche fare

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

ma Python 3 avrebbe bisogno

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

che è meno bello.


Si potrebbe incapsulare questo:

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

e poi fare

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

troppo.


Se a, b, c e d sono array NumPy si può anche fare 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])) 
14

Convertire i valori da NumPy array renderà la vita più facile:

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

In alternativa, per sbarazzarsi del caso particolare per la prima barra:

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) 
+4

Se si utilizza matplotlib, tutto finisce come narray al di sotto di _anyway_. Potrebbe anche rendere piacevole la tua vita;) – tacaswell

+0

grazie, come posso aggiungere etichette in questo? Ho una lista di etichette/nomi per ciascuna delle serie che sto accumulando, ma anche se ho provato non riesco a farle uscire correttamente. Ho anche provato a eseguire una semplice legenda come quella seguente ma non ha funzionato molto: 'code'plt.legend ((pl [0], pm [0], ph [0], pa [0]) , ('L', 'M', 'H', 'A'), bbox_to_anchor = [1.05, 0.5], loc = 'center') –

1

ho risolto così:

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 

È simile ad altre soluzioni, ma non richiede tutti i margini di essere memorizzati in ogni momento. Invece "costruisce" le pile dal basso verso l'alto, aggiungendo sempre più margine ad ogni iterazione.