# 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="")