Importance des fonctionnalités avec des variables muettes

17

J'essaie de comprendre comment je peux obtenir l'importance des fonctionnalités d'une variable catégorielle qui a été décomposée en variables fictives. J'utilise scikit-learn qui ne gère pas les variables catégorielles pour vous comme le font R ou H2O.

Si je décompose une variable catégorielle en variables fictives, j'obtiens des importances de fonctionnalités distinctes par classe dans cette variable.

Ma question est la suivante: est-il sensé de recombiner ces importances de variables fictives en une valeur d'importance pour une variable catégorielle en les additionnant simplement?

À partir de la page 368 des éléments de l'apprentissage statistique:

L'importance relative au carré de la variable est la somme de ces améliorations au carré sur tous les nœuds internes pour lesquels elle a été choisie comme variable de fractionnement.X

Cela me fait penser que puisque la valeur d'importance est déjà créée en additionnant une métrique à chaque nœud, la variable est sélectionnée, je devrais pouvoir combiner les valeurs d'importance des variables des variables factices pour "récupérer" l'importance de la variable catégorielle. Bien sûr, je ne m'attends pas à ce que ce soit exactement correct, mais ces valeurs sont de toute façon des valeurs vraiment exactes puisqu'elles sont trouvées par un processus aléatoire.

J'ai écrit le code python suivant (en jupyter) comme enquête:

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

Voici quelques résultats:

entrez la description de l'image ici

entrez la description de l'image ici

On peut observer que l'importance variable dépend principalement du nombre de catégories, ce qui m'amène à remettre en question l'utilité de ces graphiques en général. Surtout l'importance d' age atteindre des valeurs beaucoup plus élevées que son homologue continu.

Et enfin, un exemple si je les laisse comme variables fictives (seulement 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")

entrez la description de l'image ici

Dan
la source

Réponses:

8

Lorsque vous travaillez sur l '«importance des fonctionnalités» en général, il est utile de se rappeler que dans la plupart des cas, une approche de régularisation est souvent une bonne alternative. Il "sélectionne automatiquement les fonctionnalités les plus importantes" pour le problème en question. Maintenant, si nous ne voulons pas suivre la notion de régularisation (généralement dans le contexte de la régression), les classificateurs de forêt aléatoires et la notion de tests de permutation offrent naturellement une solution pour mettre en évidence l'importance du groupe de variables. Cela a en fait été demandé auparavant: " Importance relative d'un ensemble de prédicteurs dans une classification aléatoire des forêts dans R " il y a quelques années. Approches plus rigoureuses comme celles de Gregorutti et al.: " Importance des variables groupées avec forêts aléatoires et application à l'analyse de données fonctionnelles multivariées". Chakraborty & Pal's Selecting Utile Groups of Features in a Connectionist Framework examine cette tâche dans le contexte d'un Perceptron multicouche. Pour en revenir à l'article de Gregorutti et al., Leur méthodologie est directement applicable à tout type d'algorithme de classification / régression. En bref, nous utilisons une version permutée au hasard dans chaque échantillon hors sac utilisé lors de la formation.

Cela dit, bien que les tests de permutation soient finalement une heuristique, ce qui a été résolu avec précision dans le passé est la pénalisation des variables muettes dans le contexte de la régression régularisée. La réponse à cette question est Group-LASSO , Group-LARS et Group-Garotte . Les articles phares de ce travail sont ceux de Yuan et Lin: « Model selection and estimation in regression with grouped variables » (2006) et Meier et al.: « The group lasso for logistic regression » (2008). Cette méthodologie nous permet de travailler dans une situation où: " chaque facteur peut avoir plusieurs niveaux et peut être exprimé à travers un groupe de variables muettes " (Y&L 2006). L'effet est tel que "l1Kjj={1,,J}Jpyglmnetrégularisation groupée du lasso .]

Dans l'ensemble, il n'a pas de sens de simplement «additionner» l'importance des variables à partir des variables fictives individuelles car cela ne capturerait pas l'association entre elles et ne conduirait pas à des résultats potentiellement dénués de sens. Cela dit, les méthodes pénalisées par groupe ainsi que les méthodes d'importance variable par permutation fournissent un cadre cohérent et (en particulier dans le cas des procédures d'importance par permutation) généralement applicable pour ce faire.

