Практика EDA | Анализ и обработка данных

Практика EDA

Все курсы > Анализ и обработка данных > Занятие 4

На прошлом занятии мы изучали различные классификации данных, задачи EDA, а также познакомились с основными библиотеками для создания визуализаций. Сегодня мы свяжем эти концепции в практической работе по анализу датасета «Титаник» и датасета Tips.

Содержание занятия

Откроем ноутбук к этому занятию

В первую очередь подготовим датасеты.

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

Датасет «Титаник»

Скачаем обучающий датасет Титаник, подгрузим его в сессионное хранилище Google Colab и импортируем в ноутбук.

Как мы уже знаем, посмотреть на первые или последние несколько (по умолчанию, пять) значений можно с помощью методов .head() и .tail() соответственно.

датасет Titanic, первые три значения с помощью метода .head()

Иногда для получения более объективного представления о данных удобно использовать метод .sample(), который по умолчанию выдает одно случайное наблюдение.

датасет Titanic, пять случайных наблюдений с помощью метода .sample()

Метод .info() для каждого столбца выводит количество непустных (not-null) значений и тип данных. Кроме того, этот метод считает количество столбцов каждого типа и общий объем памяти, занимаемый датасетом.

Конечно, посмотреть количество пропусков удобнее, например, с помощью последовательного применения методов .isnull() и .sum().

Теперь выполним несложную предобработку данных.

Более сложные методы обработки данных мы рассмотрим в третьем и четвертом разделах курса.

Датасет Tips

Кроме того, импортируем хранящийся в библиотеке Seaborn датасет Tips. В нем содержатся 244 записи о чаевых, которые официант ресторана получал на протяжении нескольких месяцев.

датасет Tips библиотеки Seaborn

Вновь воспользуемся методом .info().

Пропущенных значений в этом датасете нет.

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

Описание

способы описания данных в EDA

Категориальные переменные

Методы .unique() и .value_counts()

Применение этих методов аналогично использованию метода библиотеки Numpy np.unique() с параметром return_counts = True. Применим его.

Теперь воспользуемся методами библиотеки Pandas.

При этом для нахождения относительной частоты делить на общее количество строк не нужно. Достаточно указать параметр normalize = True.

Долю «единичек» при наличии двух классов, обозначенных как 0 и 1, можно посчитать и так.

df.describe()

Исследование качественных переменных удобно начать с метода .describe(). Его применение к категориальным столбцам выдаст:

  • общее количество значений (count)
  • количество уникальных значений (unique)
  • наиболее часто встречающееся значение (top)
  • и количество таких значений (freq)

Применим метод .describe() к столбцам Sex и Embarked.

метод .describe(): категориальные данные

Перейдем к графическим методам.

countplot и barplot

Рассмотрим два по сути однаковых графика countplot и barplot: и тот, и другой считают количество значений в каждой из категорий. С точки зрения Питона, различие заключается в том, что в случае countplot, мы считаем количество наблюдений в каждой из категорий в процессе построения графика, а в случае barplot эти метрики уже должны быть посчитаны. Рассмотрим на примерах.

Проще всего countplot и barplot построить с помощью библиотеки Seaborn.

функция countplot() библиотеки Seaborn
функция barplot() библиотеки Seaborn
функция barplot() библиотеки Seaborn: относительное количество наблюдений

В библиотеке Matplotlib мы можем построить только barplot (функция bar()). Отдельного инструмента для построения countplot в ней нет. Количество наблюдений мы можем найти с помощью метода .value_counts().

функция bar() библиотеки Matplotlib

Горизонтальную столбчатую диаграмму (horizontal barplot) можно построить с помощью функции barh().

функция barh() библиотеки Matplotlib

Снова воспользуемся параметром normalize = True метода .value_counts() для нахождения относительной частоты каждой категории признака.

функция bar() библиотеки Matplotlib: относительная частота наблюдений

Для того чтобы построить такой график в библиотеке Pandas, вначале необходимо сгруппировать данные по столбцу Survived, затем выбрать один столбец (например, PassengerId), посчитать количество наблюдений в каждой группе через метод .count() и наконец построить столбчатую диаграмму с помощью метода .plot.bar().

столбчатая диаграмма в библиотеке Pandas: метод .groupby() и метод .count()

Код можно упростить, если сначала выбрать желаемый признак (столбец), затем воспользоваться методом .value_counts() и наконец применить метод .plot.bar().

столбчатая диаграмма в библиотеке Pandas: метод .value_counts()

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

Количественные данные

df.describe()

Если применить метод .describe() к количественным данным, то результат будет отличаться от рассмотренных выше категориальных признаков.

