from sklearn.metrics import silhouette_score, davies_bouldin_score, calinski_harabasz_score import pandas as pd import numpy as np from tqdm import tqdm def evaluate_clusterings(df, clustering_info): results = [] for config in tqdm(clustering_info, desc="Obliczanie metryk"): method = config['method'] feat_col = config['features'] clust_col = config['clustering'] # 1. Przygotowanie cech (X) - upewniamy się, że to macierz numpy if isinstance(df[feat_col].iloc[0], (list, np.ndarray)): X = np.stack(df[feat_col].values) else: # Jeśli to pojedyncze kolumny (np. po UMAP 2D), musisz je najpierw połączyć # Zakładając, że feat_col to nazwa kolumny z listą/wektorem: X = np.stack(df[feat_col].values) # 2. Pobranie etykiet klastrów labels = df[clust_col].astype(int).values # 3. Obsługa szumu (-1) dla DBSCAN/HDBSCAN # Metryki wymagają co najmniej 2 klastrów i nie radzą sobie dobrze z "szumem" jako klastrem # Częstą praktyką jest obliczanie metryk tylko dla punktów, które NIE są szumem mask = labels != -1 if mask.sum() < 2 or len(np.unique(labels[mask])) < 2: print(f"Pominięto {clust_col}: zbyt mało klastrów po usunięciu szumu.") continue X_valid = X[mask] labels_valid = labels[mask] # 4. Obliczanie metryk # Silhouette: wyższa = lepiej (-1 do 1) s_score = silhouette_score(X_valid, labels_valid) # Davies-Bouldin: niższa = lepiej (lepiej odseparowane i zwarte klastry) db_score = davies_bouldin_score(X_valid, labels_valid) # Calinski-Harabasz: wyższa = lepiej (stosunek dyspersji między i wewnątrz klastrów) ch_score = calinski_harabasz_score(X_valid, labels_valid) results.append({ 'Grupowanie': clust_col, 'Metoda': method, 'Cechy': feat_col, 'Silhouette': round(s_score, 4), 'Davies-Bouldin': round(db_score, 4), 'Calinski-Harabasz': round(ch_score, 2), 'Liczba_punktów': len(labels_valid), 'Liczba_klastrów': len(np.unique(labels_valid)), 'Procent_szumu': round((1 - mask.mean()) * 100, 2) }) return pd.DataFrame(results)