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

Peer Review

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

Задача соревнования: по характеристикам человека и его заявки на медицинскую страховку предсказать степень риска, характерную для данной страховки.

Чем будем заниматься мы:

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

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

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

Часть кода, необходимого для построения графиков, уже написана; для другой части даны инструкции, по которым вам нужно написать скрипт; такие ячейки помечены # Код X, X - число. Кроме того, вам необходимо проанализировать графики, ответив на вопросы.

Внимание! Мы будем заниматься именно визуализацией - построение изображением. Пожалуйста, отвечайте на вопросы именно по изображениям. Если вы считаете, что картинка слишком маленькая, и пытаетесь найти ответ на вопрос другими способами, сопоставляйте его с изображением, потому что в рекомендациях к проверке именно такие ответы. Если вы считаете, что вопрос не корректен, пишите об этом в специальном треде "Peer Review по визуализации: формулировки вопросов." на форуме. Если вы (после проверки других работ) считаете, что рекомендация к проверке не корректна, пишите об этом в обратной связи к заданию.

Визуализацию в python часто делают с помощью библиотеки seaborn. Установить ее можно командой pip install seaborn. Если вы по каким-то причинам не можете установить библиотеку, вам придется строить некоторые графики самостоятельно или использовать их аналоги в pyplot. Pandas также использует seaborn, чтобы строить графики.


In [1]:
import numpy as np
import pandas
from matplotlib import pyplot as plt
import seaborn
%matplotlib inline

Считываем данные:


In [2]:
data = pandas.read_csv("train.csv", na_values="NaN")

In [3]:
data.head()


Out[3]:
Id Product_Info_1 Product_Info_2 Product_Info_3 Product_Info_4 Product_Info_5 Product_Info_6 Product_Info_7 Ins_Age Ht ... Medical_Keyword_40 Medical_Keyword_41 Medical_Keyword_42 Medical_Keyword_43 Medical_Keyword_44 Medical_Keyword_45 Medical_Keyword_46 Medical_Keyword_47 Medical_Keyword_48 Response
0 2 1 D3 10 0.076923 2 1 1 0.641791 0.581818 ... 0 0 0 0 0 0 0 0 0 8
1 5 1 A1 26 0.076923 2 3 1 0.059701 0.600000 ... 0 0 0 0 0 0 0 0 0 4
2 6 1 E1 26 0.076923 2 3 1 0.029851 0.745455 ... 0 0 0 0 0 0 0 0 0 8
3 7 1 D4 10 0.487179 2 3 1 0.164179 0.672727 ... 0 0 0 0 0 0 0 0 0 8
4 8 1 D2 26 0.230769 2 3 1 0.417910 0.654545 ... 0 0 0 0 0 0 0 0 0 8

5 rows × 128 columns

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

Создадим три списка признаков, соответствующие их группам: вещественные, целочисленные и категориальные (эти списки даны на странице соревнования). Уберем признак Id, так как он не несет смысловой нагрузки.

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


In [4]:
real_features = ["Product_Info_4", "Ins_Age", "Ht", "Wt", "BMI", "Employment_Info_1", "Employment_Info_4", "Employment_Info_6",
                 "Insurance_History_5", "Family_Hist_2", "Family_Hist_3", "Family_Hist_4", "Family_Hist_5"]
discrete_features = ["Medical_History_1", "Medical_History_10", "Medical_History_15", "Medical_History_24", "Medical_History_32"]
cat_features = data.columns.drop(real_features).drop(discrete_features).drop(["Id", "Response"]).tolist()

Первичный анализ

Есть набор стандартных приемов визуализации, которые нужно попробовать, когда вы начинаете работу с набором данных; к ним относятся построение гистограмм признаков (histogram, density estimation), л статистик, оценка зависимости целевого признака от остальных (boxplot, scatterplot, violinplot), визуализация пар признаков (как правило, scatterplot). Конкретный вид графика, который вам подходит, зависит от типа признаков, хороший обзор приведен в разделе Plotting functions туториала seaborn.

Сначала рассмотрим числовые признаки, затем - категориальные.

Выведем статистики вещественных и целочисленных признаков:

In [5]:
data[real_features].describe()


Out[5]:
Product_Info_4 Ins_Age Ht Wt BMI Employment_Info_1 Employment_Info_4 Employment_Info_6 Insurance_History_5 Family_Hist_2 Family_Hist_3 Family_Hist_4 Family_Hist_5
count 59381.000000 59381.000000 59381.000000 59381.000000 59381.000000 59362.000000 52602.000000 48527.000000 33985.000000 30725.000000 25140.000000 40197.000000 17570.000000
mean 0.328952 0.405567 0.707283 0.292587 0.469462 0.077582 0.006283 0.361469 0.001733 0.474550 0.497737 0.444890 0.484635
std 0.282562 0.197190 0.074239 0.089037 0.122213 0.082347 0.032816 0.349551 0.007338 0.154959 0.140187 0.163012 0.129200
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 0.076923 0.238806 0.654545 0.225941 0.385517 0.035000 0.000000 0.060000 0.000400 0.362319 0.401961 0.323944 0.401786
50% 0.230769 0.402985 0.709091 0.288703 0.451349 0.060000 0.000000 0.250000 0.000973 0.463768 0.519608 0.422535 0.508929
75% 0.487179 0.567164 0.763636 0.345188 0.532858 0.100000 0.000000 0.550000 0.002000 0.579710 0.598039 0.563380 0.580357
max 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 0.943662 1.000000

