August 29, 2023

Красивые графики на Python с нуля. Часть 1

Сегодня Python имеет широкий набор инструментов для аналитики и Data Science, в том числе и для визуализации данных

Для подобных целей Python имеет множество библиотек, но в этой статье мы рассмотрим библиотеку Matplotlib - одно из самых популярных решений для визуализации, и пройдем путь от базовых графиков до качественной визуализации


Установка библиотек

Для этой статьи нам понадобится Matplotlib и Pandas

Чтобы их установить в ваше виртуальное окружение, выполните в консоли следующую команду

pip install matplotlib, pandas

После чего библиотеки установится на ваш компьютер

Версии библиотек в этой статье

Если ваши версии сильно отличаются, то код из статьи может выдавать неожиданные результаты
print(matplotlib.__version__) # '3.7.1'
print(pandas.__version__) # 1.5.3

Базовый график

Начнем с основ, создадим фигуру и оси

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
Результат выполнения ячейки кода

Функция subplots() возвращает кортеж из фигуры и осей

  • fig — фигура, это все белое полотно
  • ax — оси, на осях мы рисуем графики, это прямоугольник в центре
Если вы работаете традиционной среде разработке, такой как PyCharm, а не в интерактивной, например как Jupyter, то следует добавить следующую строчку кода в конце, чтобы график появился
plt.show()

Этим графиком пока никого не удивить, поэтому двигаемся дальше!


Выбираем датасет

Возьмем данные регионального этапа ВОШ по информатике 2023 года, на этом сайте как раз можно удобно скачать результаты в формате csv

Мы будем использовать библиотеку Pandas для работы с таблицей, импортируем её и загрузим датасет с помощью функции read_csv

import pandas as pd

df = pd.read_csv('reg_2023.csv') #reg_2023.csv - название таблицы
df.head(5)

При выводе нашего датафрейма мы должны получить следующий результат

Результат выполнения ячейки кода

Предобработка данных

Перед визуализацией нам следует заняться маленькой предобработкой данных. Сохраним только ненулевые значения Sum, чтобы график нес больше информативный характер

Для этого напишем следующую строчку кода

df = df[df.Sum > 0]

В квадратных скобках мы указываем фильтр, по которому чистим данные. В нашем случае - общее число баллов больше нуля


Строим график

Наконец, можем заняться тем, ради чего все собрались! Построим график гистограммы

В Matplotlib есть метод hist который строит гистограмму, в него нужно передать входные данные, которые могут быть как питоновским списком, так Numpy массивом или Pandas датафреймом (как в нашем случае)

ax.hist(df.Sum)
результат выполнения ячейки кода

График пока не подает никаких надежд, но все еще впереди!

Стилизуем график

Сейчас будет самое интересное, сделаем график красивым!

Поменяем достаточно большое количество стандартных параметров графика на кастомные и даже не пытайтесь их всех выучить!

Изменим пропорции нашей фигуры и её разрешение

для этого в функцию subplots передадим следующие параметры

  • figsize - Ширина и высота в дюймах
  • dpi - Разрешение графика в точках на дюйм

Для этого модифицируем строчку кода

fig, ax = plt.subplots(figsize=(10, 6), dpi=150)

Результат наших модификаций

Результат модификации строчки кода

Теперь график большой и четкий

Уберем лишние spines

Из документации Matplotlib: "An axis spine — the line noting the data area boundaries"

Spines, черные полоски по краям графика, ухудшают его внешний вид, поэтому удалим их всех, кроме нижней

Все эти полоски хранятся как атрибут spines у наших осей, получить список их названий можно следующей строчкой кода

[direction for direction in ax.spines]
# ['left', 'right', 'bottom', 'top']

Далее, по такому ключу можно обратиться к конкретной spine и менять её свойства

Например, можно сделать их невидимыми

ax.spines['top'].set_visible(False)

Сделаем невидимыми все spine, кроме нижней (т.е. bottom)

Это можно сделать следующим, достаточно читаемым способом

for direction in ax.spines:
    if direction != 'bottom':
        ax.spines[direction].set_visible(False)

Или таким, не очень читаемым способом, но зато в одну строчку

[ax.spines[direction].set_visible(False) for direction in ax.spines\
     if direction != 'bottom']

Изменим цвет и толщину spines

Черный цвет сильно выделяется на фоне, поэтому сделаем её серой и тонкой

ax.spines['bottom'].set_color('grey')
ax.spines['bottom'].set_linewidth(1)

Результат нашей стилизации

Удалим черные черточки (ticks)

Эти черные черточки возле шкалы значений на оси Oy называются ticks. Убрать их можно следующей строчкой кода, изменив их цвет на белый

ax.yaxis.set_tick_params(color='white')

Изменим шрифт

Стандартный шрифт Matplotlib достаточно устарел, поэтому заменим его на что-нибудь современное, например Inter - бесплатный аналог San Francisco Pro Text, шрифта от Apple

Для этого выполним следующую строчку кода

import matplotlib

matplotlib.rcParams['font.family'] = 'Inter' # Название шрифта
# шрифт должен быть установлен на компьютер

Итог нашей начальной стилизации

Уже не так безнадежно, как казалось в начале! Но не будем останавливаться на достигнутом, теперь поменяем параметры самой гистограммы


Меняем параметры гистограммы

  • color - цвет графика
  • bins - количество столбцов
  • zorder - номер слоя. Чем выше, тем больше объектов будет перекрывать

для этого изменим строчку кода с созданием гистограммы

ax.hist(df.Sum, color='#2F80ED', bins=128, zorder=2)

