Зарегистрируйтесь, чтобы продолжить обучение

Создание базовых графиков Python: Визуализация данных

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

  • Частотные графики
  • Диаграммы
  • Ящики с усами
  • Скрипичные графики
  • Гистограммы
  • Тепловые карты
  • Попарные графики

Подготовка данных

Начнем с предобработки датасета. Мы будем использовать следующие три модуля для разных задач:

# Работа с табличными данными
import pandas as pd

# Работа со структурами и алгоритмами для ускорения обработки данных
import numpy as np

# Создание регулярных выражений для поиска структур в строках
import re

Аналитики часто работают со структурированными данными в табличном виде. Для работы с ними в Python нужен модуль Pandas, который отличается скоростью и высокоуровневой реализацией функций. В этом уроке вы увидите, как этот модуль работает на практике.

Загрузим датасет в исходном виде:

# Чтение данных
train_data = pd.read_csv("./data/Titanic_train.csv")

Датасет Titanic содержит ряд столбцов c признаками, которые характеризуют пассажиров Титаника. Также есть метка Survived — она показывает, выжил ли пассажир во время крушения корабля. Представим, что нам нужно изучить набор признаков каждого пассажира и предсказать, кто выжил. Посмотрим на весь набор параметров:

# Названия столбцов
print(train_data.columns.to_list())
# ['PassengerId', 'Survived', 'Pclass',
# 'Name', 'Sex', 'Age', 'SibSp', 'Parch',
# 'Ticket', 'Fare', 'Cabin', 'Embarked']

Для просмотра данных воспользуемся методом head():

train_data.head()
# PassengerId Survived Pclass                    Name    Sex  Age SibSp Parch      Ticket     Fare   Cabin Embarked
#           1      0     3 Braund, Mr. Owen Harris male 22.0   1     0 A/5 21171   7.2500    NaN       S
#           2      1     1 Cumings, Mrs. John Brad female 38.0   1     0  PC 17599  71.2833    C85       C
#           3      1     3 Heikkinen, Miss. Laina  female 26.0   0     0  STON/O2.   7.9250    NaN       S
#           4      1     1 Futrelle, Mrs. Jacques  female 35.0   1     0    113803  53.1000   C123       S
#           5      0     3 Allen, Mr. William Henr male 35.0   0     0    373450   8.0500    NaN       S

Чтобы первично обработать данные, нужно:

  • Удалить или заполнить пропуски
  • Привести данные в столбцах к определенному типу
  • Сформировать дополнительные признаки на основе других

Эти шаги не входят в рамки этого урока, поэтому не будем подробно вдаваться в код ниже. Вы можете изучить его самостоятельно:

# Переиндексируем данные и удаляем некоторые столбцы
train_data.index = train_data.PassengerId
del train_data["PassengerId"]

# Была ли у пассажира своя каюта?
train_data["Has_Cabin"] = train_data["Cabin"].apply(
    lambda x: 0 if type(x) == float else 1
)

# Какого размера была семья пассажира?
train_data["FamilySize"] = train_data["SibSp"] + train_data["Parch"] + 1

# Пассажир был на Титанике один или с попутчиками?
train_data["Is_alone"] = train_data["FamilySize"].apply(lambda x: 0 if x > 1 else 1)


# Используем регулярные выражения, чтобы найти сокращения Ms, Mr или другие обращения
def get_title(name):
    title_search = re.search(" ([A-Za-z]+)\.", name)
    if title_search:
        return title_search.group(1)
    return ""


# Применяем к столбцу Name
train_data["Title"] = train_data["Name"].apply(get_title)

# Исправляем опечатки и группируем редкие обращения
train_data["Title"] = train_data["Title"].replace(
    [
        "Lady",
        "Countess",
        "Capt",
        "Col",
        "Don",
        "Dr",
        "Major",
        "Rev",
        "Sir",
        "Jonkheer",
        "Dona",
    ],
    "Rare",
)
train_data["Title"] = train_data["Title"].replace("Mlle", "Miss")
train_data["Title"] = train_data["Title"].replace("Ms", "Miss")
train_data["Title"] = train_data["Title"].replace("Mme", "Mrs")

# Очищаем данные от обработанных столбцов
train_data = train_data.drop(["Name", "Ticket", "Cabin"], axis=1)

Исходные данные готовы — можно приступать к анализу.

Визуализация данных

