TP clusering : kmeans et modèles de mélanges gaussiens

Author

Arthur Leroy

library(tidyverse)

Exercice 1 — Implémenter un k‑means “à la main” et visualiser les étapes

Pour illustrer l’algorithme k‑means, simulez un jeu de données 2D avec trois groupes bien séparés par exemple avec le code suivant :

set.seed(42)

# --- Données ---
X <- tibble(
  x = c(rnorm(70,-3,0.6), rnorm(70,0,0.5), rnorm(70,3,0.6)),
  y = c(rnorm(70,-1,0.6), rnorm(70,3,0.5), rnorm(70,0,0.6))
) %>% mutate(id = row_number())

A présent, implémentez une fonction my_kmeans() pour l’algorithme des k-means en 3 étapes simples :

  1. Choisir K centres au hasard.
  2. Assigner chaque point au centre le plus proche.
  3. Recalculer les centres (moyenne des points assignés).

Gardez un historique des centres à chaque itération pour pouvoir les tracer ensuite.

N’hésitez pas à relancer plusieurs fois la fonctions pour observer la variabilité des résultats selon l’initialisation.

(Bonus) : réalisez un graphique avec la fonction facet_wrap() de ggplot2 pour visualiser l’évolution des centres au fil des itérations.

Pour un code minimal qui fonctionne, vous pouvez utiliser :

  • slice_sample() pour initialisation
  • rowwise() et which.min() pour calculer les distances
  • group_by() + summarize() pour mettre à jour les centres
my_kmeans <- function(X, K, nb_iter = 10, plot = TRUE){
  # initialize centers
  centers <- X %>% 
    slice_sample(n = K) %>% 
    select(x,y) %>% 
    mutate(k = 1:K)
  
  history <- centers %>% mutate(iter = 0)
  
  for(it in 1:nb_iter){
    # assign
    assign <- X %>% rowwise() %>% 
      mutate(k = which.min((x - centers$x)^2 + (y - centers$y)^2)) %>% 
      ungroup()
    
    # update
    centers <- assign %>%
      group_by(k) %>%
      summarize(x = mean(x), y = mean(y), .groups='drop')
    
    history <- centers %>% 
      mutate(iter = it) %>% 
      bind_rows(history)
  }

  if(plot){
    # plot iterations
    gg = ggplot() +
      geom_point(data = assign, aes(x = x, y = y, colour = factor(k)), alpha = 0.6) +
      geom_point(data = centers, aes(x = x, y = y, colour = factor(k)), size = 4) +
      theme_classic()

    print(gg)
  }
  
  list(data_clust = assign, centers = centers, history = history, plot = gg) %>% 
    return()
}

# run kmeans
set.seed(17)
res <- my_kmeans(X, K = 3, nb_iter = 5)

# plot history of centers

ggplot() +
  geom_point(data = res$data_clust, aes(x = x, y = y, colour = factor(k)), alpha = 0.6) +
  geom_point(data = res$history, aes(x = x, y = y, fill = factor(k)), shape = 21, size = 4) +
  facet_wrap(~iter) +
  labs(title = 'Itération:') +
  theme_classic()

# rerun the kmeans to see variability
set.seed(2)
res2 <- my_kmeans(X, K = 3, nb_iter = 5)

Exercice 2 — K‑means, mélange de Gaussiennes, soft vs hard clustering

On simule un jeu de données 2D avec deux groupes partiellement chevauchants :

set.seed(42)

# --- Données ---
df <- tibble(
  x = c(rnorm(100,-1,1), rnorm(100,1,1)),
  y = c(rnorm(100,1,1), rnorm(100,0,1))
)
  • Appliquez l’algorithme k‑means (avec kmeans()) pour obtenir un hard clustering (chaque point appartient à un seul cluster).

  • Ensuite, utilisez la fonction Mclust() du package mclust pour ajuster un modèle de mélange gaussien (GMM) et obtenir un soft clustering (chaque point a une probabilité d’appartenance à chaque cluster).

  • Tracez les résultats des deux méthodes avec ggplot2 :

    • Pour k‑means, colorez les points selon leur cluster assigné.
    • Pour le GMM, colorez les points selon la probabilité d’appartenance au premier cluster (utilisez une échelle de couleur continue).

