Plasma proteomic signatures of early retinal neurodegeneration in diabetes: A multi-cohort study.
A correction to this paper has been published: the notice, 42461828, from Europe PMC.
The 1 match
- [1] § Methods › Statistical analysis ↔ R code for Pro-DRN.R, lines 693–707 · score 0.52 · age sex, Aspelund, Dagliati, Hippisley, ISDR, Tarasewicz
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
R · 1,144 lines · 44 KB · no license · 1 match
- #———————————————————————————————————————————————————————————————————————————————————————————————————————————####
- # 线性回归(RNFL—蛋白)------------------------------------------------------------------------------------------------
- ------------------------------------------------------------------------------------------------------------
- rm(list = ls())
- gc()
- library(haven)
- library(readr)
- library(tidyr)
- library(dplyr)
- library(readxl)
- library(openxlsx)
- library(survival)
- library(fdrtool)
- library(progress)
- library(openxlsx)
- data_GDES <- read_dta("XXX.dta")
- data_pro <- read.xlsx("XXX.xlsx")
- # 存储要拟合的蛋白变量名
- proteins <- setdiff(names(data_pro), "id")
- proname <- read_tsv("XXX.tsv")
- proname <- proname %>%
- select(-coding) %>% # 删除 coding 列
- separate(meaning, into = c("abbreviation", "name"), sep = ";") %>% # 分离 meaning 列
- mutate(pro = tolower(abbreviation)) # 增加 pro 列,将 abbreviation 转为小写
- panel <- read_excel("XXX.xlsx")
- colnames(panel) <- panel[1,]
- panel <- panel[-1,-c(5,6)]
- colnames(panel) <- c("uniprot","name","pro","panel")
- panel <- panel %>%
- mutate(
- pro = tolower(pro), # 改小写
- pro = gsub("-", "_", pro) # "-"改"_"
- )
- panel <- panel[,c("pro","panel")]
- variables <- c("y0_prnfl_aver")
- # 年龄、性别、dm_duration、HbA1c、SBP、吸烟 ####
- # 创建一个列表,用于存储每个变量的回归结果
- lm_results_list <- list()
- pb <- progress_bar$new(
- format = " Progress [:bar] :percent Elapsed: :elapsed ETA: :eta",
- total = length(variables) * length(proteins),
- clear = FALSE,
- width = 60
- )
- # 循环进行亚场的线性回归
- for (var in variables) {
- # 创建一个空的数据框来存储回归结果
- lm_result <- data.frame(variable = character(),
- coef = numeric(),
- tvalue = numeric(),
- std = numeric(),
- pvalue = numeric(),
- stringsAsFactors = FALSE)
- # 循环拟合每个模型并存储结果
- for (var_pro in proteins) {
- formula <- paste0(var,"~", var_pro, "+ age + as.factor(sex) + dmdura_year + hba1c + as.factor(smoking) + sbp")
- lm_model <- lm(formula, data = data_GDES)
- coef <- as.numeric(summary(lm_model)$coefficients[, 1][2])
- std <- as.numeric(summary(lm_model)$coefficients[, 2][2])
- tvalue <- as.numeric(summary(lm_model)$coefficients[, 3][2])
- pvalue <- as.numeric(summary(lm_model)$coefficients[, 4][2])
- lm_result <- rbind(lm_result, c(var_pro, coef, tvalue, std, pvalue))
- # ✅ 更新进度条
- pb$tick()
- }
- colnames(lm_result) <- c("pro", "coef", "tvalue", "std", "pvalue")
- lm_result$coef <- as.numeric(lm_result$coef)
- lm_result$std <- as.numeric(lm_result$std)
- lm_result$tvalue <- as.numeric(lm_result$tvalue)
- lm_result$pvalue <- as.numeric(lm_result$pvalue)
- lm_result$low_limit <- as.numeric(lm_result$coef) - 1.96 * as.numeric(lm_result$std)
- lm_result$up_limit <- as.numeric(lm_result$coef) + 1.96 * as.numeric(lm_result$std)
- lm_result <- lm_result[c("pro", "coef", "low_limit", "up_limit", "std", "tvalue", "pvalue")]
- # 整理结果数据框
- lm_result$pvalue_sig <- ifelse(lm_result$pvalue <= 0.05, "significant", "-")
- # BH校正
- lm_result$BH <- p.adjust(lm_result$pvalue, "BH")
- lm_result$BH_sig <- ifelse(lm_result$BH <= 0.05, "significant", "-")
- fdr <- fdrtool(lm_result$pvalue, statistic="pvalue", plot=FALSE) # 关闭自动绘图
- lm_result$qval <- fdr$qval
- lm_result$lfdr <- fdr$lfdr
- # Bonferroni 校正与显著性标注
- lm_result$bonf <- p.adjust(lm_result$pvalue, method = "bonferroni")
- lm_result$bonf_sig <- ifelse(lm_result$bonf <= 0.05, "significant", "-")
- lm_result$BY <- p.adjust(lm_result$pvalue, method = "BY")
- lm_result$BY_sig <- ifelse(lm_result$BY <= 0.05, "significant", "-")
- lm_result$holm <- p.adjust(lm_result$pvalue, method = "holm")
- lm_result$holm_sig <- ifelse(lm_result$holm <= 0.05, "significant", "-")
- lm_result$hochberg <- p.adjust(lm_result$pvalue, method = "hochberg")
- lm_result$hochberg_sig <- ifelse(lm_result$hochberg <= 0.05, "significant", "-")
- # 注释蛋白
- lm_result <- left_join(lm_result, proname, by = "pro")
- lm_result <- left_join(lm_result, panel, by = "pro")
- #将每一个lm_result保存到相应的lm_results_list中
- lm_results_list[[var]] <- lm_result
- }
- # 输出结果
- lm_results_list
- # 写入到Excel
- library(openxlsx)
- wb <- createWorkbook()
- for (var in variables) {
- # 创建工作表
- addWorksheet(wb, var)
- # 写入数据到工作表
- writeData(wb, var, lm_results_list[[var]])
- }
- # Save the Excel file
- saveWorkbook(wb, "XXX.xlsx", overwrite = TRUE)
- #———————————————————————————————————————————————————————————————————————————————————————————————————————————####
- # 线性回归(蛋白-prnfl下降速率)------------------------------------------------------------------------------------------------
- ------------------------------------------------------------------------------------------------------------
- rm(list = ls())
- gc()
- library(haven)
- library(readr)
- library(tidyr)
- library(dplyr)
- library(readxl)
- library(openxlsx)
- library(survival)
- library(fdrtool)
- library(progress)
- library(openxlsx)
- data_GDES <- read_dta("XXX.dta")
- # 存储要拟合的蛋白变量名
- proname <- read_tsv("XXX.tsv")
- proname <- proname %>%
- select(-coding) %>% # 删除 coding 列
- separate(meaning, into = c("abbreviation", "name"), sep = ";") %>% # 分离 meaning 列
- mutate(pro = tolower(abbreviation)) # 增加 pro 列,将 abbreviation 转为小写
- panel <- read_excel("E:/UKB数据/olink数据/3072蛋白列表.xlsx")
- colnames(panel) <- panel[1,]
- panel <- panel[-1,-c(5,6)]
- colnames(panel) <- c("uniprot","name","pro","panel")
- panel <- panel %>%
- mutate(
- pro = tolower(pro), # 改小写
- pro = gsub("-", "_", pro) # "-"改"_"
- )
- panel <- panel[,c("pro","panel")]
- data_pro <- read_excel("XXX.xlsx")
- proteins <- data_pro$pro
- variables <- c("decline_rate")
- # 年龄、性别、dm_duration、HbA1c、SBP、吸烟 ####
- # 创建一个列表,用于存储每个变量的回归结果
- lm_results_list <- list()
- pb <- progress_bar$new(
- format = " Progress [:bar] :percent Elapsed: :elapsed ETA: :eta",
- total = length(variables) * length(proteins),
- clear = FALSE,
- width = 60
- )
- # 循环进行亚场的线性回归
- for (var in variables) {
- # 创建一个空的数据框来存储回归结果
- lm_result <- data.frame(variable = character(),
- coef = numeric(),
- tvalue = numeric(),
- std = numeric(),
- pvalue = numeric(),
- stringsAsFactors = FALSE)
- # 循环拟合每个模型并存储结果
- for (var_pro in proteins) {
- formula <- paste0(var,"~", var_pro, "+ age + as.factor(sex) + dmdura_year + hba1c + as.factor(smoking) + sbp")
- lm_model <- lm(formula, data = data_GDES)
- coef <- as.numeric(summary(lm_model)$coefficients[, 1][2])
- std <- as.numeric(summary(lm_model)$coefficients[, 2][2])
- tvalue <- as.numeric(summary(lm_model)$coefficients[, 3][2])
- pvalue <- as.numeric(summary(lm_model)$coefficients[, 4][2])
- lm_result <- rbind(lm_result, c(var_pro, coef, tvalue, std, pvalue))
- # ✅ 更新进度条
- pb$tick()
- }
- colnames(lm_result) <- c("pro", "coef", "tvalue", "std", "pvalue")
- lm_result$coef <- as.numeric(lm_result$coef)
- lm_result$std <- as.numeric(lm_result$std)
- lm_result$tvalue <- as.numeric(lm_result$tvalue)
- lm_result$pvalue <- as.numeric(lm_result$pvalue)
- lm_result$low_limit <- as.numeric(lm_result$coef) - 1.96 * as.numeric(lm_result$std)
- lm_result$up_limit <- as.numeric(lm_result$coef) + 1.96 * as.numeric(lm_result$std)
- lm_result <- lm_result[c("pro", "coef", "low_limit", "up_limit", "std", "tvalue", "pvalue")]
- # 整理结果数据框
- lm_result$pvalue_sig <- ifelse(lm_result$pvalue <= 0.05, "significant", "-")
- # BH校正
- lm_result$BH <- p.adjust(lm_result$pvalue, "BH")
- lm_result$BH_sig <- ifelse(lm_result$BH <= 0.05, "significant", "-")
- fdr <- fdrtool(lm_result$pvalue, statistic="pvalue", plot=FALSE) # 关闭自动绘图
- lm_result$qval <- fdr$qval
- lm_result$lfdr <- fdr$lfdr
- # 注释蛋白
- lm_result <- left_join(lm_result, proname, by = "pro")
- lm_result <- left_join(lm_result, panel, by = "pro")
- #将每一个lm_result保存到相应的lm_results_list中
- lm_results_list[[var]] <- lm_result
- }
- # 输出结果
- lm_results_list
- # 写入到Excel
- library(openxlsx)
- wb <- createWorkbook()
- for (var in variables) {
- # 创建工作表
- addWorksheet(wb, var)
- # 写入数据到工作表
- writeData(wb, var, lm_results_list[[var]])
- }
- # Save the Excel file
- saveWorkbook(wb, "XXXX.xlsx", overwrite = TRUE)
- # 通路分析 #####
- rm(list = ls())
- gc()
- ####加载包
- library(zlibbioc)
- library(GO.db)
- library(org.Hs.eg.db)
- library(clusterProfiler)
- library(tidyverse)
- library(ggplot2)
- library(forcats)
- library(readxl)
- library(writexl)
- ####加载数据
- data <- read_excel("XXXX.xlsx")
- ##基因ID转换(genesymbol → ENTREZID)
- IDs <- bitr(data$abbreviation,
- fromType = 'SYMBOL',
- toType = c('ENTREZID'),
- OrgDb = 'org.Hs.eg.db') #不同的物种对应不同的数据库,详见https://bioconductor.org/packages/release/BiocViews.html#___OrgDb
- ##基于ENTREZID进行GO富集分析
- enrichGO_result <- enrichGO(IDs$ENTREZID,
- OrgDb="org.Hs.eg.db",
- qvalueCutoff=0.05,
- pvalueCutoff=0.05,
- ont="all")
- enrichGO_result <- setReadable(enrichGO_result, OrgDb="org.Hs.eg.db", keyType = 'ENTREZID')
- enrichGO_result_data <- data.frame(enrichGO_result)
- writexl::write_xlsx(enrichGO_result_data,
- path = "XXXX.xlsx")
- ##基于ENTREZID进行KEGG富集分析
- enrichKEGG_result <- enrichKEGG(gene=IDs$ENTREZID,
- organism ="hsa",#物种
- keyType ="kegg",
- pvalueCutoff = 0.05,
- qvalueCutoff = 1
- )
- enrichKEGG_result <- setReadable(enrichKEGG_result,
- OrgDb="org.Hs.eg.db",
- keyType ="ENTREZID"
- )
- enrichKEGG_result_data <- data.frame(enrichKEGG_result)
- writexl::write_xlsx(enrichKEGG_result_data,
- path = "XXXX.xlsx")
- #———————————————————————————————————————————————————————————————————————————————————————————————————————————####
- # 4. 预测分析 #####
- rm(list = ls())
- gc()
- library(haven)
- library(readr)
- library(tidyr)
- library(dplyr)
- library(readxl)
- library(openxlsx)
- library(survival)
- library(fdrtool)
- library(progress)
- library(openxlsx)
- #整理数据
- data_GDES <- read_dta("XXXX.dta")
- data_train <- read_dta("XXXX.dta")
- data_test <- read_dta("XXXX.dta")
- # 计算 Q1 阈值
- q1_cut <- quantile(data_GDES$decline_rate, 0.25, na.rm = TRUE)
- # 根据 Q1 定义 drn
- data_GDES <- data_GDES %>%
- mutate(
- drn = ifelse(decline_rate <= q1_cut, 1, 0)
- )
- # 蛋白组模型(xgboost) ####
- outcome_list <- c("drn")
- results_pro_all <- data.frame()
- original_seed_results <- list()
- seed_list <- createWorkbook()
- # 用全量人群 id 作为对齐基准
- stopifnot("id" %in% names(data_GDES))
- data_score <- data.frame(id = data_GDES$id)
- set.seed(2025)
- params <- list(
- booster = "gbtree",
- objective = "binary:logistic",
- eval_metric = "auc",
- eta = 0.05,
- max_depth = 7,
- min_child_weight = 1,
- gamma = 2,
- subsample = 0.9,
- colsample_bytree = 1,
- colsample_bylevel = 0.5,
- lambda = 1,
- alpha = 0.5,
- max_delta_step = 0,
- tree_method = "exact",
- scale_pos_weight = 1,
- seed = 2025
- )
- for (outcome in outcome_list) {
- # --- 清理NA(保持最小必要清理) ---
- stopifnot(all(c("id", outcome) %in% colnames(data_train)))
- stopifnot(all(c("id", outcome) %in% colnames(data_test)))
- data_train <- data_train[!is.na(data_train[[outcome]]), ]
- data_test <- data_test[ !is.na(data_test[[outcome]]), ]
- # --- 特征对齐:仅保留在 train/test 都存在的蛋白列 ---
- proteins <- intersect(proteins, intersect(colnames(data_train), colnames(data_test)))
- stopifnot(length(proteins) > 0)
- # --- 标签与特征矩阵 ---
- train_y <- as.numeric(data_train[[outcome]])
- test_y <- as.numeric(data_test[[outcome]])
- train_x <- as.matrix(data_train[, proteins, drop = FALSE])
- test_x <- as.matrix(data_test[, proteins, drop = FALSE])
- dtrain <- xgb.DMatrix(data = train_x, label = train_y)
- dtest <- xgb.DMatrix(data = test_x, label = test_y)
- # --- 用训练集做交叉验证,寻找最佳轮次 ---
- val.cv <- xgb.cv(
- params = params,
- data = dtrain,
- nfold = 10,
- nrounds = 1000,
- early_stopping_rounds = 100,
- metrics = "auc",
- maximize = TRUE,
- verbose = 0
- )
- best_ntree <- val.cv$best_iteration
- # --- 用最佳轮次在训练集上拟合最终模型 ---
- model <- xgboost(
- data = dtrain,
- params = params,
- nrounds = best_ntree,
- verbose = 0
- )
- # --- 训练/测试集预测与ROC ---
- pred_train <- predict(model, dtrain)
- pred_test <- predict(model, dtest)
- roc_pro_all_train <- pROC::roc(as.factor(train_y) ~ pred_train, ci = TRUE, quiet = TRUE)
- roc_pro_all <- pROC::roc(as.factor(test_y) ~ pred_test, ci = TRUE, quiet = TRUE)
- # --- cutoff & 混淆矩阵(基于测试集) ---
- cutoff <- cutoff::roc(pred_test, as.factor(test_y))$cutoff
- prediction <- ifelse(pred_test > cutoff, 1, 0)
- cm <- caret::confusionMatrix(
- data = factor(prediction, levels = c(0,1)),
- reference = factor(test_y, levels = c(0,1)),
- positive = "1", mode = "everything"
- )
- TN <- cm$table[1]; FP <- cm$table[2]; FN <- cm$table[3]; TP <- cm$table[4]
- Accuracy <- cm$overall[["Accuracy"]]
- Sensitivity <- cm$byClass[["Sensitivity"]]
- Specificity <- cm$byClass[["Specificity"]]
- PPV <- cm$byClass[["Precision"]]
- NPV <- cm$byClass[["Neg Pred Value"]]
- Recall <- cm$byClass[["Recall"]]
- F1 <- cm$byClass[["F1"]]
- DR <- TP/(FN+TP)
- FPR <- FP/(TN+FP)
- LR <- ifelse(FPR == 0, Inf, DR/FPR)
- # --- 结果表 ---
- test_results <- data.frame(
- outcome = outcome,
- seed_pro_all = NA_real_, # 占位
- train_roc_pro_all = as.numeric(roc_pro_all_train$auc),
- train_low_pro_all = roc_pro_all_train$ci[1],
- train_up_pro_all = roc_pro_all_train$ci[3],
- test_roc_pro_all = as.numeric(roc_pro_all$auc),
- test_low_pro_all = roc_pro_all$ci[1],
- test_up_pro_all = roc_pro_all$ci[3],
- cutoff = cutoff,
- Accuracy = Accuracy, Sensitivity = Sensitivity, Specificity = Specificity,
- PPV = PPV, NPV = NPV, Recall = Recall, F1 = F1,
- DR = DR, FPR = FPR, LR = LR,
- stringsAsFactors = FALSE
- )
- original_seed_results[[outcome]] <- test_results
- addWorksheet(seed_list, sheetName = outcome)
- writeData(seed_list, outcome, original_seed_results[[outcome]])
- # --- 保存逐个体 score 到全量框架(按 id 对齐;score 列名改为 score_drn) ---
- score_col <- "score_drn" # ← 你要求的命名
- dset_col <- "dataset_drn" # 数据集标记列,便于区分
- data_score_temp <- data_GDES %>%
- dplyr::select(id) %>%
- dplyr::left_join(data.frame(id = data_test$id, pred = pred_test), by = "id") %>%
- dplyr::left_join(data.frame(id = data_train$id, pred_train = pred_train), by = "id") %>%
- dplyr::mutate(dataset = dplyr::case_when(!is.na(pred) ~ "test",
- !is.na(pred_train) ~ "train",
- TRUE ~ NA_character_),
- pred_final = ifelse(!is.na(pred), pred, pred_train)) %>%
- dplyr::transmute(id, !!score_col := pred_final, !!dset_col := dataset)
- data_score <- dplyr::left_join(data_score, data_score_temp, by = "id")
- }
- saveWorkbook(seed_list, "XXXX.xlsx", overwrite = TRUE)
- write.csv(data_score, "XXXXX.csv", row.names = FALSE)
- # 保存环境
- save.image("XXXXX.RData")
- # ——4.2.2 变量重要性 ------------------------------------------------------------------------------------------------
- rm(list = ls())
- load("XXXXX.RData")
- library(openxlsx)
- library(DT)
- variable_importance <- createWorkbook()
- # ———4.2.2.1 xgboost包自带VIP ####
- importance <- xgb.importance(feature_names = colnames(train), model = xgb_model)
- #1 Gain:使用某个特征进行拆分时,获得的平均训练损失减少量
- #2 Cover:首先得到某个特征被用于在所有树中拆分数据的次数,然后要利用经过这些拆分点的训练数据数量赋予权重
- #3 frequency:某个特征被用于在所有树中拆分数据的次数
- addWorksheet(variable_importance, sheetName = "importance")
- writeData(variable_importance, "importance", as.data.frame(importance))
- # 图示
- xgb.plot.importance(importance, measure = "Gain")
- pdf(file="XXXXX.pdf",onefile = FALSE,
- width = 8,
- height =18)
- xgb.plot.importance(importance, measure = "Gain")
- dev.off()
- # ggplot可视化排名前10变量
- library(ggplot2)
- xgb.ggplot.importance(importance_matrix = importance, top_n = 10)
- # ———4.2.2.2 SHAP ####
- library(SHAPforxgboost)
- library(dplyr)
- # SHAP data (individual-unit)
- shap_data <- shap.prep(xgb_model, X_train = train_x)
- addWorksheet(variable_importance, sheetName = "SHAP data")
- writeData(variable_importance, "SHAP data", as.data.frame(shap_data))
- # SHAP summary
- shap_summary <- shap_data %>%
- group_by(variable) %>%
- summarise(AverageMeanValue = mean(mean_value, na.rm = TRUE))
- addWorksheet(variable_importance, sheetName = "SHAP summary")
- writeData(variable_importance, "SHAP summary", as.data.frame(shap_summary))
- # *4.4.3 总人群预测 —— 汇总预测分数 ####
- data_GDES <- read_dta("XXXXX.dta")
- data_score_temp <- read_csv("XXXXX.csv")
- data_GDES <- data_GDES %>%
- left_join(
- data_score_temp %>%
- select(id, score_drn_XGB, dataset_drn),
- by = "id"
- )
- data_train <- data_GDES %>% filter(dataset_drn == "train")
- data_test <- data_GDES %>% filter(dataset_drn == "test")
- write_dta(data_train, "XXXXX.dta")
- write_dta(data_test, "XXXXX.dta")
- write_dta(data_GDES, "XXXXX.dta")
- # 单独predictor比较 ####
- rm(list = ls())
- gc()
- library(haven)
- library(readr)
- library(tidyr)
- library(dplyr)
- library(readxl)
- library(openxlsx)
- library(survival)
- library(fdrtool)
- library(progress)
- library(pROC)
- # 整理数据
- data_train <- read_dta("XXXXX.dta")
- data_test <- read_dta("XXXXX.dta")
- data_GDES <- read_dta("XXXXX.dta")
- outcomes <- c("drn")
- auc_results <- data.frame(stringsAsFactors = FALSE)
- for (outcome in outcomes) {
- # -------- 基准:蛋白(XGB 分数) --------
- model_pro <- glm(as.formula(paste(outcome, "~ score_drn_XGB")),
- data = data_test, family = binomial)
- data_test[[paste0(outcome, "_riskscore_pro")]] <-
- predict(model_pro, newdata = data_test, type = "response")
- roc_result_pro <- pROC::roc(
- response = data_test[[outcome]],
- predictor = data_test[[paste0(outcome, "_riskscore_pro")]],
- ci = TRUE, ci.method = "delong", na.rm = TRUE, quiet = TRUE
- )
- auc_pro <- as.numeric(roc_result_pro$auc)
- ci_lower_pro <- roc_result_pro$ci[1]
- ci_upper_pro <- roc_result_pro$ci[3]
- fit_and_roc <- function(rhs) {
- m <- glm(as.formula(paste(outcome, "~", rhs)), data = data_test, family = binomial)
- sc <- predict(m, newdata = data_test, type = "response")
- r <- pROC::roc(response = data_test[[outcome]], predictor = sc,
- ci = TRUE, ci.method = "delong", na.rm = TRUE, quiet = TRUE)
- list(model = m, roc = r, auc = as.numeric(r$auc), ci = r$ci)
- }
- res_age <- fit_and_roc("age")
- res_sex <- fit_and_roc("as.factor(sex)")
- res_income <- fit_and_roc("as.factor(income)")
- res_smoking <- fit_and_roc("as.factor(smoking)")
- res_drinking <- fit_and_roc("as.factor(drinking)")
- res_education <- fit_and_roc("as.factor(education)")
- res_bmi <- fit_and_roc("as.factor(bmi_c)")
- res_statin <- fit_and_roc("as.factor(med_statin)")
- res_hbp <- fit_and_roc("as.factor(med_hbp)")
- P_age <- pROC::roc.test(roc_result_pro, res_age$roc, method = "delong")$p.value
- P_sex <- pROC::roc.test(roc_result_pro, res_sex$roc, method = "delong")$p.value
- P_income <- pROC::roc.test(roc_result_pro, res_income$roc, method = "delong")$p.value
- P_smoking <- pROC::roc.test(roc_result_pro, res_smoking$roc, method = "delong")$p.value
- P_drinking <- pROC::roc.test(roc_result_pro, res_drinking$roc, method = "delong")$p.value
- P_education <- pROC::roc.test(roc_result_pro, res_education$roc, method = "delong")$p.value
- P_bmi <- pROC::roc.test(roc_result_pro, res_bmi$roc, method = "delong")$p.value
- P_statin <- pROC::roc.test(roc_result_pro, res_statin$roc, method = "delong")$p.value
- P_hbp <- pROC::roc.test(roc_result_pro, res_hbp$roc, method = "delong")$p.value
- lab <- function(pv, auc_other) ifelse(pv >= 0.05, "Comparable",
- ifelse(auc_pro > auc_other, "Superior", "-"))
- auc_age <- res_age$auc; ci_lower_age <- res_age$ci[1]; ci_upper_age <- res_age$ci[3]
- auc_sex <- res_sex$auc; ci_lower_sex <- res_sex$ci[1]; ci_upper_sex <- res_sex$ci[3]
- auc_income <- res_income$auc; ci_lower_income <- res_income$ci[1]; ci_upper_income <- res_income$ci[3]
- auc_smoking <- res_smoking$auc; ci_lower_smoking <- res_smoking$ci[1]; ci_upper_smoking <- res_smoking$ci[3]
- auc_drinking <- res_drinking$auc; ci_lower_drinking <- res_drinking$ci[1]; ci_upper_drinking <- res_drinking$ci[3]
- auc_education <- res_education$auc; ci_lower_education <- res_education$ci[1]; ci_upper_education <- res_education$ci[3]
- auc_bmi <- res_bmi$auc; ci_lower_bmi <- res_bmi$ci[1]; ci_upper_bmi <- res_bmi$ci[3]
- auc_med_statin <- res_statin$auc; ci_lower_med_statin <- res_statin$ci[1]; ci_upper_med_statin <- res_statin$ci[3]
- auc_med_hbp <- res_hbp$auc; ci_lower_med_hbp <- res_hbp$ci[1]; ci_upper_med_hbp <- res_hbp$ci[3]
- compare_age <- lab(P_age, auc_age)
- compare_sex <- lab(P_sex, auc_sex)
- compare_income <- lab(P_income, auc_income)
- compare_smoking <- lab(P_smoking, auc_smoking)
- compare_drinking <- lab(P_drinking, auc_drinking)
- compare_education <- lab(P_education, auc_education)
- compare_bmi <- lab(P_bmi, auc_bmi)
- compare_med_statin<- lab(P_statin, auc_med_statin)
- compare_med_hbp <- lab(P_hbp, auc_med_hbp)
- auc_results <- rbind(auc_results, c(
- outcome,
- auc_pro, ci_lower_pro, ci_upper_pro,
- auc_age, ci_lower_age, ci_upper_age, P_age, compare_age,
- auc_sex, ci_lower_sex, ci_upper_sex, P_sex, compare_sex,
- auc_income, ci_lower_income, ci_upper_income, P_income, compare_income,
- auc_smoking, ci_lower_smoking, ci_upper_smoking, P_smoking, compare_smoking,
- auc_drinking, ci_lower_drinking, ci_upper_drinking, P_drinking, compare_drinking,
- auc_education, ci_lower_education, ci_upper_education, P_education, compare_education,
- auc_bmi, ci_lower_bmi, ci_upper_bmi, P_bmi, compare_bmi,
- auc_med_statin, ci_lower_med_statin, ci_upper_med_statin, P_statin, compare_med_statin,
- auc_med_hbp, ci_lower_med_hbp, ci_upper_med_hbp, P_hbp, compare_med_hbp
- ))
- colnames(auc_results) <- c(
- "outcome", "PRO", "pro_lower", "pro_upper",
- "AGE", "age_lower", "age_upper", "Pvalue_AGE", "PRO_vs._AGE",
- "SEX", "sex_lower", "sex_upper", "Pvalue_SEX", "PRO_vs._SEX",
- "INCOME", "income_lower", "income_upper", "Pvalue_INCOME", "PRO_vs._INCOME",
- "SMOKING", "smoking_lower", "smoking_upper", "Pvalue_SMOKING", "PRO_vs._SMOKING",
- "DRINKING", "drinking_lower", "drinking_upper", "Pvalue_DRINKING", "PRO_vs._DRINKING",
- "EDUCATION", "education_lower", "education_upper", "Pvalue_EDUCATION", "PRO_vs._EDUCATION",
- "BMI", "bmi_lower", "bmi_upper", "Pvalue_BMI", "PRO_vs._BMI",
- "ANTI_LIPID", "anti_lipid_lower", "anti_lipid_upper", "Pvalue_ANTI_LIPID", "PRO_vs._ANTI_LIPID",
- "ANTI_BP", "anti_bp_lower", "anti_bp_upper", "Pvalue_ANTI_BP", "PRO_vs._ANTI_BP"
- )
- }
- # 转数值
- cmp_cols <- grep("^PRO_vs\\._", colnames(auc_results), value = TRUE) # 比较标签列
- num_cols <- setdiff(colnames(auc_results), c("outcome", cmp_cols)) # 其余才转数值
- auc_results[cmp_cols] <- lapply(auc_results[cmp_cols], as.character) # 显式设为字符
- auc_results[num_cols] <- lapply(auc_results[num_cols], function(x) suppressWarnings(as.numeric(x)))
- write.xlsx(auc_results, "XXXXX.xlsx")
- # 模型和结局函数 ####
- create_models <- function() {
- models <- list("Age_Sex" = "age + as.factor(sex)",
- "Aspelund_model" = "age + as.factor(sex) + as.factor(sbp_c) + dmdura_year + hba1c",
- "Hippisley_model" = "age + as.factor(sex) + as.factor(bmi_c) + as.factor(sbp_c) + TC_divide_HDL + hba1c",
- "Dagliati_model" = "age + as.factor(sex) + dmdura_year + as.factor(bmi_c) + hba1c + as.factor(prev_hbp) + as.factor(smoking)",
- "ISDR_model" = "age + as.factor(sex) + as.factor(sbp_c) + dmdura_year + hba1c + chol",
- "JJR_model" = "age + as.factor(sbp_c) + dmdura_year + hba1c + Egfr",
- "Tarasewicz_model" = "age + as.factor(sbp_c) + as.factor(insulin) + hba1c + as.factor(bmi_c) + ldlc + crea + mau",
- "All_model" = "age + as.factor(sex) + as.factor(sbp_c) + dmdura_year + hba1c + as.factor(bmi_c) + TC_divide_HDL + chol + Egfr + as.factor(insulin) + ldlc + crea + mau + as.factor(prev_hbp) + as.factor(smoking)"
- )
- return(list(models = models))
- }
- models <- create_models()$models
- # 人群预测 ####
- library(pROC)
- library(openxlsx)
- library(dplyr)
- score_vars <- c(
- "score_drn_XGB","score_drn_CART","score_drn_KNN","score_drn_LGBM",
- "score_drn_LogReg","score_drn_NN","score_drn_RF","score_drn_SVM"
- )
- outcomes <- c("drn")
- auc_results_list <- list()
- wb <- createWorkbook()
- for (model_name in names(models)) {
- auc_results <- data.frame()
- # —— 把 outcome 循环改成 score 循环
- for (score_var in score_vars) {
- outcome <- outcomes[1]
- covariate <- models[[model_name]]
- # 传统模型
- formula1 <- as.formula(paste(outcome, "~", covariate, sep = ""))
- model1 <- glm(formula1, data = data_test, family = binomial)
- data_test[[paste(outcome, "_", model_name, "_1", sep = "")]] <- predict(model1, newdata = data_test)
- roc_result1 <- roc(
- as.formula(paste(outcome, "~", paste(outcome, "_", model_name, "_1", sep = ""))),
- data = data_test, print.thres = TRUE, print.auc = TRUE, ci = TRUE, plot = FALSE, smooth = FALSE
- )
- auc_value1 <- roc_result1$auc
- ci_lower1 <- roc_result1$ci[1]
- ci_upper1 <- roc_result1$ci[3]
- # 加入蛋白(这里用当前 score_var)
- formula2 <- as.formula(paste(outcome, "~", covariate, "+", score_var))
- model2 <- glm(formula2, data = data_test, family = binomial)
- data_test[[paste(outcome, "_", model_name, "_2", sep = "")]] <- predict(model2, newdata = data_test)
- roc_result2 <- roc(
- as.formula(paste(outcome, "~", paste(outcome, "_", model_name, "_2", sep = ""))),
- data = data_test, print.thres = TRUE, print.auc = TRUE, ci = TRUE, plot = FALSE, smooth = FALSE
- )
- auc_value2 <- roc_result2$auc
- ci_lower2 <- roc_result2$ci[1]
- ci_upper2 <- roc_result2$ci[3]
- test <- roc.test(roc_result1, roc_result2) # DeLong's test
- p <- test[["p.value"]]
- auc_diff <- auc_value2 - auc_value1
- auc_improve_proportion <- auc_diff / auc_value1
- sig <- ifelse(p >= 0.05, "",
- ifelse(p < 0.001, "***",
- ifelse(p >= 0.001 & p < 0.01, "**", "*")))
- # 单独蛋白(当前 score_var)
- roc_result3 <- roc(
- as.formula(paste(outcome, "~", score_var)),
- data = data_test, print.thres = TRUE, print.auc = TRUE, ci = TRUE, plot = FALSE, smooth = FALSE
- )
- auc_value3 <- roc_result3$auc
- ci_lower3 <- roc_result3$ci[1]
- ci_upper3 <- roc_result3$ci[3]
- test_met <- roc.test(roc_result1, roc_result3) # DeLong's test
- p_met <- test_met[["p.value"]]
- compare_met <- ifelse(p_met >= 0.05, "Comparable", ifelse(auc_value3 > auc_value1, "Superior", "-"))
- # 结果入表
- auc_results <- rbind(
- auc_results,
- c(score_var, outcome,
- auc_value1, ci_lower1, ci_upper1,
- auc_value3, ci_lower3, ci_upper3, p_met, compare_met,
- auc_value2, ci_lower2, ci_upper2, auc_diff, auc_improve_proportion, p, sig)
- )
- colnames(auc_results) <- c("score", "outcome",
- "CONV", "conv_lower", "conv_upper",
- "PRO", "pro_lower", "pro_upper", "Pvalue_PRO_vs_CONV", "PRO_vs._CONV",
- "COMBINED", "combined_lower", "combined_upper", "Diff", "Diff_proportion",
- "Pvalue_COMBINED_vs_CONV", "Significance")
- }
- # 显著标记放在最后一列(保留 score 列)
- auc_results <- auc_results %>%
- select("score", which(names(auc_results) == "outcome"):which(names(auc_results) == "Pvalue_COMBINED_vs_CONV"), "Significance")
- # as.numeric
- auc_results$CONV <- as.numeric(auc_results$CONV)
- auc_results$conv_lower <- as.numeric(auc_results$conv_lower)
- auc_results$conv_upper <- as.numeric(auc_results$conv_upper)
- auc_results$PRO <- as.numeric(auc_results$PRO)
- auc_results$pro_lower <- as.numeric(auc_results$pro_lower)
- auc_results$pro_upper <- as.numeric(auc_results$pro_upper)
- auc_results$Pvalue_PRO_vs_CONV <- as.numeric(auc_results$Pvalue_PRO_vs_CONV)
- auc_results$COMBINED <- as.numeric(auc_results$COMBINED)
- auc_results$combined_lower <- as.numeric(auc_results$combined_lower)
- auc_results$combined_upper <- as.numeric(auc_results$combined_upper)
- auc_results$Diff <- as.numeric(auc_results$Diff)
- auc_results$Diff_proportion <- as.numeric(auc_results$Diff_proportion)
- auc_results$Pvalue_COMBINED_vs_CONV <- as.numeric(auc_results$Pvalue_COMBINED_vs_CONV)
- # 汇总与写表
- auc_results_list[[model_name]] <- auc_results
- addWorksheet(wb, sheetName = model_name)
- writeData(wb, model_name, auc_results_list[[model_name]])
- }
- auc_results_list
- # ===== 按 score 汇总成“图2样子”的表(每个 score 一个 sheet)=====
- # 先把所有模型的结果合并,并带上 model 列
- all_res <- dplyr::bind_rows(lapply(names(auc_results_list), function(m) {
- df <- auc_results_list[[m]]
- df$model <- m
- df
- }))
- # 列顺序
- col_order <- c("model","score","outcome",
- "CONV","conv_lower","conv_upper",
- "PRO","pro_lower","pro_upper","Pvalue_PRO_vs_CONV","PRO_vs._CONV",
- "COMBINED","combined_lower","combined_upper",
- "Diff","Diff_proportion","Pvalue_COMBINED_vs_CONV","Significance")
- # 逐个 score 输出一个 sheet
- for (sv in score_vars) {
- tag <- sub("^score_drn_", "", sv) # e.g. "XGB"
- sheet_name <- substr(paste0("Summary_", tag), 1, 31) # Excel 名称≤31字符
- df_out <- all_res %>%
- dplyr::filter(score == sv) %>%
- # 模型顺序排
- dplyr::mutate(model = factor(model, levels = names(models))) %>%
- dplyr::arrange(model)
- # 只保留需要的列
- keep_cols <- intersect(col_order, names(df_out))
- df_out <- df_out[, keep_cols, drop = FALSE]
- # 写入新工作表
- if (sheet_name %in% openxlsx::sheets(wb)) {
- openxlsx::removeWorksheet(wb, sheet_name)
- }
- openxlsx::addWorksheet(wb, sheetName = sheet_name)
- openxlsx::writeData(wb, sheet_name, df_out)
- }
- # 保存 Excel
- saveWorkbook(wb, "XXXXX.xlsx", overwrite = TRUE)
- # 保存预测label
- data_label <- data_test[, 2845:ncol(data_test)]
- data_id <- data_test[, 1]
- data_predict_label <- cbind(data_id, data_label)
- write.xlsx(data_predict_label, "XXXXX.xlsx")
- # =========================== RF训练 ===========================
- rm(list = ls()); gc()
- library(haven)
- library(dplyr)
- library(caret)
- library(ranger)
- library(pROC)
- library(cutoff)
- library(openxlsx)
- library(readxl)
- library(readr)
- library(tidyr)
- data_train <- read_dta("XXXXX.dta")
- data_test <- read_dta("XXXXX.dta")
- data_GDES <- read_dta("XXXXX.dta")
- data_pro2 <- read_excel("XXXXX.xlsx")
- proteins <- data_pro2$pro
- # 传统模型集合
- create_models <- function() {
- models <- list(
- "Age_Sex" = "age + as.factor(sex)",
- "Aspelund_model"= "age + as.factor(sex) + as.factor(sbp_c) + dmdura_year + hba1c",
- "Hippisley_model"= "age + as.factor(sex) + as.factor(bmi_c) + as.factor(sbp_c) + TC_divide_HDL + hba1c",
- "Dagliati_model"= "age + as.factor(sex) + dmdura_year + as.factor(bmi_c) + hba1c + as.factor(prev_hbp) + as.factor(smoking)",
- "ISDR_model" = "age + as.factor(sex) + as.factor(sbp_c) + dmdura_year + hba1c + chol",
- "JJR_model" = "age + sbp + dmdura_year + hba1c + Egfr",
- "Tarasewicz_model" = "age + as.factor(sbp_c) + as.factor(insulin) + hba1c + as.factor(bmi_c) + ldlc + crea + mau",
- "All_model" = "age + as.factor(sex) + as.factor(sbp_c) + dmdura_year + hba1c + as.factor(bmi_c) + TC_divide_HDL + chol + Egfr + as.factor(insulin) + ldlc + crea + mau + as.factor(prev_hbp) + as.factor(smoking)"
- )
- models
- }
- models <- create_models()
- outcome <- "drn"
- data_score <- data.frame(id = data_GDES$id)
- seed_level_perf <- data.frame()
- wb_seed_summary <- createWorkbook()
- wb_model_compare <- createWorkbook()
- # 仅删除结局缺失(ranger 不接受 y 中有 NA);不做其它预处理/填补
- stopifnot(all(c("id","drn") %in% colnames(data_train)))
- stopifnot(all(c("id","drn") %in% colnames(data_test)))
- data_train <- data_train %>% filter(!is.na(drn))
- data_test <- data_test %>% filter(!is.na(drn))
- # 与训练/测试集取交集,确保特征存在
- proteins <- intersect(proteins, intersect(colnames(data_train), colnames(data_test)))
- stopifnot(length(proteins) > 0)
- # (沿用你原来做法)仅在训练集上去近零方差,锁定特征清单
- nzv <- nearZeroVar(data_train[, proteins, drop = FALSE])
- proteins_use <- if (length(nzv) > 0) proteins[-nzv] else proteins
- stopifnot(length(proteins_use) > 0)
- # 因变量转为 0/1 因子(仅供 ranger 用;不改变 data_train/data_test 中原始 drn)
- train_y_fac <- factor(data_train$drn, levels = c(0, 1), labels = c("0", "1"))
- test_y_fac <- factor(data_test$drn, levels = c(0, 1), labels = c("0", "1"))
- # 训练集必须包含两个类别
- stopifnot(length(unique(train_y_fac)) == 2)
- # 特征矩阵(不做填补/缩放等)
- train_x <- data_train[, proteins_use, drop = FALSE]
- test_x <- data_test[, proteins_use, drop = FALSE]
- set.seed(2025)
- y_fac <- train_y_fac
- X <- train_x
- # ——1) 构造分层 k 折(类别平衡)——
- K <- 5
- folds <- caret::createFolds(y_fac, k = K, list = TRUE, returnTrain = FALSE)
- # ——2) 参数网格(可按需精简/扩展)——
- p <- ncol(X)
- grid <- expand.grid(
- num.trees = c(500), # 可加 800, 1000
- mtry = unique(pmax(1, round(c(sqrt(p), 0.05*p, 0.1*p)))),
- min.node.size = c(5, 10, 20),
- sample.fraction = c(0.6, 0.8) # <1.0 可缓解过拟合
- )
- cv_res <- grid
- cv_res$mean_auc <- NA_real_
- cv_res$sd_auc <- NA_real_
- # ——3) 逐参数组合跑 k 折,记录验证 AUC——
- for (gi in seq_len(nrow(grid))) {
- g <- grid[gi, ]
- aucs <- numeric(K)
- for (k in seq_len(K)) {
- idx_te <- folds[[k]]
- idx_tr <- setdiff(seq_len(nrow(X)), idx_te)
- rf_k <- ranger::ranger(
- formula = as.formula("y ~ ."),
- data = data.frame(y = y_fac[idx_tr], X[idx_tr, , drop = FALSE]),
- num.trees = g$num.trees,
- mtry = g$mtry,
- min.node.size = g$min.node.size,
- sample.fraction = g$sample.fraction,
- replace = TRUE,
- probability = TRUE,
- importance = "impurity",
- oob.error = TRUE
- )
- pred_val <- predict(rf_k, data = data.frame(X[idx_te, , drop = FALSE]))$predictions[, "1"]
- aucs[k] <- as.numeric(pROC::roc(response = y_fac[idx_te], predictor = pred_val, quiet = TRUE)$auc)
- }
- cv_res$mean_auc[gi] <- mean(aucs, na.rm = TRUE)
- cv_res$sd_auc[gi] <- stats::sd(aucs, na.rm = TRUE)
- }
- # ——4) 选择最佳参数(按平均 AUC 最大;若并列取 sd 最小)——
- best_idx <- order(-cv_res$mean_auc, cv_res$sd_auc)[1]
- best_par <- cv_res[best_idx, , drop = FALSE]
- print(best_par)
- # ——5) 用最佳参数在「全部训练集」上重训最终模型,并拿到 OOB 预测/AUC——
- rf_model <- ranger::ranger(
- formula = as.formula(paste(outcome, "~ .")),
- data = data.frame(drn = y_fac, X),
- num.trees = best_par$num.trees,
- mtry = best_par$mtry,
- min.node.size = best_par$min.node.size,
- sample.fraction = best_par$sample.fraction,
- replace = TRUE,
- probability = TRUE,
- importance = "impurity"
- )
- # ——7) 训练/测试集预测与ROC ——
- pred_train_prob <- predict(rf_model, data = data.frame(train_x))$predictions[, "1"]
- pred_test_prob <- predict(rf_model, data = data.frame(test_x))$predictions[, "1"]
- roc_train <- pROC::roc(response = train_y_fac, predictor = pred_train_prob, ci = TRUE, quiet = TRUE)
- roc_test <- pROC::roc(response = test_y_fac, predictor = pred_test_prob, ci = TRUE, quiet = TRUE)
- # cutoff & 混淆矩阵与派生指标
- if (length(unique(test_y_fac)) == 2) {
- cutoff_val <- cutoff::roc(pred_test_prob, test_y_fac)$cutoff
- pred_label <- ifelse(pred_test_prob > cutoff_val, 1, 0)
- cm <- caret::confusionMatrix(
- data = factor(pred_label, levels = c(0,1), labels = c("0","1")),
- reference = test_y_fac,
- positive = "1", mode = "everything"
- )
- TN <- cm$table[1]; FP <- cm$table[2]; FN <- cm$table[3]; TP <- cm$table[4]
- Accuracy <- cm$overall[["Accuracy"]]
- Sensitivity <- cm$byClass[["Sensitivity"]]
- Specificity <- cm$byClass[["Specificity"]]
- PPV <- cm$byClass[["Precision"]]
- NPV <- cm$byClass[["Neg Pred Value"]]
- Recall <- cm$byClass[["Recall"]]
- F1 <- cm$byClass[["F1"]]
- DR <- TP/(FN+TP) # detection rate
- FPR <- FP/(TN+FP) # false positive rate
- LR <- ifelse(FPR == 0, Inf, DR/FPR)
- } else {
- # 单一类别时占位(只保留AUC;其余置NA)
- cutoff_val <- NA
- Accuracy <- Sensitivity <- Specificity <- PPV <- NPV <- Recall <- F1 <- DR <- FPR <- LR <- NA
- }
- seed_level_perf <- rbind(seed_level_perf, data.frame(
- method = "RandomForest",
- train_auc = as.numeric(roc_train$auc),
- train_ci_low = roc_train$ci[1],
- train_ci_up = roc_train$ci[3],
- test_auc = as.numeric(roc_test$auc),
- test_ci_low = roc_test$ci[1],
- test_ci_up = roc_test$ci[3],
- cutoff = cutoff_val,
- Accuracy, Sensitivity, Specificity, PPV, NPV, Recall, F1, DR, FPR, LR
- ))
- # =========================== 保存逐个体score到全量框架(附dataset标记)
- score_col <- "score_drn_RF"
- dset_col <- "dataset_drn_RF"
- data_score_temp <- data_GDES %>%
- select(id) %>%
- left_join(data.frame(id = data_test$id, pred = pred_test_prob), by = "id") %>%
- left_join(data.frame(id = data_train$id, pred_train = pred_train_prob), by = "id") %>%
- mutate(dataset = case_when(!is.na(pred) ~ "test",
- !is.na(pred_train) ~ "train",
- TRUE ~ NA_character_),
- pred_final = ifelse(!is.na(pred), pred, pred_train)) %>%
- transmute(id, !!score_col := pred_final, !!dset_col := dataset)
- data_score <- left_join(data_score, data_score_temp, by = "id")
- # 供下面模型比较使用
- data_test[[score_col]] <- pred_test_prob
- # =========================== 测试集:传统模型 vs RF score vs 组合模型
- auc_rows_all_models <- data.frame()
- for (model_name in names(models)) {
- covars <- models[[model_name]]
- # 传统模型
- f1 <- as.formula(paste(outcome, "~", covars))
- m1 <- glm(f1, data = data_test, family = binomial)
- lp1 <- predict(m1, newdata = data_test) # logit 线性预测器即可;AUC对单调变换不敏感
- roc1 <- pROC::roc(response = data_test[[outcome]],
- predictor = as.numeric(lp1),
- ci = TRUE, quiet = TRUE)
- # 单独 RF score
- roc3 <- pROC::roc(response = data_test[[outcome]],
- predictor = data_test[[score_col]],
- ci = TRUE, quiet = TRUE)
- # 组合模型(传统 + RF分数)
- f2 <- as.formula(paste(outcome, "~", covars, "+", score_col))
- m2 <- glm(f2, data = data_test, family = binomial)
- lp2 <- predict(m2, newdata = data_test)
- roc2 <- pROC::roc(response = data_test[[outcome]],
- predictor = as.numeric(lp2),
- ci = TRUE, quiet = TRUE)
- # DeLong检验(同样显式指向 pROC)
- p_met <- as.numeric(pROC::roc.test(roc1, roc3)$p.value) # RF vs CONV
- p_comb <- as.numeric(pROC::roc.test(roc1, roc2)$p.value) # COMBINED vs CONV
- auc_diff <- as.numeric(roc2$auc) - as.numeric(roc1$auc)
- auc_impr <- auc_diff / as.numeric(roc1$auc)
- sig <- ifelse(p_comb >= 0.05, "",
- ifelse(p_comb < 0.001, "***",
- ifelse(p_comb < 0.01, "**", "*")))
- compare_met <- ifelse(p_met >= 0.05, "Comparable",
- ifelse(as.numeric(roc3$auc) > as.numeric(roc1$auc), "Superior", "-"))
- auc_rows_all_models <- rbind(auc_rows_all_models, data.frame(
- method = "RandomForest",
- outcome = outcome,
- model = model_name,
- CONV = as.numeric(roc1$auc), conv_lower = roc1$ci[1], conv_upper = roc1$ci[3],
- RF = as.numeric(roc3$auc), rf_lower = roc3$ci[1], rf_upper = roc3$ci[3],
- Pvalue_RF_vs_CONV = p_met, RF_vs_CONV = compare_met,
- COMBINED = as.numeric(roc2$auc), combined_lower = roc2$ci[1], combined_upper = roc2$ci[3],
- Diff = auc_diff, Diff_proportion = auc_impr,
- Pvalue_COMBINED_vs_CONV = p_comb, Significance = sig,
- stringsAsFactors = FALSE
- ))
- }
- # 1) RF汇总
- addWorksheet(wb_seed_summary, "RF_Summary")
- writeData(wb_seed_summary, "RF_Summary", seed_level_perf)
- saveWorkbook(wb_seed_summary, "XXXX.xlsx", overwrite = TRUE)
- # 2) 按模型分sheet的AUC对比结果
- for (model_name in names(models)) {
- sub <- auc_rows_all_models %>% filter(model == model_name)
- addWorksheet(wb_model_compare, sheetName = model_name)
- writeData(wb_model_compare, model_name, sub)
- }
- saveWorkbook(wb_model_compare, "XXXX.xlsx", overwrite = TRUE)
- # 3) 个体层score(包含score_drn_RF与dataset_drn_RF)
- write.csv(data_score, "XXXX.csv", row.names = FALSE)
- # # 4) 保存模型与对象
- save.image("XXXX.RData")
R code for Pro-DRN.R at commit 459bdfe, no license · at the source
Overview
- State Key Laboratory of Ophthalmology, Zhongshan Ophthalmic Center, Sun Yat-Sen University, Guangdong Provincial Key Laboratory of Ophthalmology and Visual Science, Guangdong Provincial Clinical Research Center for Ocular Diseases, Guangzhou, China
- Guangdong Basic Research Center of Excellence for Major Blinding Eye Diseases Prevention and Treatment, Guangzhou, China
- School of Optometry, The Hong Kong Polytechnic University, Kowloon, Hong Kong SAR, China
- Research Centre for SHARP Vision (RCSV), The Hong Kong Polytechnic University, Hong Kong, Hong Kong SAR, China
- Centre for Eye and Vision Research (CEVR), Hong Kong, Hong Kong SAR, China
- Department of Biomedical Engineering, Columbia University, New York, New York, United States of America
- Clinical Medical Research Center, Children’s Hospital of Nanjing Medical University, Nanjing, Jiangsu Province, China
- Artificial Intelligence and Modelling in Epidemiology Program, Melbourne Sexual Health Centre, Alfred Health, Melbourne, Australia
- Central Clinical School, Faculty of Medicine, Nursing and Health Sciences, Monash University, Melbourne, Australia
- Centre for Eye Research Australia, Royal Victorian Eye and Ear Hospital, Melbourne, Australia
- Hainan Eye Hospital and Key Laboratory of Ophthalmology, Zhongshan Ophthalmic Center, Sun Yat-sen University, Haikou, China
Abstract
Background: Retinal neurodegeneration is an early and independent feature of diabetic retinal disease and has been proposed as a window into the systemic neural consequences of diabetes, yet accessible molecular biomarkers and individualized prediction tools remain scarce. We aimed to identify circulating plasma protein signatures of diabetic retinal neurodegeneration (DRN) and to translate them into a clinically usable risk prediction system.
Methods and findings: In this multi-cohort prospective observational study, we integrated high-throughput plasma proteomics with longitudinal optical coherence tomography (OCT) in two independent populations. The discovery cohort comprised 1,492 participants had baseline plasma proteomics and OCT, and 1,218 were followed with repeated OCT over 6 years in Guangzhou Diabetic Eye Study (GDES). DRN was quantified by the annualized OCT-derived retinal nerve fiber layer thinning rate. In multivariable analyses adjusted for age, sex, smoking, systolic blood pressure, HbA1c, and diabetes duration, we identified 71 plasma proteins associated with development and progression of DRN. These proteins mapped onto pathways governing inflammatory immune recruitment, extracellular matrix remodeling, and microvascular homeostasis, providing a plausible biological basis for DRN. We developed a proteomics-based DRN model (Pro-DRN) using eight machine learning (ML) algorithms, including XGBoost and LightGBM. In the independent test set, Pro-DRN achieved a C-index of 0.860, rising to 0.908 when integrated with clinical variables. Compared with six conventional models, Pro-DRN improved discrimination (ΔC-index 0.137 to 0.159; all P < 0.001), reclassification (IDI 0.212 to 0.245; NRI 0.226 to 0.452; all P < 0.05). In the Hippisley model, the C-index increased from 0.739 (95% CI [0.670, 0.808]) to 0.898 (95% CI [0.858, 0.937]), with IDI 0.245 (95% CI [0.177, 0.318]), NRI 0.452 (95% CI [0.222, 0.673]) (both P < 0.001), and higher net benefit. The proteins most consistently driving model performance included ACTA2, COL6A3, and HSPG2. For clinical translation, we deployed the locked model as an interactive, web-based risk-assessment tool to support early DRN screening and longitudinal monitoring. Cross-ethnic external validation in UK Biobank (n = 502; recruited 2006–2010) reproduced core protein signals and consistent effect directions, confirming robustness across populations. Principal methodological limitation lies in single time point proteomic assessment.
Conclusion: In this multi-cohort study, we present a proteomics- and ML–based precision prediction system for DRN. Pro-DRN substantially enhanced early risk stratification beyond conventional clinical factors and may support targeted screening and timely neuroprotective interventions, advancing molecularly guided strategies for diabetic eye disease prevention.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.
ZOC-skl/Pro-DRN
459bdfe6dacaa07319ae981066700c7caf894863, 24 February 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
2 files
- R code for Pro-DRN.R, R, 1,144 lines, 1 match
- README.md, Text, 2 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 1 script, each with its path and the digest of its content;
- 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data Availability
Data from the Guangzhou Diabetic Eye Study (GDES) are subject to Chinese data privacy regulations and are not publicly available. The data are managed by the Preventive Ophthalmology Data Management Unit, Zhongshan Ophthalmic Center, Sun Yat-sen University. Requests for access should be directed to the data manager via email at . Requests will be reviewed within 90 days according to Center policy, and, if approved, an inter-institutional data use agreement specifying use for non-commercial academic research will be required. UK Biobank data are available through application via the official platform (http://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 12 MeSH terms, 9 funders, 85 references, 1 integrity notice.
Cite
This paper
Li, H., Zhu, Z., Yang, S., Cheng, W., Tan, S., Xin, Z., Zhang, L., Zhu, Z., Chen, S., Huang, W., & Wang, W. (2026). Plasma proteomic signatures of early retinal neurodegeneration in diabetes: A multi-cohort study. PLoS medicine, 23(6), e1004868. https://
BibTeX
@article{li2026plasma,
author = {Li, Huangdong and Zhu, Ziyu and Yang, Shaopeng and Cheng, Weijing and Tan, Shaoying and Xin, Zhuoyao and Zhang, Lei and Zhu, Zhuoting and Chen, Shida and Huang, Wenyong and Wang, Wei},
title = {{Plasma proteomic signatures of early retinal neurodegeneration in diabetes: A multi-cohort study}},
journal = {PLoS medicine},
year = {2026},
month = jun,
volume = {23},
number = {6},
pages = {e1004868},
publisher = {PLOS},
issn = {1549-1277},
doi = {10.1371/
url = {https://
pmid = {42228639},
pmcid = {PMC13229346}
}
RIS
TY - JOUR
AU - Li, Huangdong
AU - Zhu, Ziyu
AU - Yang, Shaopeng
AU - Cheng, Weijing
AU - Tan, Shaoying
AU - Xin, Zhuoyao
AU - Zhang, Lei
AU - Zhu, Zhuoting
AU - Chen, Shida
AU - Huang, Wenyong
AU - Wang, Wei
TI - Plasma proteomic signatures of early retinal neurodegeneration in diabetes: A multi-cohort study
T2 - PLoS medicine
J2 - PLoS Med
PY - 2026
DA - 2026/
VL - 23
IS - 6
SP - e1004868
SN - 1549-1277
PB - PLOS
DO - 10.1371/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1371/
"type": "article-journal",
"title": "Plasma proteomic signatures of early retinal neurodegeneration in diabetes: A multi-cohort study",
"container-title": "PLoS medicine",
"author": [
{
"family": "Li",
"given": "Huangdong"
},
{
"family": "Zhu",
"given": "Ziyu"
},
{
"family": "Yang",
"given": "Shaopeng"
},
{
"family": "Cheng",
"given": "Weijing"
},
{
"family": "Tan",
"given": "Shaoying"
},
{
"family": "Xin",
"given": "Zhuoyao"
},
{
"family": "Zhang",
"given": "Lei"
},
{
"family": "Zhu",
"given": "Zhuoting"
},
{
"family": "Chen",
"given": "Shida"
},
{
"family": "Huang",
"given": "Wenyong"
},
{
"family": "Wang",
"given": "Wei"
}
],
"container-title-short":
"volume": "23",
"issue": "6",
"page": "e1004868",
"DOI": "10.1371/
"PMID": "42228639",
"PMCID": "PMC13229346",
"ISSN": "1549-1277",
"publisher": "PLOS",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
2
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1182/bloodadvances.2025019294 [code]
- Plasma proteome signatures are predictive of mortality in sickle cell disease.Journal: Blood advancesIn common: genetics / omics, 8 references
- [2] doi:10.3390/ijms27156925 [code]
- XGBoost-SHAP Interpretable Modeling Identifies and Validates an Eight-Gene Biomarker for Hepatic Encephalopathy Risk Prediction in Cirrhosis.Journal: International journal of molecular sciencesIn common: pROC, survival, caret, 3 other tools, genetics / omics, other condition
- [3] doi:10.1073/pnas.2516601123 [code]
- Unveiling the glymphatic system's role in brain aging: A comprehensive biomarker and modifiable intervention target.Journal: Proceedings of the National Academy of Sciences of the United States of AmericaIn common: pROC, survival, ggplot2, 1 other tool, clinical / translational, 2 references
- [4] doi:10.1093/neuonc/noag128 [code]
- Spatially-resolved single-cell imaging of melanoma brain metastases identifies localized immune patterns predictive of immune checkpoint blockade response.Journal: Neuro-oncologyIn common: pROC, survival, caret, 2 other tools, clinical / translational, genetics / omics, other condition
- [5] doi:10.21037/jtd-2026-0997 [code]
- Machine learning models based on XGBoost algorithm to predict prognosis of lung cancer brain metastases.Journal: Journal of thoracic diseaseIn common: pROC, survival, caret, 2 other tools, clinical / translational, other condition
- [6] doi:10.1038/s41467-026-77170-3 [code]
- DNA methylation profiling identifies long-range epigenetic silencing of clustered protocadherins as a key determinant of meningioma progression.Journal: Nature communicationsIn common: pROC, survival, caret, 2 other tools, genetics / omics, other condition
- [7] doi:10.1016/j.isci.2026.115657 [code]
- Integration of machine learning to develop a disulfidptosis model for predicting glioma prognosis, immunotherapy response, and drug.Journal: iScienceIn common: pROC, survival, clusterProfiler, 2 other tools, clinical / translational, other condition
- [8] doi:10.1093/bioinformatics/btag592 [code]
- Network-based stratification of allele-specific expression reveals patient subgroups in Huntington's disease.Journal: Bioinformatics (Oxford, England)In common: pROC, survival, clusterProfiler, 2 other tools, genetics / omics, other condition
- [9] doi:10.1080/20002297.2026.2705667 [code]
- Oral microbiota dysbiosis related to the cortical thinning and cognitive impairment in cerebral small vessel disease.Journal: Journal of oral microbiologyIn common: pROC, survival, caret, 2 other tools
- [10] doi:10.7717/peerj.21426 [code]
- Integrated transcriptomic identification and validation reveal key autophagy-associated biomarkers in sleep deprivation.Journal: PeerJIn common: pROC, caret, clusterProfiler, 2 other tools, genetics / omics
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 1 script, and 1 match between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:441bbc60246c8800…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