метод .describe(): количественные данные

Как мы видим метод выдает:

  • count — количество наблюдений
  • mean — среднее арифметическое
  • std или standard deviation — среднее квадратическое отклонение
  • min и max — минимальное и максимальное значения, а также
  • 25%, 50% и 75% — первый, второй (он же медиана) и третий квартили

Здесь будет полезно сделать небольшое отступление и ближе познакомиться с новыми для нас способами оценки данных.

Среднее арифметическое и СКО

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

Квантили и робастная статистика

В этом смысле медиана дает более надежную оценку среднего при наличии выбросов.

Статистические методы и алгоритмы, устойчивые к выбросам и менее зависимые от предположений (assumptions) модели, еще называют робастными (robust statistics).

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

пример расчета медианы: нечетное количество чисел

Если количество чисел четное — два срединных значения складываются и делятся на два.

пример расчета медианы: четное количество чисел

Медиану можно также определить как значение, которое наши данные (или случайная величина) не превышают с вероятностью 50 процентов (отсюда знак % в выводе метода .describe()).

Аналогично можно найти, например, значение, которое величина не будет превышать с вероятностью 25 или 75 процентов. Такие значения будут называться первым и третьим квартилями (quartile, от латинского — quarta, «четверть»), потому что они делят распределение на четыре части. По-английски первый, второй и третий квартили принято обозначать как Q1, Q2 и Q3.

первый, второй (медиана) и третий квартили

Кроме этого, можно найти децили (deciles, делят распределение на десять частей). Наконец, если мы хотим найти конкретное значение, то будем искать квантиль (quantile). Если вероятность выражена в процентах, то квантиль принято называть процентилем или перцентилем (percentile).

Вывести конкретный процентиль в методе .describe() можно с помощью параметра percentiles.

метод .describe() с параметром percentiles, примененный к количественным данным

Закрепим полученные знания, проанализировав приведенные выше метрики.

  • Медианное значение обоих признаков чуть ниже среднего арифметического
  • 40 процентов чаевых были ниже 2,48 доллара
  • 99 процентов чеков были ниже 48,23 доллара

Рассмотрим еще одну очень полезную меру разброса.

Межквартильный размах

Межквартильный размах (interquartile range) — робастная (устойчивая к выбросам) альтернатива среднему квадратическому отклонению. Рассчитывается как разница между третьим (Q3) и первым (Q1) квартилями.

пример расчета межквартильного размаха

Зачастую количественные данные удобнее анализировать с помощью графиков. Для этого есть три основных инструмента: гистограмма, график плотности и boxplot.

Гистограмма

С гистограммой мы уже знакомы.

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

Построим несколько графиков с использованием рассматриваемых нами библиотек.

гистограмма в библиотеке Matplotlib
гистограмма в библиотеке Pandas
гистограмма в библиотеке Seaborn
построение гистограммы с помощью функции displot() в Seaborn

Обратите внимание, что функция называется именно displot(), а не distplot()⧉, которая объявлена устаревшей и не рекомендуемой к использованию (deprecated).

гистограмма в библиотеке Plotly Express

Что можно сказать после изучения этих графиков? Распределение скошено вправо (skewed right или positively skewed), т.е. в нем есть несколько чеков на достаточно большую сумму, которые и создают правый «хвост».

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

Когда медиана меньше среднего арифметического, мы наблюдаем скошенное вправо распределение.

В целом соотношение скошенности распределения со средним арифметическом, медианой и модой приведено на графике ниже.

среднее арифметическое, медиана и мода в симметричном и скошенных распределениях

График плотности

С графиком плотности (density plot) мы столкнулись при изучении нормального распределения и модуля random.

Напомню, график плотности позволяет визуализировать непрерывное случайное распределение.

Построим такой график с помощью библиотеки Seaborn.

график плотности в библиотеке Seaborn

Добавлю, для справки, что в Seaborn значение параметра kde расшифровывается как kernel density estimation (ядерная оценка плотности), непараметрический способ оценки плотности случайной величины.

boxplot

После знакомства с квантилями и робастной статистикой понимание графика box plot или как его еще называют box-and-wisker plot (ящик с усами) не вызовет сложностей.

анатомия графика boxplot

В первую очередь замечу, что boxplot строится на проранжированных по возрастанию данных. Теперь обратим внимание на сам «ящик» (box):

  • его левый край отражает первый квартиль (Q1) или 25-тый процентиль (25%)
  • вертикальная полоса посередине — медиана, второй квартиль (Q2) или 50-тый процентиль (50%)
  • правый край, соответственно, третий квартиль (Q3) или 75-тый процентиль (75%)
  • ширина ящика равна межквартильному размаху (IQR)