Pour obtenir un resultat robuste, utilisez plusieurs initialisations dans kmeans() (paramètre nstart). Pour le modèle de mélange (GMM), l’argument G de Mclust() permet de spécifier le nombre de composantes. Les probabilités d’appartenance sont dans l’attribut $z de l’objet retourné par Mclust().

## mclust is used to train a GMM 
library(mclust)
Warning: package 'mclust' was built under R version 4.4.3
Package 'mclust' version 6.1.1
Type 'citation("mclust")' for citing this R package in publications.

Attaching package: 'mclust'
The following object is masked from 'package:purrr':

    map
# k-means hard clustering
km <- kmeans(df, centers=2, nstart=20)
df_km <- df %>% mutate(cluster_km = factor(km$cluster))

# GMM soft clustering
gmm <- Mclust(df, G=2)
# Get membership probabilities in one cluster (soft clustering)
df_gmm <- df %>% mutate(prob1 = gmm$z[,1])

# Plot k-means
p1 <- ggplot(df_km, aes(x,y,color=cluster_km)) + geom_point() +
  labs(title="k-means : hard clustering") + theme_minimal()

# Plot GMM 
p2 <- ggplot(df_gmm, aes(x,y,color=prob1)) + geom_point() +
  scale_color_gradientn(colours = c('pink', 'purple')) +
  labs(title="GMM : soft clustering (probabilité 1er cluster)") + theme_minimal()

p1

p2

Exercice 3 — Introduction visuelle aux modèles de mélange gaussien (1D)

Simulez un mélange gaussien 1D à 3 composantes :

  • proportions pi = c(0.2,0.5,0.3),
  • moyennes mu = c(-3,0,3),
  • écarts‑types sd = c(0.5,0.6,0.8).

Puis tracez sur un même graphique :

  • les densités des composantes individuelles,
  • la densité du mélange,
  • les données simulées (scatter plot).

Pour simuler les données, vous pouvez d’abord tirer les composantes latentes z selon les proportions pi, puis générer les observations Y en fonction de z. Pour tracer les densités, utilisez la fonction dnorm() pour chaque composante et calculez la densité du mélange comme une combinaison pondérée des densités des composantes.

set.seed(42)

# parameters of the mixture
pi  <- c(0.2,0.5,0.3)
mu <- c(-3,0,3)
sd <- c(0.5,0.6,0.8)

# simulate data from the mixture
z <- sample(1:3, 100, replace = TRUE, prob = pi)
Y <- rnorm(100, mu[z], sd[z])
data <- tibble(y = Y)

# marginals and mixture densities
x <- seq(min(Y)-1, max(Y)+1, length.out = 400)

densities <- tibble(comp1 = dnorm(x, mu[1], sd[1]),
                    comp2 = dnorm(x, mu[2], sd[2]),
                    comp3 = dnorm(x, mu[3], sd[3])) %>%
  mutate(mix = pi[1]*comp1 + pi[2]*comp2 + pi[3]*comp3) %>% 
  mutate(x = x)


# plot
ggplot(densities, aes(x = y)) +
  geom_area(aes(x = x, y = comp1), alpha = 0.25, fill = 'blue', position = "identity") +
  geom_area(aes(x = x, y = comp2), alpha = 0.25, fill = 'green', position = "identity") +
  geom_area(aes(x = x, y = comp3), alpha = 0.25, fill = 'red', position = "identity") +
  geom_line(aes(x = x, y = mix), color = "black") +
  geom_point(data = data, aes(x = y, y = -0.02), alpha = 0.7) +
  labs(title = "Mélange gaussien 1D — proba (0.2, 0.5, 0.3))",
       x = "x", y = "densité") +
  theme_minimal()