Установление расстояния между сгруппированными штриховыми графиками в matplotlib

Я пытаюсь сделать сгруппированный график в 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

Мои вопросы:

  • Как я могу управлять пространством между группами баров? Прямо сейчас расстояние огромно и выглядит глупо. Обратите внимание: я не хочу, чтобы шины были более широкими - я хочу, чтобы они имели одинаковую ширину, но были ближе друг к другу.

  • Как я могу получить метки, которые будут центрироваться ниже групп баров? Я попытался придумать некоторые арифметические вычисления, чтобы поместить xlabels в нужное место (см. Код выше), но он все еще немного отключен... он немного напоминает запись библиотеки графиков, а не ее использование. Как это можно зафиксировать? (Есть ли оболочка или встроенная утилита для matplotlib, где это поведение по умолчанию?)

РЕДАКТИРОВАТЬ: Ответ на @mlgill: спасибо за ваш ответ. Ваш код, безусловно, намного более изящный, но по-прежнему имеет такую ​​же проблему, а именно, что ширина баров и расстояние между группами не контролируются отдельно. Ваш график выглядит корректно, но бары слишком широки - это выглядит как граф Excel, и я хотел сделать панель более тонкой.

Ширина и маржа теперь связаны, поэтому, если я попробую:

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

Это делает панель более тонкой, но приносит группе далеко друг от друга, поэтому сюжет снова выглядит не так.

Как создать функцию группового графика, которая принимает два параметра: ширину каждого бара и расстояние между группами баров, и делает это правильно, как ваш код, т.е. с помощью меток по оси х, расположенных по центру группы?

Я думаю, что, поскольку пользователю приходится вычислять конкретные значения макета низкого уровня, такие как маржа и ширина, мы по-прежнему в основном пишем библиотеку построений:)

Ответ 1

Уловкой обоих ваших вопросов является понимание того, что гистограммы в Matplotlib ожидают, что каждая серия (G1, G2) будет иметь общую ширину "1,0", считая поля с обеих сторон. Таким образом, возможно, проще всего установить поля, а затем рассчитать ширину каждого бара в зависимости от того, сколько из них существует для каждой серии. В вашем случае есть две полосы в каждой серии.

Предполагая, что вы выровняете каждую полосу, вместо выравнивания по центру, как вы это делали, эта настройка приведет к серии, которая охватывает от 0.0 до 1.0, 1.0 до 2.0 и т.д. по оси x. Таким образом, точный центр каждой серии, где вы хотите, чтобы ваши метки появлялись, будет составлять 0,5, 1,5 и т.д.

Я очистил ваш код, так как было много посторонних переменных. См. Комментарии внутри.

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.

Ответ 2

На самом деле, я думаю, что эту проблему лучше всего решить путем настройки figsize и width; вот мой вывод с figsize=(2,7) и width=0.3:

enter image description here

Кстати, этот тип вещей становится много проще, если вы используете обертки pandas (я также импортировал seaborn, не необходимый для решения, но делает сюжет много красивее и более современно выглядящее, на мой взгляд):

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

Ответ 3

Я прочитал ответ, который Павел Иванов разместил на Nabble, который мог бы решить эту проблему с меньшей сложностью. Просто установите индекс, как показано ниже. Это увеличит расстояние между сгруппированными столбцами.

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