Время прочтения: 5 мин.

Подбор признаков (Feature selection) — это процесс уменьшения количества входных параметров, используемых при построении моделей. Используя различные статистические подходы, можно определить взаимоотношение между признаками и целевой переменной, для определения тех переменных, которые имеют наиболее сильную связь. Однако, выбор статистических методов зависит как от создаваемой модели, так и от типов сравниваемых данных, а потому может представлять определённые трудности для разработчика.

Числовые переменные
Для начала расскажу о методах, которые следует применять для работы с числовыми переменными. Если на выходе модели у нас формируется категориальная переменная (Номинальная, ординальная или класс, например), то правильным будет применять такие методы как ANOVA correlation coefficient или Kendall’s rank coefficient для линейной или нелинейной зависимости соответственно.

Пример программного кода для реализации данных алгоритмов:

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif
import pandas as pd
import numpy as np
#%% Датасет для демонстрации
df_africa = pd.read_csv(r"C:\Users\mmvik\Downloads\african_crises.csv")

df_africa['banking_crisis'] = df_africa['banking_crisis'].map({'crisis': 1, 'no_crisis': 0})
#%%
#для расчёта корреляции методом кендалла можно использовать библиотеку pandas
corr = df_africa.corr(method='kendall')
corr = corr['banking_crisis']
corr.drop('banking_crisis', inplace = True)
#%%
#Для использования ANOVA потребуется sklearn
# f_classif используется для ANOVA
feature_function = SelectKBest(score_func=f_classif, k=2)
X = df_africa.drop('banking_crisis', axis = 1).copy()
X = X.select_dtypes([np.number]) #оставляем только числовые поля
y = df_africa['banking_crisis'].copy()
feature_function.fit(X, y)
X_selected = feature_function.transform(X)
for i in range(len(feature_function.scores_)):
    print(corr.index[i], ': Anova: %f, Kendall: %f'% (feature_function.scores_[i],corr[i]))

Результат:

case : Anova: 0.591649, Kendall: -0.014954
year : Anova: 50.617197, Kendall: 0.200397
systemic_crisis : Anova: 2840.599138, Kendall: 0.853702
exch_usd : Anova: 30.991573, Kendall: 0.113212
domestic_debt_in_default : Anova: 56.785554, Kendall: 0.225797
sovereign_external_debt_default : Anova: 79.182398, Kendall: 0.263992
gdp_weighted_default : Anova: 0.745341, Kendall: 0.026736
inflation_annual_cpi : Anova: 10.432287, Kendall: 0.167179
independence : Anova: 27.634911, Kendall: 0.159620
currency_crises : Anova: 30.271703, Kendall: 0.167835
inflation_crises : Anova: 62.260056, Kendall: 0.235852

Видно, что максимальные и минимальные показатели совпадают, однако, для некоторых параметров есть отличия, связанные со статическим распределением исходных параметров (currency_crises и exch_usd, например).

В случае, когда на выходе тоже получается числовая переменная, распространённо применяются коэффициенты Пирсона (Pearson’s) или Спирмана (Spearman’s), для линейных и нелинейных методов соответственно.

from sklearn.datasets import make_regression
import pandas as pd
import math
# создаём датасет
X_linear, y = make_regression(n_samples=1000, n_features=10, n_informative=2)

df_samples = pd.DataFrame(X_linear)
df_samples['y'] = y
corr_pearson = df_samples.corr('pearson')['y'].drop('y')
corr_spearman = df_samples.corr('spearman')['y'].drop('y')
for i in range(len(corr_pearson)):
    print(corr_pearson.index[i], ': pearson: %f, spearman: %f'% (corr_pearson[i],corr_spearman[i]))
print('Нелинейный пример:')
X_nonlinear, y_nonlinear = make_regression(n_samples=1000, n_features=10, n_informative=2)
for i in range(1000):
    for j in range(10):
        X_nonlinear[i][j] = math.log2(abs(X_nonlinear[i][j]))
