Lab 2, 3352, Гареева Карина Радиковна
.docxЗадание 2: Кластеризация методом K-Means¶
In [12]:
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.cluster import KMeansfrom sklearn.preprocessing import StandardScalerfrom sklearn.metrics import silhouette_scoreimport warningswarnings.filterwarnings('ignore')
plt.rcParams['figure.figsize'] = (10, 6)sns.set_style('whitegrid')
df = pd.read_csv('Concrete Compressive Strength.csv')print(f"\nРазмер датасета: {df.shape}")
Размер датасета: (1030, 9)
In [17]:
new_column_names = [
'Cement', 'Slag', 'Ash', 'Water',
'Plasticizer', 'Coarse_Agg', 'Fine_Agg',
'Age', 'Comp_Strength']df.columns = new_column_namesdisplay(df.head())
|
Cement |
Slag |
Ash |
Water |
Plasticizer |
Coarse_Agg |
Fine_Agg |
Age |
Comp_Strength |
0 |
540.0 |
0.0 |
0.0 |
162.0 |
2.5 |
1040.0 |
676.0 |
28 |
79.986111 |
1 |
540.0 |
0.0 |
0.0 |
162.0 |
2.5 |
1055.0 |
676.0 |
28 |
61.887366 |
2 |
332.5 |
142.5 |
0.0 |
228.0 |
0.0 |
932.0 |
594.0 |
270 |
40.269535 |
3 |
332.5 |
142.5 |
0.0 |
228.0 |
0.0 |
932.0 |
594.0 |
365 |
41.052780 |
4 |
198.6 |
132.4 |
0.0 |
192.0 |
0.0 |
978.4 |
825.5 |
360 |
44.296075 |
In [20]:
df_kmeans = df.copy()df_kmeans['Cement/Water'] = df_kmeans['Cement'] / df_kmeans['Water']
print("Добавлен новый атрибут 'Cement/Water'")print("Первые 5 строк обновленного датасета:")display(df_kmeans.head())
Добавлен новый атрибут 'Cement/Water'
Первые 5 строк обновленного датасета:
|
Cement |
Slag |
Ash |
Water |
Plasticizer |
Coarse_Agg |
Fine_Agg |
Age |
Comp_Strength |
Cement/Water |
0 |
540.0 |
0.0 |
0.0 |
162.0 |
2.5 |
1040.0 |
676.0 |
28 |
79.986111 |
3.333333 |
1 |
540.0 |
0.0 |
0.0 |
162.0 |
2.5 |
1055.0 |
676.0 |
28 |
61.887366 |
3.333333 |
2 |
332.5 |
142.5 |
0.0 |
228.0 |
0.0 |
932.0 |
594.0 |
270 |
40.269535 |
1.458333 |
3 |
332.5 |
142.5 |
0.0 |
228.0 |
0.0 |
932.0 |
594.0 |
365 |
41.052780 |
1.458333 |
4 |
198.6 |
132.4 |
0.0 |
192.0 |
0.0 |
978.4 |
825.5 |
360 |
44.296075 |
1.034375 |
In [22]:
print("Пропущенные значения:")print(df_kmeans.isnull().sum())
print(f"\nБесконечные значения в Cement_Water_Ratio: {np.isinf(df_kmeans['Cement/Water']).sum()}")
# Удалим бесконечные значенияdf_kmeans = df_kmeans[~np.isinf(df_kmeans['Cement/Water'])]print(f"Размер датасета после очистки: {df_kmeans.shape}")
Пропущенные значения:
Cement 0
Slag 0
Ash 0
Water 0
Plasticizer 0
Coarse_Agg 0
Fine_Agg 0
Age 0
Comp_Strength 0
Cement/Water 0
dtype: int64
Бесконечные значения в Cement_Water_Ratio: 0
Размер датасета после очистки: (1030, 10)
In [24]:
def remove_outliers(df, columns):
df_clean = df.copy()
for col in columns:
Q1 = df_clean[col].quantile(0.25)
Q3 = df_clean[col].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df_clean = df_clean[(df_clean[col] >= lower_bound) & (df_clean[col] <= upper_bound)]
return df_clean
columns_to_clean = ['Cement', 'Water', 'Cement/Water', 'Comp_Strength']df_clean = remove_outliers(df_kmeans, columns_to_clean)
print(f"Размер датасета после удаления выбросов: {df_clean.shape}")print(f"Удалено записей: {len(df_kmeans) - len(df_clean)}")print(f"Дубликатов в данных: {df_clean.duplicated().sum()}")
Размер датасета после удаления выбросов: (981, 10)
Удалено записей: 49
Дубликатов в данных: 19
In [26]:
# Построим scatter plot для поиска кластеровfig, axes = plt.subplots(2, 2, figsize=(15, 12))
# График 1: Цемент vs Прочностьaxes[0,0].scatter(df_clean['Cement'], df_clean['Comp_Strength'], alpha=0.6, c='blue')axes[0,0].set_xlabel('Цемент (кг/м³)')axes[0,0].set_ylabel('Прочность (МПа)')axes[0,0].set_title('Цемент vs Прочность')axes[0,0].grid(True)
# График 2: Отношение цемент/вода vs Прочностьaxes[0,1].scatter(df_clean['Cement/Water'], df_clean['Comp_Strength'], alpha=0.6, c='green')axes[0,1].set_xlabel('Отношение Цемент/Вода')axes[0,1].set_ylabel('Прочность (МПа)')axes[0,1].set_title('Цемент/Вода vs Прочность')axes[0,1].grid(True)
# График 3: Возраст vs Прочностьaxes[1,0].scatter(df_clean['Age'], df_clean['Comp_Strength'], alpha=0.6, c='red')axes[1,0].set_xlabel('Возраст (дни)')axes[1,0].set_ylabel('Прочность (МПа)')axes[1,0].set_title('Возраст vs Прочность')axes[1,0].grid(True)
# График 4: Вода vs Прочностьaxes[1,1].scatter(df_clean['Water'], df_clean['Comp_Strength'], alpha=0.6, c='purple')axes[1,1].set_xlabel('Вода (кг/м³)')axes[1,1].set_ylabel('Прочность (МПа)')axes[1,1].set_title('Вода vs Прочность')axes[1,1].grid(True)
plt.tight_layout()plt.show()
In [27]:
# Анализ видимых кластеровprint("АНАЛИЗ ВИЗУАЛЬНЫХ КЛАСТЕРОВ:")print("-" * 40)print("• На графиках видны естественные группы бетонов:")print(" - Низкопрочные смеси (мало цемента, много воды)")print(" - Среднепрочные смеси (средние показатели)")print(" - Высокопрочные смеси (много цемента, оптимальное водоцементное отношение)")print("• Отношение цемент/вода хорошо разделяет смеси по эффективности")
АНАЛИЗ ВИЗУАЛЬНЫХ КЛАСТЕРОВ:
----------------------------------------
• На графиках видны естественные группы бетонов:
- Низкопрочные смеси (мало цемента, много воды)
- Среднепрочные смеси (средние показатели)
- Высокопрочные смеси (много цемента, оптимальное водоцементное отношение)
• Отношение цемент/вода хорошо разделяет смеси по эффективности
In [29]:
# Выберем признаки для кластеризацииfeatures = ['Cement', 'Water', 'Age', 'Cement/Water', 'Comp_Strength']X = df_clean[features]
# Стандартизация данныхscaler = StandardScaler()X_scaled = scaler.fit_transform(X)
print("Признаки для кластеризации:")print(features)print(f"Размерность данных: {X_scaled.shape}")
Признаки для кластеризации:
['Cement', 'Water', 'Age', 'Cement/Water', 'Comp_Strength']
Размерность данных: (981, 5)
In [32]:
# Метод локтя для определения оптимального числа кластеровinertia = []silhouette_scores = []k_range = range(2, 8)
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_scaled)
inertia.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X_scaled, kmeans.labels_))
# Визуализация метода локтяfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
ax1.plot(k_range, inertia, 'bo-')ax1.set_xlabel('Количество кластеров')ax1.set_ylabel('Inertia')ax1.set_title('Метод локтя')ax1.grid(True)
ax2.plot(k_range, silhouette_scores, 'ro-')ax2.set_xlabel('Количество кластеров')ax2.set_ylabel('Silhouette Score')ax2.set_title('Silhouette Score')ax2.grid(True)
plt.tight_layout()plt.show()
In [33]:
# Анализируем результаты для выбора оптимального kprint("АНАЛИЗ ОПТИМАЛЬНОГО ЧИСЛА КЛАСТЕРОВ:")print("-" * 40)for k, score in zip(k_range, silhouette_scores):
print(f"k = {k}: Silhouette Score = {score:.3f}")
# Выбираем оптимальное количество кластеров (обычно там, где "локоть" и высокий silhouette)optimal_k = 3print(f"\nВыбрано количество кластеров: {optimal_k}")
АНАЛИЗ ОПТИМАЛЬНОГО ЧИСЛА КЛАСТЕРОВ:
----------------------------------------
k = 2: Silhouette Score = 0.324
k = 3: Silhouette Score = 0.362
k = 4: Silhouette Score = 0.286
k = 5: Silhouette Score = 0.293
k = 6: Silhouette Score = 0.255
k = 7: Silhouette Score = 0.274
Выбрано количество кластеров: 3
In [34]:
# Применяем K-Means с выбранным количеством кластеровkmeans = KMeans(n_clusters=optimal_k, random_state=42, n_init=10)clusters = kmeans.fit_predict(X_scaled)
# Добавляем метки кластеров в датасетdf_clean['Cluster'] = clusters
print(f"Кластеризация завершена. Создано {optimal_k} кластера.")print("Распределение по кластерам:")cluster_counts = df_clean['Cluster'].value_counts().sort_index()for cluster, count in cluster_counts.items():
print(f"Кластер {cluster}: {count} записей ({count/len(df_clean)*100:.1f}%)")
Кластеризация завершена. Создано 3 кластера.
Распределение по кластерам:
Кластер 0: 585 записей (59.6%)
Кластер 1: 331 записей (33.7%)
Кластер 2: 65 записей (6.6%)
In [35]:
# Визуализация результатов кластеризацииfig, axes = plt.subplots(2, 2, figsize=(15, 12))
# Цемент vs Прочность с кластерамиscatter1 = axes[0,0].scatter(df_clean['Cement'], df_clean['Comp_Strength'],
c=df_clean['Cluster'], cmap='viridis', alpha=0.7)axes[0,0].set_xlabel('Цемент (кг/м³)')axes[0,0].set_ylabel('Прочность (МПа)')axes[0,0].set_title('Кластеризация: Цемент vs Прочность')plt.colorbar(scatter1, ax=axes[0,0])
# Цемент/Вода vs Прочность с кластерамиscatter2 = axes[0,1].scatter(df_clean['Cement/Water'], df_clean['Comp_Strength'],
c=df_clean['Cluster'], cmap='plasma', alpha=0.7)axes[0,1].set_xlabel('Отношение Цемент/Вода')axes[0,1].set_ylabel('Прочность (МПа)')axes[0,1].set_title('Кластеризация: Цемент/Вода vs Прочность')plt.colorbar(scatter2, ax=axes[0,1])
# Возраст vs Прочность с кластерамиscatter3 = axes[1,0].scatter(df_clean['Age'], df_clean['Comp_Strength'],
c=df_clean['Cluster'], cmap='cool', alpha=0.7)axes[1,0].set_xlabel('Возраст (дни)')axes[1,0].set_ylabel('Прочность (МПа)')axes[1,0].set_title('Кластеризация: Возраст vs Прочность')plt.colorbar(scatter3, ax=axes[1,0])
# Вода vs Прочность с кластерамиscatter4 = axes[1,1].scatter(df_clean['Water'], df_clean['Comp_Strength'],
c=df_clean['Cluster'], cmap='spring', alpha=0.7)axes[1,1].set_xlabel('Вода (кг/м³)')axes[1,1].set_ylabel('Прочность (МПа)')axes[1,1].set_title('Кластеризация: Вода vs Прочность')plt.colorbar(scatter4, ax=axes[1,1])
plt.tight_layout()plt.show()
In [36]:
# Анализ характеристик кластеровprint("ХАРАКТЕРИСТИКИ КЛАСТЕРОВ:")print("=" * 50)
cluster_stats = df_clean.groupby('Cluster')[features].mean()display(cluster_stats.round(2))
ХАРАКТЕРИСТИКИ КЛАСТЕРОВ:
==================================================
|
Cement |
Water |
Age |
Cement/Water |
Comp_Strength |
Cluster |
|
|
|
|
|
0 |
216.49 |
185.49 |
30.17 |
1.17 |
26.70 |
1 |
366.84 |
171.46 |
34.30 |
2.15 |
47.04 |
2 |
327.20 |
214.47 |
240.77 |
1.53 |
43.12 |
In [37]:
# Визуализация средних значений по кластерамcluster_means = df_clean.groupby('Cluster')[features].mean()
plt.figure(figsize=(12, 6))cluster_means.T.plot(kind='bar', figsize=(12, 6))plt.title('Средние значения признаков по кластерам')plt.xlabel('Признаки')plt.ylabel('Среднее значение')plt.xticks(rotation=45)plt.legend(title='Кластер')plt.grid(True, alpha=0.3)plt.tight_layout()plt.show()
<Figure size 1200x600 with 0 Axes>
In [38]:
# Дополнительный анализ: центры кластеров в оригинальном масштабеprint("ЦЕНТРЫ КЛАСТЕРОВ (оригинальные значения):")print("=" * 50)
# Преобразуем центры обратно в оригинальный масштабcluster_centers_original = scaler.inverse_transform(kmeans.cluster_centers_)centers_df = pd.DataFrame(cluster_centers_original, columns=features)centers_df['Cluster'] = range(optimal_k)
display(centers_df.round(2))
ЦЕНТРЫ КЛАСТЕРОВ (оригинальные значения):
==================================================
|
Cement |
Water |
Age |
Cement/Water |
Comp_Strength |
Cluster |
0 |
216.49 |
185.49 |
30.17 |
1.17 |
26.70 |
0 |
1 |
366.84 |
171.46 |
34.30 |
2.15 |
47.04 |
1 |
2 |
327.20 |
214.47 |
240.77 |
1.53 |
43.12 |
2 |
In [40]:
print("ВЫВОДЫ ПО КЛАСТЕРИЗАЦИИ K-MEANS")print("=" * 50)
# Анализируем характеристики каждого кластераcluster_0_stats = cluster_stats.loc[0]cluster_1_stats = cluster_stats.loc[1] cluster_2_stats = cluster_stats.loc[2]
conclusions = [
f"✓ Успешно создано {optimal_k} кластера бетонных смесей",
f"✓ Качество кластеризации (Silhouette Score): {silhouette_score(X_scaled, clusters):.3f}",
"",
"ХАРАКТЕРИСТИКИ КЛАСТЕРОВ:",
f"• Кластер 0 ({cluster_counts[0]} зап.): Низкопрочные смеси",
f" - Прочность: {cluster_0_stats['Comp_Strength']:.1f} МПа",
f" - Мало цемента: {cluster_0_stats['Cement']:.0f} кг/м³",
f" - Высокое водоцементное отношение",
"",
f"• Кластер 1 ({cluster_counts[1]} зап.): Высокопрочные смеси",
f" - Прочность: {cluster_1_stats['Comp_Strength']:.1f} МПа",
f" - Много цемента: {cluster_1_stats['Cement']:.0f} кг/м³",
f" - Оптимальное водоцементное отношение",
"",
f"• Кластер 2 ({cluster_counts[2]} зап.): Среднепрочные смеси",
f" - Прочность: {cluster_2_stats['Comp_Strength']:.1f} МПа",
f" - Средние показатели по всем параметрам",
"",
"ПРАКТИЧЕСКОЕ ЗНАЧЕНИЕ:",
"• Кластеризация выявила технологические группы бетонов",
"• Можно оптимизировать составы для каждой группы",
"• Метод K-means хорошо подошел для данного датасета",
"• Отношение цемент/вода оказалось важным признаком"]
for conclusion in conclusions:
print(conclusion)
ВЫВОДЫ ПО КЛАСТЕРИЗАЦИИ K-MEANS
==================================================
✓ Успешно создано 3 кластера бетонных смесей
✓ Качество кластеризации (Silhouette Score): 0.362
ХАРАКТЕРИСТИКИ КЛАСТЕРОВ:
• Кластер 0 (585 зап.): Низкопрочные смеси
- Прочность: 26.7 МПа
- Мало цемента: 216 кг/м³
- Высокое водоцементное отношение
• Кластер 1 (331 зап.): Высокопрочные смеси
- Прочность: 47.0 МПа
- Много цемента: 367 кг/м³
- Оптимальное водоцементное отношение
• Кластер 2 (65 зап.): Среднепрочные смеси
- Прочность: 43.1 МПа
- Средние показатели по всем параметрам
ПРАКТИЧЕСКОЕ ЗНАЧЕНИЕ:
• Кластеризация выявила технологические группы бетонов
• Можно оптимизировать составы для каждой группы
• Метод K-means хорошо подошел для данного датасета
• Отношение цемент/вода оказалось важным признаком
In [ ]:
