- Use the rectangular dimensions extracted with
.patches
, to set the x location and, also use ha='right'
.
- Data from data.un.org, selected for 2015 and all countries
Select data from downloaded file
- Download a data file from the link, or use the sample dataframe,
df
, at the bottom of the answer.
import pandas as pd
import matplotlib.pyplot as plt
# load the files
data = pd.read_csv('data/UNdata_Export_20210131_205829459.csv')
# filter data
data = data.loc[(data['City type'] == 'City proper') & (data['Sex'] == 'Both Sexes')][['Country or Area', 'City', 'Value', 'Year']]
# sort data
data = data.sort_values('Value', ascending=False)
# select top 10 rows
df = data.iloc[:10]
Plot using OP method
- This implementation unnecessarily extracts dataframe columns into new variables (e.g.
people
)
people = df[df.columns[2]]
y_pos = np.arange(len(people))
error = np.random.rand(len(people))
fig, ax = plt.subplots(figsize=(10,6))
rects = ax.barh(y_pos, people, xerr=error, align='center')
ax.invert_yaxis() # labels read top-to-bottom
ax.set_xlabel('population' , fontsize=15)
ax.text(0.9, 0.1, 2015, va='bottom', ha='right', transform=ax.transAxes, color='red',size=30)
for i, (p, pr) in enumerate(zip(df['Country or Area'], df['City'])):
# extract the rectangles from ax
rectangle = ax.patches[i]
# get the x for the bar
x = rectangle.get_x()
# get the width
width = rectangle.get_width()
# calculate the label position
x_label = x + width
# use the rectangle x to plot the text and set the ha to right instead of left
plt.text(s=p, x=x_label, y=i, color='black', va="top", ha="right", size=8, weight='bold')
plt.text(s=pr, x=x_label, y=i, color='black', va="bottom", ha="right", size=10, weight='bold')
display(plt.show)
Streamlined Implementation
# plot the dataframe
ax = df.plot.barh(y='Value', figsize=(10, 6), legend=False, width=0.8)
ax.set_xlabel('population' , fontsize=15)
ax.set(yticklabels=[]) # remove the y tick labels
ax.tick_params(left=False) # remove the ticks
ax.invert_yaxis()
ax.text(0.9, 0.1, 2015, va='bottom', ha='right', transform=ax.transAxes, color='red',size=30)
for i, (p, pr) in enumerate(zip(df['Country or Area'], df['City'])):
# extract the rectangles from ax
rectangle = ax.patches[i]
# get the x for the bar
x = rectangle.get_x()
# get the width
width = rectangle.get_width()
# calculate the label position
x_label = x + width
# use the rectangle x to plot the text and set the ha to right instead of left
plt.text(s=p, x=x_label, y=i, color='black', va="top", ha="right", size=8, weight='bold')
plt.text(s=pr, x=x_label, y=i, color='black', va="bottom", ha="right", size=10, weight='bold')
plt.show()
Plot Result

df
sample
- Test data if website data is no longer available
data = {'Country or Area': ['Republic of Korea', 'Japan', 'Mexico', 'United States of America', 'China, Hong Kong SAR', 'Singapore', 'Chile', 'Australia', 'Australia', 'Jordan'], 'City': ['SEOUL', 'TOKYO', 'MEXICO, CIUDAD DE', 'New York (NY)', 'HONG KONG SAR', 'SINGAPORE', 'SANTIAGO', 'Sydney', 'Melbourne', 'AMMAN'], 'Value': [9860372.0, 9272740.0, 8854560.0, 8550405.0, 7291300.0, 5535002.0, 5150010.0, 4526479.0, 4353514.0, 4007526.0], 'Year': ['2015', '2015', '2015', '2015', '2015', '2015', '2015', '2015', '2015', '2015']}
df = pd.DataFrame(data)
display(df)
Country or Area City Value Year
0 Republic of Korea SEOUL 9860372.0 2015
1 Japan TOKYO 9272740.0 2015
2 Mexico MEXICO, CIUDAD DE 8854560.0 2015
3 United States of America New York (NY) 8550405.0 2015
4 China, Hong Kong SAR HONG KONG SAR 7291300.0 2015
5 Singapore SINGAPORE 5535002.0 2015
6 Chile SANTIAGO 5150010.0 2015
7 Australia Sydney 4526479.0 2015
8 Australia Melbourne 4353514.0 2015
9 Jordan AMMAN 4007526.0 2015