36 votos

Importancia de las características con variables ficticias

Estoy tratando de entender cómo puedo obtener la importancia de la característica de una variable categórica que se ha desglosado en variables ficticias. Estoy usando scikit-learn que no maneja las variables categóricas como lo hacen R o h2o.

Si descompongo una variable categórica en variables ficticias, obtengo importancias de características separadas por clase en esa variable.

Mi pregunta es, ¿tiene sentido recombinar esas importancias de las variables ficticias en un valor de importancia para una variable categórica simplemente sumándolas?

De la página 368 de The Elements of Statistical Learning:

La importancia relativa al cuadrado de la variable $X_{}$ es la suma de dichas mejoras al cuadrado sobre todos los nodos internos para los que se eligió como variable de división

Esto me hace pensar que, dado que el valor de importancia ya se crea sumando una métrica en cada nodo en el que se selecciona la variable, debería poder combinar los valores de importancia de las variables ficticias para "recuperar" la importancia para la variable categórica. Por supuesto, no espero que sea exactamente correcto, pero estos valores son realmente valores exactos de todos modos, ya que se encuentran a través de un proceso aleatorio.

He escrito el siguiente código python (en jupyter) como investigación:

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())

Estos son algunos de los resultados:

enter image description here

enter image description here

Podemos observar que la importancia de la variable depende sobre todo del número de categorías, lo que me lleva a cuestionar la utilidad de estos gráficos en general. Especialmente la importancia de age alcanzando valores mucho más altos que su contraparte continua.

Y por último, un ejemplo si las dejo como variables ficticias (sólo 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")

enter image description here

11voto

user3227275 Puntos 29

Un enfoque que se puede adoptar en scikit-learn es utilizar el permutation_importance en una tubería que incluya la codificación de un solo disparo. Si se hace esto, entonces el permutation_importance será permutar las columnas categóricas antes de se codifican de una sola vez. Este enfoque puede verse en este ejemplo en la página web de scikit-learn. Los resultados de la permutación antes de la codificación se muestran en la segunda y tercera figuras, donde se puede ver que se reporta una sola importancia para cada variable categórica.

4voto

karatedog Puntos 1011

La pregunta es:

¿tiene sentido recombinar esas importancias de las variables ficticias en un valor de importancia para una variable categórica simplemente sumándolas?

La respuesta corta:

La respuesta sencilla es no. Según el libro de texto (página 368), la importancia de $$Importance(X_l) = I_{\ell}$$ y $$(I_{ℓ})^2 = \sum\limits_{t=1}^{J-1} i^2I(v(t)=\ell)$$ así $$I_{ℓ} = \sqrt{\sum\limits_{t=1}^{J-1} i^2I(v(t)=\ell)}$$ En conclusión, primero hay que sacar la raíz cuadrada.

La respuesta más larga y práctica..

No se puede simplemente sumar los valores de importancia de las variables individuales para las variables ficticias porque se corre el riesgo de

el enmascaramiento de variables importantes por otras con las que están muy correlacionadas. (página 368)

Cuestiones como la posible multicolinealidad pueden distorsionar los valores de importancia de las variables y las clasificaciones.

En realidad, es un problema muy interesante para entender cómo la importancia de las variables se ve afectada por cuestiones como la multicolinealidad. El documento Determinación de la importancia de los predictores en la regresión múltiple bajo una correlación variada y condiciones de distribución discute varios métodos para calcular la importancia de las variables y compara el rendimiento de los datos que violan los supuestos estadísticos típicos. Los autores descubrieron que

Aunque la multicolinealidad afectó a la el rendimiento de los métodos de importancia relativa, la no normalidad multivariante no lo hizo. (WHITTAKER p366)

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X