Estou tentando entender como posso obter a importância do recurso de uma variável categórica que foi dividida em variáveis dummy. Estou usando o scikit-learn, que não lida com variáveis categóricas para você, como R ou H2O.
Se eu dividir uma variável categórica em variáveis dummy, obtenho importâncias de recurso separadas por classe nessa variável.
Minha pergunta é: faz sentido recombinar essas importâncias variáveis variáveis em um valor de importância para uma variável categórica simplesmente somando-as?
Na página 368 de Os elementos da aprendizagem estatística:
A importância relativa ao quadrado da variável é a soma dessas melhorias ao quadrado em todos os nós internos para os quais foi escolhida como variável de divisão
Isso me faz pensar que, como o valor de importância já é criado pela soma de uma métrica em cada nó, a variável é selecionada, eu devo poder combinar os valores de importância da variável das variáveis fictícias para "recuperar" a importância da variável categórica. É claro que não espero que seja exatamente correto, mas esses valores são realmente exatos, pois são encontrados através de um processo aleatório.
Eu escrevi o seguinte código python (no jupyter) como uma investigação:
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())
Aqui estão alguns dos resultados:
Podemos observar que a variável importância depende principalmente do número de categorias, o que me leva a questionar a utilidade desses gráficos em geral. Especialmente a importância de age
atingir valores muito mais altos do que sua contraparte contínua.
E, finalmente, um exemplo, se eu os deixar como variáveis fictícias (apenas 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")