以下是给定 Python 代码每行的详细作用:
```python
import pandas as pd, numpy as np
import matplotlib.pyplot as plt
from itertools import combinations
```
- 导入 `pandas` 库并将其别名为 `pd`,用于数据处理和分析;导入 `numpy` 库并别名为 `np`,用于数值计算;导入 `matplotlib.pyplot` 库并别名为 `plt`,用于数据可视化;从 `itertools` 模块导入 `combinations` 函数,用于生成组合[^1]。
```python
feature_file = '问题二_特征构建结果.xlsx'
strat_file = '问题二_三级风险分层结果.xlsx'
out_file = '问题二_核心组合画像.xlsx'
PLOT_FILE_1 = '问题二_高风险核心组合画像图.png'
PLOT_FILE_2 = '问题二_画像特征热力图.png'
```
- 定义文件路径变量,分别存储特征构建结果文件、三级风险分层结果文件、输出的核心组合画像文件以及两个绘图文件的文件名[^1]。
```python
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'])
```
- 设置 `matplotlib` 的字体,以支持中文显示,同时避免负号显示为方块;设置图形和坐标轴的背景颜色为白色;定义网格颜色、条形图颜色、文本颜色;创建一个线性分段颜色映射 `HEAT_CMAP` 用于热力图[^1]。
```python
data = pd.read_excel(feature_file, sheet_name='建模数据')
features = pd.read_excel(feature_file, sheet_name='特征清单')['特征名'].dropna().astype(str).tolist()
```
- 从 `feature_file` 文件的 '建模数据' 工作表中读取数据到 `data` 变量;从同一文件的 '特征清单' 工作表中读取 '特征名' 列,去除空值,转换为字符串类型后存储为列表到 `features` 变量[^1]。
```python
grade = pd.read_excel(strat_file, sheet_name='全样本分层')[['idx','三级风险']]
df = data.merge(grade, left_index=True, right_on='idx', how='inner')
```
- 从 `strat_file` 文件的 '全样本分层' 工作表中读取 'idx' 和 '三级风险' 两列数据到 `grade` 变量;将 `data` 和 `grade` 按照 `data` 的索引和 `grade` 的 'idx' 列进行内连接,结果存储到 `df` 变量[^1]。
```python
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]
portrait_features = list(dict.fromkeys(priority))[:8]
```
- 筛选出优先特征列表中存在于 `df` 列名中的特征存储到 `priority` 列表;使用 `dict.fromkeys()` 去除重复元素,取前 8 个特征存储到 `portrait_features` 列表,用于后续画像分析[^1]。
```python
if not portrait_features:
raise ValueError('未找到可用于画像的特征,请检查“问题二_特征构建结果.xlsx”中的字段。')
```
- 如果 `portrait_features` 列表为空,抛出 `ValueError` 异常,提示未找到可用的画像特征,并建议检查文件字段[^1]。
```python
inverse_keys = ['HDL', '活动量表', 'ADL', 'IADL']
flags = pd.DataFrame(index=df.index)
threshold_info = []
```
- 定义逆向风险关键词列表 `inverse_keys`;创建一个空的 `DataFrame` `flags`,其索引与 `df` 相同;创建一个空列表 `threshold_info`,用于存储阈值信息[^1]。
```python
for c in portrait_features:
s = pd.to_numeric(df[c], errors='coerce')
if any(k in c for k in inverse_keys):
thr = s.quantile(0.25)
flags[c + '_风险'] = (s <= thr).astype(int)
threshold_info.append([c, '下四分位判定为风险', thr])
else:
thr = s.quantile(0.75)
flags[c + '_风险'] = (s >= thr).astype(int)
threshold_info.append([c, '上四分位判定为风险', thr])
```
- 遍历 `portrait_features` 中的每个特征:
- 将该特征列转换为数值类型,无法转换的设为 `NaN`。
- 如果特征名包含 `inverse_keys` 中的关键词,使用下四分位作为阈值,将小于等于阈值的标记为风险(值为 1),并将相关信息添加到 `threshold_info` 列表;否则使用上四分位作为阈值,将大于等于阈值的标记为风险,同样记录相关信息[^1]。
```python
merged = pd.concat([df[['三级风险','高血脂症二分类标签']].reset_index(drop=True), flags.reset_index(drop=True)], axis=1)
high = merged[merged['三级风险'] == '高风险'].copy()
flag_cols = flags.columns.tolist()
```
- 将 `df` 中的 '三级风险' 和 '高血脂症二分类标签' 列与 `flags` 按列方向拼接,得到 `merged`;从 `merged` 中筛选出 '三级风险' 为 '高风险' 的数据存储到 `high` 变量;将 `flags` 的列名转换为列表存储到 `flag_cols` 变量[^1]。
```python
rows = []
for r in [2, 3]:
for comb in combinations(flag_cols, r):
cond = high[list(comb)].all(axis=1)
support = cond.mean() if len(high) else 0
support_n = int(cond.sum())
if support_n < max(10, int(0.03 * max(len(high), 1))):
continue
all_cond = merged[list(comb)].all(axis=1)
conf = merged.loc[all_cond, '高血脂症二分类标签'].mean() if all_cond.sum() else 0
rows.append([' + '.join(comb), support_n, support, conf])
```
- 生成 `flag_cols` 中元素的 2 个和 3 个元素的组合:
- 对于每个组合,检查 `high` 数据集中该组合对应的列是否都为 `True`,得到条件 `cond`。
- 计算支持度 `support` 和支持度对应的样本数 `support_n`。
- 如果支持度对应的样本数小于 10 或者小于高风险样本数的 3%,则跳过该组合。
- 在 `merged` 数据集中检查该组合对应的列是否都为 `True`,计算置信度 `conf`。
- 将组合信息、支持度样本数、支持度和置信度添加到 `rows` 列表中[^1]。
```python
portrait_df = pd.DataFrame(rows, columns=['核心组合','高风险样本数','支持度','置信度'])
if len(portrait_df):
portrait_df = portrait_df.sort_values(['支持度','置信度'], ascending=False).head(15)
```
- 将 `rows` 列表转换为 `DataFrame` `portrait_df`;如果 `portrait_df` 不为空,按照支持度和置信度降序排序,取前 15 行[^1]。
```python
profile_rate_df = merged.groupby('三级风险', observed=False)[flag_cols].mean().T if len(merged) else pd.DataFrame()
```
- 如果 `merged` 不为空,按照 '三级风险' 分组,计算 `flag_cols` 中各列的均值,并进行转置,结果存储到 `profile_rate_df`;否则 `profile_rate_df` 为空 `DataFrame`[^1]。
```python
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='各风险层标记率')
```
- 使用 `pd.ExcelWriter` 将画像变量、风险判定阈值、高风险核心组合和各风险层标记率分别保存到 `out_file` 的不同工作表中,不保存索引[^1]。
```python
if len(portrait_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)
ax.set_xlabel('支持度(在高风险组内出现比例)')
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)
ax.grid(axis='x', linestyle='--', alpha=0.35, color=GRID_COLOR)
fig.tight_layout()
fig.savefig(PLOT_FILE_1, dpi=300, bbox_inches='tight')
plt.close(fig)
```
- 如果 `portrait_df` 不为空:
- 取 `portrait_df` 的前 10 行并倒序存储到 `top_df`。
- 创建一个画布和坐标轴,设置图形大小。
- 生成颜色渐变列表 `color_scale`。
- 绘制水平条形图展示高风险核心组合的支持度,设置条形图颜色、边框和宽度。
- 设置坐标轴标签和标题。
- 在每个条形图上添加样本数和置信度信息。
- 设置 x 轴网格线。
- 调整布局,保存图像到 `PLOT_FILE_1`,关闭画布[^1]。
```python
if not profile_rate_df.empty:
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)))
ax.set_xticks(range(len(show_df.columns)))
ax.set_xticklabels(show_df.columns)
ax.set_yticks(range(len(show_df.index)))
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()
fig.savefig(PLOT_FILE_2, dpi=300, bbox_inches='tight')
plt.close(fig)
```
- 如果 `profile_rate_df` 不为空:
- 复制 `profile_rate_df` 到 `show_df`,筛选出 '低风险'、'中风险'、'高风险' 列。
- 创建画布和坐标轴,设置图形大小。
- 绘制热力图展示画像特征在各风险层的风险标记率,设置颜色映射、最小和最大颜色值。
- 设置坐标轴刻度和标签,以及标题。
- 在热力图每个格子上添加具体的标记率信息,根据标记率设置文本颜色。
- 添加颜色条,设置颜色条标签。
- 调整布局,保存图像到 `PLOT_FILE_2`,关闭画布[^1]。
```python
msg = f'完成:{out_file}'
if len(portrait_df):
msg += f',并生成 {PLOT_FILE_1}'
if not profile_rate_df.empty:
msg += f'、{PLOT_FILE_2}'
print(msg)
```
- 定义消息字符串 `msg`,表示处理完成并输出结果文件;如果 `portrait_df` 不为空,添加生成的高风险核心组合画像图信息;如果 `profile_rate_df` 不为空,添加生成的画像特征热力图信息;最后打印消息[^1]。