2012-07-22 11 views
10

Próbuję utworzyć zgrupowany wykres słupkowy w matplotlib, zgodnie z przykładem w galerii. Używam następujące:ustawianie odstępów między zgrupowanymi wykresami słupkowymi w matplotlib

import matplotlib.pyplot as plt 
plt.figure(figsize=(7,7), dpi=300) 
xticks = [0.1, 1.1] 
groups = [[1.04, 0.96], 
      [1.69, 4.02]] 
group_labels = ["G1", "G2"] 
num_items = len(group_labels) 
ind = arange(num_items) 
width = 0.1 
s = plt.subplot(1,1,1) 
for num, vals in enumerate(groups): 
    print "plotting: ", vals 
    group_len = len(vals) 
    gene_rects = plt.bar(ind, vals, width, 
         align="center") 
    ind = ind + width 
num_groups = len(group_labels) 
# Make label centered with respect to group of bars 
# Is there a less complicated way? 
offset = (num_groups/2.) * width 
xticks = arange(num_groups) + offset 
s.set_xticks(xticks) 
print "xticks: ", xticks 
plt.xlim([0 - width, max(xticks) + (num_groups * width)]) 
s.set_xticklabels(group_labels) 

enter image description here

moje pytania są następujące:

  1. Jak mogę kontrolować przestrzeń pomiędzy grupami barów? W tej chwili rozstaw jest ogromny i wygląda głupio. Zwróć uwagę, że nie chcę, aby paski były szersze - chcę, żeby miały tę samą szerokość, ale były bliżej siebie.

  2. W jaki sposób mogę ustawić etykiety poniżej grup słupków? Próbowałem wymyślić kilka obliczeń arytmetycznych, aby umieścić we właściwym miejscu xlabele (patrz kod powyżej), ale wciąż jest trochę poza ... to trochę tak, jakby pisać bibliotekę kreślącą, zamiast jej używać. Jak to może zostać naprawione? (Czy istnieje wrapper lub wbudowane narzędzie do matplotlib, gdy jest to zachowanie domyślne?)

EDIT: Odpowiedz @mlgill: dziękuję za odpowiedź. Twój kod jest z pewnością znacznie bardziej elegancki, ale wciąż ma ten sam problem, a mianowicie, że szerokość pasków i odstępy między grupami nie są kontrolowane osobno. Twój wykres wygląda poprawnie, ale paski są o wiele za szerokie - wygląda jak wykres Excela - i chciałem, aby pasek był cieńszy.

Szerokość i marża są teraz połączone, więc jeśli próbuję:

margin = 0.60 
width = (1.-2.*margin)/num_items 

To sprawia, że ​​bar chudszy, ale przynosi grupę daleko od siebie, więc fabuła znowu nie wygląda dobrze.

Jak utworzyć zgrupowaną funkcję wykresu słupkowego, która pobiera dwa parametry: szerokość każdego słupka i odstępy między grupami prętów i kreślenie go poprawnie, tak jak zrobił to twój kod, tj. Z etykietami osi X wyśrodkowanymi poniżej grupy?

myślę, że ponieważ użytkownik musi obliczyć konkretne ilości układu niskiego poziomu, jak i szerokości marginesu, nadal jesteśmy w zasadzie pisanie kreślenia biblioteki :)

Odpowiedz

15

Sztuką na oba pytania jest zrozumienie, że wykresy słupkowe w Matplotlib oczekuje się, że każda seria (G1, G2) będzie miała całkowitą szerokość "1,0", licząc marginesy po obu stronach. Dlatego prawdopodobnie najłatwiej ustawić marginesy, a następnie obliczyć szerokość każdego paska, w zależności od tego, ile z nich przypada na serię. W twoim przypadku są dwa paski na serię.

Zakładając, że wyrównałeś każdy z pasków, zamiast wyśrodkować je tak, jak to zrobiłeś, ustawienie to spowoduje szereg od 0,0 do 1,0, 1,0 do 2,0 i tak dalej na osi X. W związku z tym dokładne centrum każdej serii, w której mają pojawić się etykiety, będzie miało wartość 0,5, 1,5 itd.

Oczyściłem kod, ponieważ było wiele zmiennych zewnętrznych. Zobacz komentarze wewnątrz.

import matplotlib.pyplot as plt 
import numpy as np 

plt.figure(figsize=(7,7), dpi=300) 

groups = [[1.04, 0.96], 
      [1.69, 4.02]] 
group_labels = ["G1", "G2"] 
num_items = len(group_labels) 
# This needs to be a numpy range for xdata calculations 
# to work. 
ind = np.arange(num_items) 

# Bar graphs expect a total width of "1.0" per group 
# Thus, you should make the sum of the two margins 
# plus the sum of the width for each entry equal 1.0. 
# One way of doing that is shown below. You can make 
# The margins smaller if they're still too big. 
margin = 0.05 
width = (1.-2.*margin)/num_items 

s = plt.subplot(1,1,1) 
for num, vals in enumerate(groups): 
    print "plotting: ", vals 
    # The position of the xdata must be calculated for each of the two data series 
    xdata = ind+margin+(num*width) 
    # Removing the "align=center" feature will left align graphs, which is what 
    # this method of calculating positions assumes 
    gene_rects = plt.bar(xdata, vals, width) 


# You should no longer need to manually set the plot limit since everything 
# is scaled to one. 
# Also the ticks should be much simpler now that each group of bars extends from 
# 0.0 to 1.0, 1.0 to 2.0, and so forth and, thus, are centered at 0.5, 1.5, etc. 
s.set_xticks(ind+0.5) 
s.set_xticklabels(group_labels) 

Output from my code.

+0

Zwróć też uwagę na znacznie zmniejszoną liczbę poleceń, gdy moje komentarze zostaną usunięte. Chociaż myślę, że funkcja kreślenia prętów Matplotlib mogłaby w jakiś sposób ulec niewielkiej poprawie, z pewnością nie jest to już pisanie biblioteki kreślącej. :) –

+0

dziękuję za komentarz, odpowiedziałem w edytorze na mój główny wpis. – user248237dfsf

+0

Z tego, co napisałeś powyżej, brzmi to tak, jakbyś chciał, aby szerokość całego wykresu była mniejsza (można to ustawić w linii, która tworzy figurę) lub aby marginesy same w sobie były większe, co pozwoli zachować proporcje takie same. Można również dostosować obliczenia szerokości i xdata, tak aby między każdym z nich był margines. Osiągnięcie tego wymaga jedynie podstawowej algebry. Poza tymi trzema pomysłami nie mam pojęcia, o co prosisz. –

2

czytam odpowiedź, że Paweł Iwanow zamieszczonych na Nabble które mogą rozwiązać ten problem przy mniejszej złożoności. Po prostu ustaw indeks jak poniżej.Spowoduje to zwiększenie odstępu między zgrupowanymi kolumnami.

ind = np.arange(0,12,2) 
9

Właściwie myślę, że ten problem jest najlepiej rozwiązane poprzez regulację figsize i width; tutaj jest moje wyjście z figsize=(2,7) i width=0.3:

enter image description here

Nawiasem mówiąc, tego typu rzeczy staje dużo prostsze jeśli używasz pandas owijarki (Ja również importowane seaborn, nie jest to konieczne dla rozwiązania , ale sprawia, że ​​fabuła dużo ładniejszy i bardziej nowoczesny wygląd moim zdaniem):

import pandas as pd   
import seaborn 
seaborn.set() 

df = pd.DataFrame(groups, index = group_labels) 
df.plot(kind='bar', legend = False, width = .8, figsize = (2,5)) 
plt.show() 

enter image description here

Powiązane problemy