Я пытаюсь понять, как я могу получить важность функции категориальной переменной, которая была разбита на фиктивные переменные. Я использую Scikit-Learn, который не обрабатывает категориальные переменные для вас, как R или H2O.
Если я разобью категориальную переменную на фиктивные переменные, я получу отдельные значения свойств для каждого класса в этой переменной.
Мой вопрос заключается в том, имеет ли смысл объединять значения этих фиктивных переменных в значение важности для категориальной переменной, просто суммируя их?
Со страницы 368 «Элементы статистического обучения»:
Квадратная относительная важность переменной является суммой таких квадратичных улучшений для всех внутренних узлов, для которых она была выбрана в качестве расщепляющей переменной.
Это заставляет меня думать, что, поскольку значение важности уже создано путем суммирования метрики в каждом узле, для которого выбрана переменная, я должен быть в состоянии объединить значения важности переменных с фиктивными переменными, чтобы «восстановить» важность для категориальной переменной. Конечно, я не ожидаю, что это будет точно правильно, но эти значения в любом случае действительно точные, поскольку они обнаруживаются в результате случайного процесса.
Я написал следующий код Python (в jupyter) в качестве расследования:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from sklearn.datasets import load_diabetes
from sklearn.ensemble import RandomForestClassifier
import re
#%matplotlib inline
from IPython.display import HTML
from IPython.display import set_matplotlib_formats
plt.rcParams['figure.autolayout'] = False
plt.rcParams['figure.figsize'] = 10, 6
plt.rcParams['axes.labelsize'] = 18
plt.rcParams['axes.titlesize'] = 20
plt.rcParams['font.size'] = 14
plt.rcParams['lines.linewidth'] = 2.0
plt.rcParams['lines.markersize'] = 8
plt.rcParams['legend.fontsize'] = 14
# Get some data, I could not easily find a free data set with actual categorical variables, so I just created some from continuous variables
data = load_diabetes()
df = pd.DataFrame(data.data, columns=[data.feature_names])
df = df.assign(target=pd.Series(data.target))
# Functions to plot the variable importances
def autolabel(rects, ax):
"""
Attach a text label above each bar displaying its height
"""
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2.,
1.05*height,
f'{round(height,3)}',
ha='center',
va='bottom')
def plot_feature_importance(X,y,dummy_prefixes=None, ax=None, feats_to_highlight=None):
# Find the feature importances by fitting a random forest
forest = RandomForestClassifier(n_estimators=100)
forest.fit(X,y)
importances_dummy = forest.feature_importances_
# If there are specified dummy variables, combing them into a single categorical
# variable by summing the importances. This code assumes the dummy variables were
# created using pandas get_dummies() method names the dummy variables as
# featurename_categoryvalue
if dummy_prefixes is None:
importances_categorical = importances_dummy
labels = X.columns
else:
dummy_idx = np.repeat(False,len(X.columns))
importances_categorical = []
labels = []
for feat in dummy_prefixes:
feat_idx = np.array([re.match(f'^{feat}_', col) is not None for col in X.columns])
importances_categorical = np.append(importances_categorical,
sum(importances_dummy[feat_idx]))
labels = np.append(labels,feat)
dummy_idx = dummy_idx | feat_idx
importances_categorical = np.concatenate((importances_dummy[~dummy_idx],
importances_categorical))
labels = np.concatenate((X.columns[~dummy_idx], labels))
importances_categorical /= max(importances_categorical)
indices = np.argsort(importances_categorical)[::-1]
# Plotting
if ax is None:
fig, ax = plt.subplots()
plt.title("Feature importances")
rects = ax.bar(range(len(importances_categorical)),
importances_categorical[indices],
tick_label=labels[indices],
align="center")
autolabel(rects, ax)
if feats_to_highlight is not None:
highlight = [feat in feats_to_highlight for feat in labels[indices]]
rects2 = ax.bar(range(len(importances_categorical)),
importances_categorical[indices]*highlight,
tick_label=labels[indices],
color='r',
align="center")
rects = [rects,rects2]
plt.xlim([-0.6, len(importances_categorical)-0.4])
ax.set_ylim((0, 1.125))
return rects
# Create importance plots leaving everything as categorical variables. I'm highlighting bmi and age as I will convert those into categorical variables later
X = df.drop('target',axis=1)
y = df['target'] > 140.5
plot_feature_importance(X,y, feats_to_highlight=['bmi', 'age'])
plt.title('Feature importance with bmi and age left as continuous variables')
#Create an animation of what happens to variable importance when I split bmi and age into n (n equals 2 - 25) different classes
# %%capture
fig, ax = plt.subplots()
def animate(i):
ax.clear()
# Split one of the continuous variables up into a categorical variable with i balanced classes
X_test = X.copy()
n_categories = i+2
X_test['bmi'] = pd.cut(X_test['bmi'],
np.percentile(X['bmi'], np.linspace(0,100,n_categories+1)),
labels=[chr(num+65) for num in range(n_categories)])
X_test['age'] = pd.cut(X_test['age'],
np.percentile(X['age'], np.linspace(0,100,n_categories+1)),
labels=[chr(num+65) for num in range(n_categories)])
X_test = pd.get_dummies(X_test, drop_first=True)
# Plot the feature importances
rects = plot_feature_importance(X_test,y,dummy_prefixes=['bmi', 'age'],ax=ax, feats_to_highlight=['bmi', 'age'])
plt.title(f'Feature importances for {n_categories} bmi and age categories')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
return [rects,]
anim = animation.FuncAnimation(fig, animate, frames=24, interval=1000)
HTML(anim.to_html5_video())
Вот некоторые из результатов:
Мы можем заметить, что значение переменной в основном зависит от количества категорий, что заставляет меня усомниться в полезности этих диаграмм в целом. Особенно важно age
достичь гораздо более высоких значений, чем его непрерывный аналог.
И, наконец, пример, если я оставлю их как фиктивные переменные (только bmi):
# Split one of the continuous variables up into a categorical variable with i balanced classes
X_test = X.copy()
n_categories = 5
X_test['bmi'] = pd.cut(X_test['bmi'],
np.percentile(X['bmi'], np.linspace(0,100,n_categories+1)),
labels=[chr(num+65) for num in range(n_categories)])
X_test = pd.get_dummies(X_test, drop_first=True)
# Plot the feature importances
rects = plot_feature_importance(X_test,y, feats_to_highlight=['bmi_B','bmi_C','bmi_D', 'bmi_E'])
plt.title(f"Feature importances for {n_categories} bmi categories")