Почему pyplot.plot() создает дополнительный прямоугольник с шириной =1, высотой =1?

Я создаю простой линейный график из DataFrame. (Метод plot для Series и DataFrame - это простая оболочка вокруг pyplot.plot)

import pandas as pd
import matplotlib as mpl

df = pd.DataFrame({'City': ['Berlin', 'Munich', 'Hamburg'],
               'Population': [3426354, 1260391, 1739117]})
df = df.set_index('City')

ax = df.plot(kind='bar')

Это сгенерированный сюжет

Теперь я хочу получить доступ к отдельным барам. И что я заметил, так это то, что есть дополнительная полоса (прямоугольник) с шириной =1, высотой = 1

rects = [rect for rect in ax.get_children() if isinstance(rect, mpl.patches.Rectangle)]
for r in rects:
   print(r)

выход:

Rectangle(xy=(-0.25, 0), width=0.5, height=3.42635e+06, angle=0)
Rectangle(xy=(0.75, 0), width=0.5, height=1.26039e+06, angle=0)
Rectangle(xy=(1.75, 0), width=0.5, height=1.73912e+06, angle=0)
Rectangle(xy=(0, 0), width=1, height=1, angle=0)

Я ожидал бы только три прямоугольника здесь. Какова цель четвертого?

2 ответа

Решение

Четвертый прямоугольник - это ограничивающий прямоугольник для вспомогательного участка оси.
Это артефакт способа, которым Pyplot обрабатывает ограничивающие прямоугольники, он не относится только к Pandas. Например, построение с обычным Pyplot:

f, ax = plt.subplots()
ax.bar(range(3), df.Population.values)
rects = [rect for rect in ax.get_children() if isinstance(rect, mpl.patches.Rectangle)]
for r in rects:
    print(r)

Все еще приводит к четырем прямоугольникам:

Rectangle(-0.4,0;0.8x3.42635e+06)
Rectangle(0.6,0;0.8x1.26039e+06)
Rectangle(1.6,0;0.8x1.73912e+06)
Rectangle(0,0;1x1)

В документах с точным макетом Pyplot есть строка, которая ссылается на этот дополнительный прямоугольник (а также почему его координаты (0,0),(1,1), Это относится к прямоугольному параметру:

... который определяет ограничивающую рамку, внутри которой будут помещаться субплоты. Координаты должны быть в нормализованных координатах фигуры и по умолчанию (0, 0, 1, 1).

Вероятно, есть более официальный раздел документации Matplotlib, который описывает эту архитектуру более подробно, но я нахожу эти документы трудными для навигации, это лучшее, что я мог придумать.

Вы не хотели бы связываться со всеми детьми топоров, чтобы получить интересующие вас. Если на осях есть только гистограммы, ax.patches дает вам прямоугольники в осях.

Что касается маркировки баров, связанная статья может быть не лучшим выбором. Он утверждает, что вычисляет расстояние от метки вручную, что не очень полезно. Вместо этого вы просто смещаете аннотацию на несколько точек по сравнению с верхом бара, используя аргумент textcoords="offset points" в plt.annotation,

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'City': ['Berlin', 'Munich', 'Hamburg'],
               'Population': [3426354, 1260391, 1739117]})
df = df.set_index('City')

ax = df.plot(kind='bar')


def autolabel(rects, ax):
    for rect in rects:
        x = rect.get_x() + rect.get_width()/2.
        y = rect.get_height()
        ax.annotate("{}".format(y), (x,y), xytext=(0,5), textcoords="offset points",
                    ha='center', va='bottom')

autolabel(ax.patches,ax)

ax.margins(y=0.1)
plt.show()

В заключение отметим, что использование фигур на графике для создания аннотаций может быть не самым оптимальным выбором. Вместо этого, почему бы не использовать сами данные?

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'City': ['Berlin', 'Munich', 'Hamburg'],
               'Population': [3426354, 1260391, 1739117]})

ax = df.plot(x = "City", y="Population", kind='bar')

def autolabel(s, ax=None, name=""):
    x = s.name
    y = s[name]
    ax.annotate("{}".format(y), (x,y), xytext=(0,5), textcoords="offset points",
                ha='center', va='bottom')

df.apply(autolabel, axis=1, ax=ax, name="Population")

ax.margins(y=0.1)
plt.show()

Это дает тот же сюжет, что и выше.

Другие вопросы по тегам