```python
# 导入 pandas 库并别名为 pd,用于数据处理和分析
import pandas as pd
# 导入 numpy 库并别名为 np,用于数值计算
import numpy as np
# 导入 matplotlib.pyplot 库并别名为 plt,用于数据可视化
import matplotlib.pyplot as plt
# 从 matplotlib.colors 模块导入 LinearSegmentedColormap 类,用于创建自定义颜色映射
from matplotlib.colors import LinearSegmentedColormap
# 从 itertools 模块导入 combinations 函数,用于生成组合
from itertools import combinations
# 定义特征构建结果文件的路径
feature_file = '问题二_特征构建结果.xlsx'
# 定义三级风险分层结果文件的路径
strat_file = '问题二_三级风险分层结果.xlsx'
# 定义输出的核心组合画像文件的路径
out_file = '问题二_核心组合画像.xlsx'
# 定义高风险核心组合画像图的文件名
PLOT_FILE_1 = '问题二_高风险核心组合画像图.png'
# 定义画像特征热力图的文件名
PLOT_FILE_2 = '问题二_画像特征热力图.png'
# 设置 matplotlib 的字体,支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS', 'DejaVu Sans']
# 避免负号显示为方块
plt.rcParams['axes.unicode_minus'] = False
# 设置图形的背景颜色为白色
plt.rcParams['figure.facecolor'] = 'white'
# 设置坐标轴的背景颜色为白色
plt.rcParams['axes.facecolor'] = 'white'
# 定义网格线的颜色
GRID_COLOR = '#D9E2EC'
# 定义条形图的颜色
BAR_COLOR = '#4C78A8'
# 定义文本的颜色
TEXT_COLOR = '#2D3142'
# 创建一个线性分段颜色映射,用于热力图
HEAT_CMAP = LinearSegmentedColormap.from_list('portrait_blue', ['#F7FBFF', '#BFD3E6', '#6BAED6', '#2171B5'])
# 从 feature_file 文件的 '建模数据' 工作表中读取数据到 data 变量
data = pd.read_excel(feature_file, sheet_name='建模数据')
# 从 feature_file 文件的 '特征清单' 工作表中读取 '特征名' 列,去除空值,转换为字符串类型后存储为列表到 features 变量
features = pd.read_excel(feature_file, sheet_name='特征清单')['特征名'].dropna().astype(str).tolist()
# 从 strat_file 文件的 '全样本分层' 工作表中读取 'idx' 和 '三级风险' 两列数据到 grade 变量
grade = pd.read_excel(strat_file, sheet_name='全样本分层')[['idx','三级风险']]
# 将 data 和 grade 按照 data 的索引和 grade 的 'idx' 列进行内连接,结果存储到 df 变量
df = data.merge(grade, left_index=True, right_on='idx', how='inner')
# 筛选出优先特征列表中存在于 df 列名中的特征存储到 priority 列表
priority = [c for c in [
'痰湿质','痰湿质_z','H_痰湿活动耦合','H_痰湿_BMI交互','H_代谢异常负担',
'活动量表总分(ADL总分+IADL总分)','活动量表总分(ADL总分+IADL总分)_z',
'BMI','BMI_z','空腹血糖','空腹血糖_z','血尿酸','血尿酸_z',
'TG(甘油三酯)','TG(甘油三酯)_z','TC(总胆固醇)','TC(总胆固醇)_z'
] if c in df.columns]
# 使用 dict.fromkeys() 去除重复元素,取前 8 个特征存储到 portrait_features 列表,用于后续画像分析
portrait_features = list(dict.fromkeys(priority))[:8]
# 如果 portrait_features 列表为空,抛出 ValueError 异常,提示未找到可用的画像特征,并建议检查文件字段
if not portrait_features:
raise ValueError('未找到可用于画像的特征,请检查“问题二_特征构建结果.xlsx”中的字段。')
# 定义逆向风险关键词列表
inverse_keys = ['HDL', '活动量表', 'ADL', 'IADL']
# 创建一个空的 DataFrame,其索引与 df 相同,用于存储风险标记信息
flags = pd.DataFrame(index=df.index)
# 创建一个空列表,用于存储阈值信息
threshold_info = []
# 遍历 portrait_features 中的每个特征
for c in portrait_features:
# 将该特征列转换为数值类型,无法转换的设为 NaN
s = pd.to_numeric(df[c], errors='coerce')
# 如果特征名包含 inverse_keys 中的关键词
if any(k in c for k in inverse_keys):
# 使用下四分位作为阈值
thr = s.quantile(0.25)
# 将小于等于阈值的标记为风险(值为 1),添加到 flags 中
flags[c + '_风险'] = (s <= thr).astype(int)
# 将特征名、风险判定方式和阈值信息添加到 threshold_info 列表中
threshold_info.append([c, '下四分位判定为风险', thr])
else:
# 使用上四分位作为阈值
thr = s.quantile(0.75)
# 将大于等于阈值的标记为风险(值为 1),添加到 flags 中
flags[c + '_风险'] = (s >= thr).astype(int)
# 将特征名、风险判定方式和阈值信息添加到 threshold_info 列表中
threshold_info.append([c, '上四分位判定为风险', thr])
# 将 df 中的 '三级风险' 和 '高血脂症二分类标签' 列与 flags 按列方向拼接,得到 merged
merged = pd.concat([df[['三级风险','高血脂症二分类标签']].reset_index(drop=True), flags.reset_index(drop=True)], axis=1)
# 从 merged 中筛选出 '三级风险' 为 '高风险' 的数据存储到 high 变量
high = merged[merged['三级风险'] == '高风险'].copy()
# 将 flags 的列名转换为列表存储到 flag_cols 变量
flag_cols = flags.columns.tolist()
# 创建一个空列表,用于存储核心组合的相关信息
rows = []
# 生成 flag_cols 中元素的 2 个和 3 个元素的组合
for r in [2, 3]:
for comb in combinations(flag_cols, r):
# 检查 high 数据集中该组合对应的列是否都为 True
cond = high[list(comb)].all(axis=1)
# 计算支持度,如果 high 数据集不为空则计算均值,否则为 0
support = cond.mean() if len(high) else 0
# 计算支持度对应的样本数
support_n = int(cond.sum())
# 如果支持度对应的样本数小于 10 或者小于高风险样本数的 3%,则跳过该组合
if support_n < max(10, int(0.03 * max(len(high), 1))):
continue
# 在 merged 数据集中检查该组合对应的列是否都为 True
all_cond = merged[list(comb)].all(axis=1)
# 计算置信度,如果 all_cond 中有值则计算均值,否则为 0
conf = merged.loc[all_cond, '高血脂症二分类标签'].mean() if all_cond.sum() else 0
# 将组合信息、支持度样本数、支持度和置信度添加到 rows 列表中
rows.append([' + '.join(comb), support_n, support, conf])
# 将 rows 列表转换为 DataFrame
portrait_df = pd.DataFrame(rows, columns=['核心组合','高风险样本数','支持度','置信度'])
# 如果 portrait_df 不为空,按照支持度和置信度降序排序,取前 15 行
if len(portrait_df):
portrait_df = portrait_df.sort_values(['支持度','置信度'], ascending=False).head(15)
# 如果 merged 不为空,按照 '三级风险' 分组,计算 flag_cols 中各列的均值,并进行转置,结果存储到 profile_rate_df;否则 profile_rate_df 为空 DataFrame
profile_rate_df = merged.groupby('三级风险', observed=False)[flag_cols].mean().T if len(merged) else pd.DataFrame()
# 使用 pd.ExcelWriter 将画像变量、风险判定阈值、高风险核心组合和各风险层标记率分别保存到 out_file 的不同工作表中,不保存索引
with pd.ExcelWriter(out_file) as w:
pd.DataFrame({'画像变量': portrait_features}).to_excel(w, sheet_name='画像变量', index=False)
pd.DataFrame(threshold_info, columns=['变量','风险判定方式','阈值']).to_excel(w, sheet_name='风险判定阈值', index=False)
portrait_df.to_excel(w, sheet_name='高风险核心组合', index=False)
profile_rate_df.to_excel(w, sheet_name='各风险层标记率')
# 如果 portrait_df 不为空
if len(portrait_df):
# 取 portrait_df 的前 10 行并倒序存储到 top_df
top_df = portrait_df.head(10).iloc[::-1].copy()
# 创建一个画布和坐标轴,设置图形大小
fig, ax = plt.subplots(figsize=(11, max(5, 0.55 * len(top_df) + 1.5)))
# 生成颜色渐变列表
color_scale = plt.cm.Blues(np.linspace(0.45, 0.9, len(top_df)))
# 绘制水平条形图展示高风险核心组合的支持度,设置条形图颜色、边框和宽度
bars = ax.barh(top_df['核心组合'], top_df['支持度'], color=color_scale, edgecolor='white', linewidth=1.2)
# 设置 x 轴标签
ax.set_xlabel('支持度(在高风险组内出现比例)')
# 设置 y 轴标签
ax.set_ylabel('核心组合')
# 设置标题
ax.set_title('问题二:高风险核心组合画像', fontsize=13, fontweight='bold')
# 在每个条形图上添加样本数和置信度信息
for rect, spt_n, conf in zip(bars, top_df['高风险样本数'], top_df['置信度']):
ax.text(rect.get_width(), rect.get_y() + rect.get_height()/2,
f' 样本数={int(spt_n)}, 置信度={conf:.2%}', va='center', fontsize=9, color=TEXT_COLOR)
# 设置 x 轴网格线
ax.grid(axis='x', linestyle='--', alpha=0.35, color=GRID_COLOR)
# 调整布局
fig.tight_layout()
# 保存图像到 PLOT_FILE_1
fig.savefig(PLOT_FILE_1, dpi=300, bbox_inches='tight')
# 关闭画布
plt.close(fig)
# 如果 profile_rate_df 不为空
if not profile_rate_df.empty:
# 复制 profile_rate_df 到 show_df
show_df = profile_rate_df.copy()
# 筛选出 '低风险'、'中风险'、'高风险' 列
cols = [c for c in ['低风险', '中风险', '高风险'] if c in show_df.columns]
show_df = show_df[cols]
# 创建画布和坐标轴,设置图形大小
fig, ax = plt.subplots(figsize=(8, max(5, 0.5 * len(show_df) + 1.5)))
# 绘制热力图展示画像特征在各风险层的风险标记率,设置颜色映射、最小和最大颜色值
im = ax.imshow(show_df.values, aspect='auto', cmap=HEAT_CMAP, vmin=0, vmax=max(1e-9, np.nanmax(show_df.values)))
# 设置 x 轴刻度
ax.set_xticks(range(len(show_df.columns)))
# 设置 x 轴刻度标签
ax.set_xticklabels(show_df.columns)
# 设置 y 轴刻度
ax.set_yticks(range(len(show_df.index)))
# 设置 y 轴刻度标签
ax.set_yticklabels(show_df.index)
# 设置标题
ax.set_title('问题二:画像特征在各风险层中的风险标记率', fontsize=13, fontweight='bold')
# 在热力图每个格子上添加具体的标记率信息,根据标记率设置文本颜色
for i in range(show_df.shape[0]):
for j in range(show_df.shape[1]):
val = show_df.iloc[i, j]
txt_color = 'white' if val >= 0.55 * np.nanmax(show_df.values) else TEXT_COLOR
ax.text(j, i, f'{val:.1%}', ha='center', va='center', fontsize=9, color=txt_color)
# 添加颜色条
cbar = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
# 设置颜色条标签
cbar.set_label('风险标记率')
# 调整布局
fig.tight_layout()
# 保存图像到 PLOT_FILE_2
fig.savefig(PLOT_FILE_2, dpi=300, bbox_inches='tight')
# 关闭画布
plt.close(fig)
# 定义消息字符串,表示处理完成并输出结果文件
msg = f'完成:{out_file}'
# 如果 portrait_df 不为空,添加生成的高风险核心组合画像图信息
if len(portrait_df):
msg += f',并生成 {PLOT_FILE_1}'
# 如果 profile_rate_df 不为空,添加生成的画像特征热力图信息
if not profile_rate_df.empty:
msg += f'、{PLOT_FILE_2}'
# 打印消息
print(msg)
```