基于Python的2型糖尿病遗传风险预测:从SNP数据清洗到机器学习建模全流程

发布时间:2026/9/13 17:19:50
基于Python的2型糖尿病遗传风险预测:从SNP数据清洗到机器学习建模全流程
简介这份人工智能辅助糖尿病遗传风险预测资源是基于Python实现的完整机器学习项目适合毕业设计、期末大作业及AI医疗方向入门实践。项目利用病人性别、体检日期、血常规及肾功能检查等指标构建血糖值预测模型覆盖数据清洗、特征工程、模型训练与对比等完整流程。压缩包共38个文件以28个Python脚本和2个Jupyter notebook为核心包含多种回归与分类算法实现如LightGBM、SVR、KMeans、贝叶斯、神经网络集成等另附CSV数据集、特征重要性图表、README说明及一键启动脚本整体大小6.67MB结构清晰便于直接运行与二次开发。已有85人学习下载适合想系统掌握糖尿病预测建模思路、算法对比调优及项目文档撰写的学习者可据此快速复现赛题方案并扩展自己的实验。1. 遗传风险预测这个 Python 项目到底解决什么问题家里有直系亲属确诊糖尿病的人都会关心自己未来得病的概率。体检只能给出当下的血糖水平而遗传风险指向的是数年甚至数十年后的趋势。这个基于 Python 实现的项目输入一组 SNP 遗传位点和年龄、BMI、糖化血红蛋白等指标输出一个 0 到 1 的风险概率。它和单基因遗传病检测最大的区别在于2 型糖尿病是多基因疾病单个位点的效应量通常只有几个百分点必须用机器学习在几十个微效位点里找出联合模式。这类源码包常见于课题研究或毕业设计拿到的第一步不是直接跑脚本而是先弄清数据怎么编码、标签怎么定义、模型凭什么这么选否则得到的结果只是一个无从解释的数字。2. 数据装配遗传位点和临床指标的预处理链路2.1 解压后通常会看到哪三类数据文件标题里明确写到“数据”所以先看数据。常规的遗传风险预测项目包里数据文件常见形态是 CSV 或 Excel每一行是样本每一列是特征。遗传特征列通常用 rs 编号命名比如 rs1121980、rs9939609这类位点来自 GWAS 数据库中与 2 型糖尿病或 BMI 相关的报道。除了 SNP 列临床特征一般包括年龄、BMI、糖化血红蛋白、空腹血糖、家族史。标签列可能是 label、y 或 diagnosis取值为 0/1。拿到包的第一件事是寻找数据字典没有的话就根据列名自己补一份否则后续所有分析都说不清特征的生物学含义。特征名类型示例值说明rs1121980基因型编码1风险等位基因个数age数值42.5入组年龄bmi数值27.2体质指数hba1c数值5.8糖化血红蛋白fasting_glucose数值5.3空腹血糖family_history二值1一级亲属糖尿病史label二值1是否确诊或进展为糖尿病遗传位点列的编码是最容易理解错的地方。文本形式的“AA/AG/GG”不能直接进入模型常见做法是先映射成风险等位基因个数0 表示没有携带风险等位基因1 表示杂合2 表示纯合风险。有些数据集直接给 0/1/2有些给原始碱基型需要对照 biallelic SNP 的 allele 定义做转换。这一步转换一旦搞反训练本身没有任何报错但学习到的权重方向会整体颠倒。2.2 位点缺失不能无脑填充中位数基因芯片数据的缺失机制和问卷数据完全不同。SNP 检出失败通常与 DNA 质量、芯片批次、样本降解程度有关某个位点在特定批次里大面积缺失本身就是一个质量信号。常见做法是两轮阈值过滤位点缺失率超过 5% 直接剔除样本缺失率超过 10% 直接丢弃剩余缺口再用训练集上的众数填充。过滤是无参数操作可以在全量数据上做但填充用的统计值必须在训练集上计算再应用到验证集和测试集否则验证结果会被系统性高估。import pandas as pd from sklearn.model_selection import train_test_split df pd.read_csv(data/diabetes_gwas.csv) snp_cols [c for c in df.columns if c.startswith(rs)] # 位点级缺失率超过 5% 直接剔除 snp_missing df[snp_cols].isna().mean(axis0) snp_cols list(snp_missing[snp_missing 0.05].index) # 样本级缺失率超过 10% 直接丢弃 sample_missing df[snp_cols].isna().mean(axis1) df df.loc[sample_missing 0.10].copy() # 先按标签分层切开再只用训练集统计量填充 df_train, df_test train_test_split( df, test_size0.2, random_state42, stratifydf[label] ) mode_vals df_train[snp_cols].mode().iloc[0] df_train[snp_cols] df_train[snp_cols].fillna(mode_vals) df_test[snp_cols] df_test[snp_cols].fillna(mode_vals)逻辑说明axis0计算每个位点在所有样本上的缺失率axis1计算每个样本在所有位点上的缺失率。先按 label 分层划分再在训练集上计算众数是为了杜绝填充统计量看到测试集信息。fillna 用众数而不是均值因为基因型编码是离散有序量均值会产生 0.7 这种生物意义上不存在的基因型。提示基因型缺失填充用众数而不是均值因为 0/1/2 编码是离散有序量均值会造出生物学上不存在的中间值。2.3 标签定义与类别不平衡标签定义是遗传风险预测项目里争议最大的环节。常见方案有两种一种用“当前是否确诊”作为正样本简单直接另一种用“基线血糖正常但在随访期内进展为糖尿病”作为正样本后者更贴合遗传风险的含义因为遗传位点在发病前就存在。如果项目只提供横断面数据随访标签无法获得研究目标就降级为“区分糖代谢异常个体”这是一个弱得多的问题设定。样本配平上直接对每个 SNP 列做 SMOTE 会引入不存在的基因型组合。SMOTE 在连续特征之间插值得到的 0.3、1.7 这类小数编码不对应任何真实基因型。常见做法是给分类器传class_weightbalanced或者在保持风险等位基因频率的前提下对少数类样本做有放回复制。与其先跑一个漂亮模型再回来补配平不如在数据阶段就确认正样本比例和风险评估目标是否匹配。3. 模型选型为什么不能只数几个致病位点3.1 单基因位点打分解决不了多基因风险在内分泌科的经典遗传咨询场景里GCK、HNF1A 这类单基因突变位点是直接看碱基型判读的一个风险等位基因就足以大幅改变临床处置方案。但 2 型糖尿病的遗传结构完全不同大量位点分布在整个基因组上每个位点的等位基因优势比通常在 1.05 到 1.2 之间单独拿出来几乎没有判别力。把若干个位点相加得到一个多基因风险评分PRS能提升区分度但 PRS 本质上是线性模型score Σ(β_i × g_i)其中 β_i 是 GWAS 报道的效应量。问题在于这些权重来自大规模公开人群并不是针对当前训练数据优化的而且权重固定后无法纳入年龄、BMI 等环境因素的交互作用。3.2 机器学习模型在遗传数据上的边界机器学习的价值出现在两个地方一是当样本量足够时用数据本身重估每个位点权重而不是完全依赖公开 GWAS 的 β二是把环境变量和 SNP 一起放进模型让算法自己捕捉交互项。模型选择的边界在这个场景下比较清晰逻辑回归可解释、方差小适合几百样本的课题随机森林能识别交互效应但容易在稀疏基因型数据上过拟合XGBoost 在表格数据上通常表现最好但需要更长的调参周期而且特征重要性会偏向高频率位点。模型交互效应小样本表现可解释性输出格式逻辑回归不自动稳定高概率Lasso 逻辑回归不自动高高概率随机森林自动易过拟合中概率/类别XGBoost自动调参敏感低–中概率遗传位点数据是高维稀疏二元矩阵树模型的 split finding 对低频率位点不友好。一个位点在训练集里的次等位基因频率只有 2%它几乎没有机会成为分裂点。处理办法是在特征筛选阶段保留 MAF 大于 1% 的位点或者在 XGBoost 里把 PRS 计算成一个连续打分列和 age、bmi 一起作为普通数值特征输入让树模型看到的是连续风险梯度而不是离散位点。另一个被低估的做法是把公开 GWAS 得到的 PRS 作为特征放进模型而不是替代模型训练。PRS 列提供了强大的先验信息尤其在小样本课题里能显著提升 AUC。但直接用 PRS 排名做预测和把 PRS 当作输入特征的区别在于后者允许模型在 PRS 基础上继续学习基因与环境的交互。计算 PRS 前要先确认 summary statistics 的 risk allele 方向和参考等位基因定义符号搞反会让整体预测水平倒退。3.3 判别分数不是风险概率校准这一步别省模型输出的 0 到 1 数值在 scikit-learn 里叫predict_proba专业术语是判别分数不等同于绝对风险概率。只有当训练集阳性率与目标人群真实发病率接近时两者才能画等号。常见做法是用训练集交叉验证预测值做 Platt 缩放或 isotonic 回归。逻辑回归的输出本身就是 log-odds使用前直接取 expit 即可但随机森林和 XGBoost 的概率输出分布偏差较大必须经过校准。from sklearn.calibration import CalibratedClassifierCV from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier(n_estimators200, max_depth4, random_state42) calibrated CalibratedClassifierCV( model, methodisotonic, cv5 ) calibrated.fit(X_train, y_train) y_prob calibrated.predict_proba(X_test)[:, 1]参数说明methodisotonic适合样本量充足时使用能把预测概率映射到真实频率methodsigmoid即 Platt 缩放假设校准曲线是单调的 S 形适合样本较少或校准曲线存在平台期的场景。校准必须放在交叉验证内部完成不能在校准完成后用同一批验证集再评测模型性能否则概率区间会被系统性压缩。4. 从清洗到可复现的训练管线参数、网格搜索和分层策略4.1 用 LogisticRegression 作为项目的第一条基线无论后续换成随机森林还是 XGBoost第一步都要先把逻辑回归管线跑通。线性模型的参数少、稳定性高、收敛行为可预期先用它得到一个 AUC 基准再替换成集成模型这样才能判断树模型的增益来自真实信号还是调参运气。项目包里如果模型选型理由含糊常见做法就是从这条管线开始补基线。import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, StratifiedKFold from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score feature_cols snp_cols [age, bmi, hba1c, fasting_glucose, family_history] X_train, X_test, y_train, y_test train_test_split( df[feature_cols], df[label], test_size0.2, random_state42, stratifydf[label] ) pipeline Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression(class_weightbalanced, max_iter2000)) ]) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) auc_scores [] for tr_idx, va_idx in cv.split(X_train, y_train): X_tr, X_va X_train.iloc[tr_idx], X_train.iloc[va_idx] y_tr, y_va y_train.iloc[tr_idx], y_train.iloc[va_idx] pipeline.fit(X_tr, y_tr) auc_scores.append(roc_auc_score(y_va, pipeline.predict_proba(X_va)[:, 1])) print(fCV AUC: {np.mean(auc_scores):.3f} ± {np.std(auc_scores):.3f}) pipeline.fit(X_train, y_train) y_prob pipeline.predict_proba(X_test)[:, 1] print(fTest AUC: {roc_auc_score(y_test, y_prob):.3f})逻辑说明train/test 划分里带了stratifydf[label]保证训练集和测试集的正负样本比例一致。5 折分层交叉验证里每一折重新拟合整个 PipelineStandardScaler 的均值和方差只在当前折的训练部分计算不会看到验证折循环跑完后再对完整训练集做一次拟合用于测试集评估。class_weightbalanced会根据标签频率自动放大少数类样本的损失权重是对抗不平衡的标准手段。参数起点参考表参数取值范围推荐起点说明C逻辑回归0.01101.0L2 正则强度越小泛化越强max_iter50020001000不收敛时加大n_estimatorsRF100500200树数量越大越稳但耗时max_depthRF364浅树显著降低过拟合learning_rateXGB0.010.30.05调小后需配合更多棵树max_depthXGB253遗传交互一般不深树不用高网格搜索在 Pipeline 上的写法要注意参数名的前缀。Pipeline 阶段名是clf参数名要写成clf__C中间用双下划线连接。换成 XGBClassifier 时max_depth、learning_rate同理全部带上前缀。from sklearn.model_selection import GridSearchCV param_grid {clf__C: [0.1, 1, 10]} gs GridSearchCV(pipeline, param_grid, cv5, scoringroc_auc, n_jobs-1) gs.fit(X_train, y_train) print(gs.best_params_)4.2 人群分层协变量PCA 这一步不能省遗传数据和普通表格数据最大的区别在于人群祖先组成。如果样本来自不同地理区域位点等位基因频率会有系统性差异某些位点在一个群体中的 MAF 是 30%在另一个群体中可能只有 5%。模型学到的东西就可能变成人群标签而不是糖尿病风险本身。常见做法是用全量基因型矩阵做主成分分析取前 2 到 5 个主成分作为协变量写进特征集。from sklearn.decomposition import PCA pca PCA(n_components3).fit(df[snp_cols].values) for i in range(3): df[fpc{i1}] pca.components_[i] df[snp_cols].values.T代码说明pca.components_的每一行是一个主成分方向向量和基因型矩阵做矩阵乘法得到对应样本的坐标。这些坐标列再拼接到feature_cols末尾。PCA 对树模型而言价值有限因为树的单变量分裂无法同时利用多个主成分的组合但它对逻辑回归和线性 SVM 几乎是必须的否则位点权重会吸收人群结构信号。主成分数量控制在 2 到 3 个即可加太多会把真实的遗传信号当作冗余维度压缩掉。注意特征选择如果放在训练集之外做交叉验证结果没有意义。任何使用全量数据计算过的统计量都会把测试集信息泄漏进验证结果。4.3 实验记录和复现 CS V 的约定源码包最后跑不通缺的往往不是代码而是记录习惯。每次实验应记录数据版本、缺失过滤阈值、PCA 维数、模型参数、AUC 和 95% 置信区间、校准曲线图形。建议建一个experiments.csv列名固定为data_version, model_name, features, params, auc_mean, auc_std, calibrated, note每次实验只在一个参数上做改动。这个 CSV 是文档说明里最容易被遗漏的部分但答辩、复核和回溯时最关键。没有实验记录所有调参过程都只是内存里短暂存在的数字。5. 从风险概率到可交付成果解释、持久化、文档规范5.1 概率转风险等级阈值怎么选校准完成后的概率曲线要转成可读的风险等级。直接用 0.5 当阈值在遗传风险预测里并不合适因为疾病在目标人群中的先验发生率远低于 50%硬切 0.5 会造成大量假阴性。课题项目里常见做法是画出 ROC 曲线后用 Youdens J 指数敏感度 特异度 - 1选最佳切点再以切点为中心划分低、中、高风险区间。这个方法对单层风险提示够用但如果项目要想对接临床路径阈值必须和合作医生根据筛查指南对齐而不能只看统计指标。5.2 模型持久化joblib 保存管线和特征顺序在纯 Python 环境下最省事的持久化方式是joblib.dump对 scikit-learn 管线原生支持不需要额外序列化层。如果项目要求跨语言服务可以再导出 ONNX 或通过 MLflow 管理模型版本对一份课题级别的源码包joblib 足够。import joblib joblib.dump(pipeline, model/diabetes_risk.joblib) joblib.dump(feature_cols, model/feature_cols.joblib) def predict_risk(genotype_row: dict, age: float, bmi: float, hba1c: float) - float: row dict(genotype_row) row.update({age: age, bmi: bmi, hba1c: hba1c}) input_df pd.DataFrame([row])[feature_cols] prob pipeline.predict_proba(input_df)[0, 1] return round(prob, 4) print(predict_risk({rs1121980: 1, rs9939609: 0}, age45, bmi27.2, hba1c5.6))用 joblib 保存管线时特征名必须一并持久化。新数据进入predict_proba前用feature_cols重新索引列才能保证列顺序和训练时完全一致。模型推断阶段最常见的两类错误都没有报错提示列顺序错位导致结果静默错误以及传入缺失列导致 KeyError。前者比后者更难排查。5.3 文档说明不是 README而是完整的交付闭环标题里的“文档说明”在交付时通常包含四份内容。README 写项目简介、环境依赖、复现命令和预期输出data_dictionary 写每一列的类型、缺失率、取值范围、清洗规则尤其是 SNP 列的含义model_report 写最终模型的 AUC、置信区间、校准曲线、阈值表和特征重要性experiments.csv 记录所有实验版本。四份内容齐全后才达到“换一台机器按文档操作能复现”的标准。提示文档说明的最低验收标准是——别人不看代码只按文档步骤能否得到同一份 AUC 和同一组预测概率。所以最终交付时除了文档清单还要把predict_risk的特征顺序约束写进 README。feature_cols.joblib保存的就是训练时的列顺序任何新数据在进入模型前都要按这个顺序重新索引。少一列会直接报错多一列且顺序错位则静默产生错误概率这一类错误比模型训练本身的调试更隐蔽全靠在文档层面提前约定。本文还有配套的精品资源点击获取