Усы (whiskers), то есть линии с ромбами на концах, отражают разброс данных за пределами IQR и рассчитываются как функция от этого значения. Данные, которые находятся за пределами этого диапазона, считаются выбросами (outliers).

Давайте построим этот график с помощью Seaborn.

boxplot в библиотеке Seaborn

Проверим расчет межквартильного размаха и усов. Вновь посмотрим на результат метода .describe().

результат метода .describe() в библиотеке Pandas

Первый и третий квартили (13,35, 24,13), а также медиана (17,80) соответствуют графику. Рассчитаем IQR.

$$ \text{IQR} = \text{Q3}-\text{Q1} = 24,13-13,35 = 10,78 $$

Теперь определим длину «усов».

$$ \text{left} = \text{Q1}-1,5 \times \text{IQR} = 13,35-1,5 \times 10,78 = -2,82 $$

$$ \text{right} = \text{Q3}+1,5 \times \text{IQR} = 24,13+1,5 \times 10,78 = 40,3 $$

Как мы видим, значения на графике совпадают с нашими расчетами. Аналогично мы можем воспользовать библиотекой Plotly.

горизонтальный boxplot в библиотеке Pandas
вертикальный boxplot в библиотеке Pandas

Также приведу код для библиотек Matplotlib и Pandas.

Гистограмма и boxplot

Гистограмма, с одной стороны, и boxplot, с другой, имеют свои достоинства и недостатки. В частности,

  • Гистограмма хорошо выявляет полимодальность (то есть несколько мод, «горбиков» в данных), при этом она сильно зависит от выбранного количества интервалов и не показывает выбросы.
  • boxplot наоборот, показывает выбросы, но не справляется с полимодальностью.

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

Вначале построим совмещенный график с помощью двух библиотек: Matplotlib и Seaborn. Первую мы будем использовать для создания подграфиков (рассмотрены ниже) и подписей, вторую — для самих визуализаций.

совмещенный график: гистограмма и boxplot, библиотеки Matplotlib и Seaborn

В Plotly такой график можно построить с меньшим количеством кода.

гистограмма + boxplot в Plotly

Прежде чем перейти к нахождению различий между признаками, еще раз приведу график плотности и boxplot нормального распределения c показателями среднего арифметического, СКО ($\sigma$, сигма), медианы и остальных квартилей, межквартильного размаха (IQR) и других метрик.

нормальное распределение: график плотности и boxplot
Источник: Википедия

После изучения материалов этого занятия, думаю, он стал более понятен.

Нахождение различий

нахождение различий в данных в процессе исследовательского анализа данных

Два категориальных признака

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

countplot и barplot

В первую очередь стоит визуально оценить, есть ли такое различие или нет. Для этого подойдут столбчатые диаграммы, где мы либо располагаем два столбца целевого признака рядом друг с другом (grouped), либо делаем один столбец и разбиваем его на две части (stacked).

Библиотека Seaborn

Начнем с того, что построим несколько counplots/barplots в библиотеке Seaborn с помощью функции countplot() и параметра hue.

countplot для визуализации двух категориальных переменных в Seaborn
горизонтальный countplot для визуализации двух категориальных переменных в Seaborn

Для создания таких графиков мы также можем использовать более универсальную функцию catplot(). Передадим ей все те же параметры, что и функции countplot(), а также параметр kind = ‘count’, который и сообщит, что мы хотим построить именно countplot.

использование функции sns.catplot() для создания countplot
визуализация трех категориальных переменных с помощью двух grouped countplots

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

Теперь посмотрим, как создать подобные графики в библиотеке Plotly.

Библиотека Plotly

Для построения графика countplot используем функцию px.histogram() (для barplot подойдет px.bar()). Начнем с варианта, когда разбитые по какому-либо признаку столбцы стоят рядом друг с другом (grouped).

использование функции px.histogram() для создания countplot

Теперь выведем вариант, когда каждый столбец диаграммы разделен на две части (stacked). Так как мы будем вручную корректировать подписи к графику и расстояние между столбцами, необходимо использовать объектно-ориентированный подход.

использование функции px.histogram() для создания stacked countplot

Теперь разобьем данные по трем категориальным переменным: полу, классу и выживаемости.

использование функции px.histogram() для создания grouped countplot с параметрами color и facet_col

Более того, мы можем добавить еще один категориальный признак, порт посадки пассажира (Embarked).

использование функции px.histogram() для создания grouped countplot с параметрами color, facet_col и facel_row

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

Таблица сопряженности

