CKD273 Comparison

Related to Supplemental Figure 6 (panels c–f).

CKD273 is a validated 273-peptide urinary proteomics classifier for chronic kidney disease. This analysis scores the CKD273 36-gene approximation alongside the CAAH 10-gene signature in both mouse models and compares discrimination performance by ROC/AUC with DeLong's test. In the Ren1c KO model the two signatures perform comparably; in the captopril model the CAAH signature is significantly superior (p = 5.9 × 10^-48^), suggesting it captures RAAS-specific rather than generic CKD biology.

Define gene lists

ckd273_genes <- c("Col1a1", "Col1a2", "Col2a1", "Col3a1", "Col4a1", "Col5a1", "Col8a2", "Col11a1",
    "Serpina1a", "Serpina1b", "Serpina1c", "Serpina1d", "Serpina1e", "Alb", "Fga", "Fgb",
    "Fgg", "Apoa1", "B2m", "Serpinc1", "A1bg", "Umod", "Pigr", "Ahsg",
    "Clu", "Prg4", "Spp1", "Serpina3n", "Ttr", "Cd99", "Cyb5r3", "Lct",
    "Vgf", "Pcsk1", "Ptgds", "Psrc1")

caah_10_genes <- c("Clu", "Lrp2", "Lamp2", "Col4a2", "Spink1",
                   "Wfdc2", "Pax8", "Lyz2", "S100a9", "Cdh13")

shared_genes <- intersect(caah_10_genes, ckd273_genes)
cat(sprintf("Shared genes: %d / %d CAAH genes in CKD273\n",
            length(shared_genes), length(caah_10_genes)))

Only Clu overlaps between the two signatures.

Load Seurat objects

ren1c_obj     <- readRDS("ren1c_obj.rds")
captopril_obj <- readRDS("captopril_obj.rds")

Score both signatures with UCell

addSignatureScores scores both the CAAH and CKD273 gene lists in a Seurat object, reporting how many genes from each list are present in the data.

addSignatureScores <- function(seurat_obj) {
    ckd273_avail <- ckd273_genes[ckd273_genes %in% rownames(seurat_obj)]
    caah_avail   <- caah_10_genes[caah_10_genes %in% rownames(seurat_obj)]
    cat("CKD273:", length(ckd273_avail), "/", length(ckd273_genes), "genes available\n")
    cat("CAAH:  ", length(caah_avail),   "/", length(caah_10_genes),  "genes available\n")

    seurat_obj <- AddModuleScore_UCell(seurat_obj,
        features = list(ckd273_avail), name = "CKD273_Score", assay = "RNA")
    seurat_obj <- AddModuleScore_UCell(seurat_obj,
        features = list(caah_avail),   name = "CAAH_Score",   assay = "RNA")

    colnames(seurat_obj@meta.data)[colnames(seurat_obj@meta.data) == "signature_1CKD273_Score"] <- "CKD273_Score"
    colnames(seurat_obj@meta.data)[colnames(seurat_obj@meta.data) == "signature_1CAAH_Score"]   <- "CAAH_Score"

    list(object = seurat_obj,
         ckd273_avail = ckd273_avail,
         caah_avail   = caah_avail)
}

ren1c_scored  <- addSignatureScores(ren1c_obj)
ren1c_obj     <- ren1c_scored$object

capt_scored   <- addSignatureScores(captopril_obj)
captopril_obj <- capt_scored$object

ROC comparison with DeLong's test

calcRocComparison computes ROC curves for both signatures, tests their AUCs with DeLong's method, and returns the roc objects, AUCs, and p-value.

calcRocComparison <- function(seurat_obj, group_var, group1, group2) {
    meta <- seurat_obj@meta.data %>%
        dplyr::filter(get(group_var) %in% c(group1, group2)) %>%
        dplyr::mutate(group_binary = ifelse(get(group_var) == group2, 1, 0))

    roc_ckd  <- roc(meta$group_binary, meta$CKD273_Score,
                    levels = c(0, 1), direction = "<", quiet = TRUE)
    roc_caah <- roc(meta$group_binary, meta$CAAH_Score,
                    levels = c(0, 1), direction = "<", quiet = TRUE)
    delong   <- roc.test(roc_ckd, roc_caah, method = "delong")

    list(roc_ckd   = roc_ckd,
         roc_caah  = roc_caah,
         auc_ckd   = as.numeric(auc(roc_ckd)),
         auc_caah  = as.numeric(auc(roc_caah)),
         p_value   = delong$p.value)
}

ren1c_roc <- calcRocComparison(ren1c_obj,     "genotype",  "WT",   "KO")
capt_roc  <- calcRocComparison(captopril_obj, "condition", "ctrl", "trt")

cat(sprintf("Ren1c KO  — CKD273 AUC: %.4f | CAAH AUC: %.4f | p = %.4e\n",
            ren1c_roc$auc_ckd, ren1c_roc$auc_caah, ren1c_roc$p_value))
