Вы не можете выбрать более 25 тем
Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
206 строки
10 KiB
Python
206 строки
10 KiB
Python
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
import pandas as pd
|
|
from scipy.cluster.hierarchy import dendrogram, linkage
|
|
from sklearn.preprocessing import StandardScaler
|
|
from scipy.spatial import distance
|
|
|
|
# Загрузка датасета MLB
|
|
mlb_data = pd.read_csv('SOCR_Data_MLB_HeightsWeights.txt', sep='\t')
|
|
mlb_data.columns = ['Name', 'Team', 'Position', 'Height', 'Weight', 'Age']
|
|
|
|
# Создаем DataFrame
|
|
mlb_pd = pd.DataFrame(data=np.c_[mlb_data[['Height', 'Weight', 'Age']]],
|
|
columns=['Height (inches)', 'Weight (pounds)', 'Age (years)'])
|
|
|
|
# Стандартизация данных
|
|
scaler = StandardScaler()
|
|
data_scaled = scaler.fit_transform(mlb_pd[['Height (inches)', 'Weight (pounds)', 'Age (years)']])
|
|
|
|
|
|
# Функция для вычисления матрицы расстояний
|
|
def calculate_distance_matrix(data, metric):
|
|
n_samples = len(data)
|
|
distance_matrix = np.zeros((n_samples, n_samples))
|
|
|
|
for i in range(n_samples):
|
|
for j in range(i + 1, n_samples):
|
|
if metric == 'euclidean':
|
|
dist = distance.euclidean(data[i], data[j])
|
|
elif metric == 'cityblock':
|
|
dist = distance.cityblock(data[i], data[j])
|
|
elif metric == 'chebyshev':
|
|
dist = distance.chebyshev(data[i], data[j])
|
|
elif metric == 'cosine':
|
|
dist = distance.cosine(data[i], data[j])
|
|
|
|
distance_matrix[i][j] = dist
|
|
distance_matrix[j][i] = dist
|
|
|
|
return distance_matrix
|
|
|
|
|
|
# Список комбинаций для исследования
|
|
combinations = [
|
|
# Евклидово расстояние
|
|
{'metric': 'euclidean', 'linkage': 'single', 'name': 'Евклидово расстояние, одиночная связь'},
|
|
{'metric': 'euclidean', 'linkage': 'complete', 'name': 'Евклидово расстояние, полная связь'},
|
|
{'metric': 'euclidean', 'linkage': 'average', 'name': 'Евклидово расстояние, средняя связь'},
|
|
{'metric': 'euclidean', 'linkage': 'ward', 'name': 'Евклидово расстояние, связь Ward'},
|
|
|
|
# Манхэттенское расстояние
|
|
{'metric': 'cityblock', 'linkage': 'single', 'name': 'Манхэттенское расстояние, одиночная связь'},
|
|
{'metric': 'cityblock', 'linkage': 'average', 'name': 'Манхэттенское расстояние, средняя связь'},
|
|
{'metric': 'cityblock', 'linkage': 'complete', 'name': 'Манхэттенское расстояние, полная связь'},
|
|
|
|
# Косинусное расстояние
|
|
{'metric': 'cosine', 'linkage': 'single', 'name': 'Косинусное расстояние, одиночная связь'},
|
|
|
|
# Расстояние Чебышева
|
|
{'metric': 'chebyshev', 'linkage': 'single', 'name': 'Расстояние Чебышева, одиночная связь'},
|
|
{'metric': 'euclidean', 'linkage': 'centroid', 'name': 'Евклидово расстояние, центроидная связь'}
|
|
]
|
|
|
|
# Построение отдельных дендрограмм
|
|
for i, combo in enumerate(combinations, 1):
|
|
try:
|
|
# Вычисляем матрицу расстояний
|
|
distance_matrix = calculate_distance_matrix(data_scaled, combo['metric'])
|
|
|
|
# Создаем linkage matrix
|
|
Z = linkage(distance_matrix, method=combo['linkage'])
|
|
|
|
# Создаем отдельную фигуру для каждой дендрограммы
|
|
plt.figure(figsize=(12, 8))
|
|
|
|
# Строим дендрограмму
|
|
dendrogram(Z,
|
|
truncate_mode='lastp',
|
|
p=12,
|
|
show_leaf_counts=True,
|
|
leaf_rotation=90)
|
|
|
|
plt.title(f'Дендрограмма: {combo["name"]}', fontsize=14, pad=20)
|
|
plt.xlabel('Количество точек в узле', fontsize=12)
|
|
plt.ylabel('Расстояние', fontsize=12)
|
|
plt.grid(True, alpha=0.3)
|
|
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
print(f"{i}. {combo['name']} - Успешно построена")
|
|
|
|
except Exception as e:
|
|
print(f"{i}. {combo['name']} - Ошибка: {e}")
|
|
|
|
# 3D ВИЗУАЛИЗАЦИИ ДЛЯ КАЖДОЙ КОМБИНАЦИИ
|
|
print("\n" + "=" * 80)
|
|
print("3D ВИЗУАЛИЗАЦИЯ КЛАСТЕРОВ ДЛЯ КАЖДОЙ КОМБИНАЦИИ")
|
|
print("=" * 80)
|
|
|
|
for i, combo in enumerate(combinations, 1):
|
|
try:
|
|
# Вычисляем матрицу расстояний
|
|
distance_matrix = calculate_distance_matrix(data_scaled, combo['metric'])
|
|
|
|
# Создаем linkage matrix
|
|
Z = linkage(distance_matrix, method=combo['linkage'])
|
|
|
|
# Определяем кластеры (берем 3 кластера для визуализации)
|
|
from scipy.cluster.hierarchy import fcluster
|
|
|
|
labels = fcluster(Z, t=3, criterion='maxclust')
|
|
|
|
# Создаем отдельную 3D визуализацию
|
|
fig = plt.figure(figsize=(10, 8))
|
|
ax = fig.add_subplot(111, projection='3d')
|
|
|
|
scatter = ax.scatter3D(mlb_pd['Height (inches)'],
|
|
mlb_pd['Weight (pounds)'],
|
|
mlb_pd['Age (years)'],
|
|
c=labels,
|
|
cmap='tab10',
|
|
s=50,
|
|
alpha=0.7)
|
|
|
|
ax.set_title(f'3D Визуализация: {combo["name"]}', fontsize=14, pad=20)
|
|
ax.set_xlabel('Height (inches)', fontsize=12)
|
|
ax.set_ylabel('Weight (pounds)', fontsize=12)
|
|
ax.set_zlabel('Age (years)', fontsize=12)
|
|
|
|
plt.colorbar(scatter, ax=ax, label='Cluster')
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
# Выводим информацию о кластерах
|
|
print(f"\n{i}. {combo['name']}:")
|
|
for cluster_id in np.unique(labels):
|
|
cluster_data = mlb_pd[labels == cluster_id]
|
|
print(f" Кластер {cluster_id}: {len(cluster_data)} игроков")
|
|
print(
|
|
f" Рост: {cluster_data['Height (inches)'].mean():.1f} ± {cluster_data['Height (inches)'].std():.1f}")
|
|
print(
|
|
f" Вес: {cluster_data['Weight (pounds)'].mean():.1f} ± {cluster_data['Weight (pounds)'].std():.1f}")
|
|
print(f" Возраст: {cluster_data['Age (years)'].mean():.1f} ± {cluster_data['Age (years)'].std():.1f}")
|
|
|
|
except Exception as e:
|
|
print(f"{i}. Ошибка в 3D визуализации для {combo['name']}: {e}")
|
|
|
|
# АНАЛИЗ РАССТОЯНИЙ ОБЪЕДИНЕНИЯ ДЛЯ ОПРЕДЕЛЕНИЯ ОПТИМАЛЬНОГО ЧИСЛА КЛАСТЕРОВ
|
|
print("\n" + "=" * 80)
|
|
print("АНАЛИЗ ОПТИМАЛЬНОГО ЧИСЛА КЛАСТЕРОВ")
|
|
print("=" * 80)
|
|
|
|
# Анализируем лучшие комбинации
|
|
best_combinations = [
|
|
{'metric': 'euclidean', 'linkage': 'ward', 'name': 'Евклидово расстояние, связь Ward'},
|
|
{'metric': 'euclidean', 'linkage': 'average', 'name': 'Евклидово расстояние, средняя связь'},
|
|
{'metric': 'cityblock', 'linkage': 'average', 'name': 'Манхэттенское расстояние, средняя связь'}
|
|
]
|
|
|
|
for i, combo in enumerate(best_combinations, 1):
|
|
try:
|
|
# Вычисляем матрицу расстояний
|
|
distance_matrix = calculate_distance_matrix(data_scaled, combo['metric'])
|
|
Z = linkage(distance_matrix, method=combo['linkage'])
|
|
|
|
# Анализ расстояний объединения
|
|
last_merges = Z[-15:, 2] # последние 15 объединений
|
|
distances_diff = np.diff(last_merges[::-1])
|
|
|
|
# Находим оптимальное число кластеров
|
|
optimal_idx = np.argmax(distances_diff) + 2
|
|
optimal_clusters = min(optimal_idx, 8)
|
|
|
|
# Создаем отдельную фигуру для анализа
|
|
plt.figure(figsize=(10, 6))
|
|
|
|
plt.plot(range(len(last_merges)), last_merges[::-1], 'bo-', linewidth=2, markersize=6)
|
|
plt.axvline(x=optimal_idx - 1, color='red', linestyle='--',
|
|
label=f'Оптимально: {optimal_clusters} кластеров')
|
|
plt.title(f'Анализ расстояний объединения: {combo["name"]}\nОптимальное число кластеров: {optimal_clusters}')
|
|
plt.xlabel('Шаг объединения')
|
|
plt.ylabel('Расстояние')
|
|
plt.legend()
|
|
plt.grid(True, alpha=0.3)
|
|
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
print(f"{i}. {combo['name']}: оптимальное число кластеров = {optimal_clusters}")
|
|
print(f" Наибольший скачок расстояния: {distances_diff[optimal_idx - 2]:.3f}")
|
|
|
|
except Exception as e:
|
|
print(f"{i}. Ошибка в анализе для {combo['name']}: {e}")
|
|
|
|
# ИТОГОВЫЙ ВЫВОД
|
|
print("\n" + "=" * 80)
|
|
print("ИТОГОВЫЕ ВЫВОДЫ")
|
|
print("=" * 80)
|
|
print("1. Все 10 дендрограмм построены по отдельности")
|
|
print("2. Для каждой комбинации создана отдельная 3D визуализация")
|
|
print("3. Метод Ward обычно дает наиболее сбалансированные кластеры")
|
|
print("4. Одиночная связь может создавать вытянутые 'цепочки' кластеров")
|
|
print("5. Оптимальное число кластеров определяется по наибольшему скачку в расстояниях объединения")
|
|
print("6. Для MLB данных оптимально 3-5 кластеров, соответствующих различным физическим типам игроков")
|
|
print("7. Разные метрики расстояния показывают различные аспекты сходства между игроками") |