Во время анализа нам нужно прийти к двум целям: выявить закономерности в данных и найти нехарактерные значения, которые могут указывать на ошибки. Для визуализации воспользуемся двумя популярными модулями:

  • Matplotlib для настройки параметров графиков и форматирования вывода
  • Seaborn для построения графиков

Эти модули помогают писать код с меньшим количеством дополнительных параметров. Это упрощает разработку и делает код более читабельным:

import matplotlib.pyplot as plt
import seaborn as sns

Мы объявили модули — можно начинать строить графики.

Частотные графики

Для начала посмотрим на частотный график для столбца Survived:

plt.figure(figsize=(10, 8))
plt.title("Проверка сбалансированности данных по выжившим", fontsize=16)
sns.countplot(x="Survived", data=train_data)

data-balance-check

На диаграмме выше видно, что погибших больше, чем выживших. Это важная характеристика датасета, потому что сбалансированность классов влияет на подходы к построению моделей, которые предсказывают тот или иной класс. В случае несбалансированности классов, нужно выполнять несколько дополнительных преобразований с данными.

Для примера приведем аналогичный график распределения пассажиров по полу:

plt.figure(figsize=(10, 8))
plt.title("Частотный анализ пола пассажиров", fontsize=16)
sns.countplot(x="Sex", data=train_data)

passenger-sex-frequency-analysis

Библиотека Matplotlib позволяет создавать несколько графиков на одном полотне в виде окон. Причем в каждое окно можно поместить различные графики как самой библиотеки Matplotlib, так и ее приемников:

fig, ax = plt.subplots(
    2, 3, sharex=False, sharey=False, figsize=(15, 15), constrained_layout=True
)
fig.suptitle("Частотный анализ данных", fontsize=25)
sns.countplot(x="Pclass", data=train_data, ax=ax[0, 0])
sns.countplot(x="Is_alone", data=train_data, ax=ax[0, 1])
sns.countplot(x="Title", data=train_data, ax=ax[0, 2])

sns.countplot(x="Embarked", data=train_data, ax=ax[1, 0])
sns.countplot(x="FamilySize", data=train_data, ax=ax[1, 1])

data-frequency-analysis

На картинке выше вы видите пять графиков. Они показывают частотную картину для значений из различных столбцов данных. Пустое окно создается по умолчанию, потому что Matplotlib отрисовывает прямоугольную таблицу по заранее заданным параметрам метода subplots(). В нашем случае размер таблицы по умолчанию — 2 на 3.

Далее попробуем предсказать выживаемость пассажира. Для этих целей аналитики строят частотные графики в разрезе целевой переменной. В случае с Seaborn можно использовать параметр hue:

fig, ax = plt.subplots(
    2, 3, sharex=False, sharey=False, figsize=(15, 15), constrained_layout=True
)
fig.suptitle("Частотный анализ данных относительно целевой переменной", fontsize=25)

sns.countplot(x="Pclass", hue="Survived", data=train_data, ax=ax[0, 0])
sns.countplot(x="Is_alone", hue="Survived", data=train_data, ax=ax[0, 1])
sns.countplot(x="Title", hue="Survived", data=train_data, ax=ax[0, 2])

sns.countplot(x="Embarked", hue="Survived", data=train_data, ax=ax[1, 0])
sns.countplot(x="FamilySize", hue="Survived", data=train_data, ax=ax[1, 1])

data-frequency-analysis-relative-to-target-variable

Графики выше показывают, что с большой вероятностью не выжили одинокие мистеры из третьего класса, севшие в Саутгемптоне — примерно такие гипотезы аналитики обычно выдвигают по графикам и диаграммам. Дальше эти гипотезы нужно подтвердить или опровергнуть методами статистического и корреляционного анализа — это типичный порядок работы аналитика.

Рассмотрим картину выживаемости для различных возрастов. По ней видно, что среди погибших много людей среднего возраста:

plt.figure(figsize=(20, 8))
plt.title("Частотный анализ Age относительно целевой переменной", fontsize=16)
sns.countplot(x="Age", hue="Survived", data=train_data)

age-frequency-analysis-relative-to-target-variabl

Столбчатые диаграммы

Перейдем от частотного анализа к оценке статистических показателей. Воспользуемся методом barplot() и построим среднее значение целевой переменной в зависимости от признака:

plt.figure(figsize=(10, 8))
plt.title("Среднее значение целевой переменной в зависимости от Title", fontsize=16)
sns.barplot(x="Title", y="Survived", data=train_data)

average-target-variable-by-title