cat(sprintf("Captopril — CKD273 AUC: %.4f | CAAH AUC: %.4f | p = %.4e\n",
            capt_roc$auc_ckd,  capt_roc$auc_caah,  capt_roc$p_value))

Results: Ren1c KO — CKD273 AUC 0.841, CAAH AUC 0.825, p = 0.15 (comparable). Captopril — CKD273 AUC 0.659, CAAH AUC 0.743, p = 5.9 × 10^-48^ (CAAH superior).

Cell-type-specific ROC

celltypeRocAnalysis <- function(seurat_obj, group_var, group1, group2) {
    meta <- seurat_obj@meta.data %>%
        dplyr::filter(get(group_var) %in% c(group1, group2)) %>%
        dplyr::mutate(group_binary = ifelse(get(group_var) == group2, 1, 0))

    results <- lapply(unique(meta$cell_type), function(ct) {
        d <- meta[meta$cell_type == ct, ]
        if (sum(d$group_binary == 0) < 10 || sum(d$group_binary == 1) < 10) return(NULL)
        tryCatch({
            data.frame(
                cell_type    = ct,
                n_group1     = sum(d$group_binary == 0),
                n_group2     = sum(d$group_binary == 1),
                auc_ckd273   = as.numeric(auc(roc(d$group_binary, d$CKD273_Score,
                                                   levels = c(0,1), direction = "<", quiet = TRUE))),
                auc_caah     = as.numeric(auc(roc(d$group_binary, d$CAAH_Score,
                                                   levels = c(0,1), direction = "<", quiet = TRUE)))
            )
        }, error = function(e) NULL)
    })
    results_df <- bind_rows(Filter(Negate(is.null), results))
    results_df$auc_difference <- results_df$auc_caah - results_df$auc_ckd273
    results_df %>% arrange(desc(auc_caah))
}

ren1c_celltype_roc <- celltypeRocAnalysis(ren1c_obj,     "genotype",  "WT",   "KO")
capt_celltype_roc  <- celltypeRocAnalysis(captopril_obj, "condition", "ctrl", "trt")

fwrite(as.data.table(ren1c_celltype_roc),
    paste0(parent_dir, "ren1c_ROC-celltype_CKD273-CAAH.csv"), sep = ",")
fwrite(as.data.table(capt_celltype_roc),
    paste0(parent_dir, "captopril_ROC-celltype_CKD273-CAAH.csv"), sep = ",")

Figures — ROC curves and score distributions

createComparisonPlots returns a named list of ggplot objects: ROC curves, score distributions by group, correlation scatter, cell-type boxplots, and per-cell-type effect sizes.

