2015-12-15 15 views
7

Ho posto questa domanda perché non ho trovato un esempio funzionante su come annotare i grafici a barre orizzontale raggruppati Pandas. Sono consapevole di quanto segue due:Grafico Panda Bar, come annotare i grafici a barre orizzontali raggruppati

ma sono tutti su grafici a barre verticali. Io, o non ho una soluzione per il grafico a barre orizzontale, o non funziona completamente.

Dopo diverse settimane di lavoro su questo problema, sono finalmente in grado di porre la domanda con un codice di esempio, che è quasi quello che voglio, solo non al 100% di lavoro. Hai bisogno del tuo aiuto per raggiungere quel 100%.

Qui andiamo, full code is uploaded here. Il risultato è simile al seguente:

Pandas chart

Si può vedere che è quasi lavorando, proprio l'etichetta non è posto dove voglio e non li può muoversi ad una migliore mettermi. Inoltre, poiché la parte superiore della barra del grafico viene utilizzata per visualizzare la barra di errore, quindi quello che voglio veramente è spostare il testo dell'annotazione verso l'asse y, line up piacevolmente sul lato sinistro o destro dell'asse y, a seconda il valore X. Per esempio, questo è ciò che i miei colleghi possono fare con MS Excel:

MS Excel chart

Questo è possibile per Python per farlo con grafico Panda?

sto compreso il codice dal mio sopra URL per l'annotazione, uno è il mio tutto-che-mi-can-do, e l'altro è per il riferimento (da In [23]):

# my all-that-I-can-do 
def autolabel(rects): 
    #if height constant: hbars, vbars otherwise 
    if (np.diff([plt.getp(item, 'width') for item in rects])==0).all(): 
     x_pos = [rect.get_x() + rect.get_width()/2. for rect in rects] 
     y_pos = [rect.get_y() + 1.05*rect.get_height() for rect in rects] 
     scores = [plt.getp(item, 'height') for item in rects] 
    else: 
     x_pos = [rect.get_width()+.3 for rect in rects] 
     y_pos = [rect.get_y()+.3*rect.get_height() for rect in rects] 
     scores = [plt.getp(item, 'width') for item in rects] 
    # attach some text labels 
    for rect, x, y, s in zip(rects, x_pos, y_pos, scores): 
     ax.text(x, 
       y, 
       #'%s'%s, 
       str(round(s, 2)*100)+'%', 
       ha='center', va='bottom') 

# for the reference 
ax.bar(1. + np.arange(len(xv)), xv, align='center') 
# Annotate with text 
ax.set_xticks(1. + np.arange(len(xv))) 
for i, val in enumerate(xv): 
    ax.text(i+1, val/2, str(round(val, 2)*100)+'%', va='center', 
ha='center', color='black')    

Per favore aiuto. Grazie.

risposta

3

Così, ho cambiato un po 'il modo in cui si costruisce i dati per semplicità:

import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd 
import seaborn as sns 
sns.set_style("white") #for aesthetic purpose only 

# fake data 
df = pd.DataFrame({'A': np.random.choice(['foo', 'bar'], 100), 
        'B': np.random.choice(['one', 'two', 'three'], 100), 
        'C': np.random.choice(['I1', 'I2', 'I3', 'I4'], 100), 
        'D': np.random.randint(-10,11,100), 
        'E': np.random.randn(100)}) 

p = pd.pivot_table(df, index=['A','B'], columns='C', values='D') 
e = pd.pivot_table(df, index=['A','B'], columns='C', values='E') 

ax = p.plot(kind='barh', xerr=e, width=0.85) 

for r in ax.patches: 
    if r.get_x() < 0: # it it's a negative bar 
     ax.text(0.25, # set label on the opposite side 
       r.get_y() + r.get_height()/5., # y 
       "{:" ">7.1f}%".format(r.get_x()*100), # text 
       bbox={"facecolor":"red", 
         "alpha":0.5, 
         "pad":1}, 
       fontsize=10, family="monospace", zorder=10) 
    else: 
     ax.text(-1.5, # set label on the opposite side 
       r.get_y() + r.get_height()/5., # y 
       "{:" ">6.1f}%".format(r.get_width()*100), 
       bbox={"facecolor":"green", 
         "alpha":0.5, 
         "pad":1}, 
       fontsize=10, family="monospace", zorder=10) 
plt.tight_layout() 

che dà:

barh plot error bar annotated

ho tracciare l'etichetta in base al valore medio e messo dall'altra parte della linea 0, quindi sei abbastanza sicuro che non si sovrapporrà mai a qualcos'altro, a parte una barra di errore a volte. Ho impostato una casella dietro il testo in modo che rifletta il valore della media. Ci sono alcuni valori che avrete bisogno di regolare a seconda delle dimensioni figura così le etichette si adattano a destra, come:

  • width=0.85
  • +r.get_height()/5. # y
  • "pad":1
  • fontsize=10
  • "{:" ">6.1f}%".format(r.get_width()*100): impostare totale quantità di carattere nell'etichetta (qui, 6 minimo, riempire con spazio bianco a destra se inferiore a 6 caratteri).Ha bisogno di family="monospace"

Dimmi se qualcosa non è chiaro.

HTH

+0

@xpt, ok, fatemi sapere se non si capisce qualcosa. Ho apportato alcune modifiche da quando hai commentato. – jrjc

+0

eccellente, eccellente! Scusa per aver risposto in ritardo. L'unica domanda che ho è, dall'intero codice non ho visto perché è necessario "importare seaborn", ma quando ho commentato quella riga, funziona ancora, ma il grafico sembra più brutto. Immagino che risponda alla domanda, ma perché è così? Di nuovo. – xpt

+0

@xpt, sì, vedere la riga 5 ('sns.set_style ...'), ho commentato che era per scopi estetici. – jrjc

Problemi correlati