barplot: между хаосом и ясностью
Почему одни графики мгновенно доносят суть, а другие заставляют зрителя страдать? Ответ кроется в деталях. Возьмем пример двух барплотов, которые я недавно встретил в аналитике рынка труда. Оба пытались показать распределение мнений респондентов по категориям, ни один не справился с задачей.
ещё больше интересного в моём telegram-канале: https://t.me/ml_maxim
Первый график: потенциал, который не раскрыли
Автор оригинала попытался добавить ценность через сортировку данных — это похвально, так как сортировка помогает выделить закономерности (например, ранжирование от наибольшего к наименьшему). Однако критичных ошибок избежать не удалось:

1. Цвет как белый шум. Использование случайных оттенков вместо семантической палитры (например, красный для негатива, зеленый для позитива) лишило график контекста. Цвет — это не украшение, а визуальная метафора.
2. Отсутствие системы координат. Без базовой линии или разделителя (например, вертикальной черты на 50%) сложно оценить соотношение категорий. Добавление такой линии превращает график в карту сравнения: зритель сразу видит, какие значения выше или ниже критической отметки.
Попробуем исправить ситуацию:

Исправленная версия демонстрирует силу простых приемов:
- Контрастная палитра (красный/зеленый) кодирует эмоциональный подтекст.
- Центральнаялиния разделяет зоны «проблем» и «достижений», превращая график в историю о дисбалансах.
Второй график: как алфавитный порядок убивает смысл
Здесь автор допустил классическую ошибку: расположил категории в алфавитном порядке. Это удобно для поиска, но бесполезно для анализа.
Почему это проблема?
- Алфавитная сортировка не учитывает вес категорий. Например, если «Удовлетворенность зарплатой» занимает 70% негативных ответов, но находится в середине списка, ее значимость теряется.
- Такой подход игнорирует принципы гештальт-психологии: группировка по смыслу или величине помогает увидеть целое, а не набор разрозненных столбцов.