Таблица сопряженности (contingency table) позволяет количественно измерить зависимость одной категориальной переменной от другой. Например, количественно оценим зависимость выживаемости от класса пассажира. Вначале оценим абсолютное количество наблюдений.

Абсолютное количество наблюдений

Для создания таблиц сопряженности в библиотеке Pandas используется функция pd.crosstab().

таблица сопряженности и функция pd.crosstab()

Теперь для каждого класса мы видим количество выживших и количество погибших. На основе таблицы сопряженности очень удобно строить столбчатую диаграмму (можно использовать график barplot, а не countplot, потому что количество значений в каждой категории уже посчитано).

Начнем с библиотеки Pandas.

grouped столбчатая диаграмма на основе таблицы сопряженности
stacked столбчатая диаграмма на основе таблицы сопряженности

Теперь посмотрим, как построить stacked barplot в библиотеке Matplotlib.

stacked столбчатая диаграмма на основе таблицы сопряженности в Matplotlib
Таблица сопряженности вместе с суммой

С помощью параметра margins = True мы можем вывести сумму наблюдений по каждой строке и каждому столбцу (эти показатели еще называют маргинальными частотами, marginal frequencies).

таблица сопряженности вместе с суммой (маргинальными частотами)
Относительное количество наблюдений

Для получения относительного количества наблюдений (относительных частот) следует использовать параметр normalize. Так как нам важно понимать долю выживших и долю погибших, укажем normalize = 'index'. В этом случае каждое значение будет разделено на общее количество наблюдений в строке.

таблица сопряженности: относительное количество наблюдений по строкам

Если бы в индексе (в строках) была выживаемость, а в столбцах — классы, то логично было бы использовать параметр normalize = 'columns' для деления на сумму по столбцам.

таблица сопряженности: относительное количество наблюдений по столбцам

Теперь на stacked barplot мы видим доли выживших в каждом из классов.

столбчатая диаграмма на основе таблицы сопряженности с относительным количеством наблюдений

Количественный и категориальный признаки

rcParams

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

В библиотеке Matplotlib и связанных с ней библиотеках (например, Seaborn) есть так называемые параметры конфигурации среды (runtime configuration parameters), то есть параметры, которые используются по умолчанию при создании графиков.

Эти параметры и их значения содержатся в словаре, к которому можно получить доступ через атрибут rcParams библиотеки Matplotlib.

Изменить эти параметры можно, обновив значение словаря rcParams по соответствующему ключу. Передадим новое значение размера по ключу figure.figuresize.

Также можно воспользоваться функцией sns.set() или, что то же самое, sns.set_theme().

Теперь все последующие графики в библиотеках Matplotlib, Seaborn и Pandas будут иметь размеры восемь на пять дюймов. Вернемся к исследованию переменных.

Гистограммы

Когда у нас есть одна количественная и одна категориальная переменные, для их визуализации проще всего построить две наложенные друг на друга гистограммы. Мы уже строили такие графики в рамках вводного курса.

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

гистограмма для визуализации количественной и категориальной переменных (Matpotlib)

Теперь посмотрим, зависит ли распределение возраста от пола пассажира.

гистограмма для визуализации количественной и категориальной переменных (Seaborn)
гистограмма для визуализации количественной и категориальной переменных (Plotly)

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

Исправить ситуацию может параметр density = True.

две гистограммы в Seaborn с параметром density = True

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

График плотности

С другой стороны, для плотности вероятности есть отдельный график, density plot. Площадь под кривой такого графика также всегда равна единице. Воспользуемся функцией .displot() с параметром kde = True.

два графика плотности с функцией .displot()

Из-за особенностей расчета графика kde мы можем получить «неестественные значения». Например, на диаграмме выше встречаются отрицательные значения чека. В реальности такого быть не может.

Избавиться от таких значений можно с помощью параметра clip, который задает диапазон значений.

два графика плотности с функцией .displot() с обрезанными значениями и заполнением цветом

boxplots

Для сравнения распределений количественной переменной, разбитой по какому-либо категориальному признаку, также очень удобно использовать несколько графиков boxplot (side-by-side boxplots).

Построим такие графики в библиотеках Seaborn и Plotly. Вначале посмотрим, как различается сумма чека по дням недели.

несколько boxplots на одном графике

Что можно сказать про эти распределения?

  • Медианный чек выше по воскресеньям
  • Самый широкий диапазон суммы по чеку наблюдается в субботу, в пятницу же наоборот разброс наименьший
  • Выбросы присутствуют только в верхних значениях распределения

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

несколько boxplots и stripplots на одном графике

Ожидаемо, как разброс, так и медианное значение меньше в обеденное время.