In [6]:
data[discrete_features].describe()


Out[6]:
Medical_History_1 Medical_History_10 Medical_History_15 Medical_History_24 Medical_History_32
count 50492.000000 557.000000 14785.000000 3801.000000 1107.000000
mean 7.962172 141.118492 123.760974 50.635622 11.965673
std 13.027697 107.759559 98.516206 78.149069 38.718774
min 0.000000 0.000000 0.000000 0.000000 0.000000
25% 2.000000 8.000000 17.000000 1.000000 0.000000
50% 4.000000 229.000000 117.000000 8.000000 0.000000
75% 9.000000 240.000000 240.000000 64.000000 2.000000
max 240.000000 240.000000 240.000000 240.000000 240.000000

In [7]:
data.shape


Out[7]:
(59381, 128)

Ответьте на вопросы (Блок 1):

  • Есть ли пропуски в данных? Перечислите вещественные и целочисленные признаки, которые известны для всех объектов.
    • Ответ: Да, у даных есть пропуски. Для вещественных: Product_Info_4, Ins_Age, Ht, Wt, BMI.
  • Перечислите вещественные и целочисленные признаки, значение которых известно менее, чем для половины объектов.
    • Ответ: Для целочисленных: Family_Hist_3, Family_Hist_5. Для целочисленных: Medical_History_10, Medical_History_15, Medical_History_24, Medical_History_32
  • Одинаков ли масштаб вещественных признаков? Масштаб целочисленных признаков? $^*$
    • Ответ: Маштаб вещественных и целочисленнных признаков одинаковый (если рассмотреть вещественные и целочисленные параметри отдельно).

$^*$ Будем считать, что масштаб двух признаков одинаков, если их минимумы отличаются не более, чем в 2 раза, и аналогично с максимумами.

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

Постройте гистограммы вещественных и целочисленных признаков.

Вместо того, чтобы в цикле по признакам строить отдельно каждую гистограмму, стоит воспользоваться методом hist датафрейма. Рекомендуется отдельно вывести гистограммы вещественных и целочисленных признаков. Установите размер изображения (20, 20) для первой группы признаков и (10, 10) для второй, bins=100.


In [8]:
# Код 1. Постройте гистограммы.
data[real_features].hist(figsize=(10, 10), bins=100)


Out[8]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x111d4d7b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1166c2a90>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1166688d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x116564fd0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x11650c710>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x11650c748>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x116475cc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1162aac50>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1162b2320>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1078f8978>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10795fc88>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1079b74e0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x112e727f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x112ecc358>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x112f32390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x112f8acf8>]], dtype=object)

In [9]:
data[discrete_features].hist(figsize=(20, 20), bins=100)


Out[9]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1164679e8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x113979b70>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1141dd828>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x11490b630>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x11508b358>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x11508bb70>]], dtype=object)

Ответьте на вопросы (Блок 2):

  • Есть ли константные признаки среди вещественных и целочисленных? Признак является константным, если множество его значений состоит из единственного элемента.
    • Ответ: Нет
  • Предположим, что мы составили выборку, в которую вошли все и только вещественные признаки. Можно ли ее описать многомерным нормальным распределением? $^*$
    • Ответ: Нет, потому что не все признаки описуются нормальным распределением (Employment_Info_1) и не все унимодальны (Ins_Age)

$^*$ Плотность многомерного нормального распределения во всех проекциях на отдельные переменные должна быть симметрична, куполообразна, а значит, унимодальна.

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

В целом данные разнородны, и описать их одним вероятностным распределением непросто.

Теперь визуализируем признаки попарно.

Построим scatterplot для пар вещественных признаков. Для этого в seaborn есть функция pairplot. Исключим признаки, распределение которых не похоже на колокол (хотя бы скошенный и неровный), тем самым мы уменьшим размер таблицы пар. На диагоналях таблицы будут стоять оценки распределений признаков (гистограммы или восстановленные плотности, diag_kind="hist" или "kde"). Если указать параметр hue = дискретный целевой признак, то разные его значения будут отображаться разными цветами.


In [10]:
seaborn.pairplot(data[real_features+["Response"]].drop(
        ["Employment_Info_4", "Employment_Info_6", "Insurance_History_5", "Product_Info_4"], axis=1), 
        hue="Response", diag_kind="kde")


/Users/alexkirnas/anaconda/lib/python3.6/site-packages/statsmodels/nonparametric/kde.py:454: RuntimeWarning: invalid value encountered in greater
  X = X[np.logical_and(X>clip[0], X<clip[1])] # won't work for two columns.
/Users/alexkirnas/anaconda/lib/python3.6/site-packages/statsmodels/nonparametric/kde.py:454: RuntimeWarning: invalid value encountered in less
  X = X[np.logical_and(X>clip[0], X<clip[1])] # won't work for two columns.
/Users/alexkirnas/anaconda/lib/python3.6/site-packages/statsmodels/nonparametric/kde.py:494: RuntimeWarning: invalid value encountered in true_divide
  binned = fast_linbin(X,a,b,gridsize)/(delta*nobs)
/Users/alexkirnas/anaconda/lib/python3.6/site-packages/statsmodels/nonparametric/kdetools.py:34: RuntimeWarning: invalid value encountered in double_scalars
  FAC1 = 2*(np.pi*bw/RANGE)**2
Out[10]:
<seaborn.axisgrid.PairGrid at 0x115dcdb38>