2025, Oct 31 19:46
Как выровнять горизонтальные stacked barh в pandas и matplotlib без смещения
Пошаговое решение смещения в горизонтальных диаграммах barh на pandas и matplotlib: единая ось, синхронизация twin-оси, метки, zorder и аккуратная легенда.
Горизонтальные столбчатые диаграммы в pandas и matplotlib кажутся простыми, пока в дело не вступают составные столбцы и вторая ось. Частая ловушка — смещение столбцов, когда один из слоёв рисуется на связанной оси (twin). Несовпадение едва заметно, но оно ломает историю, которую пытается донести график. Ниже — как вернуть выравнивание, не теряя подписей категорий, оставив фоновую полосу позади и сохранив аккуратную легенду.
Как воспроизвести проблему
Следующий фрагмент создаёт простой DataFrame и рисует два слоя горизонтальных столбцов на разных осях. Составные столбцы попадают на дополнительную ось y, а полупрозрачная фоновая полоса — на основную ось y. В итоге столбцы смещаются, а в компоновке появляются артефакты.
import pandas as pd
import matplotlib.pyplot as plt
records = {'Name': ["A", "B", "C", "D", 'E'],
'Todo': [4, 5, 6, 7, 3],
'Done': [6, 2, 6, 8, 6],
'TimeRemaining': [4, 4, 4, 4, 4]}
frame = pd.DataFrame(records)
canvas, left_ax = plt.subplots(figsize=(10, 8))
right_ax = left_ax.twinx()
pick_cols = frame.columns[0:2].to_list()
bar_ax = frame.plot(kind='barh', y=pick_cols, stacked=True, ax=right_ax)
for box in bar_ax.containers:
labels = [r.get_width() if r.get_width() > 0 else '' for r in box]
bar_ax.bar_label(box, fmt=lambda val: f'{val:.0f}' if val > 0 else '', label_type='center')
frame.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke',
alpha=0.3, ax=left_ax, align='center', width=0.8,
edgecolor='blue')
right_ax.tick_params(axis='y', labelright=False, right=False)
right_ax.set_yticklabels([])
left_ax.get_legend().remove()
plt.title('Status Chart')
plt.tight_layout()
plt.show()
Что на самом деле происходит
Задействованы две разные оси: одна для составных столбцов и одна для полупрозрачной полосы. Каждая ось сама управляет позициями по y. Поскольку столбцы нарисованы на разных осях, центры категорий не совпадают — и слои не выравниваются. Если перенести всё на одну ось, столбцы выстроятся ровно, но вы потеряете левые подписи категорий и получите лишний элемент легенды, а полупрозрачная полоса окажется сверху.
Решение
Поместите оба слоя на одну ось, чтобы центры столбцов совпали, а связанную ось используйте только для отображения левых подписей. Синхронизируйте левую ось y с положениями правой оси, спрячьте подписи делений справа, оставьте только нужную легенду и отправьте фоновую полосу на задний план с помощью zorder. Если на правой оси появляется подпись, очистите её явно.
import pandas as pd
import matplotlib.pyplot as plt
records = {'Name': ["A", "B", "C", "D", 'E'],
'Todo': [4, 5, 6, 7, 3],
'Done': [6, 2, 6, 8, 6],
'TimeRemaining': [4, 4, 4, 4, 4]}
frame = pd.DataFrame(records)
canvas, left_ax = plt.subplots(figsize=(10, 8))
right_ax = left_ax.twinx()
stack_cols = frame.columns[1:3].to_list()
bars_ax = frame.set_index('Name').plot(kind='barh', y=stack_cols, stacked=True, ax=right_ax)
for grp in bars_ax.containers:
labels = [seg.get_width() if seg.get_width() > 0 else '' for seg in grp]
bars_ax.bar_label(grp, fmt=lambda v: f'{v:.0f}' if v > 0 else '', label_type='center')
frame.set_index('Name').plot(kind='barh', y=["TimeRemaining"], color='whitesmoke',
alpha=0.3, ax=right_ax, align='center', width=0.8,
edgecolor='blue', zorder=0)
right_ax.tick_params(axis='y', labelright=False, right=False)
left_ax.tick_params(axis='y', labelleft=True, left=True)
left_ax.set_ylim(right_ax.get_ylim())
left_ax.set_yticks(right_ax.get_yticks())
left_ax.set_yticklabels(frame['Name'])
handles, labels = right_ax.get_legend_handles_labels()
right_ax.legend([handles[0]], [labels[0]], loc='upper right')
right_ax.set_ylabel('')
plt.title('Status Chart')
plt.tight_layout()
plt.show()
Почему это важно
Выравнивание категорий в горизонтальных столбчатых диаграммах легко упустить и трудно «развидеть». Когда слои показывают разные метрики, смещение может подсказать неверные связи. Общая ось для обоих слоёв сохраняет точность и читабельность, особенно когда нужны составные значения, полупрозрачная базовая полоса, центрированные подписи и опрятная легенда.
Итоги
Стройте все слои столбцов на одной оси, чтобы гарантировать выравнивание. Пусть связанная ось служит для левых подписей, отзеркальте пределы и деления по y и держите фоновую полосу позади через z-order. Приведите в порядок легенду и уберите лишнюю подпись правой оси. В результате получится ровная, читабельная горизонтальная диаграмма, которая доносит верную идею без сюрпризов в вёрстке.