Результат наших модификаций

результат модификаий

Получилось уже достаточно красиво, многие бы на этом и остановились, но не в нашем случае! Продолжим стилизовать график, меняя его параметры


Добавим разнообразия

Для начала, добавим сетку

Для того, чтобы добавить сетку в Matplotlib есть метод grid, в который мы передадим следующие параметры

  • axis - для какой оси добавляем сетку
  • linestyle - стиль линий
  • color - цвет
  • zorder - номер слоя

для этого напишем следующую строчку кода

ax.grid(axis='y', color='#EEEEEE', linestyle='--', zorder=0)

Поскольку zorder нашей сетки будет ниже чем у гистограммы, то она не будет перекрывать наш график

Добавим вертикальные линии и аннотации

Раз график регионального этапа ВОШ, то добавим границы проходных баллов на заключительный этап и подпишем их

В 2023 году проходной балл на заключительный этап ВОШ по информатике составлял 511 баллов для всех классов

Что мы достаточно точно предсказали в 👉 этом посте

Начнем с вертикальных линий

Для них в Matplotlib есть специальный метод axvline, который принимает на вход координату по иксу и уже привычные для нас параметры

Данный метод строит вертикальную линию от края до края графика

ax.axvline(x=511, color='grey', linewidth=2, linestyle='--')

Для аннотации на графике воспользуемся методом annotate и передадим ему следующие параметры

  • text - текст аннотации
  • xytext - кортеж координат текста
  • xy - кортеж координат конца стрелки
  • arrowprops - параметры стрелки

Получаем следующую строчку кода

ax.annotate(
    text="Проходной балл\nна ЗЭ ВОШ (511)", #\n - перенос строки
    xytext=(250, 450),
    xy=(505, 350),
    arrowprops=dict(arrowstyle="->", connectionstyle="arc3, rad=-0.4")
)

И вот, что у нас получилось

Графику не хватает только названия, подписей к осям и указания автора, добавим их!


Добавляем текста на график

Остался финальный штрих - подписи к графику, но не все так просто...

В Matplotlib есть встроенный метод для заголовка - title, но он не подойдет в нашем случае

  • Во-первых, нет другого метода для мини-заголовка под этим заголовком, от чего мы не сможем указать авторство
  • Во-вторых, этот метод добавляет заголовок близко к осям, без возможности это исправить без костылей, из-за чего график выглядит перегружено

Поэтому будем использовать метод text для нашей фигуры

Вызывая fig.text мы будем ставить текст на самой фигуре (а не на оси, как делает title), поэтому с её помощью можно удержать достаточно расстояния от осей

Координаты текста нужно указывать в относительных величинах, будто вся фигура заключена в квадрате от 0 до 1.

Такое позиционирование является плохой практикой, поскольку придется самостоятельно подбирать координаты, от чего этот процесс сложно автоматизировать

fig.text(
    s='Распределение баллов на региональном этапе ВОШ\nпо информатике',
    x=0.1, y=1, 
    fontdict={'size': 18, 'weight': 'bold'}
)

fig.text(
    s='Анализ данных для олимпиадников | DS.Talk',
    x=0.1, y=0.95, 
    fontdict={'size': 12, 'weight': 'light'}
)

Параметром fontdict мы передаем словарь с дополнительными аргументами для шрифта, так мы сможем точечно изменить его размеры и вес

Также добавим подпись к оси Ox

ax.set_xlabel('Балл на региональном этапе', color='grey')

Итоговый результат

Наконец! После стольких модификаций и стилизаций, мы закончили график и можем им насладиться

Итоговый результат

Весь код статьи

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib

df = pd.read_csv('reg_2023.csv')
df = df[df.Sum > 0]

matplotlib.rcParams['font.family'] = 'inter' # Название шрифта

fig, ax = plt.subplots(figsize=(10, 6), dpi=150)

for direction in ax.spines:
    if direction != 'bottom':
        ax.spines[direction].set_visible(False)
        
ax.spines['bottom'].set_color('grey')
ax.spines['bottom'].set_linewidth(1)
ax.yaxis.set_tick_params(color='white')

ax.hist(df.Sum, color='#2F80ED', bins=64, zorder=2)

ax.grid(axis='y', color='#D9D9D9', linestyle='--', zorder=0)

ax.axvline(x=511, color='grey', linewidth=2, linestyle='--', zorder=0)

ax.annotate(
    text="Проходной балл\nна ЗЭ ВОШ - 511",
    xy=(505, 350),
    xytext=(250, 450), 
    arrowprops=dict(arrowstyle="->", connectionstyle="arc3, rad=-0.4")
)

fig.text(
    s='Распределение баллов на региональном этапе ВОШ\nпо информатике',
    x=0.1,
    y=1, 
    fontdict={'size': 18, 'weight': 'bold'}
)

fig.text(
    s='Анализ данных для олимпиадников | DS.Talk',
    x=0.1,
    y=0.95, 
    fontdict={'size': 12, 'weight': 'light'}
)

ax.set_xlabel('Балл на региональном этапе', color='grey')

plt.show()

Алгоритм улучшения качества графика✨

  1. Очищаем данные
  2. Увеличиваем разрешение изображения и улучшаем соотношение сторон
  3. Меняем цвета и базовые параметры графика
  4. Добавляем сетку, аннотации, легенду графика
  5. Добавляем или улучшаем заголовок и авторство

Понравилась статья? Подписывайтесь на канал AD_olimp!

Еще больше аналитики и Data Science в сфере образования по ссылке: https://t.me/AD_olimp