Environnement et package¶
In [66]:
# activer l'environnement
using Pkg
Pkg.activate("env_julia_acp")
Activating project at `c:\Users\ricco\Desktop\demo\env_julia_acp`
In [67]:
# liste des packages installés
Pkg.status()
Status `C:\Users\ricco\Desktop\demo\env_julia_acp\Project.toml` [a93c6f00] DataFrames v1.8.2 [be5e9834] FactorRotations v0.5.2 [7073ff75] IJulia v1.34.4 [add582a8] MLJ v0.23.2 [1b6a4a23] MLJMultivariateStatsInterface v0.5.4 [23777cdb] MLJTransforms v0.1.5 [6f286f6a] MultivariateStats v0.10.4 [91a5bcdd] Plots v1.41.6 [30f210dd] ScientificTypesBase v3.1.0 [f3b207a7] StatsPlots v0.15.8 [fdbf4ff8] XLSX v0.11.6
Importation et préparation des données¶
Importation, variables actives¶
In [68]:
# packages
import DataFrames as DFR
import XLSX
# lecture des données
df = DFR.DataFrame(XLSX.readtable("./autos_acp_livre.xlsx"))
# premières lignes
println(DFR.size(df))
(18, 10)
In [69]:
# premières lignes
DFR.first(df,5)
5×10 DataFrame
| Row | Modele | FINITION | PRIX | R_POID_PUIS | CYL | PUISS | LONG | LARG | POIDS | V_MAX |
|---|---|---|---|---|---|---|---|---|---|---|
| String | String | Int64 | Float64 | Int64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | Alfasud TI | 2_B | 30570 | 11.0127 | 1350 | 79 | 393 | 161 | 870 | 165 |
| 2 | Audi 100 | 3_TB | 39990 | 13.0588 | 1588 | 85 | 468 | 177 | 1110 | 160 |
| 3 | Simca 1300 | 1_M | 29600 | 15.4412 | 1294 | 68 | 424 | 168 | 1050 | 152 |
| 4 | Citroen GS Club | 1_M | 28250 | 15.7627 | 1222 | 59 | 412 | 161 | 930 | 151 |
| 5 | Fiat 132 | 2_B | 34900 | 11.2755 | 1585 | 98 | 439 | 164 | 1105 | 165 |
In [70]:
# description
DFR.describe(df)
10×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | Modele | Alfasud TI | Toyota Corolla | 0 | String | ||
| 2 | FINITION | 1_M | 3_TB | 0 | String | ||
| 3 | PRIX | 34158.6 | 22100 | 33345.0 | 47700 | 0 | Int64 |
| 4 | R_POID_PUIS | 13.1807 | 9.72477 | 13.1818 | 18.3636 | 0 | Float64 |
| 5 | CYL | 1631.67 | 1166 | 1577.5 | 2664 | 0 | Int64 |
| 6 | PUISS | 84.6111 | 55 | 82.0 | 128 | 0 | Int64 |
| 7 | LONG | 433.5 | 393 | 434.5 | 469 | 0 | Int64 |
| 8 | LARG | 166.667 | 157 | 167.0 | 177 | 0 | Int64 |
| 9 | POIDS | 1078.83 | 815 | 1087.5 | 1370 | 0 | Int64 |
| 10 | V_MAX | 158.278 | 140 | 160.0 | 180 | 0 | Int64 |
In [71]:
# récupération des variables actives
X = df[:,[:CYL,:PUISS,:LONG,:LARG,:POIDS,:V_MAX]]
DFR.describe(X)
6×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Float64 | Int64 | Float64 | Int64 | Int64 | DataType | |
| 1 | CYL | 1631.67 | 1166 | 1577.5 | 2664 | 0 | Int64 |
| 2 | PUISS | 84.6111 | 55 | 82.0 | 128 | 0 | Int64 |
| 3 | LONG | 433.5 | 393 | 434.5 | 469 | 0 | Int64 |
| 4 | LARG | 166.667 | 157 | 167.0 | 177 | 0 | Int64 |
| 5 | POIDS | 1078.83 | 815 | 1087.5 | 1370 | 0 | Int64 |
| 6 | V_MAX | 158.278 | 140 | 160.0 | 180 | 0 | Int64 |
Types des variables¶
In [72]:
# types reconnus pas MLJ
import MLJ
MLJ.schema(X)
┌───────┬──────────┬───────┐ │ names │ scitypes │ types │ ├───────┼──────────┼───────┤ │ CYL │ Count │ Int64 │ │ PUISS │ Count │ Int64 │ │ LONG │ Count │ Int64 │ │ LARG │ Count │ Int64 │ │ POIDS │ Count │ Int64 │ │ V_MAX │ Count │ Int64 │ └───────┴──────────┴───────┘
In [73]:
# transformer les X count en variables continues
# sinon elles sont ignorées par les outils en aval
# utilisation du package ScientificTypesBase
import ScientificTypesBase
X = MLJ.coerce(X,ScientificTypesBase.Count => ScientificTypesBase.Continuous)
# schéma
MLJ.schema(X)
┌───────┬────────────┬─────────┐ │ names │ scitypes │ types │ ├───────┼────────────┼─────────┤ │ CYL │ Continuous │ Float64 │ │ PUISS │ Continuous │ Float64 │ │ LONG │ Continuous │ Float64 │ │ LARG │ Continuous │ Float64 │ │ POIDS │ Continuous │ Float64 │ │ V_MAX │ Continuous │ Float64 │ └───────┴────────────┴─────────┘
Standardisation pour ACP normée¶
In [74]:
# attention, l'ACP ici peut centrer automatiquement les données
# mais ne les standardise pas
# si on veut une ACP normée, on doit le faire en amont
Standardizer = @MLJ.load Standardizer pkg=MLJTransforms
┌ Info: For silent loading, specify `verbosity=0`. └ @ Main C:\Users\ricco\.julia\packages\MLJModels\9LbNu\src\loading.jl:159
import MLJTransforms ✔
MLJTransforms.Standardizer
In [75]:
# instancier -- ATTENTION, utilise écart-type << 1/(n-1) >> ici
# cela aura des répercussions sur les coordonnées des individus !!!
# qu'il faudra corriger plus bas
std = MLJ.machine(Standardizer(),X)
# entraîner i.e. calculer moyennes et écarts-type
MLJ.fit!(std)
# transformer
Z = MLJ.transform(std,X)
# description
DFR.describe(Z)
┌ Info: Training machine(Standardizer(features = Symbol[], …), …). └ @ MLJBase C:\Users\ricco\.julia\packages\MLJBase\krfwA\src\machines.jl:499
6×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Float64 | Float64 | Float64 | Float64 | Int64 | DataType | |
| 1 | CYL | -2.22045e-16 | -1.24533 | -0.144858 | 2.76077 | 0 | Float64 |
| 2 | PUISS | -1.4803e-16 | -1.45321 | -0.128145 | 2.12938 | 0 | Float64 |
| 3 | LONG | 2.46716e-17 | -1.83197 | 0.0452338 | 1.6058 | 0 | Float64 |
| 4 | LARG | 1.77636e-15 | -1.8192 | 0.0627311 | 1.94466 | 0 | Float64 |
| 5 | POIDS | 5.67447e-16 | -1.92638 | 0.0632798 | 2.12596 | 0 | Float64 |
| 6 | V_MAX | 4.93432e-16 | -1.50554 | 0.141859 | 1.78925 | 0 | Float64 |
ACP avec MultivariateStats sous Julia¶
Instanciation, entraînement¶
In [76]:
# chargement de la classe de calcul
PCA = @MLJ.load PCA pkg=MultivariateStats
import MLJMultivariateStatsInterface ✔
┌ Info: For silent loading, specify `verbosity=0`. └ @ Main C:\Users\ricco\.julia\packages\MLJModels\9LbNu\src\loading.jl:159
MLJMultivariateStatsInterface.PCA
In [77]:
# instancier
# mean = 0 pour dire que les variables sont déjà centrées
# par défaut, s'arrête dès que 99% de l'info est captée
acp = PCA(mean=0)
PCA( maxoutdim = 0, method = :auto, variance_ratio = 0.99, mean = 0)
In [78]:
# associer l'instance aux données
mach = MLJ.machine(acp,Z)
# lancer l'entraînement
MLJ.fit!(mach)
┌ Info: Training machine(PCA(maxoutdim = 0, …), …). └ @ MLJBase C:\Users\ricco\.julia\packages\MLJBase\krfwA\src\machines.jl:499
trained Machine; caches model-specific representations of data
model: PCA(maxoutdim = 0, …)
args:
1: Source @966 ⏎ ScientificTypesBase.Table{AbstractVector{ScientificTypesBase.Continuous}}
Accès aux résultats¶
In [79]:
# accès aux résultats
res = MLJ.report(mach)
keys(res)
(:indim, :outdim, :tprincipalvar, :tresidualvar, :tvar, :mean, :principalvars, :loadings)
In [80]:
# dimensions en entrée et en sortie
# nombre de variables vs. nb de composantes
println("Entree = $(res.indim) ; sortie = $(res.outdim)")
Entree = 6 ; sortie = 5
Variance restituée - Scree Plot¶
In [81]:
# info captée sur les composantes
res.tprincipalvar
5.956709727324061
In [82]:
# soit en proportion sur les 5 premières composantes
# 99.28% => on est bien au-delà de 99%
100.0*(res.tprincipalvar/res.indim)
99.278495455401
In [83]:
# valeurs propres
res.principalvars
5-element Vector{Float64}:
4.420858059608889
0.8560622892459593
0.37306607743997394
0.21392208902330967
0.09280121200592834
In [84]:
# sous forme de scree plot
import StatsPlots as STP
STP.plot(res.principalvars,
xlabel = "Composante",
ylabel = "Valeur propre",
title = "Scree Plot",
legend=false)
In [85]:
# en proportion de variances cumulées
explained_var = 100.0*(res.principalvars / res.indim)
cum_var = cumsum(explained_var)
println("Proportion variance cumulées : $(round.(cum_var,digits=2))")
# graphique
STP.plot(cum_var,
xlabel = "Composante",
ylabel = "% variance",
title = "Variance cumulée",
legend=false)
Proportion variance cumulées : [73.68, 87.95, 94.17, 97.73, 99.28]
Corrélations, contributions (CTR), cosinus^2 (COS2)¶
In [86]:
# les loadings
res.loadings
6×5 Matrix{Float64}:
-0.893464 0.114906 0.215983 0.373615 0.0461763
-0.886858 0.384689 0.112948 -0.165485 -0.0894812
-0.886155 -0.381029 -0.0413102 -0.12939 0.222555
-0.813536 -0.412736 -0.369448 0.0978545 -0.145672
-0.905187 -0.224532 0.295865 -0.139547 -0.0927785
-0.75471 0.573519 -0.296522 -0.0340294 0.0574706
In [87]:
# somme des carrés des loadings en colonne = valeurs propres
sum(res.loadings .^ 2,dims=1)
1×5 Matrix{Float64}:
4.42086 0.856062 0.373066 0.213922 0.0928012
In [88]:
# les loadings sur les deux premières composantes
# qui sont aussi les corrélations en réalité
corr_fact = DFR.DataFrame(var = names(X))
corr_fact = DFR.hcat(corr_fact,DFR.DataFrame(res.loadings[:,1:2],["F1","F2"]))
corr_fact
6×3 DataFrame
| Row | var | F1 | F2 |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | CYL | -0.893464 | 0.114906 |
| 2 | PUISS | -0.886858 | 0.384689 |
| 3 | LONG | -0.886155 | -0.381029 |
| 4 | LARG | -0.813536 | -0.412736 |
| 5 | POIDS | -0.905187 | -0.224532 |
| 6 | V_MAX | -0.75471 | 0.573519 |
Cercle des corrélations¶
In [89]:
# coordonnées = corrélation
coord = corr_fact
# dessiner le cercle
theta = range(0, 2*pi, length=200)
STP.plot(cos.(theta), sin.(theta),
aspect_ratio=:equal,
label="",
linestyle=:dash,
xlabel="PC1",
ylabel="PC2",
title="Cercle des corrélations",
xlims=(-1,+1),
ylims=(-1,+1),
framestyle=:box)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")
# placer les noms des variables
# les points sont masqués avec markersize=0
STP.scatter!(coord[:,:F1], coord[:,:F2], markersize=0,
series_annotations=(coord[:,:var],STP.font(9,:green)),
label="")
Contributions¶
In [90]:
# contributions en %
# diviser le carré des corrélations par les valeurs propres
CTR = 100.0.*((corr_fact[:,[:F1,:F2]] .^2) ./ res.principalvars[1:2]')
# arrondir à 1 décimale
CTR = round.(CTR,digits=1)
# ajouter les noms des variables
CTR = DFR.hcat(DFR.DataFrame(var=names(X)),CTR)
CTR
6×3 DataFrame
| Row | var | F1 | F2 |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | CYL | 18.1 | 1.5 |
| 2 | PUISS | 17.8 | 17.3 |
| 3 | LONG | 17.8 | 17.0 |
| 4 | LARG | 15.0 | 19.9 |
| 5 | POIDS | 18.5 | 5.9 |
| 6 | V_MAX | 12.9 | 38.4 |
Cosinus carré¶
In [91]:
# cosinus^2 en %
COS2 = 100.0.*((corr_fact[:,[:F1,:F2]] .^2))
# arrondir
COS2 = round.(COS2,digits=1)
# ajouter les noms des variables
COS2 = DFR.hcat(DFR.DataFrame(var=names(X)),COS2)
COS2
6×3 DataFrame
| Row | var | F1 | F2 |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | CYL | 79.8 | 1.3 |
| 2 | PUISS | 78.7 | 14.8 |
| 3 | LONG | 78.5 | 14.5 |
| 4 | LARG | 66.2 | 17.0 |
| 5 | POIDS | 81.9 | 5.0 |
| 6 | V_MAX | 57.0 | 32.9 |
In [92]:
# qui peuvent s'additionner d'une colonne à l'autre
# pour mesurer la qualité de représentation dans le repère
DFR.DataFrame(var=names(X),Cos2=vec(sum(Matrix(COS2[:,[:F1,:F2]]),dims=2)))
6×2 DataFrame
| Row | var | Cos2 |
|---|---|---|
| String | Float64 | |
| 1 | CYL | 81.1 |
| 2 | PUISS | 93.5 |
| 3 | LONG | 93.0 |
| 4 | LARG | 83.2 |
| 5 | POIDS | 86.9 |
| 6 | V_MAX | 89.9 |
Résultats additionnels - Représentation des individus¶
In [93]:
# paramètres supplémentaires
fp = MLJ.fitted_params(mach)
keys(fp)
(:projection,)
In [94]:
# matrice de projection
# à appliquer aux valeurs centrées et réduites
fp.projection
6×5 Matrix{Float64}:
-0.424936 0.124191 0.353613 0.807786 0.15158
-0.421794 0.415774 0.18492 -0.357792 -0.293735
-0.42146 -0.411818 -0.0676339 -0.279752 0.730569
-0.386922 -0.446087 -0.604868 0.211569 -0.47819
-0.430512 -0.242676 0.484396 -0.301711 -0.304558
-0.358944 0.619863 -0.485472 -0.0735743 0.188655
In [95]:
# pour obtenir les coordonnées des individus
(Matrix(Z) * fp.projection)[1:8,:]
8×5 Matrix{Float64}:
2.07866 1.73537 -0.55575 0.196238 -0.292866
-1.51746 -1.48402 -1.27821 -0.205397 0.144332
1.08785 -0.655501 -0.443724 -0.162903 -0.364788
2.50123 0.109704 -0.144384 -0.0168546 0.220462
-0.415801 0.67597 0.18784 -0.610068 0.256081
0.295666 -0.190622 -0.656763 -0.540278 -0.432546
-0.664659 -0.906768 0.249587 0.197411 0.202773
1.89359 -0.952824 0.602119 0.61256 0.284905
In [96]:
# mais que l'on peut aussi obtenir directement avec la méthode transform()
MLJ.transform(mach,Z)[1:8,:]
8×5 DataFrame
| Row | x1 | x2 | x3 | x4 | x5 |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 2.07866 | 1.73537 | -0.55575 | 0.196238 | -0.292866 |
| 2 | -1.51746 | -1.48402 | -1.27821 | -0.205397 | 0.144332 |
| 3 | 1.08785 | -0.655501 | -0.443724 | -0.162903 | -0.364788 |
| 4 | 2.50123 | 0.109704 | -0.144384 | -0.0168546 | 0.220462 |
| 5 | -0.415801 | 0.67597 | 0.18784 | -0.610068 | 0.256081 |
| 6 | 0.295666 | -0.190622 | -0.656763 | -0.540278 | -0.432546 |
| 7 | -0.664659 | -0.906768 | 0.249587 | 0.197411 | 0.202773 |
| 8 | 1.89359 | -0.952824 | 0.602119 | 0.61256 | 0.284905 |
ATTENTION !!! En réalité, ces coordonnées sont biaisées parce que l'écart-type << 1/(n-1) >> a été utilisé pour la standardisation. On doit appliquer un facteur de correction pour retrouver les valeurs produites par les librairies qui font référence !!! (factorminer, sklearn, fanalysis, scientisttools, etc.)
In [97]:
# effectifs
n = size(X,1)
print(n)
18
In [98]:
# appliquer la correction
dfFact = sqrt(n/(n-1)) .* MLJ.transform(mach,Z)
DFR.first(dfFact,8)
8×5 DataFrame
| Row | x1 | x2 | x3 | x4 | x5 |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 2.13892 | 1.78568 | -0.571862 | 0.201927 | -0.301357 |
| 2 | -1.56146 | -1.52704 | -1.31527 | -0.211352 | 0.148516 |
| 3 | 1.11939 | -0.674505 | -0.456588 | -0.167626 | -0.375364 |
| 4 | 2.57374 | 0.112884 | -0.14857 | -0.0173432 | 0.226853 |
| 5 | -0.427855 | 0.695567 | 0.193286 | -0.627754 | 0.263505 |
| 6 | 0.304238 | -0.196149 | -0.675803 | -0.555941 | -0.445086 |
| 7 | -0.683928 | -0.933057 | 0.256823 | 0.203134 | 0.208652 |
| 8 | 1.94849 | -0.980448 | 0.619575 | 0.630319 | 0.293165 |
In [99]:
# position des individus dans le premier plan factoriel
STP.scatter(dfFact[:,:x1], dfFact[:,:x2],
aspect_ratio=:equal,
label="",
xlabel="PC1",
ylabel="PC2",
title="Graphique des individus",
xlims=(-5,+5),
ylims=(-5,+5),
framestyle=:box,
markersize=0,
series_annotations=(df.Modele,STP.font(6,:green)),
size=(600,600)
)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")
Contributions des individus aux facteurs¶
In [100]:
# contributions des individus aux composantes
ctr_ind = 100.0.*((dfFact[:,[:x1,:x2]] .^2) ./ (n .* res.principalvars[1:2]'))
DFR.hcat(DFR.DataFrame(Modele=df.Modele),round.(ctr_ind,digits=1))
18×3 DataFrame
| Row | Modele | x1 | x2 |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | Alfasud TI | 5.7 | 20.7 |
| 2 | Audi 100 | 3.1 | 15.1 |
| 3 | Simca 1300 | 1.6 | 3.0 |
| 4 | Citroen GS Club | 8.3 | 0.1 |
| 5 | Fiat 132 | 0.2 | 3.1 |
| 6 | Lancia Beta | 0.1 | 0.2 |
| 7 | Peugeot 504 | 0.6 | 5.6 |
| 8 | Renault 16 TL | 4.8 | 6.2 |
| 9 | Renault 30 | 24.4 | 7.3 |
| 10 | Toyota Corolla | 20.0 | 0.4 |
| 11 | Alfetta-1.66 | 0.2 | 23.7 |
| 12 | Princess-1800 | 1.3 | 4.6 |
| 13 | Datsun-200L | 10.9 | 2.0 |
| 14 | Taunus-2000 | 2.2 | 1.5 |
| 15 | Rancho | 0.6 | 5.2 |
| 16 | Mazda-9295 | 0.2 | 0.8 |
| 17 | Opel-Rekord | 6.6 | 0.1 |
| 18 | Lada-1300 | 9.2 | 0.1 |
In [101]:
# vérification de la somme en colonne
# égale à 100% effectivement
sum(Matrix(ctr_ind),dims=1)
1×2 Matrix{Float64}:
100.0 100.0
COS2 - Qualité de représentation des individus¶
In [102]:
# distance à l'origine des individus dans l'espace initial
# sachant que les individus sont centrés et résuits
# ATTENTION encore au facteur de correction de la réduction
disto_ind = vec(sum(n/(n-1) .* Matrix(Z) .^ 2,dims=2))
18-element Vector{Float64}:
8.22517550693188
6.6737551583965145
2.159326874204685
6.78014507568542
1.169124371607794
1.1349503123144318
1.5127929344721436
5.636825753612391
21.789657262877334
16.29014276553667
4.456770360239878
1.952512623718658
11.112624034411022
2.452985724307104
1.9633731069400353
0.6845212812064279
6.08311856016591
7.922198293371691
In [103]:
# COS2 des individus
cos2_ind = 100.0.*((dfFact[:,[:x1,:x2]] .^2) ./ disto_ind)
DFR.hcat(DFR.DataFrame(Modele=df.Modele),round.(cos2_ind,digits=1))
18×3 DataFrame
| Row | Modele | x1 | x2 |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | Alfasud TI | 55.6 | 38.8 |
| 2 | Audi 100 | 36.5 | 34.9 |
| 3 | Simca 1300 | 58.0 | 21.1 |
| 4 | Citroen GS Club | 97.7 | 0.2 |
| 5 | Fiat 132 | 15.7 | 41.4 |
| 6 | Lancia Beta | 8.2 | 3.4 |
| 7 | Peugeot 504 | 30.9 | 57.5 |
| 8 | Renault 16 TL | 67.4 | 17.1 |
| 9 | Renault 30 | 89.2 | 5.2 |
| 10 | Toyota Corolla | 97.5 | 0.3 |
| 11 | Alfetta-1.66 | 4.3 | 82.1 |
| 12 | Princess-1800 | 53.1 | 36.3 |
| 13 | Datsun-200L | 77.8 | 2.8 |
| 14 | Taunus-2000 | 70.5 | 9.6 |
| 15 | Rancho | 24.3 | 41.0 |
| 16 | Mazda-9295 | 21.7 | 18.5 |
| 17 | Opel-Rekord | 86.2 | 0.2 |
| 18 | Lada-1300 | 92.6 | 0.3 |
In [104]:
# qualité de représentation des idnvidus dans le plan factoriel
DFR.DataFrame(Modele=df.Modele,Cos2=vec(sum(Matrix(cos2_ind),dims=2)))
18×2 DataFrame
| Row | Modele | Cos2 |
|---|---|---|
| String | Float64 | |
| 1 | Alfasud TI | 94.3889 |
| 2 | Audi 100 | 71.4741 |
| 3 | Simca 1300 | 79.0978 |
| 4 | Citroen GS Club | 97.8871 |
| 5 | Fiat 132 | 57.0405 |
| 6 | Lancia Beta | 11.5454 |
| 7 | Peugeot 504 | 88.469 |
| 8 | Renault 16 TL | 84.4075 |
| 9 | Renault 30 | 94.4351 |
| 10 | Toyota Corolla | 97.8645 |
| 11 | Alfetta-1.66 | 86.363 |
| 12 | Princess-1800 | 89.3802 |
| 13 | Datsun-200L | 80.6527 |
| 14 | Taunus-2000 | 80.1315 |
| 15 | Rancho | 65.3742 |
| 16 | Mazda-9295 | 40.2674 |
| 17 | Opel-Rekord | 86.369 |
| 18 | Lada-1300 | 92.8659 |
Variables supplémentaires¶
Variables quantitatives - Corrélation¶
In [105]:
# rappel des variables disponibles
DFR.describe(df)
10×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | Modele | Alfasud TI | Toyota Corolla | 0 | String | ||
| 2 | FINITION | 1_M | 3_TB | 0 | String | ||
| 3 | PRIX | 34158.6 | 22100 | 33345.0 | 47700 | 0 | Int64 |
| 4 | R_POID_PUIS | 13.1807 | 9.72477 | 13.1818 | 18.3636 | 0 | Float64 |
| 5 | CYL | 1631.67 | 1166 | 1577.5 | 2664 | 0 | Int64 |
| 6 | PUISS | 84.6111 | 55 | 82.0 | 128 | 0 | Int64 |
| 7 | LONG | 433.5 | 393 | 434.5 | 469 | 0 | Int64 |
| 8 | LARG | 166.667 | 157 | 167.0 | 177 | 0 | Int64 |
| 9 | POIDS | 1078.83 | 815 | 1087.5 | 1370 | 0 | Int64 |
| 10 | V_MAX | 158.278 | 140 | 160.0 | 180 | 0 | Int64 |
In [106]:
# rappel des coordonnées factorielles
DFR.first(dfFact,5)
5×5 DataFrame
| Row | x1 | x2 | x3 | x4 | x5 |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 2.13892 | 1.78568 | -0.571862 | 0.201927 | -0.301357 |
| 2 | -1.56146 | -1.52704 | -1.31527 | -0.211352 | 0.148516 |
| 3 | 1.11939 | -0.674505 | -0.456588 | -0.167626 | -0.375364 |
| 4 | 2.57374 | 0.112884 | -0.14857 | -0.0173432 | 0.226853 |
| 5 | -0.427855 | 0.695567 | 0.193286 | -0.627754 | 0.263505 |
In [107]:
# corrélation de PRIX avec les 2 premières composantes
import Statistics
corr_prix = [Statistics.cor(df.PRIX,col) for col in DFR.eachcol(dfFact[:,[:x1,:x2]])]
print(corr_prix)
[-0.7724752409002815, 0.08670844446517484]
In [108]:
# placer PRIX dans le cercle des corrélations
# dessiner le cercle
theta = range(0, 2*pi, length=200)
STP.plot(cos.(theta), sin.(theta),
aspect_ratio=:equal,
label="",
linestyle=:dash,
xlabel="PC1",
ylabel="PC2",
title="Cercle des corrélations",
xlims=(-1,+1),
ylims=(-1,+1),
framestyle=:box)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")
# placer les noms des variables actives
# les points sont masqués avec markersize=0
STP.scatter!(coord[:,:F1], coord[:,:F2], markersize=0,
series_annotations=(coord[:,:var],STP.font(7,:green)),
label="")
# placer la variable illustrative
STP.scatter!([corr_prix[1]],[corr_prix[2]], markersize=0,
series_annotations=(["PRIX"],STP.font(9,:blue)),
label="")
Variable qualitative - Coordonnées factorielles¶
In [109]:
# dataframe avec les coordonnées dans le plan
dfTemp = dfFact[:,1:2]
dfTemp.FINITION = df.FINITION
DFR.first(dfTemp,5)
5×3 DataFrame
| Row | x1 | x2 | FINITION |
|---|---|---|---|
| Float64 | Float64 | String | |
| 1 | 2.13892 | 1.78568 | 2_B |
| 2 | -1.56146 | -1.52704 | 3_TB |
| 3 | 1.11939 | -0.674505 | 1_M |
| 4 | 2.57374 | 0.112884 | 1_M |
| 5 | -0.427855 | 0.695567 | 2_B |
In [110]:
# calcul des moyennes conditionnelles
coord_finition = DFR.combine(DFR.groupby(dfTemp,:FINITION),[:x1,:x2] .=> Statistics.mean)
coord_finition
3×3 DataFrame
| Row | FINITION | x1_mean | x2_mean |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | 2_B | -0.235313 | 0.0452712 |
| 2 | 3_TB | -1.39243 | -0.0340006 |
| 3 | 1_M | 2.00035 | -0.022579 |
In [111]:
# position des individus dans le premier plan factoriel
STP.scatter(dfFact[:,:x1], dfFact[:,:x2],
aspect_ratio=:equal,
label="",
xlabel="PC1",
ylabel="PC2",
title="Graphique des individus",
xlims=(-5,+5),
ylims=(-5,+5),
framestyle=:box,
markersize=0,
series_annotations=(df.Modele,STP.font(7,:silver)),
size=(600,600)
)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")
# placer les points additionnels -- moyennes conditionnelles
STP.scatter!(coord_finition.x1_mean, coord_finition.x2_mean,
label="",
markersize=0,
series_annotations=(coord_finition.FINITION,STP.font(9,:blue)))
Traitements des individus supplémentaires¶
In [112]:
# chargement des données
dfSupp = DFR.DataFrame(XLSX.readtable("./autos_acp_livre.xlsx","ind_illust"))
dfSupp
2×7 DataFrame
| Row | Modele | CYL | PUISS | LONG | LARG | POIDS | V_MAX |
|---|---|---|---|---|---|---|---|
| String | Int64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | Peugeot 604 | 2664 | 136 | 472 | 177 | 1410 | 180 |
| 2 | Peugeot 304 S | 1288 | 74 | 414 | 157 | 915 | 160 |
In [113]:
# variables actives
XSupp = DFR.select(dfSupp, DFR.Not(:Modele))
XSupp
2×6 DataFrame
| Row | CYL | PUISS | LONG | LARG | POIDS | V_MAX |
|---|---|---|---|---|---|---|
| Int64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | 2664 | 136 | 472 | 177 | 1410 | 180 |
| 2 | 1288 | 74 | 414 | 157 | 915 | 160 |
In [114]:
# appliquer la standardisation
ZSupp = MLJ.transform(std,XSupp)
ZSupp
2×6 DataFrame
| Row | CYL | PUISS | LONG | LARG | POIDS | V_MAX |
|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 2.76077 | 2.522 | 1.7415 | 1.94466 | 2.41802 | 1.78925 |
| 2 | -0.919067 | -0.520758 | -0.882059 | -1.8192 | -1.19623 | 0.141859 |
In [115]:
# obtenir les coordonnées factorielles
# ne pas oublier d'appliquer le même facteur de correction
# que sur les individus actifs
dfFactSupp = sqrt(n/(n-1)) .* MLJ.transform(mach,ZSupp)
dfFactSupp
2×5 DataFrame
| Row | x1 | x2 | x3 | x4 | x5 |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | -5.56329 | 0.338609 | 0.464289 | 0.402146 | -0.389811 |
| 2 | 2.21224 | 1.25778 | 0.0930439 | -0.353702 | 0.648528 |
In [116]:
# position des individus dans le premier plan factoriel
STP.scatter(dfFact[:,:x1], dfFact[:,:x2],
aspect_ratio=:equal,
label="",
xlabel="PC1",
ylabel="PC2",
title="Graphique des individus",
xlims=(-6,+6),
ylims=(-6,+6),
framestyle=:box,
markersize=0,
series_annotations=(df.Modele,STP.font(7,:gray)),
size=(600,600)
)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")
# placer les points additionnels -- individus illustratifs
STP.scatter!(dfFactSupp.x1, dfFactSupp.x2,
label="",
markersize=0,
series_annotations=(dfSupp.Modele,STP.font(9,:red)))
Rotation des facteurs¶
In [117]:
# récupérations des loadings pour les 2 premières composantes
loadings_init = res.loadings[:,1:2]
loadings_init
6×2 Matrix{Float64}:
-0.893464 0.114906
-0.886858 0.384689
-0.886155 -0.381029
-0.813536 -0.412736
-0.905187 -0.224532
-0.75471 0.573519
In [118]:
# vérification de la variance restituée
sum(loadings_init .^ 2, dims = 1)
1×2 Matrix{Float64}:
4.42086 0.856062
In [119]:
# et la somme des deux
sum(sum(loadings_init .^ 2, dims = 1))
5.276920348854848
In [120]:
# rotation des facteurs -- VARIMAX ici
# mais d'autres sont possibles, ex. Quartimax
import FactorRotations as FR
L_rot = FR.rotate(loadings_init, FR.Varimax())
L_rot
FactorRotations.FactorRotation{Float64} with loading matrix:
6×2 Matrix{Float64}:
0.593779 0.677427
0.410288 0.87531
0.916503 0.300792
0.883045 0.228963
0.827204 0.430711
0.186252 0.92942
In [121]:
# accès aux loadings
loadings_rot = FR.loadings(L_rot)
DFR.hcat(DFR.DataFrame(var=names(X)),DFR.DataFrame(loadings_rot,["F1","F2"]))
6×3 DataFrame
| Row | var | F1 | F2 |
|---|---|---|---|
| String | Float64 | Float64 | |
| 1 | CYL | 0.593779 | 0.677427 |
| 2 | PUISS | 0.410288 | 0.87531 |
| 3 | LONG | 0.916503 | 0.300792 |
| 4 | LARG | 0.883045 | 0.228963 |
| 5 | POIDS | 0.827204 | 0.430711 |
| 6 | V_MAX | 0.186252 | 0.92942 |
In [122]:
# carré de la somme en colonne
# répartition différente
sum(loadings_rot .^ 2, dims = 1)
1×2 Matrix{Float64}:
2.85961 2.41731
In [123]:
# la la somme totale reste la même
# i.e. l'information restituée sur les 2 composantes
sum(sum(loadings_rot .^ 2, dims = 1))
5.2769203488548495
In [124]:
# cercle des corrélations << après >> rotation varimax
# dessiner le cercle
theta = range(0, 2*pi, length=200)
STP.plot(cos.(theta), sin.(theta),
aspect_ratio=:equal,
label="",
linestyle=:dash,
xlabel="PC1",
ylabel="PC2",
title="Cercle des corrélations",
xlims=(-1,+1),
ylims=(-1,+1),
framestyle=:box)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")
# placer les noms des variables actives
# les points sont masqués avec markersize=0
STP.scatter!(loadings_rot[:,1], loadings_rot[:,2], markersize=0,
series_annotations=(names(X),STP.font(7,:green)),
label="")
In [125]:
# matrice de rotation orthogonale
# permettant de calculer les coordoonnées
# des individus après rotation des facteurs
mat_rot = FR.rotation(L_rot)
mat_rot
2×2 Matrix{Float64}:
-0.749692 -0.661787
-0.661787 0.749692
In [126]:
# anciennes coordonnées
old_coord = Matrix(dfFact[:,1:2])
old_coord[1:5,:]
5×2 Matrix{Float64}:
2.13892 1.78568
-1.56146 -1.52704
1.11939 -0.674505
2.57374 0.112884
-0.427855 0.695567
In [127]:
# appliquer la matrice pour les nouvelles coordonnées
new_coord = old_coord * mat_rot
new_coord[1:5,:]
5×2 Matrix{Float64}:
-2.78527 -0.0768024
2.18119 -0.111456
-0.392815 -1.24647
-2.00422 -1.61864
-0.139558 0.80461
In [128]:
# vérifier si coordonnées correctement calculés
# somme des carrés en colonne / n = valeur propre => oui
sum(new_coord .^ 2, dims = 1)/n
1×2 Matrix{Float64}:
2.85961 2.41731
In [129]:
# nouvelles positions des individus dans le premier plan factoriel
STP.scatter(new_coord[:,1], new_coord[:,2],
aspect_ratio=:equal,
label="",
xlabel="PC1",
ylabel="PC2",
title="Graphique des individus (après rotation VARIMAX)",
xlims=(-4,+4),
ylims=(-4,+4),
framestyle=:box,
markersize=0,
series_annotations=(df.Modele,STP.font(7,:green)),
size=(600,600)
)
# lignes centrales
STP.hline!([0], linestyle = :dash, color = :gray,label="")
STP.vline!([0], linestyle = :dash, color = :gray,label="")