Актуальность темы
Категориальные данные имеет огромное значение в DataScience. Как справедливо заметили авторы в [1], мы живем в мире категорий: информация может быть сформирована в категориальном виде в самых различных областях - от диагноза болезни до результатов социологического опроса.
Частным случаем анализа категориальных данных является анализ таблиц сопряженности (contingency tables), в которые сводятся значения двух или более категориальных переменных.
Однако, прежде чем написать про статистический анализ таблиц сопряженности, остановимся на вопросах их визуализации. Казалось бы, об этом уже написано немало - есть статьи про графические возможности python, есть огромное количество информации и примеров с программным кодом. Однако, как всегда имеются нюансы - в процессе исследования возникают вопросы как с выбором средств визуализации, так и с настройкой инструментов python. В общем, есть о чем поговорить...
В данном обзоре мы рассмотрим следующие способы визуализации таблиц сопряженности:
Трехмерные гистограммы.
Мозаичные диаграммы.
Столбчатые диаграммы и графики взаимодействия частот.
Графики "тепловой карты" (heatmap).
Конечно, этот список не исчерпывающий (в этой области огромное поле деятельности - можно ознакомиться, например, здесь https://hr-portal.ru/statistica/gl7/gl7.php) - но мы остановимся на выбранных способах, ибо нельзя объять необъятное, а данный перечень охватывает достаточный набор инструментов для использования на практике. Каждый специалист волен сам выбирать инструменты для работы себе по душе.
Применение пользовательских функций
Как и в предыдущих обзорах, здесь будут использованы несколько пользовательских функций для решения разнообразных задач. Все эти функции созданы для облегчения работы и уменьшения размера программного кода. Данные функции загружается из пользовательского модуля my_module__stat.py, который доступен в моем репозитории на GitHub (https://github.com/AANazarov/MyModulePython).
Вот перечень данных функций:
df_detection_values - функция служит для первичной обработки пропусков в DataFrame: визуализирует пропуски на тепловой карте (heatmap) и определяет их количество.
Ряд пользовательских функций мы создаем в процессе данного обзора (они тоже включена в пользовательский модуль my_module__stat.py):
graph_contingency_tables_hist_3D - функция для визуализации категориальных данных: построение трехмерных гистограмм;
graph_contingency_tables_mosaicplot_sm - функция для визуализации категориальных данных: построение мозаичных диаграмм;
make_color_mosaicplot_dict - функция формирует словарь (dict) для задания цветовых свойств мозаичной диаграммы, является вспомогательной для функции graph_contingency_tables_mosaicplot_sm;
graph_contingency_tables_bar_freqint - функция для визуализации категориальных данных: построение столбчатых диаграмм и графиков взаимодействия частот;
graph_contingency_tables_heatmap - функция для визуализации категориальных данных: построение графика "тепловой карты".
Визуализация таблиц сопряжённости
Вначале мы сформируем пользовательские функции для визуализации, а затем рассмотрим их применение на примере.
Трехмерные гистограммы
Трехмерные гистограммы являются очень выигрышными в визуальном плане, но, на мой взгляд, уступают в аналитическом значении мозаичным или столбчатым диаграммам. Их реализации в python выполняется с помощью функции mpl_toolkits.mplot3d.axes3d.Axes3D.bar3d (https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.bar3d.html); для получения нормального визуального изображения требуется настройка разнообразных параметров (подписей меток осей, расстояния между метками и осями и пр.); подробнее можно также ознакомится здесь: https://matplotlib.org/stable/gallery/mplot3d/3d_bars.html, https://matplotlib.org/stable/gallery/mplot3d/hist3d.html#, https://coderslegacy.com/python/3d-bar-chart-matplotlib/, https://pythonprogramming.net/3d-bar-chart-matplotlib-tutorial/.
def graph_contingency_tables_hist_3D(
data_df_in: pd.core.frame.DataFrame = None,
data_XY_list_in: list = None,
title_figure = None, title_figure_fontsize = 14,
title_axes = None, title_axes_fontsize = 16,
rows_label = None, cols_label = None, vertical_label = None, label_fontsize = 14,
rows_ticklabels_list = None, cols_ticklabels_list = None,
tick_fontsize = 11, rows_tick_rotation = 0, cols_tick_rotation = 0,
legend = None, legend_fontsize = 14,
labelpad = 20,
color=None,
tight_layout=True,
graph_size = (297/INCH, 210/INCH),
file_name = None):
"""Функция для визуализации категориальных данных: построение трехмерных гистограмм
Args:
data_df_in (pd.core.frame.DataFrame, optional): Массив исходных данных (тип - dataframe). Defaults to None.
data_XY_list_in (list, optional): Массив исходных данных (тип - list). Defaults to None.
title_figure (_type_, optional): Заголовок рисунка (Figure). Defaults to None.
title_figure_fontsize (int, optional): Размер шрифта заголовка рисунка (Figure). Defaults to 14.
title_axes (_type_, optional): Заголовок области рисования (Axes). Defaults to None.
title_axes_fontsize (int, optional): Размер шрифта заголовка области рисования (Axes). Defaults to 16.
rows_label (_type_, optional): Подпись оси (по срокам). Defaults to None.
cols_label (_type_, optional): Подпись оси (по столбцам). Defaults to None.
vertical_label (_type_, optional): Подпись вертикальной оси. Defaults to None.
label_fontsize (int, optional): Размер шрифта подписей осей. Defaults to 14.
rows_ticklabels_list (_type_, optional): Список меток для оси (по строкам). Defaults to None.
cols_ticklabels_list (_type_, optional): Список меток для оси (по столбцам). Defaults to None.
tick_fontsize (int, optional): Размер шрифта меток осей. Defaults to 11.
rows_tick_rotation (int, optional): Угол поворота меток для оси (по строкам). Defaults to 0.
cols_tick_rotation (int, optional): Угол поворота меток для оси (по столбцам). Defaults to 0.
legend (_type_, optional): Текст легенды. Defaults to None.
legend_fontsize (int, optional): Размер шрифта легенды. Defaults to 14.
labelpad (int, optional): Расстояние между осью и метками. Defaults to 20.
color (_type_, optional): Цвет графика. Defaults to None.
tight_layout (bool, optional): Автоматическая настройка плотной компоновки графика (да/нет, True/False). Defaults to True.
graph_size (tuple, optional): Размера графика. Defaults to (297/INCH, 210/INCH).
file_name (_type_, optional): Имя файла для сохранения на диске. Defaults to None.
"""
# создание рисунка (Figure) и области рисования (Axes)
fig = plt.figure(figsize=graph_size)
ax = plt.axes(projection = "3d")
#ax = fig.gca(projection='3d')
fig.suptitle(title_figure, fontsize = title_figure_fontsize)
ax.set_title(title_axes, fontsize = title_axes_fontsize)
# данные для построения графика
if data_df_in is not None:
data = np.array(data_df_in)
NumOfCols = data_df_in.shape[1]
NumOfRows = data_df_in.shape[0]
else:
data = np.array(data_XY_list_in)
NumOfCols = np.shape(data)[1]
NumOfRows = np.shape(data)[0]
# координаты точки привязки столбцов
xpos = np.arange(0, NumOfCols, 1)
ypos = np.arange(0, NumOfRows, 1)
# формируем сетку координат
xpos, ypos = np.meshgrid(xpos + 0.5, ypos + 0.5)
xpos = xpos.flatten()
ypos = ypos.flatten()
# инициализируем для zpos нулевые значение, чтобы столбцы начинались с нуля
zpos = np.zeros(NumOfCols * NumOfRows)
# формируем ширину и глубину столбцов
dx = np.ones(NumOfRows * NumOfCols) * 0.5
dy = np.ones(NumOfCols * NumOfRows) * 0.5
# формируем высоту столбцов
dz = data.flatten()
# построение трехмерного графика
if not color:
ax.bar3d(xpos, ypos, zpos, dx, dy, dz)
else:
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=color)
# подписи осей
x_label = cols_label if cols_label else ''
y_label = rows_label if rows_label else ''
z_label = vertical_label if vertical_label else ''
ax.set_xlabel(x_label, fontsize = label_fontsize)
ax.set_ylabel(y_label, fontsize = label_fontsize)
ax.set_zlabel(z_label, fontsize = label_fontsize)
# метки осей
x_ticklabels_list = cols_ticklabels_list if cols_ticklabels_list \
else list(data_df.columns) if (data_df is not None) else ''
y_ticklabels_list = rows_ticklabels_list if rows_ticklabels_list \
else list(data_df.index) if data_df is not None else ''
# форматирование меток осей (https://matplotlib.org/stable/api/ticker_api.html)
ax.xaxis.set_major_locator(IndexLocator(1.0, 0.25))
ax.yaxis.set_major_locator(IndexLocator(1.0, 0.25))
# устанавливаем метки осей
ax.set_xticklabels(x_ticklabels_list, fontsize = tick_fontsize, rotation=rows_tick_rotation)
ax.set_yticklabels(y_ticklabels_list, fontsize = tick_fontsize, rotation=cols_tick_rotation)
# расстояние между подписями осей и метками осей
ax.xaxis.labelpad = labelpad
ax.yaxis.labelpad = labelpad
# легенда
if legend:
b1 = plt.Rectangle((0, 0), 1, 1)
ax.legend([b1], [legend], prop={'size': legend_fontsize})
# автоматическая настройка плотной компоновки графика
if tight_layout:
fig.tight_layout()
# вывод графика
plt.show()
if file_name:
fig.savefig(file_name, orientation = "portrait", dpi = 300)
return
Мозаичные диаграммы
Мозаичные диаграммы (диаграммы Маримекко) эффективны как в визуальном, так и в аналитическом плане. Их реализации в python выполняется с помощью функции statsmodels.graphics.mosaicplot.mosaic (https://www.statsmodels.org/dev/generated/statsmodels.graphics.mosaicplot.mosaic.html); для получения нормального визуального изображения требуется весьма своеобразная настройка параметров, в частности задание свойств цветов графика выполняется с помощью специального словаря (dict), для этого мы формируем пользовательскую функцию make_color_mosaicplot_dict.
Можно, конечно, сформировать мозаичную диаграмму из обычной столбчатой диаграммы (см., например, https://towardsdatascience.com/marimekko-charts-with-pythons-matplotlib-6b9784ae73a1), но здесь мы рассматривать этот способ не будем.
def graph_contingency_tables_mosaicplot_sm(
data_df_in: pd.core.frame.DataFrame = None,
data_XY_list_in: list = None,
properties: dict = {},
labelizer: bool = True,
title_figure = None, title_figure_fontsize = 12,
title_axes = None, title_axes_fontsize = 16,
x_label = None, y_label = None, label_fontsize = 14,
x_ticklabels_list = None, y_ticklabels_list = None,
x_ticklabels: bool = True, y_ticklabels: bool = True,
#tick_fontsize = 11,
tick_label_rotation = 0,
legend_list = None, legend_fontsize = 11,
text_fontsize = 16,
gap = 0.005,
horizontal: bool = True,
statistic: bool = True,
tight_layout=True,
graph_size = (297/INCH, 210/INCH),
file_name = None):
"""Функция для визуализации категориальных данных: построение мозаичных диаграмм
Args:
data_df_in (pd.core.frame.DataFrame, optional): Массив исходных данных (тип - DataFrame). Defaults to None.
data_XY_list_in (list, optional): Массив исходных данных (тип - list). Defaults to None.
properties (dict, optional): Функция возвращает словарь свойств плиток графика (цвет, штриховка и пр.). Defaults to {}.
labelizer (bool, optional): Функция генерирует текст для отображения в центре каждой плитки графика. Defaults to True.
title_figure (_type_, optional): Заголовок рисунка (Figure). Defaults to None.
title_figure_fontsize (int, optional): Размер шрифта заголовка рисунка (Figure). Defaults to 12.
title_axes (_type_, optional): Заголовок области рисования (Axes). Defaults to None.
title_axes_fontsize (int, optional): Размер шрифта заголовка области рисования (Axes). Defaults to 16.
x_label (_type_, optional): Подпись оси OX. Defaults to None.
y_label (_type_, optional): Подпись оси OY. Defaults to None.
label_fontsize (int, optional): Размер шрифта подписей. Defaults to 14.
x_ticklabels_list (_type_, optional): Список меток для оси OX. Defaults to None.
y_ticklabels_list (_type_, optional): Список меток для оси OY. Defaults to None.
x_ticklabels (bool, optional): Отображать на графике (да/нет, True/False) метки для оси OX. Defaults to True.
y_ticklabels (bool, optional): Отображать на графике (да/нет, True/False) метки для оси OY. Defaults to True.
tick_fontsize (int, optional): Временно заблокировано. Defaults to 11.
tick_label_rotation (int, optional): Угол поворота меток для оси. Defaults to 0.
legend_list (_type_, optional): Список названий категорий для легенды. Defaults to None.
legend_fontsize (int, optional): Размер шрифта легенды. Defaults to 11.
text_fontsize (int, optional): Размер шрифта подписей в центре плиток графика. Defaults to 16.
gap (float, optional): Список зазоров. Defaults to 0.005.
horizontal (bool, optional): Начальное направление разделения. Defaults to True.
statistic (bool, optional): Применять статистическую модель для придания цвета графику (да/нет, True/False). Defaults to True.
tight_layout (bool, optional): Автоматическая настройка плотной компоновки графика (да/нет, True/False). Defaults to True.
graph_size (tuple, optional): Размера графика. Defaults to (297/INCH, 210/INCH).
file_name (_type_, optional): Имя файла для сохранения на диске. Defaults to None.
"""
# создание рисунка (Figure) и области рисования (Axes)
fig, axes = plt.subplots(figsize=graph_size)
fig.suptitle(title_figure, fontsize = title_figure_fontsize)
axes.set_title(title_axes, fontsize = title_axes_fontsize)
# данные для построения графика
if data_df_in is not None:
data_df = data_df_in.copy()
if x_ticklabels_list:
data_df = data_df.set_index(pd.Index(x_ticklabels_list))
else:
data_df = pd.DataFrame(data_XY_list_in)
if x_ticklabels_list:
data_df = data_df.set_index(pd.Index(x_ticklabels_list))
if y_ticklabels_list:
data_df.columns = y_ticklabels_list
data_np = np.array(data_XY_list_in) if data_XY_list_in is not None \
else np.array(data_df_in)
# установка шрифта подписей в теле графика
if text_fontsize:
plt.rcParams["font.size"] = text_fontsize
# метки осей
if data_df is not None:
x_list = list(map(str, x_ticklabels_list)) if x_ticklabels_list \
else list(map(str, data_df.index))
y_list = list(map(str, y_ticklabels_list)) if y_ticklabels_list \
else list(map(str, data_df.columns))
else:
x_list = list(map(str, x_ticklabels_list)) if x_ticklabels_list \
else list(map(str, range(data_np.shape[0])))
y_list = list(map(str, y_ticklabels_list)) if y_ticklabels_list \
else list(map(str, range(data_np.shape[1])))
if not labelizer:
if not x_ticklabels:
axes.tick_params(axis='x', colors='white')
if not y_ticklabels:
axes.tick_params(axis='y', colors='white')
# подписи осей
x_label = x_label if x_label else ''
y_label = y_label if y_label else ''
axes.set_xlabel(x_label, fontsize = label_fontsize)
axes.set_ylabel(y_label, fontsize = label_fontsize)
# формируем словарь (dict) data
data_dict = {}
for i, x in enumerate(x_list):
for j, y in enumerate(y_list):
data_dict[(x, y)] = data_np[i, j]
print(f'data_dict = \n{data_dict}')
# формируем словарь (dict) labelizer и функцию labelizer_func
labelizer_dict = {}
for i, x in enumerate(x_list):
for j, y in enumerate(y_list):
labelizer_dict[(x, y)] = data_np[i, j] if labelizer else ''
labelizer_func = lambda k: labelizer_dict[k]
# построение графика
from statsmodels.graphics.mosaicplot import mosaic
mosaic(data_dict,
title=title_axes,
statistic=statistic,
ax=axes,
horizontal=horizontal,
gap=gap,
label_rotation=tick_label_rotation,
#axes_label=False,
labelizer=labelizer_func,
properties=properties)
# легенда
if legend_list:
axes.legend(legend_list,
bbox_to_anchor=(1, 0.5),
loc="center left",
#mode="expand",
ncol=1)
# автоматическая настройка плотной компоновки графика
if tight_layout:
fig.tight_layout()
# вывод графика
plt.show()
if file_name:
fig.savefig(file_name, orientation = "portrait", dpi = 300)
# возврат размера шрифта подписей в теле графика по умолчанию
if text_fontsize:
plt.rcParams["font.size"] = 10
return
Настройка цвета в мозаичных диаграммах
Настройку цвета в мозаичных диаграммах можно выполнить двумя способами:
С помощью функции - предпочтительно для таблиц 2х2.
С помощью словаря (dict).
Оба этих способа мы рассмотрим далее.
С помощью словаря можно задать индивидуально цвет каждой плитки диаграммы, или настроить цвет отдельных строк или столбцов. Для формирования словаря будем применять пользовательскую функцию make_color_mosaicplot_dict:
def make_color_mosaicplot_dict(
rows_list, cols_list,
props_dict_rows=None,
props_dict_cols=None):
"""Функция формирует словарь свойств плиток мозаичного графика (цвет, штриховка и пр.) для функции graph_contingency_tables_mosaicplot_sm
Args:
rows_list (_type_): Список категорий (по строкам)
cols_list (_type_): Список категорий (по столбцам)
props_dict_rows (_type_, optional): Словарь цветовых свойств категорий (по строкам). Defaults to None.
props_dict_cols (_type_, optional): Словарь цветовых свойств категорий (по столбцам). Defaults to None.
Returns:
_type_: словарь свойств плиток мозаичного графика (цвет, штриховка и пр.) для функции graph_contingency_tables_mosaicplot_sm
"""
result = {}
rows = list(map(str, rows_list))
cols = list(map(str, cols_list))
if props_dict_rows:
for col in cols:
for row in rows:
result[(col, row)] = {'facecolor': props_dict_rows[row]}
if props_dict_cols:
for col in cols:
for row in rows:
result[(col, row)] = {'facecolor': props_dict_cols[col]}
return result
Столбчатая диаграмма и графики взаимодействия частот
Старая добрая столбчатая диаграмма pandas.DataFrame.plot.bar (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.bar.html) будет более эффективной, если объединить график абсолютных частот, график относительных частот и график взаимодействия частот.
На графиках взаимодействия частот следует остановиться отдельно. Считается, что это способ экспресс-оценки значимости связей между категориальными переменными (чем больше наклон линий на графике, тем сильнее связь; горизонтальная линия означает полное отсутствие связи), но наиболее эффективным этот график будет для таблиц сопряженности 2х2 (подробнее об этом - см. например, [3], https://cyberleninka.ru/article/n/razrabotka-vizualnogo-metoda-issledovaniya-zavisimosti-kategorialnyh-peremennyh-na-osnove-tablits-sopryazhennosti/viewer). Разумеется, подтверждать оценку значимости нужно проверкой статистических гипотез.
def graph_contingency_tables_bar_freqint(
data_df_in: pd.core.frame.DataFrame = None,
data_XY_list_in: list = None,
graph_inclusion='arf',
title_figure=None, title_figure_fontsize=14, title_axes_fontsize=11,
x_label = None, y_label = None, label_fontsize = 11,
x_ticklabels_list = None, y_ticklabels_list = None, #tick_fontsize = 11,
color = None,
tight_layout=True,
result_output=False,
graph_size=None,
file_name=None):
"""Функция для визуализации категориальных данных: построение столбчатых диаграмм и графиков взаимодействия частот
Args:
data_df_in (pd.core.frame.DataFrame, optional): Массив исходных данных (тип - DataFrame). Defaults to None.
data_XY_list_in (list, optional): Массив исходных данных (тип - list). Defaults to None.
graph_inclusion (str, optional): Параметр, определяющий перечень графиков, которые строит функция:
'a' - столбчатая диаграмма (в абсолютных частотах)
'r' - столбчатая диаграмма (в относительных частотах)
'f' - график взаимодействия частот
Defaults to 'arf'.
title_figure (_type_, optional): Заголовок рисунка (Figure). Defaults to None.
title_figure_fontsize (int, optional): Размер шрифта заголовка рисунка (Figure). Defaults to 14.
title_axes_fontsize (int, optional): Размер шрифта заголовка области рисования (Axes). Defaults to 11.
x_label (_type_, optional): Подпись оси OX. Defaults to None.
y_label (_type_, optional): Подпись оси OY. Defaults to None.
label_fontsize (int, optional): Размер шрифта подписей по осям_. Defaults to 11.
x_ticklabels_list (_type_, optional): Список меток для оси OX. Defaults to None.
y_ticklabels_list (_type_, optional): Список меток для оси OY. Defaults to None.
tick_fontsize (int, optional): Временно заблокировано. Defaults to 11.
color (_type_, optional): Список, задающий цвета для категорий. Defaults to None.
tight_layout (bool, optional): Автоматическая настройка плотной компоновки графика (да/нет, True/False). Defaults to True.
result_output (bool, optional): Выводить таблицу (DataFrame) c числовыми данными (да/нетб True/False). Defaults to False.
graph_size (_type_, optional): Размера графика. Defaults to None.
file_name (_type_, optional): Имя файла для сохранения на диске. Defaults to None.
"""
# данные для построения графика
if data_df_in is not None:
data_df_abs = data_df_in.copy()
if x_ticklabels_list:
data_df_abs = data_df_abs.set_index(pd.Index(x_ticklabels_list))
if y_ticklabels_list:
data_df_abs.columns = y_ticklabels_list
else:
data_df_abs = pd.DataFrame(data_XY_list_in)
if x_ticklabels_list:
data_df_abs = data_df_abs.set_index(pd.Index(x_ticklabels_list))
if y_ticklabels_list:
data_df_abs.columns = y_ticklabels_list
data_df_rel = None
data_np = np.array(data_XY_list_in) if data_XY_list_in is not None \
else np.array(data_df_in)
# определение формы и размеров области рисования (Axes)
count_graph = len(graph_inclusion) # число графиков
ax_rows = 1
ax_cols = count_graph # размерность области рисования (Axes)
# создание рисунка (Figure) и области рисования (Axes)
graph_size_dict = {
1: (297/INCH*0.75, 210/INCH),
2: (297/INCH*1.5, 210/INCH),
3: (297/INCH*2.25, 210/INCH)}
if not(graph_size):
graph_size = graph_size_dict[count_graph]
fig = plt.figure(figsize=graph_size)
if count_graph == 3:
ax1 = plt.subplot(1,3,1)
ax2 = plt.subplot(1,3,2)
ax3 = plt.subplot(1,3,3)
elif count_graph == 2:
ax1 = plt.subplot(1,2,1)
ax2 = plt.subplot(1,2,2)
elif count_graph == 1:
ax1 = plt.subplot(1,1,1)
# заголовок рисунка (Figure)
fig.suptitle(title_figure, fontsize = title_figure_fontsize)
# столбчатая диаграмма (абсолютные частоты)
if 'a' in graph_inclusion:
if color:
data_df_abs.plot.bar(
color = color,
stacked=True,
rot=0,
legend=True,
ax=ax1)
else:
data_df_abs.plot.bar(
#color = color_list,
stacked=True,
rot=0,
legend=True,
ax=ax1)
ax1.legend(loc='best', fontsize = 12, title=data_df_abs.columns.name)
ax1.set_title('Absolute values', fontsize=title_axes_fontsize)
ax1.set_xlabel(x_label, fontsize = label_fontsize)
ax1.set_ylabel(y_label, fontsize = label_fontsize)
# столбчатая диаграмма (относительные частоты)
if 'r' in graph_inclusion:
data_df_rel = data_df_abs.copy()
sum_data = np.sum(data_np)
data_df_abs.sum(axis=1)
for col in data_df_rel.columns:
data_df_rel[col] = data_df_rel[col] / data_df_abs.sum(axis=1)
if color:
data_df_rel.plot.bar(
color = color,
stacked=True,
rot=0,
legend=True,
ax = ax1 if (graph_inclusion == 'r') or (graph_inclusion == 'rf') else ax2,
alpha = 0.5)
else:
data_df_rel.plot.bar(
#color = color,
stacked=True,
rot=0,
legend=True,
ax = ax1 if (graph_inclusion == 'r') or (graph_inclusion == 'rf') else ax2,
alpha = 0.5)
if (graph_inclusion == 'r') or (graph_inclusion == 'rf'):
ax1.legend(loc='best', fontsize = 12, title=data_df_abs.columns.name)
ax1.set_title('Relative values', fontsize=title_axes_fontsize)
ax1.set_xlabel(x_label, fontsize = label_fontsize)
ax1.set_ylabel(y_label, fontsize = label_fontsize)
else:
ax2.legend(loc='best', fontsize = 12, title=data_df_abs.columns.name)
ax2.set_title('Relative values', fontsize=title_axes_fontsize)
ax2.set_xlabel(x_label, fontsize = label_fontsize)
ax2.set_ylabel(y_label, fontsize = label_fontsize)
# график взаимодействия частот
if 'f' in graph_inclusion:
if color:
sns.lineplot(
data=data_df_abs,
palette = color,
dashes=False,
lw=3,
#markers=['o','o'],
markersize=10,
ax=ax1 if (graph_inclusion == 'f') else ax3 if (graph_inclusion == 'arf') else ax2)
else:
sns.lineplot(
data=data_df_abs,
#palette = color,
dashes=False,
lw=3,
#markers=['o','o'],
markersize=10,
ax=ax1 if (graph_inclusion == 'f') else ax3 if (graph_inclusion == 'arf') else ax2)
if (graph_inclusion == 'f'):
ax1.legend(loc='best', fontsize = 12, title=data_df_abs.columns.name)
ax1.set_title('Graph of frequency interactions', fontsize=title_axes_fontsize)
ax1.set_xlabel(x_label, fontsize = label_fontsize)
ax1.set_ylabel(y_label, fontsize = label_fontsize)
elif (graph_inclusion == 'arf'):
ax3.legend(loc='best', fontsize = 12, title=data_df_abs.columns.name)
ax3.set_title('Graph of frequency interactions', fontsize=title_axes_fontsize)
ax3.set_xlabel(x_label, fontsize = label_fontsize)
ax3.set_ylabel(y_label, fontsize = label_fontsize)
else:
ax2.legend(loc='best', fontsize = 12, title=data_df_abs.columns.name)
ax2.set_title('Graph of frequency interactions', fontsize=title_axes_fontsize)
ax2.set_xlabel(x_label, fontsize = label_fontsize)
ax2.set_ylabel(y_label, fontsize = label_fontsize)
# автоматическая настройка плотной компоновки графика
if tight_layout:
fig.tight_layout()
# вывод графика
plt.show()
if file_name:
fig.savefig(file_name, orientation = "portrait", dpi = 300)
# формирование и вывод результата
if result_output:
data_df_abs['sum'] = data_df_abs.sum(axis=1)
if data_df_rel is not None:
data_df_rel['sum'] = data_df_rel.sum(axis=1)
print('\nAbsolute values:')
display(data_df_abs)
print('\nRelative values:')
display(data_df_rel)
else:
print('\nAbsolute values:')
display(data_df_abs)
return
График «тепловой карты» (heatmap)
График "тепловой карты" (heatmap) весьма эффективен в визуальном и аналитическом плане, он реализуется в python с помощью функции seaborn.heatmap (https://seaborn.pydata.org/generated/seaborn.heatmap.html). В зависимости от особенностей исходных данных имеет смысл строить этот график либо для абсолютных, либо для относительных частот (долей); ну и для эффективной визуализации настроить цветовую шкалу (https://matplotlib.org/stable/tutorials/colors/colormaps.html).
def graph_contingency_tables_heatmap(
data_df_in: pd.core.frame.DataFrame = None,
data_XY_list_in: list = None,
title_figure = None, title_figure_fontsize = 12,
title_axes = None, title_axes_fontsize = 14,
x_label = None, y_label = None, #label_fontsize = 11,
x_ticklabels_list = None, y_ticklabels_list = None, #tick_fontsize = 11,
values_type = 'absolute',
color_map='binary',
robust = False,
fmt = '.0f',
tight_layout=True,
#result_output = False,
graph_size = (297/INCH/2, 210/INCH/2),
file_name = None):
"""Функция для визуализации категориальных данных: построение графика тепловой карты (heatmap)
Args:
data_df_in (pd.core.frame.DataFrame, optional): Массив исходных данных (тип - DataFrame). Defaults to None.
data_XY_list_in (list, optional): Массив исходных данных (тип - list). Defaults to None.
title_figure (_type_, optional): Заголовок рисунка (Figure). Defaults to None.
title_figure_fontsize (int, optional): Размер шрифта заголовка рисунка (Figure). Defaults to 12.
title_axes (_type_, optional): Заголовок области рисования (Axes). Defaults to None.
title_axes_fontsize (int, optional): Размер шрифта заголовка области рисования (Axes). Defaults to 14.
x_label (_type_, optional): Подпись оси OX. Defaults to None.
y_label (_type_, optional): Подпись оси OY. Defaults to None.
label_fontsize (int, optional): Временно заблокировано. Defaults to 11.
x_ticklabels_list (_type_, optional): Список меток для оси OX. Defaults to None.
y_ticklabels_list (_type_, optional): Список меток для оси OY. Defaults to None.
tick_fontsize (int, optional): Временно заблокировано. Defaults to 11.
values_type (str, optional): Параметр, задающий в каких частотах строится график:
абсолютные/относительные, absolute/relative.
Defaults to 'absolute'.
color_map (str, optional): Цветовая карта (colormap) для графика. Defaults to 'binary'.
robust (bool, optional): Если True и vmin или vmax отсутствуют, диапазон цветовой карты вычисляется
с надежными квантилями вместо экстремальных значений. Defaults to False.
fmt (str, optional): Числовой формат подписей в центре плиток графика. Defaults to '.0f'.
tight_layout (bool, optional): Автоматическая настройка плотной компоновки графика (да/нет, True/False). Defaults to True.
graph_size (tuple, optional): Размера графика. Defaults to (297/INCH/2, 210/INCH/2).
file_name (_type_, optional): Имя файла для сохранения на диске. Defaults to None.
"""
# создание рисунка (Figure) и области рисования (Axes)
fig, axes = plt.subplots(figsize=graph_size)
fig.suptitle(title_figure, fontsize = title_figure_fontsize)
axes.set_title(title_axes, fontsize = title_axes_fontsize)
# данные для построения графика
if data_df_in is not None:
data_df = data_df_in.copy()
if x_ticklabels_list:
data_df = data_df.set_index(pd.Index(x_ticklabels_list))
if y_ticklabels_list:
data_df.columns = y_ticklabels_list
else:
data_df = pd.DataFrame(data_XY_list_in)
if x_ticklabels_list:
data_df = data_df.set_index(pd.Index(x_ticklabels_list))
if y_ticklabels_list:
data_df.columns = y_ticklabels_list
data_np = np.array(data_XY_list_in) if data_XY_list_in is not None \
else np.array(data_df_in)
data_df_rel = None
if values_type == 'relative':
data_df_rel = data_df.copy()
sum_data = np.sum(data_np)
data_df.sum(axis=1)
for col in data_df_rel.columns:
data_df_rel[col] = data_df_rel[col] / sum_data
# построение графика
if values_type == "absolute":
if not robust:
sns.heatmap(data_df.transpose(),
#vmin=0, vmax=1,
linewidth=0.5,
cbar=True,
fmt=fmt,
annot=True,
cmap=color_map,
ax=axes)
else:
sns.heatmap(data_df.transpose(),
#vmin=0, vmax=1,
linewidth=0.5,
cbar=True,
robust=True,
fmt=fmt,
annot=True,
cmap=color_map,
ax=axes)
else:
if not robust:
sns.heatmap(data_df_rel.transpose(),
vmin=0, vmax=1,
linewidth=0.5,
cbar=True,
fmt=fmt,
annot=True,
cmap=color_map,
ax=axes)
else:
sns.heatmap(data_df_rel.transpose(),
vmin=0, vmax=1,
linewidth=0.5,
cbar=True,
robust=True,
fmt=fmt,
annot=True,
cmap=color_map,
ax=axes)
# автоматическая настройка плотной компоновки графика
if tight_layout:
fig.tight_layout()
# вывод графика
plt.show()
if file_name:
fig.savefig(file_name, orientation = "portrait", dpi = 300)
return
Примеры виртуализации таблиц сопряжённости
В качестве примера рассмотрим хорошо известную всем специалистам по DataScience задачу (известную, так сказать, "в узком кругу ограниченных людей"(С) ), а именно - задачу о "Титанике".
Замечу, что мы здесь рассматриваем датасет о "Титанике" просто как пример для визуализации таблиц сопряженности, цели решать эту задачу мы здесь не ставим, поэтому выполнять визуализацию многофакторных зависимостей (например, зависимость выживаемости от класса билета, возраста и пола пассажира) не будем. Это выходит за рамки данного обзора, так что ограничимся двухфакторными зависимостями.
Настройка заголовков отчета:
# Общий заголовок проекта
Task_Project = 'Titanic - Machine Learning from Disaster (https://www.kaggle.com/c/titanic)'
Подготовка исходных данных
Скачаем с сайта https://www.kaggle.com/c/titanic и загрузим исходные данные из csv-файлов:
тренировочный набор данных train.csv (содержит выборку пассажиров с известным исходом, т.е. выжил или нет);
набор данных для тестирования test.csv (содержит другую выборку пассажиров без зависимой переменной).
train_df = pd.read_csv('data/train.csv')
display(train_df)
#display(train_df.head(), train_df.tail())
train_df.info()
train_df.describe()
test_df = pd.read_csv('data/test.csv')
display(test_df)
#display(test_df.head(), test_df.tail())
test_df.info()
test_df.describe()
Создадим рабочие копии DataFrame:
dataset_train_df = train_df.copy()
dataset_test_df = test_df.copy()
Пример 1: визуализация состава и структуры совокупности пассажиров
Предположим, вначале мы хотим проанализировать состав и структуру совокупности пассажиров "Титаника".
Для этого объединим оба файла исходных данных в один датасет:
dataset_df = pd.concat([dataset_train_df, dataset_test_df], axis=0, ignore_index=True)
display(dataset_df)
#display(dataset_df.head(), dataset_df.tail())
dataset_df.info()
dataset_df.describe()
display(dataset_df['PassengerId'].nunique())
#display(dataset_df.describe(include = ['category']))
К слову, не всегда начинают исследование с такого анализа, наоборот, часто он выполняется как элемент дополнительного изучения отдельных закономерностей, вызвавших вопросы у исследователя. Например, несколько забегая вперед, далее при анализе факторов, влияющих на выживаемость пассажиров "Титаника", мы установим, что вероятность выжить несколько выше для пассажиров, взошедших на борт судна в порту Шербура (Cherbourg). Чтобы разобраться в причинах этого явления, придется проанализировать совокупность пассажиров в разрезе зависимости порта посадки и прочих факторов (класса билета, пола, возраста, наличия детей и т.д.). Но в данном обзоре, в целях визуализации, мы вначале все-таки остановимся на анализе состава и структуры изучаемого датасета.
Распределение пассажиров по классам билета (Pclass) и полу (Sex)
Первичная обработка и группировка данных
Проверим пропуски по полям Pclass и Sex с помощью графика "тепловой карты":
data_df = dataset_df.loc[:, ['Pclass', 'Sex']]
result_df, detection_values_df = df_detection_values(data_df, detection_values=[0, ' ', nan, None])
display(result_df)
Вывод: пропуски отсутствуют.
Группировка данных:
dataset_df_Pclass_Sex = dataset_df.pivot_table(
values='PassengerId',
index='Pclass',
columns='Sex',
aggfunc='count',
fill_value=0,
margins=True)
#display(dataset_df_Pclass_Sex)
print(dataset_df_Pclass_Sex)
Sex female male All
Pclass
1 144 179 323
2 106 171 277
3 216 493 709
All 466 843 1309
Визуализация с помощью трехмерной гистограммы и мозаичной диаграммы
dataset_df_sample = dataset_df_Pclass_Sex.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]-1]
title_axes = 'Визуализация состава и структуры совокупности пассажиров:\n распределение по классам билета (Pclass) и полу (Sex)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 16,
rows_label = 'Pclass',
cols_label = 'Sex',
vertical_label = 'Number of passengers',
#graph_size = (297/INCH*1.5, 210/INCH*1.5)
)
props_func = lambda key: {'color': 'grey' if 'male' in key else 'orange'}
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_func,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Pclass',
y_label = 'Sex',
#statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Вывод: доля мужчин среди пассажиров 3 класса выше, чем в 1 и 2 классе.
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
result_output=True,
tight_layout=False,
graph_size=(297/INCH*1.5, 210/INCH/1.25)
)
Вывод: график взаимодействия частот позволяет предположить наличие связи между признаками.
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 14,
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH/1.5, 210/INCH/1.5)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 14,
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH/1.5, 210/INCH/1.5)
)
Распределение пассажиров по классам билета (Pclass) и возрасту (Age)
Первичная обработка и группировка данных
Так как возраст (Age) в нашем датасете есть количественная категория, трансформируем его в качественную, используя следующую периодизацию:
ранний возраст (early age): до 3 лет;
раннее детство (early childhood): свыше 3 до 7 лет;
детство (childhood): свыше 7 до 13 лет;
юность (adolescence): свыше 13 до 21 года;
зрелость (maturity): свыше 21 до 55 лет;
преклонный возраст (advanced age): свыше 55 до 75 лет;
старость (old age): свыше 75 лет.
# функция для трансформации поля Age
def age_transform_func(age):
age_periods_dict = {
'early age': 3,
'early childhood': 7,
'childhood': 13,
'adolescence': 21,
'maturity': 55,
'advanced age': 75,
'old age': 130}
age_scale = list(age_periods_dict.values())
if not age or isnan(age):
result = None
else:
for i, elem in enumerate(age_scale):
if abs(age) <= elem:
result = list(age_periods_dict.keys())[i]
break
return result
# добавляем в датасет поле Age period
dataset_df['Age period'] = dataset_df['Age'].apply(age_transform_func)
display(dataset_df)
# сохраняем откорректированный датасет в формате Excel (может пригодиться)
dataset_df.to_excel('dataset_df.xlsx')
Проверим пропуски по полям Pclass и Age с помощью графика "тепловой карты":
data_df = dataset_df.loc[:, ['Pclass', 'Age', 'Age period']]
result_df, detection_values_df = df_detection_values(data_df, detection_values=[' ', 0, nan, None])
display(result_df)
Видим, что среди значений поля Age имеются пропуски, которые нужно исключить, чтобы они не исказили результаты анализа:
# формируем список строк, подлежащих удалению
drop_labels = []
for elem in detection_values_df.index:
if detection_values_df.loc[elem].any():
drop_labels.append(elem)
#display(drop_labels)
# удаляем строки
dataset_df_age = dataset_df.drop(index=drop_labels)
# проверяем результат удаления
data_df = dataset_df_age.loc[:, ['Pclass', 'Age', 'Age period']]
result_df, detection_values_df = df_detection_values(data_df, detection_values=[' ', 0, nan, None])
display(result_df)
Вывод: пропуски отсутствуют.
Группировка данных:
dataset_df_Pclass_AgePeriod = dataset_df_age.pivot_table(
values='PassengerId',
index='Pclass',
columns='Age period',
aggfunc='count',
fill_value=0,
margins=True)
#display(dataset_df_Pclass_AgePeriod)
print(dataset_df_Pclass_AgePeriod)
Age period adolescence advanced age childhood early age early childhood \
Pclass
1 25 37 2 2 2
2 38 12 7 13 5
3 128 8 24 26 18
All 191 57 33 41 25
Age period maturity old age All
Pclass
1 214 2 284
2 186 0 261
3 297 0 501
All 697 2 1046
Изменим порядок столбцов в DataFrame в соответствии с порядком увеличения возраста:
dataset_df_Pclass_AgePeriod = dataset_df_Pclass_AgePeriod.loc[:, ['early age', 'early childhood', 'childhood', 'adolescence', 'maturity', 'advanced age', 'old age']]
#display(dataset_df_Pclass_AgePeriod)
print(dataset_df_Pclass_AgePeriod)
Age period early age early childhood childhood adolescence maturity \
Pclass
1 2 2 2 25 214
2 13 5 7 38 186
3 26 18 24 128 297
All 41 25 33 191 697
Age period advanced age old age
Pclass
1 37 2
2 12 0
3 8 0
All 57 2
Визуализация с помощью трехмерной гистограммы
dataset_df_sample = dataset_df_Pclass_AgePeriod.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]]
title_axes = 'Визуализация состава и структуры совокупности пассажиров:\n распределение по классам билета (Pclass) и возрасту (Age period)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 16,
rows_label = 'Pclass',
cols_label = 'Age period',
vertical_label = 'Number of passengers',
graph_size = (420/INCH, 297/INCH)
)
Визуализация с помощью мозаичной диаграммы
Если мы построим мозаичную диаграмму с настройками по умолчанию, получится не очень визуально эстетическое изображение - из-за того,что в нашей таблице сопряженности имеются нулевые значения, которые "сбивают в кучу" метки осей и подписи в центре плиток графика:
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
#properties=props_func,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Pclass', y_label = 'Age period',
statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Поэтому, чтобы изображение было качественным, придется поработать с настройками функции graph_contingency_tables_mosaicplot_sm, а именно:
сформировать словарь props_dict со свойствами цветов для плиток графика: properties=props_dict; при этом цвета мы будем задавать из цветовой карты (colormap) tab10 (см. https://matplotlib.org/stable/tutorials/colors/colormaps.html);
отключить в функции подписи данных в центре плиток графика: labelizer=False;
отключить в функции подпись по вертикальной оси: y_ticklabels = False;
включить в функции отображение легенды: legend_list = legend_list.
Смысл настроек в том, что мы убираем на мозаичном графике весь текст, который "сбивается в кучу" (т.е. подписи по вертикальной оси и подписи в центре плиток графика), а вместо этого добавляем на графике легенду.
# формируем список цветов (из цветовой карты tab10, https://matplotlib.org/stable/tutorials/colors/colormaps.html)
legend_list = list(data_df.columns)
colors_number = len(legend_list)
color_list = []
for i in range(colors_number):
color_list.append(mpl.colors.to_hex(plt.cm.tab10.colors[i]))
print(f'color_list = {color_list}')
# задаем привязку цветов к категориям в виде словаря (dict)
color_dict = {}
for i in range(colors_number):
color_dict[legend_list[i]] = color_list[i]
print(f'props_dict_cols = {color_dict}')
# формируем словарь props_dict для реализации функции graph_contingency_tables_mosaicplot_sm
props_dict = make_color_mosaicplot_dict(
rows_list=legend_list,
cols_list=list(data_df.index),
props_dict_rows=color_dict)
print(f'\nprops_dict = {props_dict}')
# построение графика
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_dict,
labelizer=False,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Pclass', y_label = 'Age period',
y_ticklabels = False,
text_fontsize = 14,
gap = 0,
legend_list = legend_list,
statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
result_output=True,
tight_layout=False,
graph_size=(297/INCH*1.5, 210/INCH/1.25)
)
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 16,
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH, 210/INCH*1.25)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 16,
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH, 210/INCH*1.25)
)
Распределение пассажиров по классам билета (Pclass) и порту отправления (Embarked)
Первичная обработка и группировка данных
Проверим пропуски по полям Pclass и Embarked с помощью графика "тепловой карты" (в целях экономии места приводить проверку здесь не будем, процедура выполняется по аналогии с примерами рассмотренными выше, весь программный код доступен в моем репозитории на GitHub (https://github.com/AANazarov/Statistical-methods).
Группировка данных:
dataset_df_Pclass_Embarked = dataset_df_embarked.pivot_table(
values='PassengerId',
index='Pclass',
columns='Embarked',
aggfunc='count',
fill_value=0,
margins=True)
#display(dataset_df_Pclass_Embarked)
print(dataset_df_Pclass_Embarked)
Embarked C Q S All
Pclass
1 141 3 177 321
2 28 7 242 277
3 101 113 495 709
All 270 123 914 1307
Изменим порядок столбцов в DataFrame в соответствии с порядком посадки пассажиров в портах (Southampton, Cherbourg, Queenstown):
dataset_df_Pclass_Embarked = dataset_df_Pclass_Embarked.loc[:, ['S', 'C', 'Q']]
#display(dataset_df_Pclass_Embarked)
print(dataset_df_Pclass_Embarked)
Embarked S C Q
Pclass
1 177 141 3
2 242 28 7
3 495 101 113
All 914 270 123
Визуализация с помощью трехмерной гистограммы
dataset_df_sample = dataset_df_Pclass_Embarked.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]]
title_axes = 'Визуализация состава и структуры совокупности пассажиров:\n распределение по классам билета (Pclass) и порту отправления (Embarked)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 16,
rows_label = 'Pclass',
cols_label = 'Embarked',
cols_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
vertical_label = 'Number of passengers',
graph_size = (297/INCH*1.5, 210/INCH*1.5)
)
Визуализация с помощью мозаичной диаграммы
# формируем список цветов (из цветовой карты tab10, https://matplotlib.org/stable/tutorials/colors/colormaps.html)
#legend_list = list(data_df.columns)
legend_list = ['Southampton', 'Cherbourg', 'Queenstown']
colors_number = len(legend_list)
color_list = []
for i in range(colors_number):
color_list.append(mpl.colors.to_hex(plt.cm.tab10.colors[i]))
print(f'color_list = {color_list}')
# задаем привязку цветов к категориям в виде словаря (dict)
color_dict = {}
for i in range(colors_number):
color_dict[legend_list[i]] = color_list[i]
print(f'props_dict_cols = {color_dict}')
# формируем словарь props_dict для реализации функции graph_contingency_tables_mosaicplot_sm
props_dict = make_color_mosaicplot_dict(
rows_list=legend_list,
cols_list=list(data_df.index),
props_dict_rows=color_dict)
print(f'\nprops_dict = {props_dict}')
# построение графика
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_dict,
labelizer=False,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Pclass', y_label = 'Embarked',
y_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
#tick_fontsize = 11, tick_label_rotation = 45,
text_fontsize = 14,
gap = 0,
#legend_list = legend_list,
statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
y_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
result_output=True,
tight_layout=False,
graph_size=(297/INCH*1.5, 210/INCH/1.25)
)
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 16,
y_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH/1.5, 210/INCH/1.5)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 16,
y_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
Пример 2: визуализация влияния различных факторов на выживаемость пассажиров
Теперь проанализируем влияние различных факторов на выживаемость пассажиров.
dataset_df = dataset_train_df.copy()
#display(dataset_df)
display(dataset_df.head(3)), display(dataset_df.tail(3))
dataset_df.info()
Класс билета (Pclass)
Первичная обработка и группировка данных
Проверим пропуски по полям Pclass и Survived с помощью графика "тепловой карты" (в целях экономии места приводить проверку здесь не будем, процедура выполняется по аналогии с примерами рассмотренными выше, весь программный код доступен в моем репозитории на GitHub (https://github.com/AANazarov/Statistical-methods).
Группировка данных:
dataset_df_Survived_Pclass = dataset_df.pivot_table(
values='PassengerId',
index='Pclass',
columns='Survived',
aggfunc='count',
fill_value=0,
margins=True)
dataset_df_Survived_Pclass['Survival rate'] = dataset_df_Survived_Pclass[1] / dataset_df_Survived_Pclass['All']
#display(dataset_df_Survived_Pclass)
print(dataset_df_Survived_Pclass)
Survived 0 1 All Survival rate
Pclass
1 80 136 216 0.629630
2 97 87 184 0.472826
3 372 119 491 0.242363
All 549 342 891 0.383838
Визуализация с помощью трехмерной гистограммы
dataset_df_sample = dataset_df_Survived_Pclass.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]-2]
title_axes = 'Влияние различных факторов на выживаемость пассажиров (Survived):\n класс билета (Pclass)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 18,
rows_label = 'Pclass',
cols_label = 'Survived',
vertical_label = 'Number of passengers',
graph_size = (297/INCH*1.5, 210/INCH*1.5)
)
Визуализация с помощью мозаичной диаграммы
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]-2]
props_dict_rows = {'1': 'green',
'0': 'red'}
props_dict = make_color_mosaicplot_dict(
rows_list=data_df.columns,
cols_list=data_df.index,
props_dict_rows=props_dict_rows)
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_dict,
title_figure = Task_Project,
title_axes = title_axes,
x_label = 'Pclass',
y_label = 'Survived',
#label_fontsize = 14,
#statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Вывод: зависимость прослеживается, вероятность выжить для пассажиров 3 класса существенно ниже, чем для пассажиров 1 и 2 класса.
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
color = ['red', 'green'],
result_output=True,
tight_layout=False,
graph_size=(297/INCH*1.5, 210/INCH/1.25)
)
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 16,
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 16,
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
Пол (Sex)
Первичная обработка и группировка данных
Проверим пропуски по полям Sex и Survived с помощью графика "тепловой карты" (в целях экономии места приводить проверку здесь не будем, процедура выполняется по аналогии с примерами рассмотренными выше, весь программный код доступен в моем репозитории на GitHub (https://github.com/AANazarov/Statistical-methods).
Группировка данных:
dataset_df_Survived_Sex = dataset_df.pivot_table(
values='PassengerId',
index='Sex',
columns='Survived',
aggfunc='count',
fill_value=0,
margins=True)
dataset_df_Survived_Sex['Survival rate'] = dataset_df_Survived_Sex[1] / dataset_df_Survived_Sex['All']
#display(dataset_df_Survived_Sex)
print(dataset_df_Survived_Sex)
Survived 0 1 All Survival rate
Sex
female 81 233 314 0.742038
male 468 109 577 0.188908
All 549 342 891 0.383838
Визуализация с помощью трехмерной гистограммы
dataset_df_sample = dataset_df_Survived_Sex.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]-2]
title_axes = 'Влияние различных факторов на выживаемость пассажиров (Survived):\n пол пассажира (Sex)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 18,
rows_label = 'Sex',
cols_label = 'Survived',
vertical_label = 'Number of passengers',
graph_size = (297/INCH*1.5, 210/INCH*1.5))
Визуализация с помощью мозаичной диаграммы
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-1, :dataset_df_sample.shape[1]-2]
props_func = lambda key: {'color': 'green' if '1' in key else 'red'}
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_func,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Sex',
y_label = 'Survived',
#label_fontsize = 14,
#statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Вывод: зависимость прослеживается, вероятность выжить для мужчин существенно ниже, чем для женщин.
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
color = ['red', 'green'],
result_output=True,
tight_layout=False,
graph_size=(297/INCH*1.5, 210/INCH/1.25)
)
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 16,
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 16,
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
Возраст (Age)
Первичная обработка и группировка данных
По аналогии с предыдущим примером добавим в датасет качественную категорию - период возраста Age period:
# добавляем в датасет поле Age period
dataset_df['Age period'] = dataset_df['Age'].apply(age_transform_func)
display(dataset_df)
# сохраняем откорректированный датасет в формате Excel (может пригодиться)
dataset_df.to_excel('dataset_train_df.xlsx')
Проверим пропуски по полям Age и Survived с помощью графика "тепловой карты" (в целях экономии места приводить проверку здесь не будем, процедура выполняется по аналогии с примерами рассмотренными выше, весь программный код доступен в моем репозитории на GitHub (https://github.com/AANazarov/Statistical-methods).
Группировка данных:
dataset_df_Survived_AgePeriod = dataset_df_age.pivot_table(
values='PassengerId',
index='Age period',
columns='Survived',
aggfunc='count',
fill_value=0,
margins=True)
dataset_df_Survived_AgePeriod['Survival rate'] = dataset_df_Survived_AgePeriod[1] / dataset_df_Survived_AgePeriod['All']
#display(dataset_df_Survived_AgePeriod)
print(dataset_df_Survived_AgePeriod)
Survived 0 1 All Survival rate
Age period
adolescence 88 45 133 0.338346
advanced age 28 11 39 0.282051
childhood 13 8 21 0.380952
early age 10 20 30 0.666667
early childhood 6 14 20 0.700000
maturity 279 191 470 0.406383
old age 0 1 1 1.000000
All 424 290 714 0.406162
Изменим порядок строк в DataFrame в соответствии с порядком увеличения возраста:
dataset_df_Survived_AgePeriod = dataset_df_Survived_AgePeriod.loc[['early age', 'early childhood', 'childhood', 'adolescence', 'maturity', 'advanced age', 'old age']]
#display(dataset_df_Survived_AgePeriod)
print(dataset_df_Survived_AgePeriod)
Survived 0 1 All Survival rate
Age period
early age 10 20 30 0.666667
early childhood 6 14 20 0.700000
childhood 13 8 21 0.380952
adolescence 88 45 133 0.338346
maturity 279 191 470 0.406383
advanced age 28 11 39 0.282051
old age 0 1 1 1.000000
Визуализация с помощью трехмерной гистограммы
dataset_df_sample = dataset_df_Survived_AgePeriod.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0], :dataset_df_sample.shape[1]-2]
title_axes = 'Влияние различных факторов на выживаемость пассажиров (Survived):\n возраст (Age period)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 16,
rows_label = 'Age period',
cols_label = 'Survived',
vertical_label = 'Number of passengers',
graph_size = (297/INCH*1.5, 210/INCH*1.5)
)
Визуализация с помощью мозаичной диаграммы
Для задания цвета мозаичной диаграммы в случае таблиц сопряженности размерности Nх2 целесообразно использовать не словарь (dict), а менее громоздкий способ с помощью функции:
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0], :dataset_df_sample.shape[1]-2]
props_func = lambda key: {'color': 'green' if '1' in key else 'red'}
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_func,
labelizer = False,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Age period',
y_label = 'Survived',
#label_fontsize = 14,
tick_label_rotation = [90, 0],
#statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
color = ['red', 'green'],
result_output=True,
#tight_layout=False,
#graph_size=(297/INCH*1.5, 210/INCH/1.1)
)
Вывод: зависимость прослеживается, вероятность выжить отличается для разных возрастных категорий.
Обратите внимание, что для формирования компактного расположения столбцов на графике с относительно длинными подписями отключаем настройку tight_layout=False; либо, если надписи кажутся слишком мелкими. можно вывести графики по отдельности:
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='ar',
title_figure = title_axes, title_figure_fontsize = 16,
color = ['red', 'green'],
#result_output=True,
#tight_layout=False,
#graph_size=(297/INCH*1.5, 210/INCH/1.1)
)
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 16,
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH, 210/INCH/1.25)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 16,
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH, 210/INCH/1.25)
)
Порт отправления (Embarked)
Первичная обработка и группировка данных
Проверим пропуски по полям Embarked и Survived с помощью графика "тепловой карты" (в целях экономии места приводить проверку здесь не будем, процедура выполняется по аналогии с примерами рассмотренными выше, весь программный код доступен в моем репозитории на GitHub (https://github.com/AANazarov/Statistical-methods).
Группировка данных:
dataset_df_Survived_Embarked = dataset_df_embarked.pivot_table(
values='PassengerId',
index='Embarked',
columns='Survived',
aggfunc='count',
fill_value=0,
margins=True)
dataset_df_Survived_Embarked['Survival rate'] = dataset_df_Survived_Embarked[1] / dataset_df_Survived_Embarked['All']
#display(dataset_df_Survived_Embarked)
print(dataset_df_Survived_Embarked)
Survived 0 1 All Survival rate
Embarked
C 75 93 168 0.553571
Q 47 30 77 0.389610
S 427 217 644 0.336957
All 549 340 889 0.382452
Изменим порядок строк в DataFrame в соответствии с порядком посадки пассажиров в портах (Southampton, Cherbourg, Queenstown):
dataset_df_Survived_Embarked = dataset_df_Survived_Embarked.loc[['S', 'C', 'Q']]
#display(dataset_df_Survived_Embarked)
print(dataset_df_Survived_Embarked)
Survived 0 1 All Survival rate
Embarked
S 427 217 644 0.336957
C 75 93 168 0.553571
Q 47 30 77 0.389610
Визуализация с помощью трехмерной гистограммы
dataset_df_sample = dataset_df_Survived_Embarked.copy()
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-0, :dataset_df_sample.shape[1]-2]
title_axes = 'Влияние различных факторов на выживаемость пассажиров (Survived):\n порт отправления (Embarked)'
graph_contingency_tables_hist_3D(data_df,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes, title_axes_fontsize = 18,
rows_label = 'Embarked',
cols_label = 'Survived',
rows_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
vertical_label = 'Number of passengers',
graph_size = (297/INCH*1.5, 210/INCH*1.5))
Визуализация с помощью мозаичной диаграммы
data_df = dataset_df_sample.iloc[:dataset_df_sample.shape[0]-0, :dataset_df_sample.shape[1]-2]
props_func = lambda key: {'color': 'green' if '1' in key else 'red'}
graph_contingency_tables_mosaicplot_sm(
data_df_in=data_df,
properties=props_func,
title_figure = Task_Project, title_figure_fontsize = 12,
title_axes = title_axes,
x_label = 'Embarked',
y_label = 'Survived',
x_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
#label_fontsize = 14,
#statistic = False,
#graph_size = (297/INCH, 210/INCH)
)
Визуализация с помощью столбчатой диаграммы и графика взаимодействия частот
graph_contingency_tables_bar_freqint(
data_df_in=data_df,
graph_inclusion='arf',
title_figure = title_axes, title_figure_fontsize = 16,
color = ['red', 'green'],
result_output=True,
tight_layout=False,
x_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
graph_size=(297/INCH*1.5, 210/INCH/1.25)
)
Вывод: графический анализ позволяет выявить аномалию: вероятность выжить существенно выше для пассажиров, взошедших на борт судна в Шербуре (Cherbourg). Случайно ли это? Проведенный выше анализ установил, что в Шербуре существенно выше доля пассажиров 1 класса, так что объяснение этой аномалии имеется. Разумеется такие выводы нужно подтверждать проверкой соответствующих гипотез (но это выходит за рамки данной статьи).
Визуализация с помощью графика "тепловой карты" (heatmap)
Формируем графики абсолютных и относительных (доли каждой категории в общем объеме совокупности) частот:
# абсолютные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(абсолютные частоты)', title_axes_fontsize = 16,
x_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
#values_type = 'absolute',
color_map='YlGn',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
# относительные частоты
graph_contingency_tables_heatmap(
data_df_in=data_df,
title_figure = Task_Project, #title_figure_fontsize = 14,
title_axes = title_axes + '\n(относительные частоты)', title_axes_fontsize = 16,
x_ticklabels_list = ['Southampton', 'Cherbourg', 'Queenstown'],
values_type = 'relative',
#color_map='YlGn',
fmt = '.4f',
graph_size=(297/INCH/1.25, 210/INCH/1.25)
)
Небольшой offtop: визуализация многофакторных категориальных связей
Сложно удержаться от небольшого offtop'а...
Итак, мы рассмотрели визуализацию двухфакторных зависимостей категориальных переменных. Из нашего датасета мы взяли всего 4 факторных признака (Pclass, Sex, Age, Embarked) и проанализировали их влияние на один результативный признак (Survived) и частично - их влияние друг на друга, при этом отчет получился довольно объемный. Что же будет, если число факторов составит 10, 20 и более? И это все лишь на стадии разведочного анализа данных (EDA).
Очевидно, что необходим инструмент визуализации, который позволит сразу "окинуть взглядом" всю совокупность данных, получить представление о взаимосвязях в ней, выделить наиболее существенные связи и, при необходимости, подвергнуть их более тщательному исследованию. Такой инструмент есть - это график "тепловой карты" (heatmap). Однако, для его реализации необходимо выбрать показатель тесноты взаимосвязи между категориальными переменными. Таких показателей известно немало (наиболее распространенный - критерий хи-квадрат и показатели на его основе), однако в данном обзоре мы не рассматриваем особенности их расчета. Поэтому слегка забегая вперед, рассмотрим пример построения графика "тепловой карты" (heatmap) с использованием коэффициента сопряженности Крамера (Cramer's V) (подробнее об этом коэффициенте - см., например, [4, с.746]).
Данный коэффициент характеризует тесноту связи между категориальными переменными и принимает значения в интервале . Для его расчета python предлагает нам использовать стандартную функцию scipy.stats.contingency.association (https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.contingency.association.html).
Тесноту связи можно оценивать с использованием шкалы Rea&Parker - см.[5, с.44]:
Значение |
Сила взаимосвязи |
---|---|
несущественная (unessential) |
|
слабая (weak) |
|
средняя (middle) |
|
относительно сильная (relatively strong) |
|
сильная (strong) |
|
очень сильная (very strong) |
Первичная обработка данных
Проверим пропуски по выбранным полям (Pclass, Sex, Age period, Embarked, Survived) (в целях экономии места приводить проверку здесь не будем, процедура выполняется по аналогии с примерами рассмотренными выше, весь программный код доступен в моем репозитории на GitHub (https://github.com/AANazarov/Statistical-methods).
Визуализация с помощью графика "тепловой карты" (heatmap)
# формируем матрицу коэффициентов сопряженности Крамера
corr_matrix = np.eye(len(variable_list))
for i, elem_i in enumerate(variable_list):
for j, elem_j in enumerate(variable_list):
if j>i:
temp_df = dataset_df_drop.pivot_table(
values='PassengerId',
index=elem_i,
columns=elem_j,
aggfunc='count',
fill_value=0)
corr_matrix[i, j] = sci.stats.contingency.association(temp_df, method='cramer', correction='False')
corr_matrix[j, i] = corr_matrix[i, j]
print(corr_matrix)
# построение графика
fig, axes = plt.subplots(figsize=(297/INCH, 210/INCH))
fig.suptitle(Task_Project, fontsize = 12)
title_axes = 'Визуализация тесноты связи между различными факторами\n(по коэффициенту сопряженности Крамера V)'
axes.set_title(title_axes, fontsize = 16)
sns.heatmap(corr_matrix,
vmin=0, vmax=1,
cbar=True,
#center=True,
annot=True,
cmap='Reds',
fmt = '.4f',
xticklabels=variable_list,
yticklabels=variable_list)
plt.show()
Конечно, данный график можно и нужно совершенствовать: добавить другие меры связи, добавить на график расчетное значение доверительной вероятности для проверки значимости меры связи, написать пользовательскую функцию и т.д., но это все в дальнейшем, а пока ограничимся тем, что есть.
Сделаем ряд выводов на основании графика "тепловой карты".
На первый взгляд видим, что результативный признак (Survived) имеет наиболее тесную связь с двумя из факторных признаков - Pclass (V=0.3577) и Sex (V=0.5338), что в общем-то вполне логично; теснота связи с оставшимися факторами (Age period и Embarked) является слабой.
Значит ли это, что мы должны исключить из рассмотрения оставшиеся факторы (Age period и Embarked)? Конечно, нет. На одной из графиков, представленных выше, мы наблюдаем зависимость доли погибших (т.е. вероятности гибели) от возраста (Age period):
для детей (до 7 лет) вероятность гибели составляет ~0.3;
для пассажиров возрастом старше 7 до 55 лет вероятность гибели составляет ~0.6;
для пассажиров преклонного возраста (55-75 лет) вероятность гибели увеличивается до ~0.7.
Очевидно, что зависимость есть, но коэффициент V не в состоянии выявить такие зависимости. Требуется более глубокий анализ, с применением развернутой системы показателей, и проверкой их значимости (но это выходит за пределы данного обзора).
Аналогично, график тепловой карты показывает нам, что связи факторных признаков между собой также являются слабыми, то есть по аналогии с числовыми переменными можно сказать, что мультиколлинеарность не выявлена. Так ли это в действительности? Например, связь между факторами Embarked и Survived является слабой (V=0.1980). Выше мы установили, что для пассажиров, взошедших на судно в Шербуре (Cherbourg), вероятность выжить выше по сравнению с остальными портами посадки, в то же время в Шербуре существенно выше доля пассажиров 1 класса, у которых гораздо больше шансов спастись. А связь между факторами Embarked и Pclass также характеризуется всего лишь V=0.2558.
Очевидно, что и зависимости между факторными признаками также присутствуют, но с помощью коэффициента V выявить их не всегда возможно.
Все это еще раз подтверждает то, что разведочный анализ данных (EDA) - это сложный творческий процесс, требующий внимательного подхода к данным, тщательного подбора применяемых методов анализа и показателей. На этом этапе вредным будет как излишний формализм, так и принятие решений "на глазок" - нужно найти золотую середину.
Кстати, необходимо обратить внимание - если предварительно не исключить из совокупности пропущенные значения, то получим результат, отличающийся от достигнутого ранее:
# формируем матрицу коэффициентов сопряженности Крамера
corr_matrix = np.eye(len(variable_list))
for i, elem_i in enumerate(variable_list):
for j, elem_j in enumerate(variable_list):
if j>i:
temp_df = dataset_df.pivot_table(
values='PassengerId',
index=elem_i,
columns=elem_j,
aggfunc='count',
fill_value=0)
corr_matrix[i, j] = sci.stats.contingency.association(temp_df, method='cramer', correction='False')
corr_matrix[j, i] = corr_matrix[i, j]
print(corr_matrix)
# построение графика
fig, axes = plt.subplots(figsize=(297/INCH, 210/INCH))
fig.suptitle(Task_Project, fontsize = 12)
title_axes = 'Визуализация тесноты связи между различными факторами\n(по коэффициенту сопряженности Крамера V)\n(без предварительной очистки данных от пропущенных значений)'
axes.set_title(title_axes, fontsize = 13)
sns.heatmap(corr_matrix,
vmin=0, vmax=1,
cbar=True,
#center=True,
annot=True,
cmap='Reds',
fmt = '.4f',
xticklabels=variable_list,
yticklabels=variable_list)
plt.show()
Отличия незначительные, но тем не менее они есть. Это напоминает нам, как важно не забывать про первичную обработку данных перед анализом. Будьте бдительны, товарищи!
ИТОГИ
Итак, подведем итоги:
мы рассмотрели возможности python для визуализации таблиц сопряженности, что является важным элементом разведочного анализа данных (EDA) в DataScience;
предложили ряд пользовательских функций для визуализации таблиц сопряженности, что позволит облегчить работу исследователя и уменьшить размер программного кода.
Само собой, мы рассмотрели далеко не все задачи в области визуализации таблиц сопряженности, однако, мы сформировали некий минимально необходимый набор инструментов, используя которые, можем двигаться дальше и переходить собственно к математической обработке категориальных данных, что и будет целью последующих статей.
Исходный код находится в моем репозитории на GitHub
Надеюсь, данный обзор поможет специалистам DataScience в работе.
ЛИТЕРАТУРА
C.R. Bilder and T.M. Loughin. Analysis of Categorical Data with R. (2014). Boca Raton: Chapman & Hall/CRC Press. 547 pp.
A.Agresti. Categorical data analysis (2013). 3rd Edition, John Wiley & Sons, Inc., New Jersey, xvi + 714 pp.
Бакаева О.А. Разработка визуального метода исследования зависимости категориальных переменных на основе таблиц сопряженности. - Образовательные ресурсы и технологии. 2014’1(4). - с.270-275. https://cyberleninka.ru/article/n/razrabotka-vizualnogo-metoda-issledovaniya-zavisimosti-kategorialnyh-peremennyh-na-osnove-tablits-sopryazhennosti/viewer.
Кендалл М., Стюарт А. Статистические выводы и связи / пер.с англ. - М.: Гл.ред.физ.-мат.лит. изд-ва "Наука", 1973. - 899 с.
Гржибовский А.М., Унгуряну Т.Н. Анализ биомедицинских данных с использованием пакета статистических программ SPSS. - Архангельск: Изд-во Северного государственного медицинского университета, 2017. – 293 с.