y_nonlinear = [abs(y) for y in y_nonlinear]
df_samples_nonlinear = pd.DataFrame(X_nonlinear)
df_samples_nonlinear['y'] = y_nonlinear
corr_pearson = df_samples_nonlinear.corr('pearson')['y'].drop('y')
corr_spearman = df_samples_nonlinear.corr('spearman')['y'].drop('y')
for i in range(len(corr_pearson)):
    print(corr_pearson.index[i], ': pearson: %f, spearman: %f'% (corr_pearson[i],corr_spearman[i]))

Результат:

0 : pearson: 0.075557, spearman: 0.093597
1 : pearson: 0.384044, spearman: 0.374281
2 : pearson: -0.074026, spearman: -0.069885
3 : pearson: 0.007651, spearman: -0.013319
4 : pearson: -0.045287, spearman: -0.054908
5 : pearson: -0.034626, spearman: -0.038182
6 : pearson: -0.008276, spearman: 0.001085
7 : pearson: -0.012967, spearman: -0.005222
8 : pearson: 0.929335, spearman: 0.918733
9 : pearson: 0.000531, spearman: 0.015545
Нелинейный пример:
0 : pearson: 0.059650, spearman: 0.062185
1 : pearson: 0.010152, spearman: 0.013855
2 : pearson: -0.035513, spearman: -0.042905
3 : pearson: 0.044442, spearman: 0.022478
4 : pearson: 0.034085, spearman: 0.060602
5 : pearson: -0.005633, spearman: -0.018592
6 : pearson: -0.009302, spearman: -0.024156
7 : pearson: 0.340414, spearman: 0.376554
8 : pearson: 0.012585, spearman: 0.016479
9 : pearson: 0.327219, spearman: 0.364198

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

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

Случаи, когда у нас встречаются категориальные переменные, но при этом выходная переменная числовая, можно рассматривать так же, как и в первом примере, случаи, где у нас выходная переменная категориальная, а входные – числовые. Осталось рассмотреть пример для случая, когда у нас и входные и выходные переменные категориальные. И для этого можно использовать Chi-Squared test или Mutual Information.

Пример кода:

import pandas as pd
import numpy as np
from sklearn.feature_selection import SelectKBest
from sklearn.preprocessing import OrdinalEncoder
from sklearn.feature_selection import chi2
from sklearn.feature_selection import mutual_info_classif
df_africa = pd.read_csv(r"C:\Users\mmvik\Downloads\african_crises.csv")
X = df_africa[['case', 'country', 'year', 'systemic_crisis', 'domestic_debt_in_default','sovereign_external_debt_default','independence','currency_crises','inflation_crises']].copy()
y = df_africa[['banking_crisis']].copy()
X = X.astype(str)
oe = OrdinalEncoder()
oe.fit(y.to_numpy())
y_labeled = oe.transform(y.to_numpy())
oe.fit(X.to_numpy())
X_labeled = oe.transform(X.to_numpy())
feature_function_chi = SelectKBest(score_func=chi2, k='all')
feature_function_mutual = SelectKBest(score_func=mutual_info_classif, k='all')
feature_function_chi.fit(X_labeled, y_labeled)
feature_function_mutual.fit(X_labeled, y_labeled)
X_selected_chi = feature_function_chi.transform(X)
X_selected_mutual = feature_function_chi.transform(X)
for i in range(len(feature_function_chi.scores_)):
    print(str(i), ': chi: %f, mutual: %f'% (feature_function_chi.scores_[i],feature_function_mutual.scores_[i]))

Полученный в данном случае результат:

case: chi: 1.409371, mutual: 0.020210
country: chi: 0.549365, mutual: 0.024483
year: chi: 504.416057, mutual: 0.076050
systemic_crisis: chi: 712.044841, mutual: 0.194785
domestic_debt_in_default: chi: 51.851013, mutual: 0.013567
sovereign_external_debt_default: chi: 62.513388, mutual: 0.032492
independence: chi: 6.038413, mutual: 0.021688
currency_crises: chi: 27.271533, mutual: 0.005459
inflation_crises: chi: 51.287251, mutual: 0.023750

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

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