Interprétez une ACP et découvrez ses cousines
Le calcul du chapitre précédent a produit des valeurs propres, des vecteurs propres et des coordonnées. Mais que disent-ils des villes ? Brest et Rennes se ressemblent-elles vraiment ? Que signifie le second axe ? Et peut-on faire confiance à la position de chaque point sur le graphique ?
Ce chapitre vous donne les outils de lecture d’une ACP : le nuage des individus et ses deux indicateurs (qualité de représentation et contribution), le cercle des corrélations pour les variables, et les variables illustratives pour expliquer les axes. Nous terminerons par un aperçu des méthodes cousines de l’ACP. Le fil rouge reste celui des six villes et de leurs températures par saison.
Lisez le nuage des individus
Section intitulée « Lisez le nuage des individus »Rappelons les résultats : le premier axe explique 81,8 % de l’inertie, le second 17,9 %, soit 99,7 % pour le plan. Voici les coordonnées des villes sur ces deux axes, ainsi que la carte qui les représente.
| Ville | cos² axe 1 | cos² axe 2 | cos² plan | CTR axe 1 | CTR axe 2 | ||
|---|---|---|---|---|---|---|---|
| Brest | −1,327 | 1,367 | 0,485 | 0,515 | 1,000 | 9,0 % | 43,6 % |
| Rennes | −1,112 | 0,533 | 0,809 | 0,186 | 0,995 | 6,3 % | 6,6 % |
| Strasbourg | −2,134 | −0,959 | 0,831 | 0,168 | 0,998 | 23,2 % | 21,5 % |
| Lyon | −0,322 | −0,949 | 0,103 | 0,893 | 0,995 | 0,5 % | 21,0 % |
| Nice | 2,495 | 0,398 | 0,971 | 0,025 | 0,995 | 31,7 % | 3,7 % |
| Marseille | 2,399 | −0,391 | 0,970 | 0,026 | 0,996 | 29,3 % | 3,6 % |
Le premier axe range les villes de la plus froide (Strasbourg, à gauche) aux plus chaudes (Nice et Marseille, à droite). Le second place Brest tout en haut et Strasbourg et Lyon en bas. Deux villes proches sur la carte, comme Nice et Marseille, ont des profils de températures semblables… à une condition, que nous allons voir.
La qualité de représentation : le cos²
Section intitulée « La qualité de représentation : le cos² »Une projection peut rapprocher deux points qui sont loin dans l’espace d’origine, comme deux étoiles alignées dans le ciel qui sont en fait à des distances très différentes de la Terre. Pour savoir si un individu est bien représenté sur un axe, on mesure la part de sa distance au centre qui est conservée par la projection :
où est la coordonnée de l’individu sur l’axe et le carré de sa distance au centre dans l’espace complet. C’est le carré du cosinus de l’angle entre le vecteur et l’axe. Les axes étant orthogonaux, les cos² s’additionnent : le cos² d’un plan est la somme des cos² de ses deux axes. Un cos² proche de 1 signifie « bien représenté », proche de 0 « mal représenté ».
Ici, toutes les villes ont un cos² sur le plan supérieur à 0,99 : la carte est fidèle. Mais regardez Lyon : son cos² sur l’axe 1 n’est que de 0,103. Sa coordonnée proche de zéro sur cet axe indique seulement qu’elle n’est ni particulièrement chaude ni particulièrement froide sur l’année ; ce qui la distingue vraiment des autres villes se lit sur l’axe 2 (cos² de 0,893). Juger Lyon sur le seul axe 1, ce serait passer à côté de l’essentiel.
La contribution : qui construit l’axe ?
Section intitulée « La contribution : qui construit l’axe ? »La contribution d’un individu à un axe mesure la part de l’inertie de l’axe qui vient de lui :
où est le nombre d’individus et la valeur propre de l’axe. Pour chaque axe, les contributions somment à 100 %. Si tous les individus pesaient autant, chacun contribuerait pour , soit environ 17 % avec six villes. Les individus dont la contribution dépasse nettement cette moyenne sont ceux qui caractérisent l’axe.
Ici, l’axe 1 est construit par Nice (31,7 %), Marseille (29,3 %) et Strasbourg (23,2 %) : l’opposition entre le Sud chaud et l’Est froid. L’axe 2 est dominé par Brest (43,6 %), à lui seul près de la moitié de l’inertie de l’axe.
Lisez le nuage des variables : le cercle des corrélations
Section intitulée « Lisez le nuage des variables : le cercle des corrélations »Pour comprendre ce que mesure chaque axe, on regarde ses liens avec les variables d’origine. On calcule la corrélation entre chaque variable et chaque composante principale. En ACP normée, elle vaut
où est la -ième coordonnée du vecteur propre . Chaque variable devient alors une flèche dont les coordonnées sont ses corrélations avec les axes 1 et 2 : c’est le cercle des corrélations.
| Variable | corrélation avec l’axe 1 | corrélation avec l’axe 2 |
|---|---|---|
| Hiver | 0,790 | 0,613 |
| Printemps | 0,979 | −0,188 |
| Été | 0,847 | −0,530 |
| Automne | 0,986 | 0,151 |
Les flèches pleines sont les variables actives ; les flèches en pointillés sont des variables illustratives, que nous verrons plus bas. La lecture suit quelques règles simples.
- Une flèche proche du cercle désigne une variable bien projetée : l’essentiel de sa variance est capté par le plan. Ici, les quatre saisons touchent presque le cercle (leurs corrélations au carré, sommées sur les deux axes, dépassent 0,99).
- Deux flèches proches (angle aigu) désignent des variables corrélées positivement ; opposées, corrélées négativement ; à angle droit, non corrélées. L’angle entre deux flèches approche l’angle entre les variables, dont le cosinus est leur corrélation.
- L’axe 1 est corrélé positivement à toutes les saisons, de 0,79 à 0,99 : c’est un effet taille. Une ville à droite est chaude toute l’année, une ville à gauche froide toute l’année, par comparaison avec les autres villes.
- L’axe 2 oppose l’hiver (0,613) à l’été (−0,530). En haut, les villes à l’hiver doux et à l’été frais (Brest) ; en bas, les hivers froids et les étés chauds (Strasbourg, Lyon). L’axe 2 mesure l’amplitude thermique : faible en haut, forte en bas.
import numpy as np
# acp et Z viennent du chapitre précédent ; on refait les valeurs propres « divisées par n »valeurs_propres = acp.explained_variance_ * (len(Z) - 1) / len(Z)correlations = acp.components_.T * np.sqrt(valeurs_propres) # une ligne par variableprint(correlations[:, :2].round(3))Ce cercle a une propriété remarquable, la dualité : les axes qui représentent au mieux le nuage des variables (en maximisant la somme des carrés des corrélations avec les variables) ont exactement les mêmes valeurs propres que ceux du nuage des individus. On vérifie ici que . Les deux lectures sont les deux faces d’un même calcul.
Expliquez les axes avec des variables illustratives
Section intitulée « Expliquez les axes avec des variables illustratives »Une variable illustrative (ou supplémentaire) ne participe pas au calcul des axes : on la projette après coup, pour aider à les interpréter.
Une variable illustrative quantitative se place sur le cercle grâce à ses corrélations avec les composantes. Ajoutons la latitude des six villes : sa corrélation vaut −0,974 avec l’axe 1 et 0,201 avec l’axe 2. La flèche pointe vers la gauche : plus une ville est au nord, plus elle est froide toute l’année. Ajoutons aussi l’amplitude thermique (été moins hiver) : corrélation 0,041 avec l’axe 1 et −0,998 avec l’axe 2. L’interprétation du second axe est confirmée de façon éclatante.
Une variable illustrative qualitative se représente par ses modalités, chacune placée au barycentre des individus qui la portent. Avec une variable « climat », les villes atlantiques (Brest, Rennes) ont pour barycentre le point , les villes continentales (Strasbourg, Lyon) le point et les villes méditerranéennes (Nice, Marseille) le point . Les deux premiers climats ne se distinguent pas sur l’axe 1, mais s’opposent nettement sur l’axe 2.
Une application célèbre : les visages propres
Section intitulée « Une application célèbre : les visages propres »En 1991, une méthode de reconnaissance faciale a popularisé l’ACP sous le nom d’eigenfaces (visages propres). Chaque image de visage est un vecteur de pixels. On calcule le visage moyen , on centre chaque visage (), puis on cherche les vecteurs propres de la matrice de covariance des visages centrés. Ces vecteurs, remis en forme d’image, ressemblent à des visages fantomatiques : ce sont les visages propres. Tout visage s’écrit alors approximativement comme le visage moyen plus une combinaison de quelques dizaines de visages propres, et l’on ne stocke que les poids .
Le gain est considérable. Stocker 100 000 images de pixels (un octet par pixel) demande octets. Stocker 100 visages propres et, pour chaque image, 100 poids codés sur 4 octets, n’en demande que , soit environ 140 fois moins.
Une astuce de calcul rend la méthode praticable : la matrice de covariance (où contient les visages centrés en colonnes) a la taille du nombre de pixels au carré, soit plus de 4 milliards de termes. On diagonalise plutôt la petite matrice , de taille , et l’on remonte aux vecteurs propres par ; ou, plus simplement, on calcule la SVD de .
Les cousines de l’ACP
Section intitulée « Les cousines de l’ACP »L’ACP ne traite que des variables quantitatives, et seulement des structures linéaires. D’autres méthodes prennent le relais.
- L’analyse factorielle des correspondances (AFC) étudie un tableau de contingence croisant deux variables qualitatives (par exemple la catégorie socioprofessionnelle et le journal lu). Elle compare les profils des lignes et des colonnes avec la distance du , et représente sur une même carte les modalités qui s’attirent.
- L’analyse des correspondances multiples (ACM) généralise l’AFC à plusieurs variables qualitatives, typiquement les réponses à un questionnaire. Elle travaille sur le tableau disjonctif complet (un encodage one-hot de toutes les réponses).
- t-SNE (t-distributed stochastic neighbor embedding) est une méthode non linéaire conçue pour la visualisation. Elle cherche une carte en 2D où les voisins proches dans l’espace d’origine restent voisins. Elle révèle très bien des groupes, mais les distances entre groupes éloignés et la taille apparente des groupes ne sont pas fiables, le résultat dépend d’un réglage (la perplexité) et du hasard, et elle ne sait pas projeter de nouveaux individus. UMAP poursuit le même but, plus rapidement.
| Critère | ACP | t-SNE |
|---|---|---|
| Type | linéaire | non linéaire |
| Résultat | déterministe (au signe près) | dépend du hasard et de la perplexité |
| Axes | interprétables (cercle des corrélations) | sans signification |
| Distances globales | conservées au mieux | non fiables |
| Nouveaux individus | se projettent directement | il faut tout recalculer |
| Usage type | résumer, décorréler, compresser | visualiser des groupes |
En résumé
Section intitulée « En résumé »- Sur la carte des individus, deux points proches ne se ressemblent que s’ils sont bien représentés : le cos² mesure cette qualité, et s’additionne sur les axes.
- La contribution désigne les individus qui construisent un axe.
- Le cercle des corrélations place chaque variable selon ses corrélations avec les axes ; seules les flèches proches du cercle se lisent.
- Sur les villes, l’axe 1 est un effet taille (chaud ou froid toute l’année), l’axe 2 l’amplitude thermique.
- Les variables illustratives (quantitatives sur le cercle, qualitatives au barycentre) aident à interpréter sans modifier les axes.
- L’AFC et l’ACM traitent des variables qualitatives ; t-SNE et UMAP visualisent des structures non linéaires, sans axes interprétables.
Exercices
Section intitulée « Exercices »Exercice · Lire un cercle des corrélations
facileDans une ACP normée sur des performances sportives, le cercle des corrélations montre : « 100 m » en , « saut en longueur » en , « 1 500 m » en et « lancer de poids » en . Quelles variables sont bien projetées ? Lesquelles sont corrélées ? Que peut-on dire du lancer de poids ?
Voir la solution
On calcule la somme des carrés des deux coordonnées (la qualité de projection sur le plan) :
- 100 m : , bien projeté ;
- saut en longueur : , assez bien projeté ;
- 1 500 m : , bien projeté ;
- lancer de poids : , très mal projeté.
Le 100 m et le saut en longueur pointent dans des directions presque opposées : ils sont corrélés négativement. C’est logique, un bon sprinteur a un temps faible et saute loin. Le 1 500 m fait avec eux des angles d’environ 64° et 113°, loin de 0° comme de 180° : il n’est que faiblement corrélé avec eux. On ne peut rien dire du lancer de poids à partir de ce plan : sa flèche est trop courte, sa variance se trouve sur d’autres axes.
En code
import numpy as np
fleches = {"100 m": (-0.85, 0.30), "longueur": (0.82, -0.25), "1 500 m": (-0.10, 0.90), "poids": (0.15, 0.20)}for nom, (x, y) in fleches.items(): print(nom, round(x**2 + y**2, 2)) # qualité : 0.81 0.73 0.82 0.06
def angle(a, b): a, b = np.array(fleches[a]), np.array(fleches[b]) return np.degrees(np.arccos(a @ b / np.linalg.norm(a) / np.linalg.norm(b)))
# Le produit scalaire des flèches approche la corrélation (variables bien projetées)for a, b in [("100 m", "longueur"), ("100 m", "1 500 m"), ("longueur", "1 500 m")]: print(a, b, round(angle(a, b)), round(np.dot(fleches[a], fleches[b]), 2))# 100 m / longueur : 178 degrés, -0.77# 100 m / 1 500 m : 64 degrés, 0.36 ; longueur / 1 500 m : 113 degrés, -0.31fleches <- rbind("100 m" = c(-0.85, 0.30), longueur = c(0.82, -0.25), "1 500 m" = c(-0.10, 0.90), poids = c(0.15, 0.20))round(rowSums(fleches^2), 2) # qualité : 0.81 0.73 0.82 0.06
angle <- function(a, b) { u <- fleches[a, ]; v <- fleches[b, ] acos(sum(u * v) / sqrt(sum(u^2) * sum(v^2))) * 180 / pi}# Le produit scalaire des flèches approche la corrélation (variables bien projetées)round(c(angle("100 m", "longueur"), sum(fleches["100 m", ] * fleches["longueur", ])), 2)# 178 degrés, -0.77round(c(angle("100 m", "1 500 m"), sum(fleches["100 m", ] * fleches["1 500 m", ])), 2)# 64 degrés, 0.36round(c(angle("longueur", "1 500 m"), sum(fleches["longueur", ] * fleches["1 500 m", ])), 2)# 113 degrés, -0.31Exercice · Calculer un cos² et une contribution
moyenDans l’ACP des six villes, Lyon a pour coordonnées et , et le carré de sa distance au centre vaut . Les valeurs propres sont et . Calculez le cos² de Lyon sur chaque axe et sur le plan, puis ses contributions aux deux axes. Commentez.
Voir la solution
- .
- .
- Sur le plan : (0,995 avec les valeurs non arrondies).
- , soit 0,5 %.
- , soit 21,0 %.
Lyon est parfaitement représentée sur le plan, mais presque uniquement par l’axe 2 : sa position au milieu de l’axe 1 correspond à une température annuelle moyenne parmi ces villes, tandis que sa forte amplitude thermique (hiver froid, été chaud) la place en bas de l’axe 2, qu’elle contribue à construire pour un cinquième.
En code
F1, F2, d2 = -0.322, -0.949, 1.008 # Lyonlambda1, lambda2, I = 3.273, 0.714, 6
cos2_1, cos2_2 = F1**2 / d2, F2**2 / d2print(round(cos2_1, 3), round(cos2_2, 3), round(cos2_1 + cos2_2, 3)) # 0.103 0.893 0.996print(round(F1**2 / I / lambda1, 3), round(F2**2 / I / lambda2, 3)) # 0.005 0.21
# Avec l'ACP complète (valeurs non arrondies)import numpy as npX = np.array([[7.0, 10.5, 16.5, 12.0], [5.5, 11.0, 18.0, 12.0], [1.5, 10.5, 19.5, 10.0], [3.5, 12.0, 21.5, 12.5], [9.5, 13.5, 23.0, 17.0], [8.0, 14.0, 24.0, 16.0]])Z = (X - X.mean(axis=0)) / X.std(axis=0)lambdas, U = np.linalg.eigh(Z.T @ Z / I)lambdas, U = lambdas[::-1], U[:, ::-1] # ordre décroissantF = Z @ Ulyon = 3cos2 = F[lyon, :2] ** 2 / (Z[lyon] ** 2).sum()print(cos2.round(3), round(cos2.sum(), 3)) # [0.103 0.893] 0.995F1 <- -0.322; F2 <- -0.949; d2 <- 1.008 # Lyonlambda1 <- 3.273; lambda2 <- 0.714; I <- 6
cos2 <- c(F1^2, F2^2) / d2round(c(cos2, sum(cos2)), 3) # 0.103 0.893 0.996round(c(F1^2 / I / lambda1, F2^2 / I / lambda2), 3) # 0.005 0.21
# Avec l'ACP complète (valeurs non arrondies)X <- matrix(c(7.0, 10.5, 16.5, 12.0, 5.5, 11.0, 18.0, 12.0, 1.5, 10.5, 19.5, 10.0, 3.5, 12.0, 21.5, 12.5, 9.5, 13.5, 23.0, 17.0, 8.0, 14.0, 24.0, 16.0), ncol = 4, byrow = TRUE)Z <- scale(X, scale = apply(X, 2, function(x) sqrt(mean((x - mean(x))^2))))F <- Z %*% eigen(t(Z) %*% Z / I)$vectorscos2_lyon <- F[4, 1:2]^2 / sum(Z[4, ]^2)round(c(cos2_lyon, sum(cos2_lyon)), 3) # 0.103 0.893 0.995Exercice · Démontrer la formule des corrélations
difficileEn ACP normée, montrez que la corrélation entre la variable standardisée (la colonne de ) et la composante vaut . Vérifiez-la pour l’hiver sur l’axe 1, sachant que et .
Voir la solution
Les deux variables sont centrées ; a un écart-type de 1 et un écart-type de (chapitre 5). La corrélation est donc
Or est la -ième ligne de , si bien que est la -ième coordonnée de , c’est-à-dire . Finalement :
Vérification pour l’hiver : , ce qui correspond (aux arrondis près) à la valeur 0,790 du tableau. On en déduit aussi que , puisque est unitaire : c’est la dualité entre les deux nuages.
En code
import numpy as np
X = np.array([[7.0, 10.5, 16.5, 12.0], [5.5, 11.0, 18.0, 12.0], [1.5, 10.5, 19.5, 10.0], [3.5, 12.0, 21.5, 12.5], [9.5, 13.5, 23.0, 17.0], [8.0, 14.0, 24.0, 16.0]])I = len(X)Z = (X - X.mean(axis=0)) / X.std(axis=0)lambdas, U = np.linalg.eigh(Z.T @ Z / I)l1, u1 = lambdas[-1], U[:, -1] * np.sign(U[0, -1]) # plus grande valeur propre, signe fixéF1 = Z @ u1
r_direct = np.corrcoef(Z[:, 0], F1)[0, 1] # corrélation hiver / axe 1print(round(r_direct, 3), round(np.sqrt(l1) * u1[0], 3)) # 0.79 0.79print(round(np.sqrt(3.273) * 0.437, 3)) # 0.791 avec les valeurs arrondiesr_toutes = np.sqrt(l1) * u1print(round((r_toutes ** 2).sum(), 3), round(l1, 3)) # 3.273 3.273 : la dualitéX <- matrix(c(7.0, 10.5, 16.5, 12.0, 5.5, 11.0, 18.0, 12.0, 1.5, 10.5, 19.5, 10.0, 3.5, 12.0, 21.5, 12.5, 9.5, 13.5, 23.0, 17.0, 8.0, 14.0, 24.0, 16.0), ncol = 4, byrow = TRUE)I <- nrow(X)Z <- scale(X, scale = apply(X, 2, function(x) sqrt(mean((x - mean(x))^2))))e <- eigen(t(Z) %*% Z / I)l1 <- e$values[1]; u1 <- e$vectors[, 1] * sign(e$vectors[1, 1]) # signe fixéF1 <- Z %*% u1
round(c(cor(Z[, 1], F1), sqrt(l1) * u1[1]), 3) # 0.79 0.79 (cor() ne dépend pas de n ou n - 1)round(sqrt(3.273) * 0.437, 3) # 0.791 avec les valeurs arrondiesround(c(sum((sqrt(l1) * u1)^2), l1), 3) # 3.273 3.273 : la dualité