Этот график показывает среднюю вероятность выживаемости в зависимости от признака Title. Черная вертикальная линия демонстрирует разброс вероятности относительно среднего — чем длиннее линия, тем разброс больше. Аналогичный график зависимости вероятности выживаемости в зависимости от размера семьи:

plt.figure(figsize=(10, 8))
plt.title(
    "Среднее значение целевой переменной в зависимости от FamilySize", fontsize=16
)

sns.barplot(x="FamilySize", y="Survived", data=train_data)

average-target-variable-by-familysize

Можно строить более сложные диаграммы для среднего, добавляя разрез по признакам. Для этого нужно добавить параметр hue`. Изучим пример для среза по классу каюты:

plt.figure(figsize=(10, 8))
plt.title(
    "Среднее значение целевой переменной в зависимости от Sex в срезе по Pclass",
    fontsize=16,
)

sns.barplot(x="Sex", y="Survived", hue="Pclass", data=train_data)

average-target-variable-by-sex-in-pclass-slice

Ящики с усами

Более детальный статистический анализ можно провести с помощью метода boxplot():

plt.figure(figsize=(15, 10))
plt.title("Среднее значение Age в зависимости от Survived в срезе по Sex", fontsize=16)
sns.boxplot(x="Age", y="Survived", hue="Sex", orient="h", data=train_data)

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

average-age-by-survived-in-sex-slice

Обратите внимание на точки, не попавшие в границы усов — там могут быть ошибки, поэтому лучше проверить эти данные.

Скрипичные графики

Для дополнительного отображения формы распределения используют метод violinplot(). Его следует читать по аналогии с boxplot():

plt.figure(figsize=(15, 10))
plt.title("Распределение Age в зависимости от Sex в срезе по Survived", fontsize=16)
sns.violinplot(x="Age", y="Sex", hue="Survived", orient="h", data=train_data)

age-distribution-by-sex-in-survived-slice

Гистограммы

Для анализа характера распределения значений используют гистограммы. По ним можно судить о типе распределения, его значениях среднего, разброса и скошенности. Эта информация используется для дальнейшей подготовки данных в методах машинного обучения. Для построения гистограммы используем метод displot():

plt.figure(figsize=(12, 8))
plt.title("Гистограмма распределения возраста пассажиров", fontsize=16)
sns.displot(train_data["Age"], rug=True)

age-distribution-histogram-of-passengers

Попарные графики

Оценка влияния признаков на целевую переменную является основной целью анализа. Но взаимосвязь признаков друг с другом также несет важную информацию. На ее основе могут приниматься решения о формировании новых признаков или же наоборот удалении зависимых признаков как избыточных и влияющих на качество предиктивных моделей.

Для построения попарных графиков для всех признаков датасета используется метод pairplot():

plt.figure(figsize=(20, 20))
sns.pairplot(train_data)

pairwise-distribution-plots

Тепловые карты

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

Для построения тепловой карты в Seaborn существует метод heatmap(). Попробуем составить карту вероятности выживания в зависимости от размера семьи и пола пассажира:

plt.figure(figsize=(15, 10))
plt.title(
    "Тепловая карта значений Survived в зависимости от FamilySize и Sex", fontsize=16
)

sns.heatmap(
    train_data.groupby(["FamilySize", "Sex"])["Survived"].mean().unstack().fillna(0)
)

survived-values-heatmap-by-familysize-and-sex

Ниже пример для зависимости от признаков is_alone и Sex:

plt.figure(figsize=(10, 8))
plt.title(
    "Тепловая карта значений Survived в зависимости от is_alone и Sex", fontsize=16
)

sns.heatmap(
    train_data.groupby(["Is_alone", "Sex"])["Survived"].mean().unstack().fillna(0)
)

survived-values-heatmap-by-is_alone-and-sex

Самые светлые и темные области тепловой карты говорят о явной зависимости целевой переменной для конкретного квадрата — пары соответствующих значений признаков.

Выводы

В этом уроке мы реализовали основные графики, который аналитик использует в своей работе. На практическом примере разобрали, в каких случаях стоит применять:

  • Частотные графики
  • Диаграммы
  • Ящики с усами
  • Скрипичные графики
  • Гистограммы
  • Тепловые карты
  • Попарные графики

Визуализация данных позволила выдвинуть ряд гипотез о качестве данных и закономерностях в них. Результаты этого урока могут использоваться как отчетный материал по первичному анализу данных.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff