Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does pyplot.plot() create an additional Rectangle with width=1, height=1?

I'm creating a simple bar plot from a DataFrame. (The plot method on Series and DataFrame is just a simple wrapper around 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')

This is the generated plot
enter image description here

Now I want to access the individual bars. And what I've noticed is that there is an additional bar (Rectangle) with width=1, height=1

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

output:

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)

I would expect only three rectangles here. What is the purpose of the fourth?

like image 944
killakalle Avatar asked Aug 13 '26 01:08

killakalle


1 Answers

You would not want to mess with all the children of the axes to get those of interest. If there are only bar plots in the axes, ax.patches gives you the rectangles in the axes.

Concerning the labeling of the bars, the linked article may not be the best choice. It argues to calculate the distance of the label manually, which is not really useful. Instead you would just offset the annotation by some points compared to the bar top, using the argument textcoords="offset points" to 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()

enter image description here

Finally note that using the shapes in the plot to create the annotations may still not be the optimal choice. Instead why not using the data itself?

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

This produces the same plot as above.

like image 121
ImportanceOfBeingErnest Avatar answered Aug 15 '26 15:08

ImportanceOfBeingErnest



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!