Решение: Сортировка по значению (например, по убыванию доли негативных ответов) превращает график в иерархию проблем. Теперь зритель сразу видит, на чем стоит сфокусироваться.
Три правила идеального барплота
- Сортировка — ваш союзник. Ранжируйте категории по ключевой метрике, если только порядок не несет самостоятельной ценности (например, временные периоды).
- Цвет как язык. Используйте палитры с семантикой (теплые/холодные тона) и учитывайте доступность: 8% мужчин имеют дальтонизм, поэтому избегайте комбинаций вроде красный/зеленый.
- Контекст через аннотации. Добавьте подписи, линии среднего или доверительные интервалы — они превращают данные в историю.
Зачем это нужно?
Эффективная визуализация — это не про эстетику. Это про влияние. Разные исследования показывают: решения, подкрепленные грамотными графиками, принимаются на 28% быстрее и воспринимаются как более обоснованные. Ваш барплот может стать тем самым триггером, который убедит инвесторов, изменит стратегию компании или поможет коллегам избежать ошибок.
Код можно найти в моём github или скопировать отсюда
import os
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
# Configuration
CONFIG = {
"xlabel": "% компаний",
"img_name": "img_2.png",
"colormaps": {
"green": ["#2D5F2E", "#8CB369"],
"red": ["#C14953", "#6D1A1A"]
},
"bar_width": 0.65,
"left_ticks": 3,
"font": "Arial",
"grid_color": "#EAEAEA",
"text_colors": {
"green": "#FFFFFF",
"red": "#FFFFFF"
},
"title": "Как прошёл 2024 год в вашей компании?",
"ticks": [
"Лучше, чем 2023",
"Лучше, чем 2023, но хуже 2022",
"Лучше, чем 2022, но хуже 2020",
"Так же, как и прошлый",
"Хуже, чем 2023"
],
"data": {
"В целом": [31, 5, 2, 19, 43],
"Тяжелая промышленность": [40, 4, 0, 18, 38],
"ИТ": [34, 6, 4, 20, 36],
"Легкая промышленность": [33, 6, 0, 19, 42],
"Финансовые технологии": [31, 3, 4, 14, 48],
"Розничная торговля": [31, 4, 3, 16, 46],
"Строительство, недвижимость": [30, 5, 0, 22, 43],
"Услуги для бизнеса и населения": [30, 5, 2, 22, 41],
"Добыча и переработка ископаемых": [28, 5, 1, 20, 46],
"Гостиницы, рестораны": [27, 2, 2, 15, 54],
"Логистика": [23, 7, 2, 18, 50]
},
"xlim": [-65, 75]
}
# Sort the data
CONFIG['data'] = dict(sorted(CONFIG['data'].items(), key=lambda x:
sum(x[1][: CONFIG['left_ticks']]), reverse=False))
def create_colormaps():
"""Create LinearSegmentedColormap objects from the CONFIG
colormaps."""
cmaps = {}
for name, colors in CONFIG["colormaps"].items():
cmaps[name] = mcolors.LinearSegmentedColormap.from_list(name,
colors)
return cmaps
def add_labels(ax, bars, values, color_type):
"""Add value labels to the bars with appropriate text color."""
text_color = CONFIG["text_colors"][color_type]
for bar, value in zip(bars, values):
if value == 0:
continue
width = bar.get_width()
x = bar.get_x() + width / 2
y = bar.get_y() + bar.get_height() / 2
ax.text(x, y, f"{int(value)}",
ha='center', va='center',
color=text_color, fontsize=13)
def add_style(ax):
"""Apply professional styling elements."""
plt.rcParams['font.family'] = CONFIG["font"]
ax.xaxis.grid(True, color=CONFIG["grid_color"], linestyle='--')
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
ax.axvline(0, color='#404040', linewidth=0.8, alpha=0.8)
def plot_segments(ax, data, cmaps):
"""Plot the horizontal bar segments with correct colors."""
categories = list(data.keys())
params = [
{'direction': 'left', 'cmap': cmaps['green'], 'color': 'green',
'indices': range(CONFIG["left_ticks"]), 'edgecolor': '#404040'},
{'direction': 'right', 'cmap': cmaps['red'], 'color': 'red',
'indices': range(CONFIG["left_ticks"], len(CONFIG["ticks"])), 'edgecolor': '#404040'}
]
for param in params:
base = np.zeros(len(categories))
for idx, data_idx in enumerate(param['indices']):
values = np.array([v[data_idx] for v in data.values()])
width = -values if param['direction'] == 'left' else values
norm_idx = idx / (len(param['indices']) - 0.8)
color = param['cmap'](norm_idx)
bars = ax.barh(
np.arange(len(categories)), width,
left=base, height=CONFIG["bar_width"],
color=color, edgecolor=param['edgecolor'],
linewidth=0.5, alpha=0.95
)
add_labels(ax, bars, values, param['color'])
base += width
def create_plot(data):
"""Create the final plot with correct legend colors."""
plt.rcParams['font.size'] = 12
fig, ax = plt.subplots(figsize=(15, 8))
ax.set_title(CONFIG["title"], fontsize=18, pad=20, color='#2D2D2D', weight='semibold', y=1.2)
ax.set_yticks(np.arange(len(data)))
ax.set_yticklabels(data.keys(), color='#404040', fontsize=11)
ax.set_xlim(*CONFIG['xlim'])
ax.set_xticks([])
cmaps = create_colormaps()
plot_segments(ax, data, cmaps)
add_style(ax)
# Generate correct legend colors
tick_colors = []
for i in range(len(CONFIG["ticks"])):
if i < CONFIG["left_ticks"]:
cmap = cmaps['green']
pos = i / (CONFIG["left_ticks"] - 0.8)
else:
cmap = cmaps['red']
idx = i - CONFIG["left_ticks"]
pos = idx / ((len(CONFIG["ticks"]) - CONFIG["left_ticks"]) - 0.8)
tick_colors.append(cmap(pos))
# Create legend handles
legend_handles = [plt.Rectangle((0,0), 1, 1, color=color) for color in tick_colors]
# Add legend
legend = ax.legend(
handles=legend_handles,
labels=CONFIG["ticks"],
ncol=2, frameon=False,
loc=(0.13, 1.05), fontsize=12
)
for text in legend.get_texts():
text.set_color('#606060')
plt.subplots_adjust(top=0.88, bottom=0.15)
plt.xlabel(CONFIG['xlabel'])
plt.tight_layout()
plt.savefig(CONFIG['img_name'], format='png', dpi=120)
if __name__ == "__main__":
create_plot(CONFIG['data'])
ключевые слова:
Визуализация данных, интерпретация данных, анализ данных, barplot, data science, analytics, big data, dashboard