Une variable peut sembler moins importante après l’ajout d’une copie très corrélée, alors que l’information portée par les deux colonnes reste essentielle. Pour le voir, nous allons comparer la permutation de chaque colonne séparément et leur permutation conjointe, sans réentraîner la forêt entre les permutations.
Dans ce cas synthétique, permuter le signal seul fait baisser le R² de validation de 0,671 en moyenne ; permuter le signal et sa copie ensemble le fait baisser de 1,971. Ces valeurs sont des pertes de score, pas des pourcentages d’explication. Le guide Random Forest présente le modèle ; cet article examine ce que mesure une importance calculée après l’apprentissage.
Télécharger le code, les 30 répétitions et les six tests (ZIP gratuit).
Ce que mesure une permutation
Nous calculons d’abord le R² sur un lot de validation. Nous mélangeons ensuite une colonne entre les lignes, en gardant les cibles fixes, puis recalculons le score du même modèle. La différence indique à quel point ce mélange dégrade sa performance sur ces observations.
Si une autre colonne porte une information semblable, le modèle peut en conserver une partie. La documentation scikit-learn sur les variables corrélées explique ce piège. Son ampleur dépend du modèle appris : les importances individuelles ne deviennent pas nécessairement nulles. Ici, les deux variables ont encore une importance individuelle notable.
Cette mesure diffère de feature_importances_, fondée sur les diminutions d’impureté utilisées pendant la construction des arbres. Elle dépend du modèle, du score, du lot évalué et de la perturbation choisie. Elle ne donne pas une propriété absolue de la variable, ni une preuve causale.
Une expérience avec une copie presque identique
Le jeu comporte 1 800 observations et trois variables : signal, sa copie perturbée par un faible bruit, et une colonne bruit indépendante. La cible synthétique combine 3 × signal + sin(2 × signal) et un bruit indépendant. Les variables n’ont pas d’unité réelle.
La partition fixe réserve 1 080 observations à l’apprentissage, 360 à la validation et 360 au test. La corrélation entre signal et copie sur l’apprentissage vaut environ 0,999954. La forêt utilise 120 arbres et au moins quatre observations par feuille, avec les réglages fixés avant l’analyse.
Un témoin prévu dès le départ apprend une seconde forêt sur le signal et le bruit seulement. Il sert à comparer le rôle apparent du signal avec et sans sa copie. Nous ne sélectionnons ni variables ni paramètres à partir des scores de test.
Pourquoi permuter les deux colonnes avec le même ordre ?
Pour la permutation groupée, une seule permutation de lignes est appliquée aux deux colonnes. Leur relation interne est ainsi conservée : chaque couple signal/copie reste un couple effectivement présent dans le lot. Le groupe perd en revanche son alignement avec la cible et avec les colonnes laissées en place.
Mélanger les deux colonnes indépendamment détruirait aussi leur relation mutuelle. Cela mesurerait une perturbation différente. La permutation individuelle casse déjà cette relation et peut créer des associations peu plausibles ; il faut tenir compte de cette limite dans l’interprétation.
Nous réalisons 30 permutations. Les mêmes ordres sont réutilisés pour comparer les groupes, ce qui facilite une lecture cohérente de la variabilité. La forêt reste figée pendant ces calculs.
Résultats sur la validation
Le R² initial vaut 0,9893 avec la copie et 0,9894 pour le témoin sans copie. Le tableau donne les baisses de R² après permutation, avec leur écart-type entre les 30 mélanges.
| Modèle | Colonnes permutées | Baisse moyenne de R² | Écart-type |
|---|---|---|---|
| Avec copie | Signal | 0,6711 | 0,0323 |
| Avec copie | Copie | 0,3639 | 0,0174 |
| Avec copie | Bruit | 0,0002 | 0,0002 |
| Avec copie | Signal et copie ensemble | 1,9711 | 0,0859 |
| Sans copie | Signal | 1,9738 | Voir les répétitions du ZIP |
La permutation du signal seul est moins destructrice lorsqu’une copie est disponible. Le témoin et la permutation groupée montrent que l’information commune reste déterminante. Ne supprimez pas les deux colonnes parce que chacune paraît moins importante que le groupe. Et n’additionnez pas les importances individuelles pour prédire l’importance groupée : le calcul n’est pas additif.