Дополнительно замечу, что с помощью параметра points = ‘all’ в библиотеке Plotly для каждого распределения мы построили график, который называется stripplot. Он, в частности, показывает, что гостей за ужином бывает существенно больше. Об этом графике мы дополнительно поговорим чуть ниже.

Гистограммы и boxplots

Гистограммы и boxplots можно совместить. Сделать это проще всего в Plotly.

гистограммы и boxplots на одном графике в библиотеке Plotly

stripplot, violinplot

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

несколько stripplots на одном графике

График stripplot можно построить как с помощью приведенной в примере выше функции sns.stripplot(), так и с помощью функции sns.catplot() с параметром kind = ‘strip’.

функция sns.catplot()

Хотя stripplot достаточно информативен сам по себе, его очень удобно применять совместно с boxplot (как мы это делали выше).

График violinplot (от англ. violin, «скрипка») представляет собой комбинацию boxplot и графика плотности.

функция sns.violinplot()

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

Преобразования данных

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

Логарифмическая шкала

Например, возьмем вот такие данные о продажах.

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

преобразование данных с помощью логарифмической шкалы (до преобразования)

Для того чтобы эти продажи все-таки были видны, можно перевести ось y в логарифмическую шкалу.

преобразование данных с помощью логарифмической шкалы (после преобразования)

Границы по оси y

В ноутбуке с моделью текучести кадров сотрудников⧉ (в разделе, посвященном практике), один из признаков — это баллы на последней аттестации. Для покинувших и продолжающих работать сотрудников различие не велико.

границы по оси y (до преобразования)

Иногда для наглядности бывает полезно ограничить диапазон значений по оси y.

границы по оси y (после преобразования)

Перейдем к выявлению взаимосвязи между переменными.

Выявление взаимосвязи

способы выявления взаимосвязи в данных

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

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

Линейный график

Базовым способом визуализации двух количественных переменных является линейный график (linear plot). Построить его можно с помощью функции plt.plot() библиотеки Matplotlib.

линейный график

Точечная диаграмма

Еще один базовый график — уже знакомая нам точечная диаграмма (scatter plot). Ее удобно использовать, когда одна переменная не имеет строгой зависимости от другой. Воспользуемся функцией plt.scatter() библиотеки Matplotlib.

точечная диаграмма matpltolib

Такой же график можно построить в библиотеке Pandas.

точечная диаграмма pandas

На графиках выше мы видим, что в среднем с ростом суммы чека растет и размер чаевых (другими словами, взаимосвязь прослеживается).

При этом мы видим гетероскедастичность (различную изменчивость) данных, когда при небольшом чеке диапазон чаевых меньше, чем когда сумма чека увеличивается.

Почему это влияет на качество модели и как с этим бороться, мы поговорим на следующем курсе.

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

категориальный признак в точечной диаграмме

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

pairplot

График pairplot позволяет визуализировать взаимосвязи сразу нескольких количественных переменных. В библиотеке Pandas такой график строится с помощью функции pd.plotting.scatter_matrix().

pairplot в библиотеке pandas

Как вы видите, там, где перемекаются разные признаки, строится точечная диаграмма, на пересечении одного и того же признака по главной диагонали — его гистограмма.

Примерно такой же график можно построить с помощью функции sns.pairplot() библиотеки Pandas.

pairplot в библиотеке seaborn

Обратите внимание на метод .sample() с параметром frac = 0,2, который мы применили к датафрейму titanic. Таким образом, мы сделали случайную выборку из 20% или $ 891 \times 0,2 \approx 178 $ наблюдений.

случайная выборка с помощью метода .sample() в pandas

Метод .sample() в данном случае применяется для того, чтобы ускорить создание pairplot. Зачастую, при наличии большого числа наблюдений, график может строиться очень долго.

При добавлении параметра hue (разделение по категориальной переменной) гистограмма по умолчанию превращается в график плотности.

параметр hue в sns.pairplot()

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

Функция sns.pairplot() является надстройкой (упрощенной версией) другой функции этой библиотеки, sns.PairGrid(). Ее стоит использовать, если требуются более продвинутые настройки графика pairplot.

sns.PairGrid()

При построении таких сложных графиков важно помнить про их информативность. В примере выше некоторые графики (например, точечную диаграмму) уже достаточно сложно анализировать.

jointplot

Совместное распределение двух переменных

График плотности (kde plot) двух количественных признаков (верхний справа в примере выше) представляет собой визуализацию совместного распределения (joint distribution) двух количественных признаков (tip и total_bill) с разделением по категориальному признаку (time). Другими словами, мы смотрим на то, как изменяется распределение одного количественного признака под воздействием другого. И так для каждой из двух категорий.

