Fouille de données avec R pour la data science et l’intelligence
artificielle
III.TD 3 : Partie II - ANALYSE FACTORIELLE DISCRIMINANTE
Badr TAJINI – ESIEE Paris
Source : Bertrand Roudier – ESIEE Paris
Code complété par Thibault LE GUIDEVAIS, Elysee MUGABIRE et Ahmed DIAKITE
Ce TD a pour objectif de réaliser une analyse factorielle discriminante nous permettant :
Au total, l’AFD est analogue à une MANOVA, nous permettant de réaliser, de manière concomitante, une visualisation des données.
Pour y parvenir, nous utiliserons le jeu de données VIN_QUALITE.txt
library(MASS)
library(car)
library(ggplot2)
library(tidyverse)
library(broom)
library(kableExtra)
vin_data <- read.table("VIN_QUALITE.txt", header = TRUE, sep = "", dec = ".")
head(vin_data)
## TP Soleil Chaleur Pluie Qualite
## 1 3064 1201 10 361 moyen
## 2 3000 1053 11 338 mauvais
## 3 3155 1133 19 393 moyen
## 4 3085 970 4 467 mauvais
## 5 3245 1258 36 294 bon
## 6 3267 1386 35 225 bon
vin_data <- as.data.frame(vin_data)
vin_data$Qualite <- as.factor(vin_data$Qualite)
# Définition des variables nécessaires
N <- nrow(vin_data) # Nombre total d'individus
P <- ncol(vin_data) - 1 # Nombre de variables prédictives
X <- vin_data[, -ncol(vin_data)] # Variables prédictives
Y <- vin_data[, ncol(vin_data)] # Variable catégorielle (qualité du vin)
K <- length(unique(Y))
X <- as.data.frame(sapply(X, as.numeric))
X <- as.matrix(X)
Les résultats sont les suivants:
SS_Tot <- t(X - colMeans(X)) %*% (X - colMeans(X))
SS_Tot
## TP Soleil Chaleur Pluie
## TP 175597750 -32102913 -28354799 -90141329
## Soleil -32102913 52144836 -44776357 47617866
## Chaleur -28354799 -44776357 103298479 -7604084
## Pluie -90141329 47617866 -7604084 71000345
SS_Intra <- Reduce(`+`, lapply(split(as.data.frame(X), Y), function(df) {
df_matrix <- as.matrix(df)
t(df_matrix - colMeans(df_matrix)) %*% (df_matrix - colMeans(df_matrix))
}))
if (!all(dim(SS_Tot) == dim(SS_Intra))) {
stop("Erreur : SS_Tot et SS_Intra n'ont pas la même dimension !")
}
SS_Inter <- SS_Tot - SS_Intra
Comme nous l’avons vu en cours, l’analyse factorielle discriminante consiste à trouver une succession d’axes factoriels, tous orthogonaux entre eux et qui maximisent les projections des distances entre les groupes (cf. schéma suivant pour rappel)
Maximiser les distances entre les groupes revient à maximiser les projections suivantes :
ou bien
Fort heureusement, les deux méthodes conduisent aux mêmes résultats. Cependant, les approches sont légèrement différentes.
La méthode anglo-saxonne à un raisonnement analogue à la construction du test d’analyse de variance ou l’on teste le rapport Signal / Bruit (cf. cours page 11). Dans ce cadre, la projection des distances inter groupes est pondérée par les distances intra-groupes
La méthode française privilégie quant à elle la corrélation canonique c.a.d la part de variation liés au traitement (cf. cours page 11). Dans ce cadre, la projection des distances inter groupes est pondérée par la variation totale
Vous réaliserez une fonction permettant de réaliser une AFD selon la projection P1 (méthode anglo saxonne). Pour y parvenir, nous allons décrire les différentes étapes avec les résultats intermédiaires.
Nous calculons la matrice du ratio :
\(\frac{B}{W} = \frac{{S{S_{{\rm{inter}}}}}}{{S{S_{{\rm{intra}}}}}} = B \times {W^{ - 1}} = S{S_{{\rm{inter}}}} \times SS_{{\rm{intra}}}^{ - 1}\)
Attention la fraction ici correspond à une division matricielle et non à une division élément par élément !
Le résultat est le suivant :
BW_matrix <- SS_Inter %*% solve(SS_Intra)
BW_matrix
## TP Soleil Chaleur Pluie
## TP 1.205431 -2.233420 3.036666 -2.614384
## Soleil -1.419786 1.395692 -2.938808 2.272362
## Chaleur 1.279102 -2.219272 2.202497 -1.841402
## Pluie -1.681329 2.379451 -2.887687 1.486311
Nous calculons maintenant les vecteurs directeurs u des axes
factoriels et leur coefficient. De manière analogue à l’ACP, la
maximisation de \[{P_1} = {\max _u}\left(
{\frac{{{u^t}Bu}}{{{u^t}Wu}}} \right)\] revient à diagonaliser la
matrice \(S{S_{{\rm{inter}}}} \times
SS_{{\rm{intra}}}^{ - 1}\). Les vecteurs propres correspondent
alors aux vecteurs directeurs des axes factoriels. Ces derniers devront
être cependant normalisés.
Comme en ACP, les valeurs propres correspondent à la part de dispersion
expliquée par chaque axe.
La diagonalisation est réalisée à l’aide de la fonction eigen()
remarque importante : Les algorithmes utilisés peuvent conduire à des matrices de vecteurs propres dont les éléments sont des complexes. On ne retiendra donc que les parties réels en éliminant les parties complexes (utiliser la fonction Re)
eigen_res <- eigen(BW_matrix)
U <- Re(eigen_res$vectors) # On ne garde que la partie réelle
U
## [,1] [,2] [,3] [,4]
## [1,] -0.5466831 0.4148611 0.5061096 0.77291640
## [2,] 0.4844421 -0.6503169 0.5185018 0.08367022
## [3,] -0.4558778 -0.2917847 0.4901411 -0.55549043
## [4,] 0.5085557 0.5655438 0.4845314 -0.29500835
Contrairement à l’ACP où nous diagonalisons une matrice symétrique (matrice des variances covariances ou de corrélation), la matrice du ratio B/W n’est pas symétrique ce qui conduit à des résultats non exploitables car non normées. Pour y parvenir, nous normalisons la matrice des vecteurs propres :
norm_matrix <- sqrt(diag(t(U) %*% SS_Intra %*% U))
norm_matrix
## [1] 6368.447 9069.524 8023.832 15776.892
U_norm <- U / norm_matrix
U_norm
## [,1] [,2] [,3] [,4]
## [1,] -8.584245e-05 6.514322e-05 7.947143e-05 1.213665e-04
## [2,] 5.341428e-05 -7.170354e-05 5.716968e-05 9.225426e-06
## [3,] -5.681547e-05 -3.636476e-05 6.108567e-05 -6.923007e-05
## [4,] 3.223422e-05 3.584634e-05 3.071146e-05 -1.869876e-05
values <- Re(eigen_res$values) # On garde uniquement la partie réelle
values
## [1] 8.1488892 -0.9933066 -0.6447394 -0.2209124
values[values < 1e-7] <- 0
values
## [1] 8.148889 0.000000 0.000000 0.000000
Les parts de dispersion sont les suivantes :
dispersion_parts <- values / sum(values)
dispersion_parts
## [1] 1 0 0 0
Comme on peut le constater, le premier axe explique à lui seul prés de 96 % de la dispersion et le second 4% !.
Le calcul des coordonnées des individus sur les axes factoriels (appelées scores en anglais) s’effectue en réalisant la projection des observations X (centrées) sur les axes factoriels. Les scores sont simplement calculés comme suit :
\(Score = Z \times {U_{Norm}}\)
Sachant que pour chaque variable i de X avec \({Z_j} = {X_j} - {{\bar X}_j}\), il suffit donc de multiplier les données centrées Z par Unorm
Seuls les deux premiers axes factoriels sont à prendre en compte puisqu’ils représentent à eux seul l’intégralité de la dispersion et donc de “l’information contenu dans le tableau initial Z”.
Nous créons un data.frame Scores_df qui inclue les coordonnées des individus sur les deux premiers axes factoriels ainsi que les différentes classes auxquelles ils appartiennent (variable ‘Class’). On prendra soin de bien vérifier que l’entête de la variable (nom de la variable) soit égale à ‘Class’
## Axe1 Axe2 Class
## 1 0.006104079 -0.002453340 moyen
## 2 0.002894480 0.003128787 mauvais
## 3 -0.004819599 0.009170333 moyen
## 4 -0.004279591 0.019496085 mauvais
## 5 -0.010025685 0.001903292 bon
## 6 -0.007244536 -0.008278643 bon
Nous utilisons la fonction AFD_graph1 (fournie ci dessous) pour représenter les individus dans le plan factoriel. Les couleurs permettent de différentier les classes. les losanges représentent le centre de gravité des différentes classes
ggplot(Scores_df, aes(x = Axe1, y = Axe2, color = Class)) +
geom_point() +
geom_point(stat = "summary", fun = mean, shape = 18, size = 5, color = "black") +
theme_minimal() +
ggtitle("Projection des individus sur les axes factoriels")
Nous effectuons un test de Wilks. En analyse factorielle discriminante, il s’agit de tester successivement le caractère discriminant des axes vis à vis des différentes classes.
Pour y parvenir, nous calculons les corrélations canoniques de chaque axe. Soit \(\rho\), la valeur propre associée à chaque axe, la corrélation canonique est :
\[{\eta ^2} = \frac{\rho }{{1 + \rho }}\]
les corrélations canoniques des deux premiers axes sont:
eta_squared <- values / (1 + values) # Corrélations canoniques
Lambda <- prod(1 - eta_squared) # Statistique de Wilks
# Calcul de la statistique de test
chi_sq <- -(N - 1 - (P + K)/2) * log(Lambda)
p_value <- pchisq(chi_sq, df = P * (K - 1), lower.tail = FALSE)
# Affichage des résultats
data.frame(Axe = 1:length(values), Eta2 = eta_squared, Lambda = Lambda, Chi_sq = chi_sq, p_value = p_value)
## Axe Eta2 Lambda Chi_sq p_value
## 1 1 0.8906971 0.1093029 65.30216 4.205659e-11
## 2 2 0.0000000 0.1093029 65.30216 4.205659e-11
## 3 3 0.0000000 0.1093029 65.30216 4.205659e-11
## 4 4 0.0000000 0.1093029 65.30216 4.205659e-11
Dans ce TD, nous allons simplifier les hypothèses. Sous Ho, nous posons que les corrélations canoniques des axes factoriels retenus sont égales à zero versus H1: au moins une des corrélations différent.
Le test est le suivant:
\[\left\{ \begin{array}{l} {H_0}:\eta _{{\rm{axe 1}}}^2 = \eta _{{\rm{axe 2}}}^2 = 0\\ {H_1}:{\rm{ }}\eta _{{\rm{axe 1}}}^2{\rm{ et / ou }}\eta _{{\rm{axe 2}}}^2 \ne 0 \end{array} \right.\]
Nous calculons la quantité de Wilks \(Wilks = {\Lambda _i} = \prod\limits_{i = 1}^2 {(1 - \lambda _i^2)}\)
La quantité suivante suit une distribution de Chi-deux
\[ - \left( {n - 1 - \frac{{p + k}}{2}} \right)\log (\Lambda ) \to \chi _{p(k - 1)ddl}^2\]
avec:
Pour les deux premiers axes retenus (axe_selected = 2), L’ensemble des résultats de l’ADF sont résumés dans le tableau :
eta_squared <- values / (1 + values) # Corrélations canoniques des axes
Lambda <- prod(1 - eta_squared[1:2]) # Statistique de Wilks (on garde les 2 premiers axes)
# Calcul de la statistique de test
chi_sq <- -(N - 1 - (P + K)/2) * log(Lambda)
p_value <- pchisq(chi_sq, df = P * (K - 1), lower.tail = FALSE)
# Résumé des résultats sous forme de data frame
Wilks_test_results <- data.frame(Axe = 1:2, Eta2 = eta_squared[1:2], Lambda = Lambda, Chi_sq = chi_sq, p_value = p_value)
Wilks_test_results
## Axe Eta2 Lambda Chi_sq p_value
## 1 1 0.8906971 0.1093029 65.30216 4.205659e-11
## 2 2 0.0000000 0.1093029 65.30216 4.205659e-11
Les résultats précédents montrent que les deux axes discriminent parfaitement les classes (les probas associées à la discrimination sur chaque axe étant proche de zéro). Attention cela ne prédispose pas de la classification de chaque individus dans les différentes groupes (ce que nous verrons au prochains TD (TD final)).
Pour rappel, l’AFD est une méthode de classification supervisée. Le présent TD nous a permis de développer des scripts permettant:
De calculer les axes factoriels relatives aux projections du compromis B/W
De positionner les individus dans le plan factoriel ce qui permet de visualiser les positions des individus et des groupes les uns par rapport aux autres
De tester la qualité de la projection et de la discrimination des groupes sur les axes factoriels
Dans notre exemple, la qualité de discrimination des différents groupes selon les axes est excellente.
Le prochain TD aura pour objectif d’utiliser les coordonnées des observations dans le plan factoriel pour réaliser un classifieur supervisée dont nous testerons la qualité à l’aide d’une matrice des confusions.
Remarque : En cas de non discrimination des classes par les axes factoriels (acceptation de Ho et rejet de H1), il est évident qu’il n’est pas possible d’utiliser les projections des individus sur les axes factoriels pour réaliser un classifieur. Dans ce cas, la classification supervisée n’est pas réalisable par cette méthode
Nous créons une fonction générique que nous nommons AFD. Les arguments de la fonction sont les suivants :
AFD <- function(X,Y,SS_tot, SS_intra, SS_inter, nb_axes = 2)
X et Y sont respectivement les variables prédictives et Y la variable à prédire
SS_tot, SS_intra, SS_inter sont les sommes des carrés calculées à partir de la fonction MANOVA que vous avez développée
nb_axes est le nombre d’axes sélectionné pour réaliser les projections. Par défaut, il est égal à 2 (projection dans un plan)
Cette fonction retourne une liste dont les éléments sont les suivants (sous forme de data frame):
les vecteurs propres normalisés (appelés aussi loading factors en anglais)
les valeur propres
les scores
le tableau des résultats du test de Wilks
Le data frame des Scores calculés à partir da la fonction AFD corresppondent à l’argument de la fonction AFD_graph1
Le script final (avec les résultats) doit être le suivant :
AFD <- function(X, Y, SS_tot, SS_intra, SS_inter, nb_axes = 2) {
BW_matrix <- SS_inter %*% solve(SS_intra)
eigen_res <- eigen(BW_matrix)
U <- Re(eigen_res$vectors)
values <- Re(eigen_res$values)
norm_matrix <- sqrt(diag(t(U) %*% SS_intra %*% U))
U_norm <- U / norm_matrix
Z <- scale(X, center = TRUE, scale = FALSE)
Scores <- Z %*% U_norm
Scores_df <- data.frame(Scores[, 1:nb_axes], Class = Y)
eta_squared <- values / (1 + values)
Lambda <- prod(1 - eta_squared[1:nb_axes])
chi_sq <- -(nrow(X) - 1 - (ncol(X) + length(unique(Y)))/2) * log(Lambda)
p_value <- pchisq(chi_sq, df = ncol(X) * (length(unique(Y)) - 1), lower.tail = FALSE)
return(list(
Vectors = U_norm,
Values = values,
Scores = Scores_df,
Wilks = data.frame(Axe = 1:nb_axes, Lambda = Lambda, Chi_sq = chi_sq, p_value = p_value)
))
}
Pour valider votre fonction, vous utiliserez le fichier iris fourni par défaut en R.
Les résultats sont les suivants :
# Chargement des données iris
iris_df <- iris
# Séparation des variables prédictives X et de la variable catégorielle Y
X_iris <- iris_df[, -ncol(iris_df)] # Sélectionne toutes les colonnes sauf la dernière
Y_iris <- iris_df[, ncol(iris_df)] # Sélectionne la colonne des espèces
X_iris <- as.data.frame(sapply(X_iris, as.numeric)) # Convertir en numérique
X_iris <- as.matrix(X_iris) # Convertir en matrice pour les calculs matriciels
# Calcul des sommes des carrés
SS_tot_iris <- t(X_iris - colMeans(X_iris)) %*% (X_iris - colMeans(X_iris))
SS_intra_iris <- Reduce(`+`, lapply(split(as.data.frame(X_iris), Y_iris), function(df) {
df_matrix <- as.matrix(df)
t(df_matrix - colMeans(df_matrix)) %*% (df_matrix - colMeans(df_matrix))
}))
SS_inter_iris <- SS_tot_iris - SS_intra_iris
# Application de la fonction AFD
afd_result_iris <- AFD(X_iris, Y_iris, SS_tot_iris, SS_intra_iris, SS_inter_iris)
# Affichage des résultats
afd_result_iris$Wilks
## Axe Lambda Chi_sq p_value
## 1 1 1.091967 -12.80112 1
## 2 2 1.091967 -12.80112 1
afd_result_iris$Vectors
## [,1] [,2] [,3] [,4]
## [1,] -0.011376712 0.008616117 -0.022169969 2.262659e-02
## [2,] -0.005121466 0.025115474 0.004631096 -9.701873e-03
## [3,] -0.016236604 -0.002730887 -0.004995955 -5.836629e-05
## [4,] -0.011375610 0.010167671 0.013734741 -1.282845e-02
afd_result_iris$Values
## [1] 4.328872e-01 -3.608855e-01 -1.234179e-02 4.807376e-05
afd_result_iris$Scores
## X1 X2 Class
## 1 5.584352e-02 9.916758e-04 setosa
## 2 6.067960e-02 -1.328928e-02 setosa
## 3 6.355431e-02 -9.716324e-03 setosa
## 4 6.195681e-02 -1.363566e-02 setosa
## 5 5.646905e-02 2.641611e-03 setosa
## 6 4.323582e-02 1.483697e-02 setosa
## 7 6.090647e-02 -4.811163e-03 setosa
## 8 5.586968e-02 -2.654572e-03 setosa
## 9 6.688010e-02 -2.010889e-02 setosa
## 10 5.968135e-02 -1.206759e-02 setosa
## 11 4.978256e-02 8.326517e-03 setosa
## 12 5.652136e-02 -4.650884e-03 setosa
## 13 6.295483e-02 -1.516766e-02 setosa
## 14 7.351417e-02 -1.865646e-02 setosa
## 15 4.856641e-02 2.012687e-02 setosa
## 16 4.050940e-02 3.052572e-02 setosa
## 17 4.973046e-02 1.592932e-02 setosa
## 18 5.470596e-02 2.008443e-03 setosa
## 19 4.147252e-02 1.389349e-02 setosa
## 20 5.154586e-02 9.269996e-03 setosa
## 21 4.807168e-02 2.456973e-04 setosa
## 22 5.092045e-02 7.775216e-03 setosa
## 23 6.751438e-02 2.875197e-04 setosa
## 24 4.858415e-02 -1.800384e-03 setosa
## 25 5.165038e-02 -5.470150e-03 setosa
## 26 5.629461e-02 -1.297385e-02 setosa
## 27 5.197090e-02 -8.941264e-04 setosa
## 28 5.308219e-02 1.580199e-03 setosa
## 29 5.521800e-02 -6.582598e-04 setosa
## 30 5.868333e-02 -1.053559e-02 setosa
## 31 5.805780e-02 -1.218553e-02 setosa
## 32 4.904388e-02 2.825409e-03 setosa
## 33 5.114687e-02 1.563272e-02 setosa
## 34 4.770781e-02 2.201895e-02 setosa
## 35 5.854379e-02 -1.105083e-02 setosa
## 36 6.176496e-02 -6.858400e-03 setosa
## 37 5.291650e-02 4.711211e-03 setosa
## 38 5.874428e-02 7.632327e-04 setosa
## 39 6.799162e-02 -1.732425e-02 setosa
## 40 5.473201e-02 -1.792960e-03 setosa
## 41 5.746730e-02 1.419920e-03 setosa
## 42 6.930141e-02 -3.302671e-02 setosa
## 43 6.696732e-02 -1.230116e-02 setosa
## 44 4.918363e-02 3.650955e-03 setosa
## 45 4.391366e-02 9.194408e-03 setosa
## 46 6.067971e-02 -1.313413e-02 setosa
## 47 5.105976e-02 7.980140e-03 setosa
## 48 6.306832e-02 -1.085102e-02 setosa
## 49 5.092023e-02 7.464905e-03 setosa
## 50 5.800549e-02 -4.893031e-03 setosa
## 51 -3.146731e-02 1.301693e-02 versicolor
## 52 -2.253153e-02 9.410207e-03 versicolor
## 53 -3.420238e-02 1.011436e-02 versicolor
## 54 2.710259e-03 -2.161631e-02 versicolor
## 55 -2.324427e-02 -4.745916e-05 versicolor
## 56 -1.024412e-02 -8.700798e-03 versicolor
## 57 -2.629088e-02 1.153073e-02 versicolor
## 58 2.380245e-02 -2.541312e-02 versicolor
## 59 -2.261897e-02 1.292166e-03 versicolor
## 60 4.560785e-03 -1.286510e-02 versicolor
## 61 2.146604e-02 -3.514387e-02 versicolor
## 62 -1.094790e-02 8.983204e-04 versicolor
## 63 9.467321e-04 -2.287010e-02 versicolor
## 64 -1.969183e-02 -2.272214e-03 versicolor
## 65 4.994349e-03 -4.593064e-03 versicolor
## 66 -2.267117e-02 8.739817e-03 versicolor
## 67 -1.240586e-02 -2.505781e-03 versicolor
## 68 -9.623188e-04 -1.230868e-02 versicolor
## 69 -1.513472e-02 -1.742849e-02 versicolor
## 70 4.447077e-03 -1.749205e-02 versicolor
## 71 -2.512683e-02 7.333184e-03 versicolor
## 72 -6.676502e-03 -3.888908e-03 versicolor
## 73 -2.430347e-02 -1.012459e-02 versicolor
## 74 -1.690456e-02 -6.817296e-03 versicolor
## 75 -1.547264e-02 3.882085e-04 versicolor
## 76 -2.102135e-02 5.366658e-03 versicolor
## 77 -2.876704e-02 9.744313e-04 versicolor
## 78 -3.531367e-02 7.640038e-03 versicolor
## 79 -1.644440e-02 -1.570882e-03 versicolor
## 80 1.042946e-02 -1.404331e-02 versicolor
## 81 7.720555e-03 -2.059212e-02 versicolor
## 82 1.048178e-02 -2.133580e-02 versicolor
## 83 9.879954e-06 -9.728969e-03 versicolor
## 84 -2.629963e-02 -7.215742e-03 versicolor
## 85 -1.013052e-02 -4.229004e-03 versicolor
## 86 -2.014269e-02 1.200362e-02 versicolor
## 87 -2.867971e-02 8.937318e-03 versicolor
## 88 -1.288575e-02 -1.581578e-02 versicolor
## 89 -3.636099e-03 -3.446960e-03 versicolor
## 90 1.685966e-03 -1.659322e-02 versicolor
## 91 -4.183262e-03 -1.619079e-02 versicolor
## 92 -1.858032e-02 5.124217e-04 versicolor
## 93 -1.101634e-03 -1.251360e-02 versicolor
## 94 2.317692e-02 -2.706305e-02 versicolor
## 95 -3.723320e-03 -1.125469e-02 versicolor
## 96 -5.259870e-03 -3.875204e-03 versicolor
## 97 -5.885284e-03 -5.369985e-03 versicolor
## 98 -1.319730e-02 -1.335015e-03 versicolor
## 99 2.474838e-02 -1.934231e-02 versicolor
## 100 -3.749477e-03 -7.608443e-03 versicolor
## 101 -5.763652e-02 1.713148e-02 virginica
## 102 -2.743697e-02 -5.888664e-03 virginica
## 103 -5.902754e-02 1.269575e-02 virginica
## 104 -4.113036e-02 1.060279e-03 virginica
## 105 -5.171542e-02 8.815941e-03 virginica
## 106 -7.608152e-02 1.509219e-02 virginica
## 107 -4.156553e-03 -1.906127e-02 virginica
## 108 -6.387270e-02 7.764774e-03 virginica
## 109 -4.687978e-02 -6.085641e-03 virginica
## 110 -7.103566e-02 3.214754e-02 virginica
## 111 -3.909896e-02 1.371712e-02 virginica
## 112 -3.751032e-02 -1.265171e-03 virginica
## 113 -4.911989e-02 1.120327e-02 virginica
## 114 -2.478891e-02 -1.048351e-02 virginica
## 115 -3.363692e-02 1.706719e-03 virginica
## 116 -4.462130e-02 1.535963e-02 virginica
## 117 -4.229419e-02 5.568138e-03 virginica
## 118 -8.407759e-02 3.678986e-02 virginica
## 119 -8.231671e-02 7.121882e-03 virginica
## 120 -2.097768e-02 -2.051716e-02 virginica
## 121 -5.680429e-02 1.857534e-02 virginica
## 122 -2.356402e-02 -3.537395e-03 virginica
## 123 -7.668100e-02 9.640853e-03 virginica
## 124 -2.874045e-02 -2.051195e-03 virginica
## 125 -5.276598e-02 1.733013e-02 virginica
## 126 -5.940048e-02 1.525707e-02 virginica
## 127 -2.649126e-02 -1.281703e-04 virginica
## 128 -2.800154e-02 3.760224e-03 virginica
## 129 -4.516857e-02 2.460644e-03 virginica
## 130 -5.285375e-02 8.746620e-03 virginica
## 131 -6.238846e-02 7.677783e-03 virginica
## 132 -7.920683e-02 3.729882e-02 virginica
## 133 -4.630613e-02 3.477412e-03 virginica
## 134 -2.908723e-02 -3.136126e-03 virginica
## 135 -3.276834e-02 -1.226466e-02 virginica
## 136 -7.137601e-02 1.935278e-02 virginica
## 137 -5.051646e-02 1.971862e-02 virginica
## 138 -4.166867e-02 7.218074e-03 virginica
## 139 -2.524021e-02 3.171701e-03 virginica
## 140 -4.914604e-02 1.484952e-02 virginica
## 141 -5.353071e-02 1.563042e-02 virginica
## 142 -4.655019e-02 1.770232e-02 virginica
## 143 -2.743697e-02 -5.888664e-03 virginica
## 144 -5.891394e-02 1.716755e-02 virginica
## 145 -5.731622e-02 2.139720e-02 virginica
## 146 -4.538636e-02 1.319446e-02 virginica
## 147 -3.047737e-02 -6.330611e-03 virginica
## 148 -3.969833e-02 8.420939e-03 virginica
## 149 -4.499391e-02 1.838642e-02 virginica
## 150 -2.897352e-02 1.490823e-03 virginica
manova_res <- manova(as.matrix(X_iris) ~ Y_iris)
summary(manova_res, test = 'Wilks')
## Df Wilks approx F num Df den Df Pr(>F)
## Y_iris 2 0.023439 199.15 8 288 < 2.2e-16 ***
## Residuals 147
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1