Визуализация¶
В модуле собраны скрипты для визуализации данных. В основном они полезны для анализа задачи бинарной классификации.
Для начала сгенерируем игрушечный пример.
In [1]:
from sklearn.datasets import make_classification
import pandas as pd
import numpy as np
np.random.seed(42)
N=10000
X, y = make_classification(n_features=4, n_samples=N)
df = pd.DataFrame(X, columns=['feature_%d' % i for i in range(X.shape[1])])
df['y'] = y
df['sample_date'] = np.random.choice(pd.date_range('2025-01-01', '2025-12-31'), N)
df['category_feature'] = np.random.choice(['foo', 'bar', np.nan], N)
df.head(2)
Out[1]:
feature_0 | feature_1 | feature_2 | feature_3 | y | sample_date | category_feature | |
---|---|---|---|---|---|---|---|
0 | 1.522650 | -0.934560 | -0.465022 | 0.058874 | 0 | 2025-09-20 | bar |
1 | 1.048103 | -0.746806 | 0.436853 | 0.859628 | 1 | 2025-07-15 | nan |
В игрушечной выборке содержатся:
- несколько признаков
feature_*
- бинарная целевая переменная -
y
- поле с датой -
sample_date
- категорияльное поле
category_feature
Для работы скриптов потребуется пакет holoviews. Большинство скриптов - функции, возвращающие одну картинку, а точнее Overlay с различными точками, линиями, …
In [2]:
import holoviews as hv
hv.extension('matplotlib')
Проилюстируем работу некоторых функций
In [3]:
from risksutils.visualization import woe_line, woe_stab, cross_tab, isotonic, distribution
woe_line¶
In [4]:
woe_line(df=df, feature='feature_2', target='y', num_buck=30)
Out[4]:
Данная функция разбивает числовой признак feature
на num_buck
бакетов. И в каждом бакете считается
\(\text{Weight of Evidence} = \ln\left(\frac{\text{доля 1 в бакте}}{\text{доля 0 в бакете}}\right) - \ln\left(\frac{\text{доля 1 во всей выборке}}{\text{доля 0 во всей выборке}}\right)\).
Если в бакете доля объектов класса 1 совпадает с долей 1 во всей выборке, то \(WoE = 0\). Если в бакете присутствуют только объекты одного класса, то WoE будет равно бесконечности – из-за взятия логарифма. В данной функции доля объектов каждого класса ограничивается 0.001 - снизу и 0.999 - сверху.
На самом графике woe_line показана зависимость WoE в
бакете от среднего значения признака feature
в нем.
На примере графика выше можно сказать, что среди объектов со значением feature_2 > 1 гораздо чаще присутствует 1.
isotonic¶
In [5]:
isotonic(df=df, predict='feature_2', target='y')
Out[5]:
График isotonic похож на график woe_line - так же отображается зависимость частоты объектов класса 1 от значений признака, только явная разбивка на бакеты отсутствует. Построение зависимости основано на Isotonic Regression, которая восстанавливает монотонную зависимость.
Использовать isotonic совместно с доверительными интервалами удобно для проверки совпадения прогноза с фактическими данными. Так, как обычно, предполагается монотонное влияние прогноза на целевую переменную.
distribution¶
In [6]:
distribution(df=df, feature='feature_2', date='sample_date', num_buck=4)
Out[6]:
Данная диаграмма отражает изменение распределений признака feature
во времени date
. Признак дискретезируется разбивкой на бакеты. Затем
выборка разбивается на группы (по умолчанию на месяца – параметр
date_freq
), и в каждой группе считается доля объектов из каждого
бакета. По данному графику distribution удобно
обнаруживать изменения в расчете признака во времени.
woe_stab¶
In [7]:
woe_stab(df=df, feature='feature_2', target='y', date='sample_date', num_buck=3)
Out[7]:
На данном графике отображается изменение влияния признака feature
на
целевую переменную target
во времени date
. Для этого признак
разбивается на бакеты и для каждой временной группы считаются значения
WoE.
В данном игрушечном примере видно, что влияние feature_2
на y
стабильно по времени. Это и должно быть, так как мы сгенерировали поле
sample_date
случайно и независимо от остальной выборки.
cross_tab¶
In [8]:
cross_tab(df, 'feature_2', 'category_feature', 'y', num_buck1=3, num_buck2=3)
Out[8]:
category_feature | bar | foo | nan | All |
---|---|---|---|---|
feature_2 | ||||
[-4.964; -0.838] | 3.97% | 4.49% | 4.06% | 4.17% |
[-0.837; 0.637] | 59.96% | 59.53% | 57.92% | 59.14% |
[0.637; 5.5] | 86.80% | 87.02% | 86.18% | 86.68% |
All | 50.16% | 51.13% | 48.67% | 50.00% |
category_feature | bar | foo | nan | All |
---|---|---|---|---|
feature_2 | ||||
[-4.964; -0.838] | 1134 | 1091 | 1108 | 3333 |
[-0.837; 0.637] | 1099 | 1117 | 1117 | 3333 |
[0.637; 5.5] | 1136 | 1156 | 1042 | 3334 |
All | 3369 | 3364 | 3267 | 10000 |
Данный скрипт отличается тем, что возвращает не объект holoviews, а набор pandas.dataframe, а точнее набор Styler - dataframe c визуальными настройками.
В cross_tab визуализируется совместное влияние пары признаков на целевую переменную (аналогично pandas.crosstab). Каждый признак разбивается на бакеты и считается доля объектов класса 1 в каждой комбинации пары бакетов - первая таблица. А так же считается общее количество объектов - вторая таблица. Вместе с этим выводятся агрегированные статистики - последние строка и столбец.
Настройка графиков¶
Для придания графикам различных свойств используются насторойки holoview. Каждый тип графика состоит из набора базовых диаграмм, например, woe_line - это наложенные (Overlay) друг на друга диаграммы:
- Диаграмма рассеивания (Scatter) со значениями WoE;
- Диаграмма ошибок (ErrorBars) со значениями доверительный интервалов для WoE;
- Линия (Curve) с реультатом зависимости целевой переменной от признака из логистической регрессии.
Для того, чтобы вывести структуру диаграммы нужно вызвать print
от
неё.
In [9]:
diagram = woe_line(df=df, feature='feature_2', target='y', num_buck=30)
print(diagram)
:Overlay
.Weight_of_evidence.I :Scatter [feature_2] (woe)
.Confident_Intervals.I :ErrorBars [feature_2] (woe,woe_u,woe_b)
.Logistic_interpolations.I :Curve [feature_2] (logreg)
В выводе присутствуют в дополнение пользовательские названия диаграмм, например, «Confident_Intervals» для ErrorBars, а так же названия осей.
Для настройки графиков можно воспользоваться магической командой
%%opts
.
In [10]:
%%opts Curve [xrotation=45 yaxis=None] (color='red') Scatter (marker='s' s=100)
diagram
Out[10]:
Синтаксис команды следующий:
%%opts Diagram [plotting options] (style options) {normalization}
:
plotting options
(те, что в квадратных скобках) отвечают за функцианальное наполнение графиков, например, опциейxrotation=45
мы повернули подписи у оси x на 45 градусов, а за счетyaxis=None
убрали ось y. Заметим, что эти настройки были применены к типу Curve, но повлияли на всю диаграмму.style options
(в круглых скобках) изменяют визуальное оформление диаграмм. С помощьюcolor='red'
поменялся цвет у Curve, а с помощьюmarker='s' s=100
мы сделали у Scatter маркеры в виде квадратов (squere) и размера 100.normalization
(в фигурных скобках) отвечает за связь разных диаграмм между собой. Далее мы рассмотрим пример.
В jupyter notebook-ах для настроек работает автодополнение, например:
%%opts C<TAB>
выдаст подсказкиCollator
,Contours
,Curve
;%%opts Curve [xaxis=None sh<TAB>
выдаетshow_frame=
,show_grid=
, … .
Для более подробного описания настроек можно вызвать справку, например,
hv.help(hv.Overlay)
, а так же посмотреть примеры из документации.
Помимо настройки %%opts
(с двумя процентами) так же есть и настройка
%opts
с одним они различаются следущим:
%%opts
- локальные настройки, применяются ко всем диаграммам, созданным в данной ячейке;%opts
- глобальные настройки, применяется ко всем диагрммам в ноутбуке.
In [11]:
simple_curve = hv.Curve([1, 3, 2, 4])
diagram + simple_curve
Out[11]:
В примере выше я вывел вместе две диаграммы: diagram
и созданную
Curve
, при этом настройки у diagram
сохранились, так как мы их
применили ячейкой выше, а у новой диаграммы они остались прежними. Если
мы теперь захотим поменять настройки только у одной кривой из двух,
можно воспользоваться её именем.
In [12]:
print(diagram + simple_curve)
:Layout
.Woe_line.Feature_2 :Overlay
.Weight_of_evidence.I :Scatter [feature_2] (woe)
.Confident_Intervals.I :ErrorBars [feature_2] (woe,woe_u,woe_b)
.Logistic_interpolations.I :Curve [feature_2] (logreg)
.Curve.I :Curve [x] (y)
In [13]:
%%opts Curve.Logistic_interpolations (color='green') Curve (color='black')
diagram + simple_curve
Out[13]:
Помимо настроек %%opts
так же доступны настройки %%output
,
позволяющие менять размер и вывод
In [14]:
%%output size=50
diagram
Out[14]:
Из полезных настроек: c помощью %%output filename=
можно сохранить
картинку в файл.
Backend¶
В самом начале мы подключали backend matplotlib с помощью
hv.extension('matplotlib')
, но нам так же доступен и другой backend
- bokeh.
In [15]:
hv.extension('bokeh')
In [16]:
isotonic(df=df, predict='feature_2', target='y')
Out[16]:
В нем появляется возможность делать интерактивные диаграммы.
Совмещение диаграмм¶
Для визуализации данных бывает полезно выводить не по одной диаграмме а сразу несколько, и holoviews позволяет это сделать очень удобно.
Над диаграммами переопределены арифметические операции:
In [17]:
wl_2 = woe_line(df=df, feature='feature_2', target='y', num_buck=30)
ws_2 = woe_stab(df=df, feature='feature_2', target='y', date='sample_date', num_buck=3)
wl_2 + ws_2
Out[17]:
При рисовании диаграмм рядом друг с другом происходит совмещение осей
(если они называются одинаково). Чтобы этого не происходило можно
воспольоваться настройками normalization (те, что в фигурных скобках) -
если добавить %%opts Spread {+axiswise}
, то сцепление первого типа
диаграммы Spread
из второго графика ws_2
пропадет.
Также есть удобная возможность рисовать сразу несколько диаграмм
напрямую через конструктор hv.Layout
.
In [18]:
hv.extension('matplotlib')
In [19]:
%%opts Layout [hspace=1 vspace=0.5]
features = ['feature_' + str(i) for i in range(4)] + ['category_feature']
hv.Layout([woe_stab(df, f, 'y', 'sample_date', num_buck=3) for f in features]).cols(2)
Out[19]:
cols(2)
нарисовали все в 2 колонки.hspace=1
позволяет сделать отступы между графиками,
расположенными горизонтально друг от друга для того, чтобы уместились
легенды, а vspace=0.5
- между вертикально расположенными
графиками.Интерактивность¶
Один из мощных инструментом в holoviews - это создание интерактивных графиков, позволящих с помощью виджетов перебирать различные диаграммы. Доступно два базовых типа:
- HoloMap - из словаря с ключем - название диаграммы, а значением самими диаграммами создается интервактивный график (пример ниже).
- DynamicMap
- динамичная диаграммы, вычисляющая по положениям виджетов встроенную
диаграмму. Для DynamicMap нужно задать функцию, которая это сделает и
вычислению будут происходить только при запущенной сессии Python (за
то не тратится место на хранение сразу всех диаграмм как у
HoloMap
).
In [20]:
# hv.extension('bokeh')
# hv.HoloMap({i: woe_line(df, 'feature_2', 'y', num_buck=i) for i in range(10, 100, 50)}, kdims=['buckets'])
В данном примере мы внутри hv.Holomap
создали словарь с ключем i
и значением - диаграммой woe_line, с разбивкой признака feature_2
как раз на i
бакетов. Теперь с помощью виджета можно посмотреть как
меняется график при изменении количества бакетов. Видно, что график
становится подробнее, вместе с тем растут доверительные интервалы у
оцененных значений woe.
Можно так же создавать сразу несколько виджетов, если ключ будет более сложным объектом tuple (в случае ниже пара - название признака и количество бакетов).
In [21]:
# hv.HoloMap({(f, i): woe_line(df, f, 'y', num_buck=i)
# for f in ['feature_1', 'feature_2']
# for i in [10, 100]}, kdims=['feature', 'buckets'])
С другими интересными возможностями работы диаграмм стоит обращаться к документации holoviews.