В результате мы получаем графики изолиний (contour lines), которые показывают, что между суммой чека и чаевыми есть взаимосвязь (если бы ее не было, изолинии представляли бы собой круги). Теоретические основы совместных распределений мы рассмотрим на курсе по статистике вывода, а пока изучим инструмент их визуализации, который называется jointplot.

sns.jointplot()

Вначале построим точно такой же график плотности (kde plot) совместного распределения tip и total_bill с разделением по признаку time. Для этого функции sns.jointplot() передадим данные и укажем параметр kind = ‘kde’.

sns.jointplot()

По краям мы видим графики плотности так называемого безусловного распределения (marginal distribution) каждого из признаков. Это одномерные распределения (univariate distribution). Основной график показывает совместное распределение (joint distribution) уже двух переменных. Это двумерное распределение (bivariate distribution).

Возможно более интуитивным покажется использование точечной диаграммы (kind = ‘scatter’) вместо графика плотности.

sns.jointplot() + точечная диаграмма

Кроме того, мы можем построить линию регрессии, проходящую через точки. Правда в этом случае придется отказаться от параметра hue, разделять данные на категории и одновременно строить линию регрессии sns.jointplot() не умеет.

sns.jointplot() + линейная регрессия

heatmap

Наконец, если мы хотим вывести какие-либо статистические показатели взаимосвязи двух количественных переменных (например, корреляцию), это можно сделать с помощью чисел. Выведем корреляционную матрицу между total_bill и tip с помощью метода .corr().

корреляция в датасете tips

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

Или с помощью цвета. Во втором случае мы будем строить то, что называется тепловой картой (heatmap). Поместим созданную выше корреляционную матрицу в функцию sns.heatmap().

sns.heatmap()

Более насыщенный красный цвет (верхняя граница шкалы) демонстрирует корреляцию признака с самим собой, менее насыщенный — достаточно сильную положительную корреляцию признаков.

Сравнение датасетов

Рассмотрим еще одну библиотеку, которая позволяет не просто сравнивать количественные и качественные переменные в датасете, а сразу сравнивать два датасета. Зачастую, сравнение двух датасетов имеет смысл, когда перед нами обучающая и тестовая выборки.

Скачаем и подгрузим в сессионное хранилище тестовую часть датасета «Титаник».

Библиотека Sweetviz

Теперь установим и импортируем библиотеку sweetviz.

Импортируем обучающую и тестовую выборки.

Передадим оба датасета в функцию sv.compare(). Эта функция создаст объект DataframeReport, к которому мы сможем применить метод .show_notebook() для выведения результата.

формирование сравнительного отчета в Sweetviz
сравнительный отчет в Sweetviz

Интерактивную версию этого отчета вы найдете в ноутбуке к занятию⧉.

Количественные переменные

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

количественная переменная в Sweetviz

В отчете есть информация о присутствующих (values) и отсутствующих значениях (missing), количестве уникальных (distinct) и нулевых (zeroes) значений. Кроме того, мы видим базовые статистические показатели и гистограмму распределения переменной в каждом из датафреймов.

Отдельно стоит отметить выявление взаимосвязи:

  • для двух количественных переменных используется коэффициент корреляции Пирсона (Pearson correlation coefficient); и здесь мы видим, что корреляция возраста со столбцами Fare и PassengerId ожидаемо близка к нулю
  • для выявления взаимосвязи между количественной и качественной переменными используется корреляционное отношение (correlation ratio); например, мы видим, что возраст в некоторой степени связан с классом пассажира Pclass

Качественные переменные

Обратимся к столбцу Sex.

качественная переменная в Sweetviz

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

Для поиска же взаимосвязи между двумя категориальными переменными используется коэффициент неопределенности (uncertainty coefficient) или U Тиля, и мы видим некоторую связь с целевой переменной Survived. Для количественной и качественной переменных по-прежнему используется корреляционное отношение.

Более подробную информацию об этой библиотеке можно посмотреть на странице документации⧉.

График в Matplotlib

Теперь более детально рассмотрим некоторые из составляющих графика в Matplotlib. В частности, приведем его «анатомию».

анатомия графика в библиотеке Matplotlib
Код для построения этого графика можно посмотреть здесь

И пройдемся по некоторым наиболее важным компонентам с помощью функции np.linspace().

Стиль графика

Цвет графика

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

В документации можно найти более полный перечень названий цветов⧉.

Тип линии графика

Посмотрим на различные типы линии.

Строки форматирования

Цветом и типом линии можно также управлять с помощью строки форматирования (format string).

Стиль точечной диаграммы

Стиль графика в целом

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

Теперь используем стиль bmh.

