使用bind_rows合并for循环生成的列表时出错,求tidyverse解决方案
数据背景
假设存在如下数据框a:
> head(a) Ki67 simpson WHO event event.tid 1 25 1 WHO1 1 17 2 46 3 WHO3 2 48 3 99 2 WHO2 1 110 4 57 4 WHO1 2 148 5 66 1 WHO3 1 3 6 75 3 WHO2 2 173
数据生成代码:
a <- data.frame( Ki67 = sample(1:100, size = 6000, replace = TRUE), simpson = as.factor(sample(1:4)), WHO = sample(c("WHO1", "WHO2", "WHO3")), event = sample(1:2), event.tid = sample(1:200) )
现有代码与报错
使用riskRegression计算不同分组下Ki67的生存ROC曲线,代码如下:
library(riskRegression) library(tidyverse) df_list <- list() for(i in c("WHO1", "WHO2", "WHO3")){ for(j in c("1", "2", "3", "4")){ y <- filter(a, WHO == i, simpson == j) %>% droplevels() x <- Score(list("Ki67" = y$Ki67), formula = Hist(event.tid, event) ~ 1, data = y, metrics = c("auc"), times = c(36, 60, 120), plots = "ROC") df_list[[length(df_list) + 1]] <- list( WHO = i, simpson = j, times = x$ROC$plotframe$times, tpr = x$ROC$plotframe$TPR, fpr = x$ROC$plotframe$FPR, ki67 = sort(unique(y$Ki67)) ) } }
尝试用bind_rows(df_list)合并列表时,出现维度不匹配错误:
bind_rows(df_list)
Error in
vctrs::data_frame(): ! Can't recycleWHO(size 300) to matchki67(size 99). Runrlang::last_trace()to see where the error occurred.
尝试过ki67 = rep(sort(unique(y$Ki67)), length(i))但未解决问题,需要tidyverse框架下的解决方案。
解决方案
原因分析
报错核心是每个列表元素内的向量长度不一致:WHO/simpson是单值,times/tpr/fpr是等长向量(对应ROC曲线的每个点),而ki67是该分组下所有唯一Ki67值的向量,长度与前者不匹配,导致bind_rows无法自动对齐。
方案1:关联ROC点对应的Ki67截断值(推荐)
Score函数输出的x$ROC$plotframe中已经包含每个ROC点对应的Ki67截断值(cutoff列),无需手动提取唯一Ki67值。直接基于该数据框构造结果,确保所有字段长度一致:
library(riskRegression) library(tidyverse) # 生成所有分组组合 groups <- expand_grid(WHO = c("WHO1", "WHO2", "WHO3"), simpson = c("1", "2", "3", "4")) # 用purrr替代for循环,tidy风格处理每个分组 result <- groups %>% mutate(roc_data = pmap(list(WHO, simpson), function(w, s) { y <- filter(a, WHO == w, simpson == s) %>% droplevels() if(nrow(y) == 0) return(tibble()) # 处理空分组 x <- Score(list("Ki67" = y$Ki67), formula = Hist(event.tid, event) ~ 1, data = y, metrics = c("auc"), times = c(36, 60, 120), plots = "ROC") # 直接提取plotframe,添加分组信息 x$ROC$plotframe %>% mutate(WHO = w, simpson = s, ki67 = cutoff) %>% select(WHO, simpson, times, tpr, fpr, ki67) })) %>% unnest(roc_data)
这样得到的result是标准的tidy数据框,每行对应一个分组、一个时间点、一个ROC点及其对应的Ki67截断值。
方案2:将唯一Ki67与所有ROC结果做笛卡尔积(若业务需要)
如果确实需要将分组内的每个唯一Ki67值与所有ROC曲线点组合(即每个Ki67对应所有时间点的TPR/FPR),可以通过笛卡尔积实现:
library(riskRegression) library(tidyverse) groups <- expand_grid(WHO = c("WHO1", "WHO2", "WHO3"), simpson = c("1", "2", "3", "4")) result <- groups %>% mutate(roc_data = pmap(list(WHO, simpson), function(w, s) { y <- filter(a, WHO == w, simpson == s) %>% droplevels() if(nrow(y) == 0) return(tibble()) x <- Score(list("Ki67" = y$Ki67), formula = Hist(event.tid, event) ~ 1, data = y, metrics = c("auc"), times = c(36, 60, 120), plots = "ROC") # 提取ROC基础数据和唯一Ki67,做笛卡尔积 roc_df <- x$ROC$plotframe %>% select(times, tpr, fpr) ki67_df <- tibble(ki67 = sort(unique(y$Ki67))) crossing(roc_df, ki67_df) %>% mutate(WHO = w, simpson = s) %>% select(WHO, simpson, times, tpr, fpr, ki67) })) %>% unnest(roc_data)
此方案会生成大量行(每个Ki67 × 每个ROC点),需根据实际业务需求选择。
内容的提问来源于stack exchange,提问作者cmirian