createComparisonPlots <- function(seurat_obj, roc_results, group_var, model_name) {
    meta <- seurat_obj@meta.data

    # Panel: ROC curves
    p_roc <- ggroc(list(CKD273 = roc_results$roc_ckd, CAAH = roc_results$roc_caah),
                   legacy.axes = TRUE) +
        geom_abline(intercept = 0, slope = 1, linetype = "dashed", colour = "grey60") +
        annotate("text", x = 0.65, y = 0.25,
                 label = sprintf("CKD273 AUC: %.3f\nCAAH AUC:  %.3f\np = %.2e",
                                 roc_results$auc_ckd, roc_results$auc_caah, roc_results$p_value),
                 hjust = 0, size = 3) +
        scale_colour_manual(values = c("CKD273" = "#E69F00", "CAAH" = "#56B4E9")) +
        labs(title = paste(model_name, "— ROC curves"),
             x = "1 - Specificity", y = "Sensitivity", colour = NULL) +
        theme_classic(base_size = 11)

    # Panel: score distributions by group
    score_long <- meta %>%
        dplyr::select(all_of(c(group_var, "CKD273_Score", "CAAH_Score", "cell_type"))) %>%
        tidyr::pivot_longer(cols = c("CKD273_Score", "CAAH_Score"),
                            names_to = "Signature", values_to = "Score")

    p_dist <- ggplot(score_long, aes(x = get(group_var), y = Score,
                                     fill = Signature, colour = Signature)) +
        geom_violin(trim = FALSE, alpha = 0.7, position = position_dodge(0.9)) +
        geom_boxplot(width = 0.15, position = position_dodge(0.9),
                     outlier.shape = NA, fill = "white") +
        scale_fill_manual(values   = c("CKD273_Score" = "#E69F00", "CAAH_Score" = "#56B4E9")) +
        scale_colour_manual(values = c("CKD273_Score" = "#E69F00", "CAAH_Score" = "#56B4E9")) +
        labs(title = paste(model_name, "— score distributions"), x = NULL, y = "UCell score") +
        theme_classic(base_size = 11) + theme(legend.position = "bottom")

    # Panel: correlation between scores
    cor_res <- cor.test(meta$CKD273_Score, meta$CAAH_Score, method = "spearman")
    p_cor <- ggplot(meta, aes(x = CKD273_Score, y = CAAH_Score, colour = get(group_var))) +
        geom_point(alpha = 0.3, size = 0.5) +
        geom_smooth(method = "lm", colour = "black", linewidth = 0.8, se = FALSE) +
        annotate("text", x = min(meta$CKD273_Score), y = max(meta$CAAH_Score),
                 label = sprintf("rho = %.3f\np = %.2e", cor_res$estimate, cor_res$p.value),
                 hjust = 0, vjust = 1, size = 3) +
        scale_colour_manual(values = c("#E69F00", "#56B4E9")) +
        labs(title = paste(model_name, "— signature correlation"),
             x = "CKD273 UCell score", y = "CAAH UCell score", colour = group_var) +
        theme_classic(base_size = 11) + theme(legend.position = "bottom")

    # Panel: scores by cell type
    p_celltype <- ggplot(score_long, aes(x = cell_type, y = Score, fill = get(group_var))) +
        geom_boxplot(alpha = 0.7, outlier.size = 0.3) +
        facet_wrap(~ Signature, ncol = 1) +
        scale_fill_manual(values = c("#E69F00", "#56B4E9")) +
        labs(title = paste(model_name, "— scores by cell type"),
             x = NULL, y = "UCell score", fill = group_var) +
        theme_classic(base_size = 11) +
        theme(axis.text.x = element_text(angle = 45, hjust = 1),
              legend.position = "bottom")

    # Panel: Cohen's d per cell type
    effect_sizes <- score_long %>%
        dplyr::group_by(Signature, cell_type) %>%
        dplyr::summarise(
            cohens_d = {
                g1 <- Score[get(group_var) == unique(get(group_var))[1]]
                g2 <- Score[get(group_var) == unique(get(group_var))[2]]
                sd_p <- sqrt((var(g1) + var(g2)) / 2)
                (mean(g2) - mean(g1)) / sd_p
            }, .groups = "drop")

    p_effect <- ggplot(effect_sizes, aes(x = cell_type, y = cohens_d, fill = Signature)) +
        geom_col(position = "dodge", alpha = 0.8) +
        geom_hline(yintercept = 0, linetype = "dashed") +
        scale_fill_manual(values = c("CKD273_Score" = "#E69F00", "CAAH_Score" = "#56B4E9")) +
        labs(title = paste(model_name, "— Cohen's d by cell type"),
             x = NULL, y = "Cohen's d") +
        theme_classic(base_size = 11) +
        theme(axis.text.x = element_text(angle = 45, hjust = 1),
              legend.position = "bottom")

    list(roc = p_roc, distributions = p_dist,
         correlation = p_cor, celltype = p_celltype, effect_sizes = p_effect)
}

ren1c_plots <- createComparisonPlots(ren1c_obj,     ren1c_roc, "genotype",  "Ren1c KO")
capt_plots  <- createComparisonPlots(captopril_obj, capt_roc,  "condition", "Captopril")

for (nm in names(ren1c_plots)) {
    ggsave(paste0(parent_dir, "ren1c_CKD273-comparison_", nm, "_", Sys.Date(), ".svg"),
           ren1c_plots[[nm]], units = "mm", dpi = 300)
    ggsave(paste0(parent_dir, "captopril_CKD273-comparison_", nm, "_", Sys.Date(), ".svg"),
           capt_plots[[nm]], units = "mm", dpi = 300)
}

Summary AUC bar chart (both models)

summary_data <- data.frame(
    Model     = rep(c("Ren1c KO", "Captopril"), each = 2),
    Signature = rep(c("CKD273", "CAAH"), 2),
    AUC       = c(ren1c_roc$auc_ckd, ren1c_roc$auc_caah,
                  capt_roc$auc_ckd,  capt_roc$auc_caah)
)

p_summary <- ggplot(summary_data, aes(x = Model, y = AUC, fill = Signature)) +
    geom_col(position = "dodge", alpha = 0.85) +
    geom_text(aes(label = sprintf("%.3f", AUC)),
              position = position_dodge(0.9), vjust = -0.4, size = 3) +
    geom_hline(yintercept = 0.5, linetype = "dashed", colour = "grey50") +
    scale_fill_manual(values = c("CKD273" = "#E69F00", "CAAH" = "#56B4E9")) +
    ylim(0, 1) +
    labs(x = NULL, y = "AUC", fill = NULL) +
    theme_classic(base_size = 11) + theme(legend.position = "bottom")

ggsave(paste0(parent_dir, "CKD273-CAAH_summary-AUC_", Sys.Date(), ".svg"),
       p_summary, width = 100, height = 90, units = "mm", dpi = 300)