Seamless bars
Use width=1
and set both color
and ec
to the same color:
fig, ax = plt.subplots()
width = 1
color = 'blue'
ax.bar(langs, students, width=width, color=color, ec=color)
ax.set_ylim(0, 35)
Merged labels
Use pandas with groupby.mean
. Find the transition points of each block and then compute the mean lang (x) per block (y):
import pandas as pd
df = pd.DataFrame(dict(langs=langs, students=students))
blocks = df.students.ne(df.students.shift()).cumsum()
labels = df.groupby(blocks, as_index=False)[['students', 'langs']].mean()
# students langs
# 0 10 1.5
# 1 15 9.5
# 2 20 4.5
# 3 30 7.5
for x, y in zip(labels['langs'], labels['students']):
ax.text(x, y + 1, f'{y:.0f}', ha='center')
Note that in your sample data, it's not necessary to find the transition points because none of the blocks are repeated. If the blocks are guaranteed to never repeat, we can just group by students
without determining the blocks:
# only if blocks are guaranteed to never repeat
labels = df.groupby('students', as_index=False)['langs'].mean()
However, in practice there might be repeated blocks, like the following example which has an extra block of 20s on the far right.
Complete example

import matplotlib.pyplot as plt
import pandas as pd
fig, ax = plt.subplots()
langs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
students = [10, 10, 20, 20, 20, 20, 30, 30, 15, 15, 20, 20, 20]
# ^ another block of 20s
width = 1
color = 'blue'
ax.bar(langs, students, width=width, color=color, ec=color)
ax.set_ylim(0, 35)
df = pd.DataFrame(dict(langs=langs, students=students))
blocks = df.students.ne(df.students.shift()).cumsum()
labels = df.groupby(blocks, as_index=False)[['langs', 'students']].mean()
# langs students
# 0 1.5 10.0
# 1 4.5 20.0
# 2 7.5 30.0
# 3 9.5 15.0
# 4 12.0 20.0 <- repeated 20, so groupby('students') would not have worked
for x, y in zip(labels['langs'], labels['students']):
ax.text(x, y + 1, f'{y:.0f}', ha='center')