Enfin pour énoncer l'évidence: ne regroupez pas les données continues . Il est une mauvaise pratique, il y a un excellent fil à ce sujet ici (et ici ). Le fait que nous observions des résultats erronés après la discrétisation d'une variable continue, comme age, n'est pas surprenant. Frank Harrell a également écrit de nombreux articles sur les problèmes causés par la catégorisation des variables continues .

usεr11852 dit Reinstate Monic
la source
Vous liez L'importance relative d'un ensemble de prédicteurs dans une classification aléatoire des forêts dans R répond directement à la question. Je serais heureux d'accepter si vous déplacez la référence à ce lien au début, car je ne pense pas que le reste soit aussi directement pertinent et le lien peut facilement se perdre dans la réponse.
Dan
Aucun problème. J'ai fait quelques modifications pertinentes. Ne rejetez pas le concept de régression régularisée, comme je le mentionne dans le texte, les approches de régularisation offrent une alternative parfaitement valable à l'importance / classement des fonctionnalités.
usεr11852 dit Réintégrer Monic
La régression régularisée n'est pas une réponse à cette question, elle peut répondre à une question différente, c'est-à-dire des alternatives à l'importance des caractéristiques, mais cette question concerne l'agrégation des caractéristiques en une seule caractéristique catégorielle dans un graphique d'importance des caractéristiques. Je pense vraiment que vous devriez déplacer le lien qui répond réellement à la question au début.
Dan
2

La question est:

est-il sensé de recombiner ces importances de variables fictives en une valeur d'importance pour une variable catégorielle en les additionnant simplement?

La réponse courte:

jemportunence(Xl)=je
(je)2=t=1J-1je2je(v(t)=)
je=t=1J-1je2je(v(t)=)

La réponse plus longue et plus pratique ..

Vous ne pouvez pas simplement additionner les valeurs d'importance des variables individuelles pour les variables fictives, car vous risquez

le masquage de variables importantes par d'autres avec lesquelles elles sont fortement corrélées. (page 368)

Des problèmes tels que la multicolinéarité possible peuvent fausser les valeurs d'importance variable et les classements.

C'est en fait un problème très intéressant de comprendre à quel point l'importance des variables est affectée par des problèmes comme la multicolinéarité. L'article Déterminer l'importance des prédicteurs dans les régressions multiples dans des conditions de corrélation et de distribution variées examine diverses méthodes de calcul de l'importance des variables et compare les performances des données violant les hypothèses statistiques typiques. Les auteurs ont constaté que

Bien que la multicolinéarité ait affecté les performances des méthodes d'importance relative, la non-normalité multivariée ne l'a pas été. (WHITTAKER p366)

ecedavis
la source
Je ne pense pas que votre deuxième citation soit pertinente. Ce ne sont pas des variables hautement corrélées, ce sont les mêmes variables et une bonne implémentation d'un arbre de décision ne nécessiterait pas d'OHE mais les traiterait comme une seule variable. Au contraire, la multicolinéarité est introduite artificiellement par OHE.
Dan
En ce qui concerne votre premier point, cela me blesse comme le nombre d'importance relative proposé par Breiman est la valeur au carré. Je ne suis donc pas convaincu que sklearn prenne d'abord racine carrée comme vous l'avez suggéré. De plus, si elles le sont, ne devrais-je pas alors d'abord mettre les valeurs au carré, les additionner et ensuite enraciner la somme au carré? Je ne suis pas sûr d'avoir compris votre suggestion de prendre la racine carrée en premier.
Dan
@ecedavis Que voulez-vous dire par le manuel? Pouvez-vous fournir un lien ou une citation plus complète s'il vous plaît.
see24
Salut, merci pour les critiques et pour mon premier vote positif en tant que nouveau membre. Vos commentaires indiquent des détails spécifiques que j'aborderai dans ma révision, mais puis-je également avoir votre avis sur la qualité globale de ma réponse? Ceci est mon premier message et je prévois de devenir un contributeur régulier. Au minimum, j'espère que ma réponse sera généralement utile et de bon style. Quelles sont vos pensées?
ecedavis
Le style de votre réponse est bon, mais certaines informations et contenus ne semblent pas tout à fait corrects. L'article auquel vous vous connectez concerne l'importance des prédicteurs dans la régression multiple, tandis que la question porte sur l'importance dans la forêt aléatoire. Je trouve également que l'extraction de la citation pose problème, car la phrase complète est "En outre, en raison du rétrécissement (section 10.12.1), le masquage de variables importantes par d'autres avec lesquelles elles sont fortement corrélées est beaucoup moins problématique." qui a un sens très différent.
voir24