diff --git a/.idea/misc.xml b/.idea/misc.xml index 897458c5c5638e5b22888eef783f38f57e398f99..8d619b327b5eb65a8b9f51f47722bad8696bdbc3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> + <component name="Black"> + <option name="sdkName" value="Python 3.9 (tp-clustering)" /> + </component> <component name="MarkdownSettingsMigration"> <option name="stateVersion" value="1" /> </component> diff --git a/main.py b/main.py index 86006cceb8752e2dc74a8da00fdd97cb719df662..63cc38f2cd46d62c0df7049fdcd43637952f0135 100644 --- a/main.py +++ b/main.py @@ -1,135 +1,57 @@ # Importation des bibliothèques -from typing import Tuple, List import pandas as pd import numpy as np import matplotlib.pyplot as plt -def import_csv(filename: str, h: int or None) -> pd.DataFrame: - """ - Importe un fichier CSV et retourne un DataFrame pandas. - - Args: - filename (str): Le chemin du fichier CSV. - h (int or None, optional): L'entête du fichier CSV. - - Returns: - pandas.DataFrame: Les données importées sous forme de DataFrame. - """ - return pd.read_csv(filename, header=h) - - def manhattan_distance(x1: np.ndarray, x2: np.ndarray) -> float: - """ - Calcule la distance de Manhattan (L2) entre deux vecteurs. - - Args: - x1 (numpy.ndarray): Le premier vecteur. - x2 (numpy.ndarray): Le deuxième vecteur. - - Returns: - float: La distance de Manhattan entre x1 et x2. - """ - squared_diff = (x1 - x2) ** 2 # Carré des différences - l2_manhattan = np.sqrt(np.sum(squared_diff)) # Racine carrée de la somme des carrés - return l2_manhattan - - -def assign_clusters(X: np.ndarray, centroids: np.ndarray) -> np.ndarray: - """ - Attribue chaque point de données à son cluster le plus proche en utilisant la distance de Manhattan. - - Args: - X (numpy.ndarray): Les données. - centroids (numpy.ndarray): Les centroïdes des clusters. - - Returns: - numpy.ndarray: Les étiquettes de cluster attribuées à chaque point de données. - """ - distances = np.array([[manhattan_distance(x, centroid) for centroid in centroids] for x in X]) - return np.argmin(distances, axis=1) - - -def k_means(X: np.ndarray, k: int, max_iterations: int = 100) -> Tuple[np.ndarray, np.ndarray]: - """ - Implémente l'algorithme K-Means pour regrouper les données en k clusters. - - Args: - X (numpy.ndarray): Les données à regrouper. - k (int): Le nombre de clusters à former. - max_iterations (int, optional): Le nombre maximal d'itérations. Défaut à 100. + distance = 0 + if x1.shape == x2.shape: + for i in range(x1.size): + distance += np.abs(x1[i] - x2[i]) + return distance - Returns: - tuple: Une paire contenant les étiquettes de cluster et les centroïdes finaux. - """ - # Initialisation des k-centroïdes de manière aléatoire - np.random.seed(0) - centroids = X[np.random.choice(X.shape[0], k, replace=False)] # séléctionne k points aléatoirement dans la liste X - old_centroids = np.zeros(centroids.shape) - new_centroids = centroids.copy() - - distances_history = [] # Pour stocker la somme des distances - - iteration = 0 - # pour vérifier que les centroides ne changent plus , il faut check que l'ancien et le nouveau soient pareils , on ajoute tout de même un nombre d'itérations max pour ne pas tourner à l'infini - while not np.array_equal(old_centroids, new_centroids) and iteration < max_iterations: - iteration += 1 - labels = assign_clusters(X, new_centroids) - old_centroids = new_centroids.copy() - for i in range(k): - new_centroids[i] = np.mean(X[labels == i], axis=0) - - # Calcul de la somme des distances au sein de chaque cluster (fonction objectif) - total_distance = 0 - for i in range(k): - cluster_points = X[labels == i] - cluster_distance = np.sum([manhattan_distance(point, new_centroids[i]) for point in cluster_points]) - total_distance += cluster_distance +if __name__ == '__main__': + dataset = pd.read_csv("Data/student-data-test.csv", header=0) + X = dataset.iloc[:, 1:].values + k = 3 - distances_history.append(total_distance) - # Affichage des clusters à cette itération - plt.figure(figsize=(8, 6)) - for i in range(k): - cluster_points = X[labels == i] - plt.scatter(cluster_points[:, 0], cluster_points[:, 1], label=f"Cluster {i + 1}") - plt.scatter(new_centroids[:, 0], new_centroids[:, 1], marker="X", color="black", label="Centroids") - plt.xlabel("Caractéristique 1") - plt.ylabel("Caractéristique 2") + # Initialisation des k-centroides de manière aléatoire + centroids = X[np.random.choice(X.shape[0], k, replace=False)] + + max_iter = 100 + + # Algorithme K-Means + for i in range(max_iter): + # Créer des clusters vides + clusters = [[] for _ in range(k)] + # Sauvegarder les anciens centroïdes + for point in X: + distances = [manhattan_distance(point, centroid) for centroid in centroids] + cluster_index = np.argmin(distances) + clusters[cluster_index].append(point) + + # Sauvegarder les anciens centroïdes + old_centroids = centroids.copy() + + # Mettre à jour les centroïdes en calculant la moyenne des points dans chaque cluster + for j in range(k): + if len(clusters[j]) > 0: + centroids[j] = np.mean(clusters[j], axis=0) + + # Afficher les centroides finaux et les clusters + for iteration, centroid in enumerate(centroids): + cluster_points = np.array(clusters[iteration]) + plt.scatter(cluster_points[:, 0], cluster_points[:, 1], label=f"Cluster {iteration}") + plt.scatter(centroid[0], centroid[1], marker="x", s=200, c="red", label=f"Centroïde {iteration + 1}") + + plt.xlabel("Grade 1") + plt.ylabel("Grade 2") + plt.title(f"Clustering K-Means {i}") plt.legend() - plt.title(f"Iteration {iteration}") plt.show() - # Tracer la somme des distances à chaque itération - plt.figure(figsize=(8, 6)) - plt.plot(range(1, iteration + 1), distances_history, marker='o') - plt.xlabel("Iteration") - plt.ylabel("Somme des distances") - plt.title("Evolution de la somme des distances") - plt.show() - - return labels, new_centroids - - -if __name__ == "__main__": - # Initialiser k-centroides - # attribuer les points aux centroïde le plus proche (Manhattan / L2) - # Tant que les centroïdes bougent : - # Pour chaque cluster, calculer le point central du cluster (moyenne des coordonnées) - # noter si ancien centroïde ~= nouveau centroïde - # attribution des points aux nouveaux centroïdes - # calcul de la somme des distances au sein d'un cluster - # fonction objectif à minimiser - # plot la somme de la somme des distances à chaque itération - # afficher les nouveaux cluster à chaque itérations - - # Charger les données depuis le fichier CSV - df = import_csv("Data/iris.csv", h=None) - X = df.iloc[:, :-1].values - - k = 3 - - labels, centroids = k_means(X, k) - - print("Nouveaux centroïdes finaux:") - print(centroids) + # Convergence ? + if np.all(old_centroids == centroids): + break \ No newline at end of file