Акроним bmh расшифровывается как Bayesian Methods for Hackers или «Байесовские методы для хакеров». Так называется онлайн-книга по байесовской статистике⧉, в которой этот стиль используется для форматирования графиков.

Важно отметить, что изменение стиля коснется всех последующих графиков в рамках действующей сессии. Изменить это можно, например, с помощью стиля default, т.е. стиля по умолчанию (такой стиль существует, хотя и не значится в списке выше).

Теперь все графики будут иметь базовый стиль Matplotlib (по сути, белый прямоугольник).

Пределы шкалы и деления осей графика

Пределы шкалы

Пределы шкалы по оси x и по оси y можно задавать двумя способами.

Способ 1. Использовать функции plt.xlim() и plt.ylim().

Способ 2. Кроме этого, мы можем воспользоваться функцией plt.axis().

Деления

Иногда бывает необходимо принудительно изменить деления (tick) графика. Например, слишком большие числа могут не помещаться на шкале или для наглядности мы хотим изменить их формат. Сделать это можно с помощью функций plt.xticks() и plt.yticks().

Подписи, легенда и размеры графика

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

В целом комментарии к коду, я думаю, понятны и дальнейших пояснений не требуют. Замечу лишь, что для выведения легенды важны как функция plt.legend(), так и параметр label внутри функции, строящей график (в данном случае plt.plot()). Без обоих этих элементов легенда не отобразится.

plt.figure() и plt.axes()

На прошлом занятии мы узнали, что Matplotlib поддерживает объектно-ориентированный подход к созданию графиков, и plt.figure() — это класс, который создает некий контейнер для хранения графиков, а plt.axes() строит графики внутри него. Рассмотрим простой пример создания объектов этих классов.

В данном случае объект fig мы не видим, изображение выше — объект ax (чтобы в этом убедиться, создайте объект класса plt.figure() без класса plt.axes()). Добавим к этому объекту кривую функции синуса.

Для добавления других элементов графика можно использовать соответствующие методы, которые немного отличаются от принятых в стиле MATLAB.

  • plt.xlabel() $ \rightarrow $ ax.set_xlabel()
  • plt.ylabel() $ \rightarrow $ ax.set_ylabel()
  • plt.xlim() $ \rightarrow $ ax.set_xlim()
  • plt.ylim() $ \rightarrow $ ax.set_ylim()
  • plt.title() $ \rightarrow $ ax.set_title()

Продемонстрируем их применение.

Кроме этого, можно применить общий метод .set(), в параметрах которого прописать необходимые настройки.

Построение подграфиков

Остается рассмотреть важный вопрос создания подграфиков (subplots), то есть нескольких объектов класса plt.axes() внутри plt.figure(). Сделать это можно несколькими способами.

Способ 1. Создание вручную

Вначале создадим два объекта plt.axes(), один стандартный, второй — по координатам и размерам окна по следующей схеме plt.axes([left, bottom, width, height]). Другими словами, мы передаем координату левого нижнего угла и размеры по ширине и высоте.

Например, создадим вложенный график, левый нижний угол которого будет находиться в центре основного графика [0.5, 0.5] и иметь размеры [0.3, 0.3] по шкале основного графика.

Посмотрим, как расположить графики один над другим.

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

Способ 2. Метод .add_subplot()

Еще один способ создания подграфиков — применять к объекту класса plt.figure() метод .add_subplot(). По большому счету .add_subplot() принимает три основных параметра: количество столбцов, количество строк и индекс подграфика. Например, если мы хотим создать сетку из двух подграфиков в один ряд, количество строк будет равно одному, столбцов — двум (т.е. 1 х 2).

Потребуется, соответственно, два раза использовать метод .add_subplot().

Создать подграфики можно и с помощью цикла for.

Способ 3. Функция plt.subplots()

С функцией plt.subplots() в принципе мы уже давно знакомы. Мы использовали ее на занятии по компьютерному зрению, а затем более подробно рассмотрели в ответах на вопросы к этому же занятию.

Функция plt.subplots() сразу создает фигуру и набор подграфиков. Приведем пример.

Функция plt.tight_layout() корректирует внешние отступы (padding) фигуры и отступы между графиками таким образом, чтобы подграфики не перекрывали друг друга.

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

Еще один вариант использования функции plt.subplots(): «на ходу», т.е. в цикле for, заполнять объекты класса plt.axes() нужными графиками. Начнем с данных.

Теперь создадим сетку из четырех подграфиков (2 х 2) и в цикле будем создавать столбчатую диаграмму продаж каждого магазина по годам.

Мы так делали опять же на занятии по компьютерному зрению или, например, обсуждая центральную предельную теорему на занятии по модулю random.

