import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
# 1. 读取Excel文件
file_name = "../数模1/工作簿4.xlsx"
try:
# 读取Excel文件
df = pd.read_excel(file_name, sheet_name='Sheet1')
print(f"成功读取Excel文件: {file_name}")
print(f"数据形状: {df.shape}")
# 显示前几行数据
print("\n数据前5行:")
print(df.head())
except FileNotFoundError:
print(f"错误: 找不到文件 '{file_name}'")
print("请确保Excel文件与Python脚本在同一目录下")
exit()
# 2. 数据预处理
if '孕妇BMI' not in df.columns:
print("错误: 数据中未找到'孕妇BMI'列")
print("可用列:", df.columns.tolist())
exit()
# 处理缺失值
print(f"\nBMI列缺失值数量: {df['孕妇BMI'].isnull().sum()}")
df_clean = df.dropna(subset=['孕妇BMI']).copy()
# 3. 删除重复的BMI值
print(f"原始数据中BMI值数量: {len(df_clean['孕妇BMI'])}")
print(f"唯一BMI值数量: {df_clean['孕妇BMI'].nunique()}")
# 创建只包含唯一BMI值的数据集
unique_bmi_df = df_clean.drop_duplicates(subset=['孕妇BMI']).copy()
print(f"删除重复BMI值后剩余样本数: {len(unique_bmi_df)}")
# 4. 确定最佳K值(使用肘部法则和轮廓系数)
bmi_data = unique_bmi_df['孕妇BMI'].values.reshape(-1, 1)
sse = {}
silhouette_scores = []
k_range = range(2, 11) # 测试2到10个聚类
print("\n正在计算最佳聚类数量...")
for k in k_range:
kmeans = KMeans(n_clusters=k, init='k-means++', random_state=42, n_init=10)
kmeans.fit(bmi_data)
sse[k] = kmeans.inertia_ # SSE(Sum of Squared Errors)
# 计算轮廓系数(需要至少2个聚类)
if k > 1:
silhouette_avg = silhouette_score(bmi_data, kmeans.labels_)
silhouette_scores.append(silhouette_avg)
else:
silhouette_scores.append(0)
# 绘制肘部法则图和轮廓系数图
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(list(sse.keys()), list(sse.values()), 'bx-')
plt.xlabel('聚类数量 (k)')
plt.ylabel('SSE (误差平方和)')
plt.title('肘部法则 - 最佳k值')
plt.subplot(1, 2, 2)
plt.plot(range(2, 11), silhouette_scores, 'bx-')
plt.xlabel('聚类数量 (k)')
plt.ylabel('轮廓系数')
plt.title('轮廓系数 - 最佳k值')
plt.tight_layout()
plt.savefig('bmi_clustering_analysis.png') # 保存图像
plt.show()
# 基于轮廓系数选择最佳K值
optimal_k = np.argmax(silhouette_scores) + 2 # +2是因为我们从k=2开始
print(f"\n基于轮廓系数的最佳聚类数量: {optimal_k}")
# 5. 使用K-means++进行聚类
kmeans = KMeans(n_clusters=optimal_k, init='k-means++', random_state=42, n_init=10)
kmeans.fit(bmi_data)
# 将聚类结果添加到唯一BMI数据集
unique_bmi_df['BMI_Cluster'] = kmeans.labels_
# 6. 将聚类结果映射回原始数据
# 创建一个BMI到聚类的映射字典
bmi_cluster_map = dict(zip(unique_bmi_df['孕妇BMI'], unique_bmi_df['BMI_Cluster']))
# 将映射应用到原始数据
df_clean['BMI_Cluster'] = df_clean['孕妇BMI'].map(bmi_cluster_map)
# 7. 分析聚类结果
cluster_summary = unique_bmi_df.groupby('BMI_Cluster')['孕妇BMI'].agg([
('最小值', 'min'),
('最大值', 'max'),
('平均值', 'mean'),
('样本数', 'count')
]).round(2)
print("\n聚类摘要 (基于唯一BMI值):")
print(cluster_summary)
# 8. 可视化聚类结果
plt.figure(figsize=(12, 6))
# 绘制BMI分布直方图,按聚类着色
colors = ['red', 'blue', 'green', 'purple', 'orange', 'brown', 'pink', 'gray', 'olive', 'cyan']
for i in range(optimal_k):
cluster_data = unique_bmi_df[unique_bmi_df['BMI_Cluster'] == i]['孕妇BMI']
plt.hist(cluster_data, bins=30, alpha=0.6, color=colors[i % len(colors)],
label=f'聚类 {i} (n={len(cluster_data)})')
# 添加WHO BMI分类标准线
plt.axvline(x=18.5, color='black', linestyle='--', label='偏瘦 (WHO)')
plt.axvline(x=25, color='black', linestyle='--', label='正常 (WHO)')
plt.axvline(x=30, color='black', linestyle='--', label='超重 (WHO)')
plt.axvline(x=35, color='black', linestyle='--', label='肥胖 I级 (WHO)')
plt.axvline(x=40, color='black', linestyle='--', label='肥胖 II级 (WHO)')
plt.xlabel('BMI')
plt.ylabel('频数')
plt.title('BMI分布 - 按聚类着色 (基于唯一BMI值)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('bmi_cluster_distribution.png') # 保存图像
plt.show()
# 9. 保存结果到新Excel文件
output_file = "bmi_clustering_results.xlsx"
with pd.ExcelWriter(output_file) as writer:
df_clean.to_excel(writer, sheet_name='聚类结果', index=False)
cluster_summary.to_excel(writer, sheet_name='聚类摘要')
print(f"\n结果已保存到: {output_file}")
# 10. 打印每个聚类的详细描述
print("\n=== BMI聚类分组结果 ===")
for i in range(optimal_k):
cluster_data = unique_bmi_df[unique_bmi_df['BMI_Cluster'] == i]['孕妇BMI']
min_bmi = cluster_data.min()
max_bmi = cluster_data.max()
mean_bmi = cluster_data.mean()
count = len(cluster_data)
# 根据WHO标准确定分类
if mean_bmi < 18.5:
category = "偏瘦"
elif mean_bmi < 25:
category = "正常"
elif mean_bmi < 30:
category = "超重"
elif mean_bmi < 35:
category = "肥胖 I级"
elif mean_bmi < 40:
category = "肥胖 II级"
else:
category = "肥胖 III级"
print(f"聚类 {i}: BMI范围 {min_bmi:.1f}-{max_bmi:.1f}, 平均 {mean_bmi:.1f} ({category}), 样本数: {count}")
# 11. 显示原始数据中每个聚类的样本数量
print("\n=== 原始数据中各聚类样本数量 ===")
original_cluster_counts = df_clean['BMI_Cluster'].value_counts().sort_index()
for cluster, count in original_cluster_counts.items():
print(f"聚类 {cluster}: {count} 个样本") 这个聚类结果还可以再精确一些吗,代码有没有别的选择
最新发布