Avant toute chose, il faut preparer l’espace de travail.
rm(list=ls())
library("FactoMineR"); library("factoextra"); library("corrplot"); library("tidyr"); library("ggplot2"); library("ggpubr"); library("ggthemes"); library("RColorBrewer"); library("dplyr"); library("vegan");library("corrplot"); library("dendextend"); library("randomForest"); library("e1071"); library("nlme")
L’ACP est une méthode de la famille de l’analyse des données et plus généralement de la statistique multivariée, qui consiste à transformer des variables liées entre elles (dites « corrélées » en statistique) en nouvelles variables décorrélées les unes des autres. Ces nouvelles variables sont nommées « composantes principales », ou axes principaux.
En effet, si notre jeu de données possède plus de trois variables (éléments dans notre cas), il pourrait être très difficile de visualiser les données dans une “hyper-espace” multidimensionnelle. L’ACP permet donc de réduire les dimensions d’une donnée multivariée à deux ou trois composantes principales, qui peuvent être visualisées graphiquement, en perdant le moins d’information possible.
Avant de pouvoir effectuer le test, il faut ouvrir le fichier contenant les données.
dataPCA <- read.csv("/Users/WillzyX/Desktop/Maitrise/Atlas_FSL_2019_log_R.csv", header=T, sep=',',dec=".", stringsAsFactors = T)[,-1] %>%
drop_na(SrCa, BaCa, MgCa, NaCa)
options(max.print=60)
dataPCA dans le futur."" par l’adresse de votre dossier.; si la virgule est utilisée pour les décimales, il faut cependant aller modifier les options du .csv dans Excel, qui est utilisée par défaut.read_excel1000000 pour autoriser un grand nombre d’affichage pour que les tableaux complets apparaissent plutôt que seulement les entêtesOn peut ensuite préparer les données et les variables.
x<-subset(dataPCA, select = c(SrCa, BaCa, MgCa, NaCa))
xnum<-subset(dataPCA,select = c(SrCa, BaCa, MgCa, NaCa))
res.pca <- PCA(xnum)
var <- get_pca_var(res.pca)
ind <- get_pca_ind(res.pca)
View(x)View(xnum)theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()). On peut aussi faire le choix entre la matrice de correlation et la matrice de covariance. On choisit si on veut centrer et/ou réduire les données comme ceci : res.pca<-prcomp(xnum, center = TRUE, scale = TRUE), remplacer par FALSE si on ne veut pas centrer et/ou réduire. Il est aussi possible de l’écrire sous la forme fviz_pca_var(res.pca, col.var = "black") si vous voulez changer la couleur.varindLa suite d’opérations est facultative, elle permet d’obtenir plus de détails sur l’ACP.
print(res.pca) # Liste contenant les détails de ce qui a été effectué pendant l'ACP
## **Results for the Principal Component Analysis (PCA)**
## The analysis was performed on 923 individuals, described by 4 variables
## *The results are available in the following objects:
##
## name description
## 1 "$eig" "eigenvalues"
## 2 "$var" "results for the variables"
## 3 "$var$coord" "coord. for the variables"
## 4 "$var$cor" "correlations variables - dimensions"
## 5 "$var$cos2" "cos2 for the variables"
## 6 "$var$contrib" "contributions of the variables"
## 7 "$ind" "results for the individuals"
## 8 "$ind$coord" "coord. for the individuals"
## 9 "$ind$cos2" "cos2 for the individuals"
## 10 "$ind$contrib" "contributions of the individuals"
## 11 "$call" "summary statistics"
## 12 "$call$centre" "mean of the variables"
## 13 "$call$ecart.type" "standard error of the variables"
## 14 "$call$row.w" "weights for the individuals"
## 15 "$call$col.w" "weights for the variables"
eig.val <- get_eigenvalue(res.pca)
eig.val # % de la variance expliquée par chaque dimension
## eigenvalue variance.percent cumulative.variance.percent
## Dim.1 1.9673606 49.18401 49.18401
## Dim.2 0.9289273 23.22318 72.40720
## Dim.3 0.6986069 17.46517 89.87237
## Dim.4 0.4051053 10.12763 100.00000
fviz_eig(res.pca, addlabels = TRUE, ylim = c(0, 50)) # Graph du % de la variance expliquée par chaque dimension
fviz_contrib(res.pca, choice = "var", axes = 1, top = 10) # Graph du % de la variance expliquée par la PC1
fviz_contrib(res.pca, choice = "var", axes = 2, top = 10) # Graph du % de la variance expliquée par la PC2
head(var$cos2) # Qualité de représentation des variables sur le graph de l’ACP à l'aide du cosinus carré
## Dim.1 Dim.2 Dim.3 Dim.4
## SrCa 0.5989794 0.0034595607 0.27920838 0.11835266
## BaCa 0.7424321 0.0005253381 0.01200325 0.24503930
## MgCa 0.2960269 0.5187214126 0.16023048 0.02502121
## NaCa 0.3299222 0.4062209464 0.24716475 0.01669210
fviz_cos2(res.pca, choice = "var", axes = 1:2) # Graph du cos2 des variables sur les Dim.1 et Dim.2
head(var$contrib) # Contributions des éléments à chacune des composantes principales en %
## Dim.1 Dim.2 Dim.3 Dim.4
## SrCa 30.44584 0.3724254 39.966453 29.215285
## BaCa 37.73747 0.0565532 1.718169 60.487808
## MgCa 15.04691 55.8409077 22.935716 6.176471
## NaCa 16.76979 43.7301137 35.379663 4.120436
res.desc <- dimdesc(res.pca, axes = c(1,2), proba = 0.05)
res.desc$Dim.1 # Donne la corrélation des variables à la PC1
## $quanti
## correlation p.value
## BaCa 0.8616450 1.584300e-273
## SrCa 0.7739376 6.150812e-185
## MgCa 0.5440835 3.037277e-72
## NaCa -0.5743885 3.892287e-82
##
## attr(,"class")
## [1] "condes" "list"
res.desc$Dim.2 # Donne la corrélation des variables à la PC2
## $quanti
## correlation p.value
## MgCa 0.7202232 2.020209e-148
## NaCa 0.6373546 2.338192e-106
##
## attr(,"class")
## [1] "condes" "list"
head(ind$coord) # Coordonnéess des individus
## Dim.1 Dim.2 Dim.3 Dim.4
## 1 2.540642 -0.9172929 -2.949217 0.08801606
## 2 1.488525 0.2601334 -1.470317 -0.03599542
## 3 1.195112 -0.4185731 -1.848146 -0.19801623
## 4 1.671327 0.4554859 -1.735733 0.43790007
## 5 1.772030 -1.2086361 -2.283531 0.42419899
## 6 2.262230 0.8686664 -1.893387 0.02157735
head(ind$cos2) # Qualité des individus
## Dim.1 Dim.2 Dim.3 Dim.4
## 1 0.4033807 0.05258284 0.5435524 0.0004841187
## 2 0.4983033 0.01521856 0.4861868 0.0002913908
## 3 0.2823632 0.03463647 0.6752487 0.0077516243
## 4 0.4501510 0.03343376 0.4855133 0.0309019065
## 5 0.3141552 0.14614810 0.5216938 0.0180028526
## 6 0.5411162 0.07978532 0.3790493 0.0000492281
head(ind$contrib) # Contributions des individus
## Dim.1 Dim.2 Dim.3 Dim.4
## 1 0.35546873 0.098136972 1.3488971 0.0020718307
## 2 0.12201877 0.007892395 0.3352642 0.0003465172
## 3 0.07865589 0.020434270 0.5297099 0.0104865343
## 4 0.15382867 0.024197277 0.4672307 0.0512838260
## 5 0.17292450 0.170375728 0.8086845 0.0481248806
## 6 0.28183047 0.088008107 0.5559609 0.0001245164
On peut ensuite procéder à la création de la figure de l’ACP. Il peut être préférable de passer à la section 2. Regroupement hiérarchique avant d’effectuer cette figure si vous souhaitez séparer les stations par cluster comme dans l’exemple.
ind.p <- fviz_pca_biplot(res.pca, geom.ind = "point",
pointshape = 21,
pointsize = 2.5,
fill.ind = dataPCA$Clusters,
col.ind = "black",
palette = c("#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"),
arrowsize = 0.8,
col.var = "black",
addEllipses = FALSE,
axes.linetype = "blank",
mean.point=FALSE)
ggpubr::ggpar(ind.p,
title = "",
caption = "Source: factoextra",
xlab = "PC1 (49.2%)", ylab = "PC2 (23.2%)",
col.axis="black",
col.lab="black",
cex.axis = 1.5,
cex.lab=1.5,
legend.title = "Cluster", legend.position = "top",
ggtheme = theme_classic(base_size = 12, base_family = "Times"))
geom.ind = "point" pour afficher uniquement les points; geom.ind = "text" pour afficher uniquement le texte d’annotation des individus et geom.ind = c("point", "texte") pour afficher à la fois les points et le texte d’annotation (valeur par défaut)ggpubr::show_point_shapes() pour voir les formes de points disponiblesdataPCA$Clusters fait appel à la colonne “Clusters” du jeu de données correspondant à la variable “dataPCA” déterminé dans la prochaine section de guide, les formes seront doc coloriées par groupe, en fonction du cluster attribué.“grey” pour les palettes de couleurs grises; les palettes brewer, par exemple “RdBu”, “Blues”, etc. Pour afficher tout, tapez ceci RColorBrewer::display.brewer.all(), palette de couleurs personnalisées, par ex. c(“blue”, “red”); et des palettes de journaux scientifiques du package ggsci, par exemple: “npg”, “aaas”, “lancet”, “jco”, “ucscgb”, “uchicago”, “simpsons” et “rickandmorty”addEllipses = TRUE.Notez que l’argument ellipse.type peut être utilisé pour modifier le type d’ellipses. Les valeurs possibles sont: "convex": trace une coque convexe autour de chaque groupe de points; "confidence": trace les ellipses de confiance autour des points moyens des groupes, "t": suppose une distribution t multivariée; "norm" : suppose une distribution normale multivariée, "euclid": dessine un cercle avec le rayon égal au niveau, représentant la distance euclidienne du centre. Cette ellipse ne sera probablement pas circulaire sauf si coord_fixed() est appliqué. L’argument ellipse.level est également disponible pour modifier la taille de l’ellipse de concentration en probabilité normale. Pour changer le type et la taille de l’ellipse, remplacer la ligne par addEllipses = TRUE, ellipse.type = "confidence", ellipse.level = 0.95ggpubr::show_line_types()theme() si vous avez juste besoin de modifier l’affichage d’un thème existant.La dernière étape cosiste à créer un .csv avec les résultats de l’ACP. Vous pouvez copier ces données et les coller à la suite de vos résultats dans votre fichier original.
write.infile(res.pca, "pca.csv", sep = ",")
""; si la virgule est utilisée pour les décimales, il faut cependant aller modifier les options du .csv dans ExcelPour plus de détails sur les fondements de l’ACP, lire l’article d’où le code a été tiré : http://www.sthda.com/french/articles/38-methodes-des-composantes-principales-dans-r-guide-pratique/73-acp-analyse-en-composantes-principales-avec-r-l-essentiel/
La notion de regroupement hiérarchique recouvre différentes méthodes de clustering, et se catégorise en deux grandes familles : les méthodes ’ascendantes’ et les méthodes ’descendantes’.
L’approche ascendante, aussi appelée clustering agglomératif : on considère tout d’abord que chaque point est un cluster. Il y a donc autant de clusters que de points. Ensuite, on cherche les deux clusters les plus proches, et on les agglomère en un seul cluster. On répète cette étape jusqu’à ce que tous les points soient regroupés en un seul grand cluster. La fonction utilisée dans ce guide utilise l’approche ascendante.
L’approche descendante, aussi appelée clustering divisif : c’est l’inverse. On part d’un grand cluster contenant tous les points, puis on le divise successivement jusqu’à obtenir autant de clusters que de points.
On obtient donc une arborescence qui a un cluster tout en haut, et qui se divise petit à petit jusqu’à avoir autant de clusters que de points. On appelle cette arborescence un dendrogramme.Si vous avez beaucoup de points, le bas du dendrogramme risque d’être illisible, c’est pourquoi on ne représente parfois que le haut de l’arborescence.
Avant de pouvoir effectuer le test, il faut ouvrir le fichier contenant les données.
dataHC <- read.csv("C:/Users/WillzyX/Desktop/Maitrise/Atlas_FSL_2019_Stations_log_PCA_R.csv", header=T, sep=',',dec=".", stringsAsFactors = T)[,-1]
On peut ensuite préparer les données et les variables.
chimie <- as.matrix(dataHC[,c(2:3)])
rownames(chimie) = dataHC$Stations
head(chimie)
## PC1 PC2
## Batiscan -2.0251 0.7026
## Becancour -1.0975 -0.5721
## Chateauguay -0.0985 0.8750
## DuChene -0.5585 0.0741
## DuLoup -0.9694 0.3117
## Etchemin -2.6240 1.5013
chimie <- na.omit(chimie)
chimie <- scale(chimie)
dataHC (qui fait référence à notre jeu de données), sur les colonnes 2 et 3 (colonnes correspondant aux valeurs de PC1 et PC2). Changer ces valeurs si vous avez plus d’éléments ou de colonnes dans votre fichier.On peut ensuite procéder à la création du dendrogramme.
dend <- chimie %>%
dist %>%
hclust(method = "average") %>%
as.dendrogram %>%
set("branches_lwd",2) %>%
set("labels_cex", 1.2) %>%
set ("branches_k_col",
value = c("#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"), k = 7) %>%
plot(main = "", ylim = c(-0.4,4))
"ward.D"; "ward.D2"; "single"; "complete"; "average" (= UPGMA); "mcquitty" (= WPGMA); "median" (= WPGMC) ou "centroid" (= UPGMC)set() peut être utilisée pour modifier les paramètres.value = c(). Il faut ensuite déterminer le nombre de groupes de clusters (groupes) qu’on veut colorer avec k =. Plus on met une valeur élevée, plus on va séparer les clusters en petits groupes, en descendant dans le dendrogramme. Dans l’exemple, il y a sept clusters de couleurs différentes.Pour plus de détails sur les fondements du regroupement hiérarchique, lire l’article d’où le code a été tiré : http://www.sthda.com/english/wiki/beautiful-dendrogram-visualizations-in-r-5-must-known-methods-unsupervised-machine-learning
RF est l’un des algorithmes d’apprentissage supervisés les plus populaires et les plus performants, permettant de réaliser à la fois des régressions et des tâches de classification. Pour comprendre cet algorithme, il faut d’abord comprendre les fondements d’un arbre de décision. Un arbre de décision est un outil d’aide à la décision, représentant un ensemble de choix sous la forme graphique d’un arbre. Chaque nœud interne représente un test sur un attribut, chaque branche désigne les différents résultats possibles, et les « feuilles » de l’arbre, aussi nommés nœuds externes, représentent les étiquettes de classes qui peuvent être atteintes en fonction de décisions prises à chaque étape.
Avec RF, de nombreux arbres binaires sont construits à partir d’un sous-ensemble aléatoire des données en utilisant un rééchantillonnage bootstrap avec remplacement des individus. Ceux-ci sont appelés individus « in-bag ». Des individus « out-of-bag » sont ensuite utilisés pour mesurer la précision de prédiction de cet arbre, puisqu’ils ne sont pas utilisés pour construire un arbre spécifique. Pour un arbre donné, à chaque nœud, un sous-ensemble aléatoire de prédicteurs est recherché pour en trouver un qui maximise l’homogénéité au sein du groupe lorsqu’il est utilisé comme critère pour la division des nœuds. Chaque nouveau groupe est ensuite scindé à nouveau de manière récursive. Le processus récursif s’arrête lorsqu’une nouvelle division n’entraîne aucun gain supplémentaire d’homogénéité. Ainsi, deux niveaux de randomisation se produisent dans le processus RF : un dans la sélection initiale des individus (signatures de poissons) pour la construction de chaque arbre, et un dans la sélection des variables (éléments chimiques) utilisées pour la division des groupes à chaque nœud. RF construit une collection d’arbres de classification à partir de laquelle un consensus est établi : des individus « in-bag » sont descendus dans chaque arbre pour obtenir une seule prédiction d’origine, chaque arbre “votent” donc pour une classe. Les prédictions sur les arbres composant une forêt sont combinées selon des règles de majorité, RF choisira donc la classification ayant obtenu le plus de votes pour obtenir une prédiction finale de la forêt. Ainsi, on peut mesurer la précision de notre modèle, sous forme de pourcentage de bonne classification, en regardant la proportion d’individus « out-of-bag » qui ont été correctement classés.
Il faut d’abord aller chercher les données et les filtrer.
dataRF <- read.csv("/Users/Willzyx/Desktop/Maitrise/Atlas_FSL_2019_log_7Cluster_Moy_PCA_R.csv", header=T,
sep=',',dec=".", stringsAsFactors = T)[, -1]
trib<-filter(dataRF, Clusters == "A" | Clusters == "B" | Clusters == "C" |
Clusters == "D" | Clusters == "E" | Clusters == "F" |
Clusters == "G") %>% droplevels()
chim<-dataRF %>% drop_na(BaCa, MgCa, NaCa, SrCa)
Il faut ensuite déterminer les variables qui seront utilisées :
NUMLOOPS = 10
NTREES = 1000
PROPTRAINING = 0.75
confusion = NULL
Il faut créer la matrice d’affectation avant de pouvoir utiliser l’algorithme Random Forest.
stations = sort(as.character(unique(chim$Clusters)))
nstations = length(stations)
assignments = data.frame(matrix(0L, nrow=nstations, ncol=nstations))
names(assignments) = stations
rownames(assignments) = stations
matrix) à deux dimensions dans laquelle chaque colonne contient les valeurs d’une variable et chaque ligne contient un ensemble de valeurs de chaque colonne. Ce sera donc un tableau avec autant de lignes (nrow) et de colonnes (ncol) que le nombre de stations (ou clusters) présents dans le jeu de données. 0L permet de déterminer la valeur spécifique à “0” avec laquelle on initialise la matrice.On peut maintenant passer à travers les boucles de Random Forest.
for (i in 1:NUMLOOPS) {
sample.ind = sample(2,nrow(chim),replace = T,prob = c(PROPTRAINING, 1 - PROPTRAINING))
chim.train = chim[sample.ind==1,]
chim.test = chim[sample.ind==2,]
chim_RF <- randomForest(x=chim.train[, c(2:5)],
y=chim.train[,1],
ntree=NTREES,
proximity = T,
oob.prox=TRUE)
if (is.null(confusion)) {
confusion = chim_RF$confusion
} else {
confusion = confusion + chim_RF$confusion
}
chim.test$predicted <- predict(chim_RF, chim.test[, c(2:5)])
predicted_stations = as.data.frame(cbind(as.character(chim.test$Clusters),
as.character(chim.test$predicted)))
print(head(predicted_stations))
# Le texte qui suit permet de compter combien de prédicitons ont été attribuées à chaque stations (ou clusters). La ligne précédente permet de visualiser comment l'algorithme procède (Valeur réelle en V1 et valeur prévue en V2).
for (i in 1:nrow(predicted_stations)) {
s = predicted_stations[i, 1]
p = predicted_stations[i, 2]
assignments[s, p] = assignments[s, p] + 1
}
}
## V1 V2
## 1 F F
## 2 F F
## 3 F B
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F F
## 4 F A
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F B
## 3 F A
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F B
## 2 F F
## 3 F F
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F F
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F F
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F F
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F B
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F F
## 4 F F
## 5 F F
## 6 F F
## V1 V2
## 1 F F
## 2 F F
## 3 F F
## 4 F F
## 5 F F
## 6 F F
NUMLOOPS (valeur attribuée à 1 pour les tests et 1000 pour les résultats finaux)replace = T. On peut demander à l’algorithme de séparer le jeu de données dans des proportions voulues avec prob, dans notre cas il s’agit de 75% (PROPTRAINING) et 25% (1 - PROPTRAINING). C’est sous-ensembles sont ensuitre renomées séparées en données d’Entrainement (« in-bag », chim.train) et en données test (« out-of-bag », chim.test).oob.prox=TRUE.is.null(confusion) est repectée, c’est-à-dire qu’il n’y a une valeur nulle dans la matrice de confusion, la nouvelle matrice de confusion va conserver cette valeur nulle.Les prochaines étapes permettent de prendre des décisions lors des tests. Ne pas effectuer cette partie lorsque vous ferez des boucles avec plus de 1 test, puisque les variables vont se référer à la valeur qu’ils avaient lors de la dernière boucle et non à la moyenne de toutes les boucles.
plot(chim_RF)
print(chim_RF)
##
## Call:
## randomForest(x = chim.train[, c(2:5)], y = chim.train[, 1], ntree = NTREES, proximity = T, oob.prox = TRUE)
## Type of random forest: classification
## Number of trees: 1000
## No. of variables tried at each split: 2
##
## OOB estimate of error rate: 33.24%
## Confusion matrix:
## A B C D E F G class.error
## A 7 0 0 0 0 7 0 0.5000000
## B 1 102 16 11 0 8 17 0.3419355
## C 0 12 118 23 1 0 3 0.2484076
## D 0 10 23 119 2 1 12 0.2874251
## E 0 3 1 8 19 2 1 0.4411765
## F 2 7 0 0 1 41 4 0.2545455
## G 0 29 7 10 3 2 50 0.5049505
varImpPlot(chim_RF, main = "Importance relative des variables")
chim_RF$importance
## MeanDecreaseGini
## NaCa 128.6153
## MgCa 162.2333
## SrCa 131.8600
## BaCa 126.1519
MDSplot(chim_RF, chim.train$Clusters)
NTREES = 400) si le jeu de données est énorme. Sinon, il peut être plus prudent d’utiliser une valeur de 1000.print(confusion / NUMLOOPS) # Performance de la reclassification. La classe.erro est juste, le problème est que c'est le nombre d'échantillons correspndant aux données d'entrainement qui sont dans chaque assignation.
## A B C D E F G class.error
## A 10.1 0.0 0.0 0.0 0.0 4.6 0.0 0.3160827
## B 0.7 99.3 14.0 9.5 1.0 6.7 18.5 0.3374927
## C 0.0 13.2 123.1 25.0 0.7 0.0 4.6 0.2613744
## D 0.0 10.3 21.5 122.8 3.3 0.9 9.0 0.2682312
## E 0.0 2.3 1.0 6.9 19.0 2.0 0.4 0.3987355
## F 1.2 4.0 0.0 0.2 1.2 46.4 2.4 0.1628658
## G 0.0 24.5 6.4 9.9 1.5 1.5 59.1 0.4263419
print(assignments / NUMLOOPS) # Assignation donnees test. C'est la qu'on peut voir la performance du modele. Comme c'est un procede aleatoire il serait bien de générer une boucle (ex. 1000 fois) et utiliser la moyenne pour avoir une valeur plus fiable.
## A B C D E F G
## A 3.6 0.1 0.0 0.0 0.0 1.6 0.0
## B 0.3 30.1 5.2 2.4 0.3 2.1 5.9
## C 0.0 5.4 43.5 10.2 0.2 0.0 1.1
## D 0.0 3.3 6.8 41.8 1.7 0.4 3.2
## E 0.0 0.2 0.0 2.3 7.1 0.4 0.4
## F 0.8 1.6 0.0 0.0 0.4 16.8 1.0
## G 0.0 8.4 1.4 4.2 0.1 0.7 19.3
print(apply(confusion / NUMLOOPS, 1, sum)) # Nombre d'échantillons moyen par station du groupe d'entrainement
## A B C D E F G
## 15.01608 150.03749 166.86137 168.06823 31.99874 55.56287 103.32634
print(apply(assignments / NUMLOOPS, 1, sum)) # Nombre d'échantillons moyen par station du groupe 'test'
## A B C D E F G
## 5.3 46.3 60.4 57.2 10.4 20.6 34.1
print(table(chim$Clusters) * (1 - PROPTRAINING)) # Devrait tendre vers ces chiffres (nombre attendu avec PROPTRAINING):
##
## A B C D E F G
## 5.00 49.00 56.75 56.25 10.50 19.00 34.25
print(sum(apply(assignments / NUMLOOPS, 1, sum))) # Nombre d'échantillons totaux moyens du groupe 'test'
## [1] 234.3