Une baisse supérieure à 1 n’est pas une erreur : le R² après permutation peut être négatif. La permutation groupée conduit ici à un score moyen proche de −0,982, inférieur à la référence qui prédit la moyenne des cibles du lot. Le score final sur le test réservé vaut 0,9860 avec copie et 0,9858 sans copie ; cette proximité sur un tirage ne démontre aucune supériorité générale.
Code Python complet
Exécuté avec Python 3.12.14, NumPy 2.5.3 et scikit-learn 1.9.1. L’archive conserve les 30 pertes individuelles de chaque groupe, les deux scores finaux et le script de figure.
python -m pip install -r requirements.txt
python importance_groupee.py
python test_exemple.py
python figure.py
"""Permutation individuelle ou conjointe de deux variables redondantes."""
from pathlib import Path
import json
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
NOMS = ('signal', 'copie', 'bruit')
def donnees():
rng = np.random.default_rng(42)
signal = rng.normal(size=1800)
copie = signal + .01 * rng.normal(size=len(signal))
bruit = rng.normal(size=len(signal))
X = np.column_stack([signal, copie, bruit])
y = 3 * signal + np.sin(2 * signal) + .3 * rng.normal(size=len(signal))
dev, test = train_test_split(np.arange(len(y)), test_size=.2, random_state=42)
train, val = train_test_split(dev, test_size=.25, random_state=42)
return X, y, train, val, test
def permuter(X, colonnes, ordre):
copie = X.copy()
# Même ordre de lignes pour toutes les colonnes du groupe.
copie[:, colonnes] = X[ordre][:, colonnes]
return copie
def importance(modele, X, y, groupes, repetitions=30):
baseline = float(r2_score(y, modele.predict(X)))
rng = np.random.default_rng(100)
ordres = [rng.permutation(len(y)) for _ in range(repetitions)]
lignes = []
for nom, cols in groupes.items():
pertes = [baseline - r2_score(y, modele.predict(permuter(X, cols, ordre))) for ordre in ordres]
lignes.append({'groupe': nom, 'colonnes': cols, 'baisse_r2_moyenne': float(np.mean(pertes)),
'ecart_type': float(np.std(pertes, ddof=1)) if len(pertes)>1 else None,
'repetitions': [float(p) for p in pertes]})
return baseline, lignes
def experience():
X, y, train, val, test = donnees()
foret = RandomForestRegressor(n_estimators=120, min_samples_leaf=4, max_features=1.,
random_state=42, n_jobs=1).fit(X[train], y[train])
baseline, lignes = importance(foret, X[val], y[val],
{'signal_seul': [0], 'copie_seule': [1], 'bruit_seul': [2], 'signal_et_copie': [0,1]})
# Témoin prévu : même réglage de forêt sans la copie, sans sélection de variables.
colonnes_temoin = [0, 2]
temoin = RandomForestRegressor(n_estimators=120, min_samples_leaf=4, max_features=1.,
random_state=42, n_jobs=1).fit(X[train][:, colonnes_temoin], y[train])
base_temoin, lignes_temoin = importance(temoin, X[val][:, colonnes_temoin], y[val], {'signal_sans_copie': [0]})
resume = {'donnees': 'synthétiques', 'colonnes': list(NOMS), 'tailles': {k: len(v) for k,v in [('train',train),('validation',val),('test',test)]},
'correlation_apprentissage': float(np.corrcoef(X[train,0], X[train,1])[0,1]),
'r2_validation': baseline, 'lignes': lignes,
'temoin': {'r2_validation': base_temoin, 'lignes': lignes_temoin},
'r2_test': float(r2_score(y[test], foret.predict(X[test]))),
'r2_test_temoin': float(r2_score(y[test], temoin.predict(X[test][:,colonnes_temoin])))}
return {'X': X, 'y': y, 'train': train, 'val': val, 'test': test,
'foret': foret, 'temoin': temoin, 'resume': resume}
if __name__ == '__main__':
r = experience()
Path(__file__).with_name('resultats.json').write_text(json.dumps(r['resume'], indent=2, ensure_ascii=False), encoding='utf-8')
compact = dict(r['resume'])
compact['lignes'] = [{k:v for k,v in l.items() if k!='repetitions'} for l in compact['lignes']]
compact['temoin'] = {'r2_validation':compact['temoin']['r2_validation'],
'importance_signal': compact['temoin']['lignes'][0]['baisse_r2_moyenne']}
print(json.dumps(compact, indent=2, ensure_ascii=False))
Passer du diagnostic à une décision
Commencez par vérifier que le modèle prédit suffisamment bien pour que l’analyse soit utile. Définissez ensuite les groupes de colonnes à partir de leur sens et des dépendances observées dans l’apprentissage. Une corrélation élevée peut justifier une inspection commune, mais elle ne dit pas à elle seule quelle colonne conserver.
Si vous décidez de supprimer des variables, réentraînez le modèle et validez cette nouvelle procédure sur les données de développement. Gardez un test indépendant pour le résultat final. L’importance d’un modèle déjà appris n’est pas le score qu’obtiendrait automatiquement un modèle réentraîné sans cette variable.
La permutation groupée préserve les dépendances à l’intérieur du groupe, mais peut casser celles qui relient ce groupe au reste du tableau. Un écart-type sur 30 permutations ne capture pas non plus l’incertitude liée à un autre échantillon ou à un autre entraînement. Ce sont des limites à documenter, pas des détails à masquer.
Les six tests vérifient les partitions, les couples conservés, les colonnes intactes, l’absence de modification du tableau source et un calcul indépendant de la baisse de R². Pour une explication locale d’une seule prédiction, poursuivez avec le guide SHAP/LIME ; pour l’évaluation, utilisez le cas de validation sans contamination du test.