Обратите внимание, как используется объект ax с функциями библиотеки Seaborn.

Объект ax передается в одноименный параметр ax с указанием соответствующего индекса ax = ax[i, j].

Способ 4. Метод .plot() библиотеки Pandas

Метод .plot() библиотеки Pandas также позволяет строить подграфики. Причем в базовом варианте (без дополнительных настроек) мы можем обойтись относительно небольшим количеством кода.

Если же мы захотим создать график, практически идентичный созданному с помощью функции .subplots(), потребуется уточнить некоторые настройки.

Здесь важно понимать, что метод .plot() также основан на библиотеке Matplotlib и также создает объекты класса plt.axes(). Для того чтобы ими воспользоваться, передадим результат метода .plot() в переменную ax и через индексы модифицируем каждый из подграфиков.

Также для закрепления понимания выведем индексы каждого из объектов сетки.

Дополнительные возможности

Метод .twinx()

Если мы хотим построить две визуализации на одном графике с, например, разными шкалами по оси y, то мы можем воспользоваться методом .twinx(). По сути, этот метод накладывает один объект ax на другой.

Мы уже использовали этот прием, когда при изучении нормального распределения, рассматривали связь функции плотности (pdf) и функции распределения (cdf).

3D визуализации

Кроме этого, в следующем разделе мы будем использовать 3D визуализации при изучении оптимизации многомерной функции методом градиентного спуска.

Такой график можно построить с помощью метода .add_subplot() с параметром projection = ‘3d’.

Подведем итог

На сегодняшнем занятии мы на практике разобрали основные инструменты, применяемые для решения каждой из трех задач EDA (описания данных, выявления различий и нахождения зависимостей), а также посмотрели некоторые технические детали создания графиков в основных библиотеках Питона.

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

Вопросы для закрепления

Вопрос. Что такое stacked и grouped графики?

Ответ: проще всего еще раз привести графики из датасета «Титаник».

Посмотрим на grouped barplot выживших в разрезе класса пассажира (столбцы выживших и погибших расположены рядом друг с другом, сгруппированы).

Теперь посмотрим на относительное количество выживших и погибших в каждом классе каюты с помощью stacked barplot.

Stacked (т.е. «поставленный один на другой», «нагроможденный») график как раз удобно использовать, когда нужно увидеть доли, занимаемые той или иной категорией.

Вопрос. Чем barplot отличается от countplot?

Ответ: говоря упрощенно, barplot можно построить если уже посчитана некоторая метрика в каждой категории (объем продаж в каждой категории или в каждом году, количество пациентов принимавших каждое из лекарств и т.п.), countplot же самостоятельно считает количество наблюдений в каждой категории и выводит столбчатую диаграмму.

Вопрос. Что такое plt.figure() и plt.axes() в Matplotlib?

Ответ: класс plt.figure() создает объект (обычно помещается в переменную fig), который позволяет управлять общем «полотном», на котором может располагаться один или несколько объектов класса plt.axes() (обычно помещаются в переменную ax).

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

Перейдем к изучению способов обработки данных. В первую очередь поговорим про ошибки в данных.


Ответы на вопросы

Вопрос. Зачем использовать Питон, в котором нужно прописывать элементы графиков руками, когда можно использовать инструменты BI, например Tableau?

Ответ. В моем понимании у них разное назначение. Tableau — инструмент для создания интерактивных графиков, которые удобно показывать бизнес-пользователям. Графические инструменты Питона чаще используются внутри ноутбука, например, как часть пайплайна по созданию модели ML.


Вопрос. Что такое грамматика графики?

Ответ. Грамматика графики (the grammar of graphics) — подход к построению визуализаций, который разбивает график на части подобно тому как это делает обычная грамматика с естественным языком. По большому счету, это попытка систематизировать графический способ передачи информации и сделать его более осмысленным.

На практике этот подход наилучшим образом реализован в библиотеке ggplot (эта библиотека есть в R и Питоне). Более подробно можно почитать вот в этой статье⧉.


Вопрос. Как посмотреть, какая версия библиотеки используется в Google Colab?

Ответ. Версию библиотеки можно посмотреть с помощью атрибута __version__.

Более полную информацию о библиотеке можно посмотреть с помощью команды show.

О программе pip мы уже говорили на прошлом курсе. В Google Colab к ней можно получить доступ через !pip.

Кроме этого, мы можем совместить три команды, list (выводит список библиотек), | (pipe operator, оператор объединения команд) и grep <название библиотеки> (ищет совпадения с передаваемой строкой), для поиска упоминания конкретной библиотеки и ее версии в общем списке.