一、 研究背景与科学问题
-
临床痛点:
肾透明细胞癌的异质性: ccRCC在生物学行为上具有高度异质性,低级别肿瘤(WHO/ISUP 1-2级)进展缓慢,高级别肿瘤(3-4级)侵袭性强、预后差。
治疗决策依赖分级: 核分级直接影响临床决策,如主动监测(适用于小体积、低级别)、肾部分切除术(尽可能保留肾功能)或根治性肾切除术,以及是否需要进行更密切的随访。
当前金标准的局限: 核分级的确诊完全依赖于术后病理切片,属于有创、滞后性诊断。术前穿刺活检因肿瘤异质性、出血风险等原因,并非ccRCC的常规检查。
-
科学机遇:
常规超声的优势: 作为肾脏肿瘤最常用的一线筛查和诊断工具,普及率高、无辐射、成本低。
影像组学的潜力: 肿瘤的超声回声纹理、形态、边缘等宏观特征,与其微观层面的细胞排列密度、核大小、异型性、坏死/出血等存在内在关联。影像组学可以量化这些人眼难以分辨的细微差异。
-
核心科学问题:
能否基于术前常规灰阶超声图像,通过影像组学技术提取定量特征,并结合常规超声语义特征及临床信息,构建一个可视化的列线图模型,实现对ccRCC核分级(低级别 vs. 高级别)的术前精准、无创预测?
二、 研究目标
- 主要目标: 开发并验证一个融合常规超声影像组学特征、超声语义特征及临床变量的列线图模型,用于术前预测ccRCC的WHO/ISUP核分级(二分类:低级别[1-2级] vs. 高级别[3-4级])。
- 次要目标:
筛选与ccRCC核分级最相关的关键影像组学特征。
比较纯影像组学模型、临床模型以及二者联合模型的预测性能。
评估该列线图模型的临床实用性。
三、 研究方法与技术路线
阶段一:研究对象与金标准
研究对象: 经手术切除且术后病理证实为ccRCC的患者。所有患者在术前均接受了肾脏常规灰阶超声检查,并保存了原始的DICOM图像。
金标准: 以术后石蜡切片病理报告为准,由两位资深病理科医师重新复核并确认WHO/ISUP核分级。将1-2级定义为"低级别组",3-4级定义为"高级别组"。
排除标准: 接受过新辅助治疗、图像质量差无法分析、肿瘤最大径<1cm(特征提取可靠性低)、病理信息不全者。
阶段二:数据采集与处理
- 临床与超声语义数据:
临床变量: 年龄、性别、症状(有无)、肿瘤最大径(来自超声或CT报告)。
超声语义特征: 由两名高年资超声医师在不知病理结果的情况下,根据图像记录:肿瘤位置、形态(规则/不规则)、边界(清晰/模糊)、内部回声(均匀/不均匀、高/等/低/混合回声)、有无囊变/钙化、后方回声(增强/衰减) 等。计算观察者间一致性。 - 影像组学流程:
图像获取与预处理: 调取包含肿瘤最大切面的原始灰阶超声纵、横切面DICOM图像。进行图像重采样、灰度归一化。
肿瘤分割(ROI): 由医师在上述两个切面上,沿肿瘤边界(包括所有实性部分,排除明显囊变区)手动勾画ROI。这是关键步骤,需保证精确性和可重复性。
特征提取: 使用PyRadiomics等工具,从每个ROI中高通量提取:
形状特征(如球形度、表面积体积比,高级别肿瘤可能更不规则)。
一阶统计特征(回声强度均值、偏度、峰度,高级别肿瘤可能因坏死出血导致回声更不均匀,峰度变化)。
纹理特征(核心):GLCM(对比度、能量、同质性)、GLRLM、GLSZM、NGTDM等。高级别肿瘤预期表现出更高的异质性(更高对比度、更低能量)和更复杂的纹理模式。
特征稳定性筛选: 基于观察者间/观察者内一致性分析(ICC > 0.75),保留稳定特征。
阶段三:特征工程、模型构建与验证
- 特征筛选与降维:
在训练集中,使用单变量分析(如Mann-Whitney U检验)初步筛选与分级相关的特征。
使用LASSO回归或最小绝对收缩与选择算子进行多变量特征选择,得到最精简、最具判别力的影像组学特征子集,并计算每个患者的影像组学评分(Rad-score)。 - 模型构建:
数据集划分: 按时间顺序或随机将患者分为训练队列和内部验证队列(建议7:3)。有条件则使用外部验证队列(来自其他中心的数据)。
构建三个逻辑回归模型进行比较:
临床模型: 仅基于临床和超声语义特征(如肿瘤大小、回声均匀性)。
影像组学模型: 仅基于Rad-score。
联合模型: 融合了临床特征、超声语义特征和Rad-score。此模型将用于构建最终的列线图。 - 列线图构建: 将联合逻辑回归模型的结果转化为一个可视化的、易于使用的列线图(Nomogram)。临床医生可根据患者的各项指标得分,加总后对应预测其为高级别ccRCC的概率。
阶段四:模型评估与临床验证
- 性能评估: 在训练集和验证集上分别评估各模型。
区分度: 主要指标为AUC(ROC曲线下面积)。使用DeLong检验比较不同模型AUC的差异。期望联合模型的AUC最高(例如,在验证集上 > 0.80)。
校准度: 绘制校准曲线,并计算Hosmer-Lemeshow检验的P值,评估模型预测概率与实际发生概率的一致性。
临床实用性: 绘制决策曲线分析(DCA),量化模型在不同阈值概率下能为临床决策带来的净收益。 - 模型解释: 分析联合模型中各变量的回归系数,解释其对预测的贡献。展示关键影像组学特征的权重。
四、 预期结果与创新点
预期结果:
-
成功构建一个性能优良的联合预测列线图,其在验证集上预测高级别ccRCC的AUC显著高于单纯临床或影像组学模型。
-
筛选出关键的影像组学特征标签(如"熵"、"小面积低灰度强调"等),这些特征可能与肿瘤细胞核的异型性、排列紊乱及坏死相关。
-
提供一款直观的临床工具(列线图),医生输入几个简单指标(如肿瘤大小、回声是否均匀、Rad-score)即可获得个体化的风险概率。
创新点与临床意义:
-
聚焦常规超声: 利用最普及的影像技术解决关键临床问题,推广潜力巨大。
-
目标明确: 直接针对指导治疗决策的核心病理指标------核分级。
-
方法整合: 将客观的影像组学定量数据与医生主观的超声语义特征相结合,取长补短。
-
产出实用工具: 列线图的形式非常符合临床医生的使用习惯,便于快速进行风险评估,可用于:
指导手术方式选择(部分切 vs. 根治切)。
识别高危患者进行更积极的治疗或随访。
为不适合或拒绝活检的患者提供重要的风险分层信息。
五、 挑战与展望
挑战:
肿瘤异质性与ROI勾画: 如何准确勾画不均质肿瘤的边界,以及是否应包含囊变/坏死区,需要统一标准。
超声设备与参数差异: 需严格的图像预处理。
样本不均衡: 高级别肿瘤的样本量可能较少,需采用过采样等策略。
展望:
融合多模态影像: 后续可整合CT或MRI的影像组学特征,构建更强大的多模态模型。
预测更细化的分级或预后: 尝试区分1级与2级,或直接预测无进展生存期。
前瞻性验证: 开展前瞻性研究,验证该模型在真实临床决策中的有效性。
总结:
本研究通过挖掘常规超声图像中蕴含的深层信息,构建一个可解释、易使用的列线图模型,旨在实现ccRCC核分级的术前无创精准预测。该成果将有力推动肾脏肿瘤的个体化、精准化诊疗,帮助临床医生在术前制定更优的治疗策略,具有重要的科学价值和明确的临床应用前景。
提供一个完整的、可运行的代码框架,用于构建和验证基于常规超声影像组学预测肾透明细胞癌核分级的列线图模型。
python
# -*- coding: utf-8 -*-
"""
肾透明细胞癌核分级预测模型构建与验证
常规超声影像组学联合临床特征的列线图模型
"""
# ==================== 1. 环境配置与库导入 ====================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
# 影像组学相关
import radiomics
from radiomics import featureextractor
import SimpleITK as sitk
import six
# 机器学习相关
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression, Lasso
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import (roc_auc_score, accuracy_score, recall_score,
precision_score, f1_score, confusion_matrix,
roc_curve, classification_report)
from sklearn.feature_selection import SelectFromModel
import xgboost as xgb
# 统计与可视化
import statsmodels.api as sm
from scipy.stats import mannwhitneyu, chi2_contingency
import scipy.stats as stats
from lifelines import CoxPHFitter
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
# 列线图相关
import rpy2.robjects as robjects
from rpy2.robjects import pandas2ri, Formula
from rpy2.robjects.packages import importr
pandas2ri.activate()
# ==================== 2. 数据加载与预处理 ====================
class DataLoader:
"""数据加载与预处理类"""
def __init__(self, clinical_csv, radiomics_csv, image_dir=None):
"""
初始化数据加载器
参数:
clinical_csv: 临床数据CSV文件路径
radiomics_csv: 影像组学特征CSV文件路径
image_dir: 原始图像目录(可选)
"""
self.clinical_df = pd.read_csv(clinical_csv, encoding='utf-8')
self.radiomics_df = pd.read_csv(radiomics_csv, encoding='utf-8')
self.image_dir = image_dir
def merge_data(self):
"""合并临床数据和影像组学数据"""
# 确保ID列一致
self.clinical_df['PatientID'] = self.clinical_df['PatientID'].astype(str)
self.radiomics_df['PatientID'] = self.radiomics_df['PatientID'].astype(str)
# 合并数据
self.merged_df = pd.merge(
self.clinical_df,
self.radiomics_df,
on='PatientID',
how='inner'
)
# 定义目标变量:高级别(3-4级)为1,低级别(1-2级)为0
self.merged_df['HighGrade'] = self.merged_df['WHO_ISUP_Grade'].apply(
lambda x: 1 if x >= 3 else 0
)
print(f"合并后数据形状: {self.merged_df.shape}")
print(f"高级别样本数: {self.merged_df['HighGrade'].sum()}")
print(f"低级别样本数: {len(self.merged_df) - self.merged_df['HighGrade'].sum()}")
return self.merged_df
def handle_missing_values(self):
"""处理缺失值"""
print("处理前缺失值统计:")
print(self.merged_df.isnull().sum())
# 删除缺失值过多的特征
missing_threshold = 0.2
cols_to_drop = self.merged_df.columns[
self.merged_df.isnull().mean() > missing_threshold
]
self.merged_df = self.merged_df.drop(columns=cols_to_drop)
print(f"\n删除缺失率>{missing_threshold}的特征: {list(cols_to_drop)}")
# 对于数值特征,使用中位数填充
numeric_cols = self.merged_df.select_dtypes(include=[np.number]).columns
for col in numeric_cols:
if self.merged_df[col].isnull().any():
self.merged_df[col].fillna(self.merged_df[col].median(), inplace=True)
# 对于分类特征,使用众数填充
categorical_cols = self.merged_df.select_dtypes(include=['object']).columns
for col in categorical_cols:
if self.merged_df[col].isnull().any():
self.merged_df[col].fillna(self.merged_df[col].mode()[0], inplace=True)
print("\n处理后缺失值统计:")
print(self.merged_df.isnull().sum().sum())
return self.merged_df
def extract_radiomics_features(self, image_path, mask_path):
"""从单张图像提取影像组学特征"""
try:
# 读取图像和掩码
image = sitk.ReadImage(image_path)
mask = sitk.ReadImage(mask_path)
# 设置特征提取参数
params = {}
params['binWidth'] = 25 # 直方图bin宽度
params['resampledPixelSpacing'] = None # 重采样
params['interpolator'] = sitk.sitkBSpline
params['label'] = 1 # ROI标签
# 创建特征提取器
extractor = featureextractor.RadiomicsFeatureExtractor(**params)
# 提取特征
result = extractor.execute(image, mask)
# 转换为DataFrame
features = {}
for key, value in six.iteritems(result):
if key.startswith('original_'):
features[key] = value
return features
except Exception as e:
print(f"特征提取失败: {e}")
return None
# ==================== 3. 特征工程与选择 ====================
class FeatureEngineer:
"""特征工程与选择类"""
def __init__(self, df, target_col='HighGrade', clinical_cols=None):
"""
初始化特征工程器
参数:
df: 合并后的DataFrame
target_col: 目标变量列名
clinical_cols: 临床特征列名列表
"""
self.df = df.copy()
self.target_col = target_col
self.clinical_cols = clinical_cols if clinical_cols else []
def split_dataset(self, test_size=0.3, random_state=42):
"""划分训练集和测试集"""
from sklearn.model_selection import train_test_split
# 分离特征和目标变量
X = self.df.drop(columns=[self.target_col])
y = self.df[self.target_col]
# 获取特征列名
self.feature_names = list(X.columns)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=test_size, random_state=random_state, stratify=y
)
print(f"训练集形状: {X_train.shape}")
print(f"测试集形状: {X_test.shape}")
print(f"训练集中高级别比例: {y_train.mean():.3f}")
print(f"测试集中高级别比例: {y_test.mean():.3f}")
return X_train, X_test, y_train, y_test, self.feature_names
def feature_selection_lasso(self, X_train, y_train, alpha=0.01):
"""使用LASSO进行特征选择"""
# 标准化特征
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# LASSO回归
lasso = Lasso(alpha=alpha, max_iter=10000, random_state=42)
lasso.fit(X_train_scaled, y_train)
# 获取非零系数特征
coef = pd.DataFrame({
'feature': X_train.columns,
'coef': lasso.coef_
})
selected_features = coef[coef['coef'] != 0]['feature'].tolist()
print(f"LASSO选择前特征数: {X_train.shape[1]}")
print(f"LASSO选择后特征数: {len(selected_features)}")
return selected_features, coef
def feature_selection_rf(self, X_train, y_train, threshold='median'):
"""使用随机森林进行特征选择"""
# 随机森林分类器
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
# 获取特征重要性
importances = pd.DataFrame({
'feature': X_train.columns,
'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)
# 选择特征
selector = SelectFromModel(rf, threshold=threshold, prefit=True)
selected_mask = selector.get_support()
selected_features = X_train.columns[selected_mask].tolist()
print(f"随机森林选择后特征数: {len(selected_features)}")
return selected_features, importances
def calculate_rad_score(self, X, selected_features, coef):
"""计算影像组学评分(Rad-score)"""
# 只使用选择的特征
X_selected = X[selected_features].copy()
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_selected)
# 获取特征对应的系数
feature_coef_dict = dict(zip(coef['feature'], coef['coef']))
coefficients = np.array([feature_coef_dict.get(f, 0) for f in selected_features])
# 计算Rad-score
rad_score = np.dot(X_scaled, coefficients)
return rad_score
# ==================== 4. 模型构建与训练 ====================
class ModelBuilder:
"""模型构建与训练类"""
def __init__(self):
self.models = {}
self.results = {}
def build_logistic_model(self, X_train, y_train, X_test, y_test,
model_name='logistic', penalty='l2', C=1.0):
"""构建逻辑回归模型"""
# 创建并训练模型
model = LogisticRegression(
penalty=penalty,
C=C,
solver='liblinear',
random_state=42,
max_iter=1000
)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
# 计算性能指标
metrics = self.calculate_metrics(y_test, y_pred, y_pred_proba)
# 保存模型和结果
self.models[model_name] = model
self.results[model_name] = {
'model': model,
'y_pred': y_pred,
'y_pred_proba': y_pred_proba,
'metrics': metrics
}
print(f"\n{model_name.upper()} 模型性能:")
for key, value in metrics.items():
print(f"{key}: {value:.4f}")
return model, metrics
def build_random_forest(self, X_train, y_train, X_test, y_test,
model_name='random_forest', n_estimators=100):
"""构建随机森林模型"""
model = RandomForestClassifier(
n_estimators=n_estimators,
max_depth=5,
min_samples_split=5,
min_samples_leaf=2,
random_state=42,
class_weight='balanced'
)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
# 计算性能指标
metrics = self.calculate_metrics(y_test, y_pred, y_pred_proba)
# 保存模型和结果
self.models[model_name] = model
self.results[model_name] = {
'model': model,
'y_pred': y_pred,
'y_pred_proba': y_pred_proba,
'metrics': metrics
}
return model, metrics
def build_xgboost_model(self, X_train, y_train, X_test, y_test,
model_name='xgboost', n_estimators=100):
"""构建XGBoost模型"""
model = xgb.XGBClassifier(
n_estimators=n_estimators,
max_depth=3,
learning_rate=0.1,
subsample=0.8,
colsample_bytree=0.8,
random_state=42,
use_label_encoder=False,
eval_metric='logloss'
)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
# 计算性能指标
metrics = self.calculate_metrics(y_test, y_pred, y_pred_proba)
# 保存模型和结果
self.models[model_name] = model
self.results[model_name] = {
'model': model,
'y_pred': y_pred,
'y_pred_proba': y_pred_proba,
'metrics': metrics
}
return model, metrics
def calculate_metrics(self, y_true, y_pred, y_pred_proba):
"""计算模型性能指标"""
metrics = {
'AUC': roc_auc_score(y_true, y_pred_proba),
'Accuracy': accuracy_score(y_true, y_pred),
'Sensitivity': recall_score(y_true, y_pred),
'Specificity': recall_score(y_true, y_pred, pos_label=0),
'Precision': precision_score(y_true, y_pred),
'F1_Score': f1_score(y_true, y_pred),
'PPV': precision_score(y_true, y_pred),
'NPV': recall_score(y_true, y_pred, pos_label=0)
}
# 计算混淆矩阵
cm = confusion_matrix(y_true, y_pred)
metrics['Confusion_Matrix'] = cm
return metrics
# ==================== 5. 列线图构建 ====================
class NomogramBuilder:
"""列线图构建类"""
def __init__(self):
# 导入R语言包
self.r = robjects.r
self.rmda = importr('rms')
self.rmda = importr('rmda')
def build_nomogram_r(self, df, formula_str, feature_names):
"""使用R语言构建列线图"""
# 将Python DataFrame转换为R DataFrame
with robjects.conversion.localconverter(robjects.default_converter + pandas2ri.converter):
r_df = robjects.conversion.py2rpy(df)
# 构建逻辑回归模型
formula = Formula(formula_str)
env = formula.environment
env['df'] = r_df
# 使用rms包的lrm函数
lrm = robjects.r['lrm']
model = lrm(formula, data=r_df, x=True, y=True)
# 构建列线图
nomogram = robjects.r['nomogram']
nom = nomogram(model,
fun=robjects.r['plogis'],
funlabel="Probability of High Grade")
# 绘制列线图
plot = robjects.r['plot']
plot(nom)
return model, nom
def build_nomogram_python(self, model, feature_names, scaler=None):
"""Python实现的列线图(简化版)"""
if hasattr(model, 'coef_'):
# 逻辑回归模型
coefficients = model.coef_[0]
intercept = model.intercept_[0]
# 创建评分表格
score_table = pd.DataFrame({
'Feature': feature_names,
'Coefficient': coefficients,
'Importance': np.abs(coefficients) / np.sum(np.abs(coefficients))
}).sort_values('Importance', ascending=False)
print("\n列线图特征贡献:")
print(score_table.to_string())
return score_table
return None
# ==================== 6. 模型评估与可视化 ====================
class ModelEvaluator:
"""模型评估与可视化类"""
def __init__(self):
pass
def plot_roc_curves(self, results_dict, title="ROC曲线比较"):
"""绘制多个模型的ROC曲线"""
plt.figure(figsize=(10, 8))
colors = plt.cm.Set1(np.linspace(0, 1, len(results_dict)))
for i, (model_name, result) in enumerate(results_dict.items()):
y_true = result.get('y_true')
y_pred_proba = result.get('y_pred_proba')
if y_true is not None and y_pred_proba is not None:
fpr, tpr, _ = roc_curve(y_true, y_pred_proba)
auc = roc_auc_score(y_true, y_pred_proba)
plt.plot(fpr, tpr, color=colors[i], lw=2,
label=f'{model_name} (AUC = {auc:.3f})')
plt.plot([0, 1], [0, 1], 'k--', lw=2)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('假阳性率', fontsize=12)
plt.ylabel('真阳性率', fontsize=12)
plt.title(title, fontsize=14, fontweight='bold')
plt.legend(loc="lower right")
plt.grid(True, alpha=0.3)
plt.show()
def plot_calibration_curve(self, y_true, y_pred_proba, n_bins=10):
"""绘制校准曲线"""
from sklearn.calibration import calibration_curve
prob_true, prob_pred = calibration_curve(y_true, y_pred_proba, n_bins=n_bins)
plt.figure(figsize=(8, 6))
plt.plot(prob_pred, prob_true, 's-', label='模型')
plt.plot([0, 1], [0, 1], 'k--', label='理想校准')
plt.xlabel('预测概率', fontsize=12)
plt.ylabel('实际比例', fontsize=12)
plt.title('校准曲线', fontsize=14, fontweight='bold')
plt.legend(loc='best')
plt.grid(True, alpha=0.3)
plt.show()
return prob_true, prob_pred
def plot_decision_curve(self, y_true, y_pred_proba, model_name):
"""绘制决策曲线"""
# 计算净收益
thresholds = np.linspace(0, 1, 100)
net_benefits = []
for threshold in thresholds:
# 计算真阳性和假阳性
y_pred = (y_pred_proba >= threshold).astype(int)
tp = np.sum((y_pred == 1) & (y_true == 1))
fp = np.sum((y_pred == 1) & (y_true == 0))
n = len(y_true)
# 计算净收益
net_benefit = (tp / n) - (fp / n) * (threshold / (1 - threshold))
net_benefits.append(net_benefit)
# 绘制曲线
plt.figure(figsize=(10, 6))
plt.plot(thresholds, net_benefits, 'b-', label=model_name, lw=2)
plt.plot(thresholds, np.zeros_like(thresholds), 'k--', label='全不治疗')
plt.plot(thresholds, y_true.mean() * np.ones_like(thresholds),
'r--', label='全治疗')
plt.xlabel('阈值概率', fontsize=12)
plt.ylabel('净收益', fontsize=12)
plt.title('决策曲线分析', fontsize=14, fontweight='bold')
plt.legend(loc='upper right')
plt.grid(True, alpha=0.3)
plt.xlim([0, 1])
plt.ylim([-0.1, max(net_benefits) * 1.1])
plt.show()
def plot_feature_importance(self, importance_df, top_n=20):
"""绘制特征重要性图"""
top_features = importance_df.head(top_n)
plt.figure(figsize=(12, 8))
plt.barh(range(len(top_features)), top_features['importance'].values)
plt.yticks(range(len(top_features)), top_features['feature'].values)
plt.xlabel('特征重要性', fontsize=12)
plt.title(f'Top {top_n} 特征重要性', fontsize=14, fontweight='bold')
plt.gca().invert_yaxis()
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.show()
# ==================== 7. 主执行流程 ====================
def main():
"""主执行函数"""
print("=" * 60)
print("肾透明细胞癌核分级预测模型构建与验证")
print("=" * 60)
# 1. 数据加载与预处理
print("\n1. 数据加载与预处理...")
loader = DataLoader(
clinical_csv='clinical_data.csv', # 替换为实际文件路径
radiomics_csv='radiomics_features.csv' # 替换为实际文件路径
)
data_df = loader.merge_data()
data_df = loader.handle_missing_values()
# 2. 特征工程
print("\n2. 特征工程...")
# 定义临床特征(根据实际数据调整)
clinical_features = [
'Age', 'Gender', 'Tumor_Size', 'Tumor_Location',
'Echogenicity', 'Margin', 'Internal_Echo', 'Calcification'
]
engineer = FeatureEngineer(
df=data_df,
target_col='HighGrade',
clinical_cols=clinical_features
)
# 划分数据集
X_train, X_test, y_train, y_test, feature_names = engineer.split_dataset()
# 特征选择
selected_features, coef_df = engineer.feature_selection_lasso(X_train, y_train)
# 计算Rad-score
rad_score_train = engineer.calculate_rad_score(X_train, selected_features, coef_df)
rad_score_test = engineer.calculate_rad_score(X_test, selected_features, coef_df)
# 3. 模型构建
print("\n3. 模型构建...")
model_builder = ModelBuilder()
# 构建不同模型
print("\n构建逻辑回归模型...")
logistic_model, logistic_metrics = model_builder.build_logistic_model(
X_train[selected_features], y_train,
X_test[selected_features], y_test,
model_name='Logistic_Regression'
)
print("\n构建随机森林模型...")
rf_model, rf_metrics = model_builder.build_random_forest(
X_train[selected_features], y_train,
X_test[selected_features], y_test,
model_name='Random_Forest'
)
print("\n构建XGBoost模型...")
xgb_model, xgb_metrics = model_builder.build_xgboost_model(
X_train[selected_features], y_train,
X_test[selected_features], y_test,
model_name='XGBoost'
)
# 4. 构建联合模型(临床+影像组学)
print("\n4. 构建联合模型...")
# 准备联合特征
X_train_combined = pd.DataFrame({
'Rad_Score': rad_score_train,
'Tumor_Size': X_train['Tumor_Size'],
'Age': X_train['Age']
})
X_test_combined = pd.DataFrame({
'Rad_Score': rad_score_test,
'Tumor_Size': X_test['Tumor_Size'],
'Age': X_test['Age']
})
combined_model, combined_metrics = model_builder.build_logistic_model(
X_train_combined, y_train,
X_test_combined, y_test,
model_name='Combined_Model'
)
# 5. 构建列线图
print("\n5. 构建列线图...")
# 准备列线图数据
nomogram_data = pd.DataFrame({
'HighGrade': y_test.values,
'Rad_Score': rad_score_test,
'Tumor_Size': X_test['Tumor_Size'].values,
'Age': X_test['Age'].values
})
nomogram_builder = NomogramBuilder()
# 使用R构建列线图(需要安装R和rms包)
try:
formula_str = "HighGrade ~ Rad_Score + Tumor_Size + Age"
r_model, r_nom = nomogram_builder.build_nomogram_r(
nomogram_data, formula_str, ['Rad_Score', 'Tumor_Size', 'Age']
)
except:
print("R环境不可用,使用Python简化版列线图")
score_table = nomogram_builder.build_nomogram_python(
combined_model, ['Rad_Score', 'Tumor_Size', 'Age']
)
# 6. 模型评估与可视化
print("\n6. 模型评估与可视化...")
evaluator = ModelEvaluator()
# 准备结果数据
results_for_plot = {
'Logistic': {
'y_true': y_test,
'y_pred_proba': model_builder.results['Logistic_Regression']['y_pred_proba']
},
'Random Forest': {
'y_true': y_test,
'y_pred_proba': model_builder.results['Random_Forest']['y_pred_proba']
},
'XGBoost': {
'y_true': y_test,
'y_pred_proba': model_builder.results['XGBoost']['y_pred_proba']
},
'Combined Model': {
'y_true': y_test,
'y_pred_proba': model_builder.results['Combined_Model']['y_pred_proba']
}
}
# 绘制ROC曲线
evaluator.plot_roc_curves(results_for_plot)
# 绘制校准曲线(以联合模型为例)
evaluator.plot_calibration_curve(
y_test,
model_builder.results['Combined_Model']['y_pred_proba']
)
# 绘制决策曲线
evaluator.plot_decision_curve(
y_test,
model_builder.results['Combined_Model']['y_pred_proba'],
'联合模型'
)
# 7. 结果汇总
print("\n7. 结果汇总...")
results_summary = pd.DataFrame({
'Model': ['Logistic Regression', 'Random Forest', 'XGBoost', 'Combined Model'],
'AUC': [
logistic_metrics['AUC'],
rf_metrics['AUC'],
xgb_metrics['AUC'],
combined_metrics['AUC']
],
'Accuracy': [
logistic_metrics['Accuracy'],
rf_metrics['Accuracy'],
xgb_metrics['Accuracy'],
combined_metrics['Accuracy']
],
'Sensitivity': [
logistic_metrics['Sensitivity'],
rf_metrics['Sensitivity'],
xgb_metrics['Sensitivity'],
combined_metrics['Sensitivity']
],
'Specificity': [
logistic_metrics['Specificity'],
rf_metrics['Specificity'],
xgb_metrics['Specificity'],
combined_metrics['Specificity']
]
})
print("\n模型性能比较:")
print(results_summary.to_string())
# 8. 保存模型和结果
print("\n8. 保存模型和结果...")
import joblib
import json
# 保存最佳模型
joblib.dump(combined_model, 'best_combined_model.pkl')
joblib.dump(logistic_model, 'radiomics_logistic_model.pkl')
# 保存特征重要性
coef_df.to_csv('feature_coefficients.csv', index=False)
# 保存性能指标
with open('model_metrics.json', 'w') as f:
json.dump({
'logistic': logistic_metrics,
'random_forest': rf_metrics,
'xgboost': xgb_metrics,
'combined': combined_metrics
}, f, indent=4)
print("\n" + "=" * 60)
print("模型构建完成!")
print("=" * 60)
return {
'data': data_df,
'models': model_builder.models,
'results': model_builder.results,
'summary': results_summary
}
# ==================== 8. 辅助功能 ====================
class PredictionPipeline:
"""预测管道类(用于新数据预测)"""
def __init__(self, model_path, feature_scaler_path=None):
"""初始化预测管道"""
self.model = joblib.load(model_path)
if feature_scaler_path:
self.scaler = joblib.load(feature_scaler_path)
else:
self.scaler = None
def predict_single(self, features_dict):
"""单样本预测"""
# 转换为DataFrame
features_df = pd.DataFrame([features_dict])
# 特征缩放
if self.scaler:
features_scaled = self.scaler.transform(features_df)
else:
features_scaled = features_df.values
# 预测
probability = self.model.predict_proba(features_scaled)[0, 1]
prediction = self.model.predict(features_scaled)[0]
return {
'probability_high_grade': float(probability),
'prediction': int(prediction),
'risk_category': '高风险' if probability > 0.5 else '低风险'
}
def predict_batch(self, features_df):
"""批量预测"""
if self.scaler:
features_scaled = self.scaler.transform(features_df)
else:
features_scaled = features_df.values
probabilities = self.model.predict_proba(features_scaled)[:, 1]
predictions = self.model.predict(features_scaled)
results = pd.DataFrame({
'PatientID': features_df.index,
'Probability_High_Grade': probabilities,
'Prediction': predictions,
'Risk_Category': ['高风险' if p > 0.5 else '低风险' for p in probabilities]
})
return results
# ==================== 9. 运行主程序 ====================
if __name__ == "__main__":
# 检查必要的库是否安装
required_libraries = ['numpy', 'pandas', 'scikit-learn', 'radiomics']
missing_libs = []
for lib in required_libraries:
try:
__import__(lib)
except ImportError:
missing_libs.append(lib)
if missing_libs:
print(f"缺少必要的库: {missing_libs}")
print("请使用以下命令安装: pip install {' '.join(missing_libs)}")
else:
# 运行主程序
results = main()
# 示例:使用预测管道
print("\n示例:使用训练好的模型进行预测")
# 创建模拟数据
sample_features = {
'Rad_Score': 1.5,
'Tumor_Size': 4.2, # cm
'Age': 65
}
# 初始化预测管道(需要先训练并保存模型)
try:
pipeline = PredictionPipeline('best_combined_model.pkl')
prediction = pipeline.predict_single(sample_features)
print(f"\n样本预测结果:")
for key, value in prediction.items():
print(f"{key}: {value}")
except:
print("预测管道示例需要先运行完整的训练流程")
这个完整的代码框架包含以下关键组件:
主要功能模块:
-
数据加载与预处理 (
DataLoader类)- 合并临床和影像组学数据
- 处理缺失值
- 特征标准化
-
特征工程 (
FeatureEngineer类)- LASSO特征选择
- 随机森林特征重要性评估
- Rad-score计算
-
模型构建 (
ModelBuilder类)- 逻辑回归
- 随机森林
- XGBoost
- 联合模型(临床+影像组学)
-
列线图构建 (
NomogramBuilder类)- 使用R语言的rms包构建
- Python简化版实现
-
模型评估 (
ModelEvaluator类)- ROC曲线分析
- 校准曲线
- 决策曲线分析
- 特征重要性可视化
-
预测管道 (
PredictionPipeline类)- 单样本预测
- 批量预测
使用步骤:
-
准备数据:
python# clinical_data.csv 应包含: # PatientID, Age, Gender, Tumor_Size, WHO_ISUP_Grade, 超声语义特征等 # radiomics_features.csv 应包含: # PatientID, original_shape_XXX, original_firstorder_XXX, original_glcm_XXX 等 -
运行主程序:
pythonpython ccRCC_nomogram_model.py -
调整参数:
- 在
main()函数中调整特征选择阈值 - 修改模型超参数
- 根据实际数据调整特征列名
- 在
输出结果:
-
模型文件:
best_combined_model.pkl- 最佳联合模型radiomics_logistic_model.pkl- 纯影像组学模型
-
结果文件:
feature_coefficients.csv- 特征系数model_metrics.json- 模型性能指标- 各种可视化图表
-
列线图:
- R生成的交互式列线图
- Python特征重要性图
注意事项:
-
数据要求:
- 样本量建议不少于200例
- 高级别和低级别样本需要平衡
- 超声图像需要一致的采集参数
-
环境配置:
bashpip install numpy pandas scikit-learn xgboost radiomics # 如需R列线图,需安装R和rms包 -
临床应用:
- 模型可用于术前风险分层
- 指导手术方式选择
- 辅助临床决策
这个框架提供了端到端解决方案,可以根据实际数据和研究需求进行调整和优化。