Восприятие и кодирование данных

Глаз сравнивает длины точно, а площади и углы — плохо. Это закон, а не мнение.

«Любой график — это кодирование чисел в визуальные признаки. Качество графика определяется тем, насколько точно зритель сможет их раскодировать».

Когда вы строите график, вы кодируете число в визуальный канал: длину столбика, позицию точки, угол сектора, площадь круга, насыщенность цвета. Кливленд и МакГилл экспериментально измерили, насколько точно люди декодируют каждый канал. Результат — иерархия точности.

ТОЧНОСТЬ ДЕКОДИРОВАНИЯ (от высокой к низкой)
   высокая
      ^   положение на общей шкале      <- scatter, bar
      |   положение на разных шкалах
      |   длина / направление
      |   угол / наклон                 <- pie
      |   площадь                       <- bubble
      |   объём / кривизна
   низкая  оттенок / насыщенность       <- heatmap

Отсюда практический вывод: то, что важно сравнить точно, кодируйте позицией или длиной. То, что второстепенно — цветом. Поэтому bar (длина) бьёт pie (угол) почти всегда.

Эта иерархия — не абстрактная теория, а практический фильтр при проектировании каждого графика. Спросите себя: «какое сравнение в этих данных самое важное?» — и отдайте под него самый точный канал. Если читателю критично понять, что продукт A продаётся ровно на 12% лучше продукта B, кодируйте это длиной столбиков на общей оси, а не размером пузырьков и не оттенком. Цвет и площадь приберегите для вторичных измерений, где достаточно качественного «больше/меньше». Так один и тот же датасет можно подать честно или обманчиво, не меняя ни одного числа — меняя лишь то, какой канал несёт главную мысль.

Важно и то, что каналы не равноценны по числу различимых уровней. Глаз надёжно различает лишь 5–7 ступеней насыщенности и не более десятка категориальных цветов, прежде чем они начинают путаться; зато по позиции на оси он различает сотни уровней. Поэтому непрерывную величину с большим диапазоном бессмысленно кодировать только цветом — теряется разрешение. А категориальную переменную с двадцатью значениями нельзя честно показать двадцатью цветами: половина сольётся. В таких случаях прибегают к фасетингу (small multiples) — разбивают данные на сетку маленьких графиков, перенося часть нагрузки с цвета на позицию.

Как работает под капотом

Преаттентивные признаки — цвет, ориентация, размер — обрабатываются параллельно за ~200 мс. Поэтому одна красная точка среди синих «выпрыгивает» сама. Но точное сравнение требует усилия: глаз отлично говорит «этот столбик выше», но плохо — «этот сектор на 7% больше».

Нормализация — частый шаг перед кодированием цветом: переводим числа в диапазон 0..1, чтобы сопоставить им интенсивность.

# Min-max нормализация: число -> позиция на шкале 0..1 (как для цвета)
values = [120, 340, 200, 90, 410, 260]

lo, hi = min(values), max(values)
norm = [(v - lo) / (hi - lo) for v in values]

for v, x in zip(values, norm):
    blocks = int(round(x * 20))
    print("{:>4} | {:.2f} | {}".format(v, x, "#" * blocks))

«Попробуй сам ▶» — это ровно то, что делает colormap: сопоставляет нормализованному значению интенсивность. Понимая нормализацию, вы не удивитесь, почему один выброс «съедает» всю цветовую шкалу heatmap.

Частые ошибки

Кодировать важное цветом, а не длиной. Использовать площадь круга для точного сравнения (глаз недооценивает площади). Радужная палитра (jet) для непрерывных данных — она создаёт ложные «границы» там, где их нет.

Добавим частые ловушки восприятия. Кодировать величину диаметром круга вместо площади: если радиус пропорционален значению, площадь растёт как квадрат, и величины выглядят преувеличенными в разы — диаметр и площадь нельзя путать. Игнорировать дальтонизм: красно-зелёная пара неразличима примерно для 8% мужчин, поэтому статусы «хорошо/плохо» нельзя кодировать только этими цветами — добавляйте форму или подпись. Использовать неупорядоченную (qualitative) палитру для упорядоченной величины: зритель не поймёт, какой цвет «больше». И обратная ошибка — последовательная палитра для категорий без порядка: она навяжет ложную идею ранжирования там, где его нет. Наконец, расходящаяся (diverging) палитра без осмысленного центра (нуля или среднего) сбивает с толку: её середина должна что-то значить.

Best practices

  • Главное сравнение — позицией или длиной.
  • Для непрерывных величин — последовательные (sequential) палитры, не радуга.
  • Цвет — для категорий и акцентов, не для точных чисел.

Итог: теперь вы знаете, почему одни графики «читаются», а другие — нет. Эта иерархия будет с нами весь курс. Переходим к практике — Matplotlib.

Проверьте себя
1. Согласно иерархии Кливленда–МакГилла, что декодируется точнее всего?
AПлощадь
BПоложение на общей шкале
CОттенок цвета
DУгол сектора
2. Почему bar chart обычно предпочтительнее pie chart для сравнения?
ABar рисуется быстрее
BДлина декодируется точнее, чем угол сектора
CPie не поддерживается библиотеками
DBar занимает меньше места