hierarchical_clustering
czym jest hierarchiczna klasteryzacja? ⭐
- Hierarchiczna klasteryzacja to algorytm, który buduje hierarchię klastrów.
- Może być aglomeracyjna (bottom-up) lub dywizywna (top-down).
- Tworzy drzewo klastrów (dendrogram), które pokazuje hierarchię.
- Nie wymaga określenia liczby klastrów z góry.
- Pozwala na analizę struktury danych na różnych poziomach szczegółowości.
jak działa hierarchiczna klasteryzacja? ⭐
Aglomeracyjna (Agglomerative):
- Inicjalizacja: Każdy punkt to osobny klaster.
- Łączenie: Łączenie najbliższych klastrów.
- Aktualizacja: Przeliczanie odległości między klastrami.
- Iteracja: Powtarzanie kroków 2-3 aż do jednego klastra.
Metody łączenia klastrów:
- Single Linkage: Minimalna odległość między punktami z różnych klastrów.
- Complete Linkage: Maksymalna odległość między punktami z różnych klastrów.
- Average Linkage: Średnia odległość między punktami z różnych klastrów.
- Ward: Minimalizacja wariancji wewnątrz klastrów.
przykładowy kod (hierarchiczna klasteryzacja)
import pandas as pd
import numpy as np
from sklearn.cluster import AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
from scipy.spatial.distance import pdist
import matplotlib.pyplot as plt
import seaborn as sns
# Generowanie danych
np.random.seed(42)
n_samples = 200
# Generowanie czterech klastrów
cluster1 = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], n_samples//4)
cluster2 = np.random.multivariate_normal([4, 0], [[1, 0], [0, 1]], n_samples//4)
cluster3 = np.random.multivariate_normal([0, 4], [[1, 0], [0, 1]], n_samples//4)
cluster4 = np.random.multivariate_normal([4, 4], [[1, 0], [0, 1]], n_samples//4)
# Łączenie danych
X = np.vstack([cluster1, cluster2, cluster3, cluster4])
# Tworzenie DataFrame
data = pd.DataFrame(X, columns=['feature1', 'feature2'])
print("Rozmiar danych:", data.shape)
print("Przykładowe dane:")
print(data.head())
# Wizualizacja oryginalnych danych
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.scatter(data['feature1'], data['feature2'], alpha=0.6)
plt.title('Oryginalne dane')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.grid(True, alpha=0.3)
1. podstawowa hierarchiczna klasteryzacja
# Skalowanie danych
scaler = StandardScaler()
X_scaled = scaler.fit_transform(data)
# Hierarchiczna klasteryzacja z różnymi metodami łączenia
linkage_methods = ['single', 'complete', 'average', 'ward']
clustering_results = {}
for method in linkage_methods:
print(f"\nTrenowanie z metodą {method}...")
# Obliczanie linkage matrix
linkage_matrix = linkage(X_scaled, method=method)
# AglomerativeClustering dla porównania
agg_clustering = AgglomerativeClustering(n_clusters=4, linkage=method)
agg_clustering.fit(X_scaled)
clustering_results[method] = {
'linkage_matrix': linkage_matrix,
'labels': agg_clustering.labels_,
'n_leaves': agg_clustering.n_leaves_
}
print(f"Liczba liści: {agg_clustering.n_leaves_}")
# Wizualizacja wyników
colors = ['red', 'blue', 'green', 'orange']
for i, (method, result) in enumerate(clustering_results.items()):
plt.subplot(1, 3, i+2)
data[f'cluster_{method}'] = result['labels']
for j in range(4):
cluster_mask = result['labels'] == j
plt.scatter(data.loc[cluster_mask, 'feature1'],
data.loc[cluster_mask, 'feature2'],
c=colors[j], alpha=0.6, label=f'Klaster {j}')
plt.title(f'Hierarchiczna - {method}')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
2. dendrogram
# Tworzenie dendrogramów
plt.figure(figsize=(15, 10))
for i, (method, result) in enumerate(clustering_results.items()):
plt.subplot(2, 2, i+1)
# Rysowanie dendrogramu
dendrogram(result['linkage_matrix'],
leaf_rotation=90,
leaf_font_size=8,
labels=range(len(X_scaled)))
plt.title(f'Dendrogram - {method}')
plt.xlabel('Indeks próbki')
plt.ylabel('Odległość')
plt.tight_layout()
plt.show()
# Szczegółowy dendrogram dla metody Ward
plt.figure(figsize=(12, 6))
dendrogram(clustering_results['ward']['linkage_matrix'],
leaf_rotation=90,
leaf_font_size=8,
labels=range(len(X_scaled)),
color_threshold=2.5)
plt.title('Dendrogram - Ward Method (z kolorami)')
plt.xlabel('Indeks próbki')
plt.ylabel('Odległość')
plt.show()
3. wybór optymalnej liczby klastrów
# Analiza dendrogramu dla wyboru liczby klastrów
def analyze_dendrogram(linkage_matrix, max_clusters=10):
"""Analiza dendrogramu dla różnych liczb klastrów"""
from scipy.cluster.hierarchy import fcluster
results = []
for n_clusters in range(2, max_clusters + 1):
# Wybór klastrów na podstawie liczby
labels = fcluster(linkage_matrix, n_clusters, criterion='maxclust')
# Obliczanie metryk
from sklearn.metrics import silhouette_score, calinski_harabasz_score
silhouette = silhouette_score(X_scaled, labels)
calinski = calinski_harabasz_score(X_scaled, labels)
results.append({
'n_clusters': n_clusters,
'silhouette': silhouette,
'calinski': calinski
})
return pd.DataFrame(results)
# Analiza dla metody Ward
ward_analysis = analyze_dendrogram(clustering_results['ward']['linkage_matrix'])
print("Analiza optymalnej liczby klastrów (Ward):")
print(ward_analysis)
# Wizualizacja analizy
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.plot(ward_analysis['n_clusters'], ward_analysis['silhouette'], 'bo-')
plt.xlabel('Liczba klastrów')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Analysis')
plt.grid(True, alpha=0.3)
plt.subplot(1, 3, 2)
plt.plot(ward_analysis['n_clusters'], ward_analysis['calinski'], 'ro-')
plt.xlabel('Liczba klastrów')
plt.ylabel('Calinski-Harabasz Score')
plt.title('Calinski-Harabasz Analysis')
plt.grid(True, alpha=0.3)
# Dendrogram z zaznaczonymi poziomami
plt.subplot(1, 3, 3)
dendrogram(clustering_results['ward']['linkage_matrix'],
leaf_rotation=90,
leaf_font_size=8,
labels=range(len(X_scaled)))
# Dodanie linii dla różnych liczb klastrów
for n_clusters in [2, 3, 4, 5]:
max_dist = np.max(clustering_results['ward']['linkage_matrix'][:, 2])
threshold = max_dist * (1 - n_clusters / len(X_scaled))
plt.axhline(y=threshold, color='red', linestyle='--', alpha=0.7)
plt.text(len(X_scaled) + 1, threshold, f'K={n_clusters}',
verticalalignment='center')
plt.title('Dendrogram z poziomami klastrów')
plt.xlabel('Indeks próbki')
plt.ylabel('Odległość')
plt.tight_layout()
plt.show()
4. porównanie metod łączenia
# Porównanie różnych metod łączenia
comparison_data = []
for method, result in clustering_results.items():
# Testowanie dla różnych liczb klastrów
for n_clusters in [2, 3, 4, 5]:
from scipy.cluster.hierarchy import fcluster
labels = fcluster(result['linkage_matrix'], n_clusters, criterion='maxclust')
# Obliczanie metryk
silhouette = silhouette_score(X_scaled, labels)
calinski = calinski_harabasz_score(X_scaled, labels)
comparison_data.append({
'method': method,
'n_clusters': n_clusters,
'silhouette': silhouette,
'calinski': calinski
})
comparison_df = pd.DataFrame(comparison_data)
print("Porównanie metod łączenia:")
print(comparison_df)
# Wizualizacja porównania
plt.figure(figsize=(15, 5))
# Silhouette score
plt.subplot(1, 3, 1)
for method in linkage_methods:
method_data = comparison_df[comparison_df['method'] == method]
plt.plot(method_data['n_clusters'], method_data['silhouette'],
marker='o', label=method)
plt.xlabel('Liczba klastrów')
plt.ylabel('Silhouette Score')
plt.title('Porównanie metod - Silhouette')
plt.legend()
plt.grid(True, alpha=0.3)
# Calinski-Harabasz score
plt.subplot(1, 3, 2)
for method in linkage_methods:
method_data = comparison_df[comparison_df['method'] == method]
plt.plot(method_data['n_clusters'], method_data['calinski'],
marker='o', label=method)
plt.xlabel('Liczba klastrów')
plt.ylabel('Calinski-Harabasz Score')
plt.title('Porównanie metod - Calinski-Harabasz')
plt.legend()
plt.grid(True, alpha=0.3)
# Heatmapa porównania
plt.subplot(1, 3, 3)
pivot_df = comparison_df.pivot(index='method', columns='n_clusters', values='silhouette')
sns.heatmap(pivot_df, annot=True, cmap='viridis', fmt='.3f')
plt.title('Heatmapa Silhouette Score')
plt.xlabel('Liczba klastrów')
plt.ylabel('Metoda łączenia')
plt.tight_layout()
plt.show()
5. hierarchiczna klasteryzacja dla danych wielowymiarowych
# Generowanie danych wielowymiarowych
np.random.seed(42)
n_samples_multi = 300
n_features = 4
# Trzy klastry w 4D
cluster1_multi = np.random.multivariate_normal([0, 0, 0, 0], np.eye(4), n_samples_multi//3)
cluster2_multi = np.random.multivariate_normal([3, 3, 3, 3], np.eye(4), n_samples_multi//3)
cluster3_multi = np.random.multivariate_normal([0, 3, 0, 3], np.eye(4), n_samples_multi//3)
X_multi = np.vstack([cluster1_multi, cluster2_multi, cluster3_multi])
data_multi = pd.DataFrame(X_multi, columns=[f'feature_{i+1}' for i in range(n_features)])
# Skalowanie
scaler_multi = StandardScaler()
X_multi_scaled = scaler_multi.fit_transform(data_multi)
# Hierarchiczna klasteryzacja dla danych wielowymiarowych
linkage_multi = linkage(X_multi_scaled, method='ward')
# Analiza dla różnych liczb klastrów
multi_analysis = analyze_dendrogram(linkage_multi, max_clusters=8)
print("Analiza danych wielowymiarowych:")
print(multi_analysis)
# Wizualizacja
plt.figure(figsize=(15, 5))
# Dendrogram
plt.subplot(1, 3, 1)
dendrogram(linkage_multi, leaf_rotation=90, leaf_font_size=8)
plt.title('Dendrogram - Dane wielowymiarowe')
plt.xlabel('Indeks próbki')
plt.ylabel('Odległość')
# Analiza metryk
plt.subplot(1, 3, 2)
plt.plot(multi_analysis['n_clusters'], multi_analysis['silhouette'], 'bo-', label='Silhouette')
plt.xlabel('Liczba klastrów')
plt.ylabel('Silhouette Score')
plt.title('Analiza Silhouette')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(1, 3, 3)
plt.plot(multi_analysis['n_clusters'], multi_analysis['calinski'], 'ro-', label='Calinski-Harabasz')
plt.xlabel('Liczba klastrów')
plt.ylabel('Calinski-Harabasz Score')
plt.title('Analiza Calinski-Harabasz')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Klasteryzacja z optymalną liczbą klastrów
optimal_n_clusters = multi_analysis.loc[multi_analysis['silhouette'].idxmax(), 'n_clusters']
print(f"Optymalna liczba klastrów: {int(optimal_n_clusters)}")
# Wizualizacja wyników (pierwsze 2 wymiary)
from scipy.cluster.hierarchy import fcluster
optimal_labels = fcluster(linkage_multi, int(optimal_n_clusters), criterion='maxclust')
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
for i in range(1, int(optimal_n_clusters) + 1):
cluster_mask = optimal_labels == i
plt.scatter(X_multi[cluster_mask, 0], X_multi[cluster_mask, 1],
c=plt.cm.Set1(i-1), alpha=0.6, label=f'Klaster {i}')
plt.title(f'Hierarchiczna klasteryzacja (K={int(optimal_n_clusters)})')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True, alpha=0.3)
# Statystyki klastrów
plt.subplot(1, 2, 2)
cluster_counts = pd.Series(optimal_labels).value_counts().sort_index()
plt.bar(range(len(cluster_counts)), cluster_counts.values)
plt.title('Rozkład klastrów')
plt.xlabel('Klaster')
plt.ylabel('Liczba punktów')
plt.xticks(range(len(cluster_counts)), cluster_counts.index)
plt.tight_layout()
plt.show()
6. praktyczne zastosowania
# Przykład: Segmentacja produktów
np.random.seed(42)
n_products = 500
# Symulacja danych produktów
price = np.random.lognormal(3, 0.5, n_products) # Cena
rating = np.random.beta(2, 2, n_products) * 5 # Ocena (1-5)
sales = np.random.poisson(100, n_products) # Sprzedaż
reviews = np.random.poisson(50, n_products) # Liczba recenzji
# Tworzenie klastrów produktów
product_data = pd.DataFrame({
'price': price,
'rating': rating,
'sales': sales,
'reviews': reviews
})
# Skalowanie
scaler_products = StandardScaler()
product_data_scaled = scaler_products.fit_transform(product_data)
# Hierarchiczna klasteryzacja produktów
linkage_products = linkage(product_data_scaled, method='ward')
# Analiza optymalnej liczby klastrów
product_analysis = analyze_dendrogram(linkage_products, max_clusters=10)
optimal_n_products = product_analysis.loc[product_analysis['silhouette'].idxmax(), 'n_clusters']
print(f"Optymalna liczba segmentów produktów: {int(optimal_n_products)}")
# Klasteryzacja z optymalną liczbą klastrów
product_labels = fcluster(linkage_products, int(optimal_n_products), criterion='maxclust')
product_data['segment'] = product_labels
# Analiza segmentów
segment_analysis = product_data.groupby('segment').agg({
'price': ['mean', 'std'],
'rating': ['mean', 'std'],
'sales': ['mean', 'std'],
'reviews': ['mean', 'std']
}).round(2)
print("\nAnaliza segmentów produktów:")
print(segment_analysis)
# Nazwanie segmentów na podstawie charakterystyki
segment_names = {
1: 'Produkty premium',
2: 'Produkty popularne',
3: 'Produkty budżetowe',
4: 'Produkty niszowe'
}
product_data['segment_name'] = product_data['segment'].map(segment_names)
print("\nCharakterystyka segmentów:")
for segment in range(1, int(optimal_n_products) + 1):
segment_data = product_data[product_data['segment'] == segment]
print(f"\n{segment_names.get(segment, f'Segment {segment}')}:")
print(f" Liczba produktów: {len(segment_data)}")
print(f" Średnia cena: {segment_data['price'].mean():.2f}")
print(f" Średnia ocena: {segment_data['rating'].mean():.2f}")
print(f" Średnia sprzedaż: {segment_data['sales'].mean():.0f}")
print(f" Średnia liczba recenzji: {segment_data['reviews'].mean():.0f}")
# Wizualizacja segmentacji
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
for segment in range(1, int(optimal_n_products) + 1):
segment_data = product_data[product_data['segment'] == segment]
plt.scatter(segment_data['price'], segment_data['rating'],
c=plt.cm.Set1(segment-1), alpha=0.6, label=segment_names.get(segment, f'Segment {segment}'))
plt.xlabel('Cena')
plt.ylabel('Ocena')
plt.title('Segmentacja produktów - Cena vs Ocena')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(1, 3, 2)
for segment in range(1, int(optimal_n_products) + 1):
segment_data = product_data[product_data['segment'] == segment]
plt.scatter(segment_data['sales'], segment_data['reviews'],
c=plt.cm.Set1(segment-1), alpha=0.6, label=segment_names.get(segment, f'Segment {segment}'))
plt.xlabel('Sprzedaż')
plt.ylabel('Liczba recenzji')
plt.title('Segmentacja produktów - Sprzedaż vs Recenzje')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(1, 3, 3)
segment_counts = product_data['segment'].value_counts().sort_index()
plt.bar(range(len(segment_counts)), segment_counts.values)
plt.title('Rozkład segmentów')
plt.xlabel('Segment')
plt.ylabel('Liczba produktów')
plt.xticks(range(len(segment_counts)), [segment_names.get(i, f'Segment {i}') for i in segment_counts.index], rotation=45)
plt.tight_layout()
plt.show()
praktyczne ćwiczenia
-
Eksperymentuj z metodami - przetestuj różne metody łączenia klastrów.
-
Analiza dendrogramu - naucz się interpretować dendrogramy.
-
Wybór liczby klastrów - użyj różnych metryk do wyboru optymalnej liczby.
-
Porównanie z innymi algorytmami - porównaj z K-means i DBSCAN.
-
Real-world data - zastosuj do rzeczywistych problemów.
dobre praktyki
- Skalowanie: Zawsze skaluj dane przed klasteryzacją.
- Wybór metody: Ward jest często najlepszy dla danych numerycznych.
- Interpretacja dendrogramu: Uważaj na interpretację odległości.
- Walidacja: Używaj różnych metryk do oceny jakości.
wady i zalety
Zalety:
- Nie wymaga określenia liczby klastrów z góry
- Tworzy hierarchię klastrów
- Wizualizacja przez dendrogram
- Elastyczność w wyborze poziomu szczegółowości
Wady:
- Wolniejszy niż K-means
- Wrażliwy na outliers
- Trudny w interpretacji dla dużych zbiorów danych
- Nie może cofnąć błędów łączenia
polecane źródła
- Hierarchical Clustering Tutorial – scikit-learn
- Hierarchical Clustering Guide – Towards Data Science
- Hierarchical Clustering – StatQuest (YouTube)
- Hierarchical Clustering Tutorial – Machine Learning Mastery