2.7 MiB
Лаб. 5
import pandas as pd
# Загрузка датасета
df = pd.read_csv("data/kl.csv")
# Выбираем числовые столбцы и категориальный столбец "Club"
numerical_columns = [
"Overall",
"Potential",
"ShortPassing",
"Jersey Number",
"Age",
"Dribbling",
"SprintSpeed",
"International Reputation",
"Skill Moves",
"Weak Foot",
]
categorical_column = "Club"
# Создаем новый DataFrame с выбранными столбцами
selected_columns = numerical_columns + [categorical_column]
df_selected = df[selected_columns]
# Отображаем первые и последние строки
display(df_selected.head())
display(df_selected.tail())
# Уникальные значения в категориальном столбце "Club"
unique_clubs = df_selected[categorical_column].unique()
print(f"Количество уникальных клубов: {len(unique_clubs)}")
# Фильтруем для оставления только 5 клубов
clubs_to_keep = [
"FC Barcelona",
"Juventus",
"Chelsea",
"Real Madrid",
"Napoli",
]
df_filtered = df_selected[df_selected["Club"].isin(clubs_to_keep)].copy()
print("Фильтрованная таблица:")
display(df_filtered.head())
print(f"Фильтрованные клубы: {clubs_to_keep}")
unique_clubs = df_filtered[categorical_column].unique()
print(f"Количество уникальных клубов: {len(unique_clubs)}")
Визуализация данных (с учетом особенностей)
import matplotlib.pyplot as plt
def draw_data_2d(data, x_col, y_col, labels, label_names, subplot, color=None):
# Если задан параметр color, используем его для всех точек
if color is not None:
subplot.scatter(data[x_col], data[y_col], color=color, alpha=0.7)
else:
# Если color не задан, рисуем для каждой категории свой цвет
for idx, label_name in enumerate(label_names):
filtered = data[data["ClubIndex"] == idx] # Фильтрация данных по метке
subplot.scatter(
filtered[x_col], filtered[y_col], label=label_name, alpha=0.7
)
# Оформление графика
subplot.set_xlabel(x_col)
subplot.set_ylabel(y_col)
subplot.legend()
# Получаем список уникальных клубов и их индексы
clubs = df_filtered["Club"].unique()
club_to_index = {club: idx for idx, club in enumerate(clubs)}
# Добавляем столбец ClubIndex
df_filtered["ClubIndex"] = df_filtered["Club"].map(club_to_index)
# Проверяем результат
display(df_filtered.head())
# Подготовка данных для визуализации
y = df_filtered["ClubIndex"]
y_names = clubs
# Визуализация
plt.figure(figsize=(16, 12))
draw_data_2d(df_filtered, "Overall", "Age", y.tolist(), y_names, plt.subplot(3, 2, 1))
draw_data_2d(
df_filtered,
"Potential",
"ShortPassing",
y.tolist(),
y_names,
plt.subplot(3, 2, 2),
)
draw_data_2d(
df_filtered, "Overall", "Potential", y.tolist(), y_names, plt.subplot(3, 2, 3)
)
draw_data_2d(
df_filtered,
"Age",
"ShortPassing",
y.tolist(),
y_names,
plt.subplot(3, 2, 4),
)
draw_data_2d(df_filtered, "Age", "Potential", y.tolist(), y_names, plt.subplot(3, 2, 5))
draw_data_2d(
df_filtered,
"Overall",
"ShortPassing",
y.tolist(),
y_names,
plt.subplot(3, 2, 6),
)
plt.tight_layout()
plt.show()
Визуализация данных (без учета особенностей)
# Создаём фигуру
plt.figure(figsize=(16, 12))
# Указываем цвет для точек
color = "blue"
draw_data_2d(
df_filtered,
"Overall",
"Age",
y.tolist(),
y_names,
plt.subplot(3, 2, 1),
color=color,
)
draw_data_2d(
df_filtered,
"Potential",
"ShortPassing",
y.tolist(),
y_names,
plt.subplot(3, 2, 2),
color=color,
)
draw_data_2d(
df_filtered,
"Overall",
"Potential",
y.tolist(),
y_names,
plt.subplot(3, 2, 3),
color=color,
)
draw_data_2d(
df_filtered,
"Age",
"ShortPassing",
y.tolist(),
y_names,
plt.subplot(3, 2, 4),
color=color,
)
draw_data_2d(
df_filtered,
"Age",
"Potential",
y.tolist(),
y_names,
plt.subplot(3, 2, 5),
color=color,
)
draw_data_2d(
df_filtered,
"Overall",
"ShortPassing",
y.tolist(),
y_names,
plt.subplot(3, 2, 6),
color=color,
)
plt.tight_layout()
plt.show()
Иерархическая агломеративная кластеризация (+дендрограмма)
from utils_clusters import get_linkage_matrix, run_agglomerative
from visual import draw_dendrogram
from scipy.cluster import hierarchy
df_filtered_noclub = df_filtered.drop(columns=["Club"])
df_filtered_noclub_nona = df_filtered_noclub.dropna()
tree = run_agglomerative(df_filtered_noclub_nona)
linkage_matrix = get_linkage_matrix(tree)
draw_dendrogram(linkage_matrix)
linkage_matrix = get_linkage_matrix(tree)
draw_dendrogram(linkage_matrix)
result = hierarchy.fcluster(linkage_matrix, 10, criterion="distance")
Получение результатов иерархической кластеризации (+сравнение со случайным разбиением)
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import adjusted_rand_score
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
# Генерация данных
X, y = make_classification(n_samples=144, n_features=10, n_informative=6, n_classes=4)
# 1. Иерархическая кластеризация
agglomerative = AgglomerativeClustering(n_clusters=4)
cluster_labels_agg = agglomerative.fit_predict(X)
# 2. Случайное разбиение на 5 кластера
random_labels = np.random.randint(0, 5, size=len(X))
# 3. Сравнение результатов кластеризации
ari_score = adjusted_rand_score(y, cluster_labels_agg)
ari_random = adjusted_rand_score(y, random_labels)
# Близко к 1 - супер, близко к 0 - плохо
print(f"ARI между иерархической кластеризацией и реальными метками: {ari_score}")
print(f"ARI между случайным разбиением и реальными метками: {ari_random}")
# Визуализация кластеров
plt.figure(figsize=(16, 8))
# Кластеры на основе иерархической кластеризации
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], c=cluster_labels_agg, cmap="rainbow", alpha=0.7)
plt.title("Иерархическая кластеризация")
# Случайное разбиение
plt.subplot(1, 2, 2)
plt.scatter(X[:, 0], X[:, 1], c=random_labels, cmap="rainbow", alpha=0.7)
plt.title("Случайное разбиение")
plt.tight_layout()
plt.show()
Неирархическая кластеризация
from utils_clusters import print_cluster_result, run_kmeans
# Предположим, что у вас есть DataFrame df с нужными данными
random_state = 9
# Выполняем кластеризацию KMeans
labels, centers = run_kmeans(df_filtered_noclub_nona, 2, random_state)
# Выводим результаты кластеризации
print_cluster_result(df_filtered_noclub_nona, 2, labels)
display(centers)
display(df_filtered_noclub_nona.head()) # Выводим несколько строк для проверки
Визуализация результатов кластеризации
from visual import draw_cluster_results
plt.figure(figsize=(16, 12))
draw_cluster_results(df_filtered_noclub_nona, 0, 1, labels, centers, plt.subplot(2, 2, 1))
draw_cluster_results(df_filtered_noclub_nona, 2, 3, labels, centers, plt.subplot(2, 2, 2))
draw_cluster_results(df_filtered_noclub_nona, 0, 2, labels, centers, plt.subplot(2, 2, 3))
draw_cluster_results(df_filtered_noclub_nona, 1, 3, labels, centers, plt.subplot(2, 2, 4))
Разбиение на 5 кластеров и сравнение с реальным разбиением
from visual import draw_data_2d
labels, centers = run_kmeans(df_filtered_noclub_nona, 5, random_state)
yy = df_filtered_noclub_nona["ClubIndex"]
plt.figure(figsize=(16, 24))
draw_data_2d(
df_filtered_noclub_nona, 0, 1, labels.tolist(), y_names, plt.subplot(4, 2, 1)
)
draw_data_2d(df_filtered_noclub_nona, 0, 1, yy.tolist(), y_names, plt.subplot(4, 2, 2))
draw_data_2d(
df_filtered_noclub_nona, 2, 3, labels.tolist(), y_names, plt.subplot(4, 2, 3)
)
draw_data_2d(df_filtered_noclub_nona, 2, 3, yy.tolist(), y_names, plt.subplot(4, 2, 4))
draw_data_2d(
df_filtered_noclub_nona, 0, 2, labels.tolist(), y_names, plt.subplot(4, 2, 5)
)
draw_data_2d(df_filtered_noclub_nona, 0, 2, yy.tolist(), y_names, plt.subplot(4, 2, 6))
draw_data_2d(
df_filtered_noclub_nona, 1, 3, labels.tolist(), y_names, plt.subplot(4, 2, 7)
)
draw_data_2d(df_filtered_noclub_nona, 1, 3, yy.tolist(), y_names, plt.subplot(4, 2, 8))
Понижение размерности до n=2
from sklearn.decomposition import PCA
reduced_data = PCA(n_components=2).fit_transform(df_filtered_noclub_nona)
reduced_data
Визуализация данных после понижения
# данные после понижения размерности
plt.figure(figsize=(16, 6))
print(reduced_data)
# Первый график: отображение кластеров с метками
draw_data_2d(
pd.DataFrame(
{"Overall": reduced_data[:, 0], "Potential": reduced_data[:, 1]}
),
0,
1,
subplot=plt.subplot(1, 2, 1),
)
# Второй график: отображение с метками кластеров из y
draw_data_2d(
pd.DataFrame(
{"Overall": reduced_data[:, 0], "Potential": reduced_data[:, 1]}
),
0,
1,
yy.tolist(),
y_names,
plt.subplot(1, 2, 2),
)
Визуализация результатов неиерархической кластеризации для двух кластеров с учетом понижения размерности
from utils_clusters import fit_kmeans
from visual import draw_clusters
kmeans = fit_kmeans(reduced_data, 2, random_state)
draw_clusters(reduced_data, kmeans)
Визуализация результатов неиерархической кластеризации для пяти кластеров с учетом понижения размерности
kmeans = fit_kmeans(reduced_data, 5, random_state)
draw_clusters(reduced_data, kmeans)
Сравнение результатов кластеризации с реальным разбиением с учетом понижения размерности
labels = [2 if val == 1 else 1 if val == 2 else val for val in kmeans.labels_]
plt.figure(figsize=(16, 12))
draw_data_2d(
pd.DataFrame({"Overall": reduced_data[:, 0], "Potential": reduced_data[:, 1]}),
0,
1,
labels,
y_names,
plt.subplot(2, 2, 1),
)
draw_data_2d(
pd.DataFrame({"Overall": reduced_data[:, 0], "Potential": reduced_data[:, 1]}),
0,
1,
result,
y_names,
plt.subplot(2, 2, 2),
)
draw_data_2d(
pd.DataFrame({"Overall": reduced_data[:, 0], "Potential": reduced_data[:, 1]}),
0,
1,
yy.tolist(),
y_names,
plt.subplot(2, 2, 3),
)
Выбор количества кластеров на основе инерции¶
Инерция - сумма квадратов расстояний выборок до ближайшего центра кластера, взвешенная по весам выборок, если таковые имеются.
from utils_clusters import get_clusters_inertia
from visual import draw_elbow_diagram
inertias, clusters_range = get_clusters_inertia(df_filtered_noclub_nona, random_state)
display(clusters_range)
display(inertias)
draw_elbow_diagram(inertias, clusters_range)
Выбор количества кластеров на основе коэффициента силуэта¶ Коэффициент силуэта рассчитывается с использованием среднего расстояния внутри кластера (а) и среднего расстояния до ближайшего кластера (b) для каждого образца. Коэффициент силуэта для образца равен (b - a) / max(a, b). Для пояснения: b — это расстояние между образцом и ближайшим кластером, частью которого образец не является. Обратите внимание, что коэффициент силуэта определяется только в том случае, если количество меток равно 2 <= n_labels <= n_samples - 1.
Эта функция возвращает средний коэффициент силуэта по всем образцам.
Лучшее значение — 1, худшее — -1. Значения около 0 указывают на перекрывающиеся кластеры. Отрицательные значения обычно указывают на то, что образец был отнесен к неправильному кластеру.
from utils_clusters import get_clusters_silhouette_scores
from visual import draw_silhouettes_diagram
silhouette_scores, clusters_range = get_clusters_silhouette_scores(df_filtered_noclub_nona, random_state)
display(clusters_range)
display(silhouette_scores)
draw_silhouettes_diagram(silhouette_scores, clusters_range)
Пример анализа силуэтов для разбиения от 2 до 12 кластеров
https://scikit-learn.org/1.5/auto_examples/cluster/plot_kmeans_silhouette_analysis.html
from utils_clusters import get_clusters_silhouettes
from visual import draw_silhouettes
silhouettes = get_clusters_silhouettes(reduced_data, random_state)
draw_silhouettes(reduced_data, silhouettes)