You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R中使用Copula模型生成相关生存终点(如PFS和OS)

多生存终点(PFS/OS)模拟与Copula模型实现(R语言)

一、实现逻辑

PFS(无进展生存期)与OS(总生存期)存在天然正相关性,Copula模型可以通过构建联合分布生成符合这种相关性的成对生存数据。这里以Clayton Copula为例(适合刻画正相关的生存事件),核心步骤如下:

  1. 定义PFS和OS的边缘生存分布(沿用原函数的指数分布);
  2. 用Copula生成两个相关的均匀分布随机数;
  3. 将均匀分布转换为对应的生存时间(基于边缘分布的分位数函数);
  4. 结合入组时间、失访时间和研究时长,计算最终的观测时间与删失状态。

需提前安装并加载依赖包:

install.packages(c("copula", "survival"))
library(copula)
library(survival)

二、扩展后的多终点模拟函数

基于你提供的单终点函数修改,支持生成带相关性的PFS和OS数据:

# 多生存终点(PFS/OS)模拟函数
generate_multiple_survival_data <- function(
    seed = NULL,
    mt_pfs_trt = NULL,    # 试验组PFS中位时间(向量,对应各试验臂)
    mt_os_trt = NULL,     # 试验组OS中位时间(向量,对应各试验臂)
    mt_pfs_ctrl = NULL,   # 对照组PFS中位时间
    mt_os_ctrl = NULL,    # 对照组OS中位时间
    copula_param = 2,     # Copula参数(Clayton Copula,>0表示正相关,值越大相关性越强)
    n_trt = NULL,         # 每个试验组样本量
    n_ctrl = NULL,        # 对照组样本量
    enrl_rate = NULL,     # 入组速率
    drpot_hr = NULL,      # 失访率(指数分布参数)
    nsim = NULL,          # 模拟次数
    study_duration = NULL,# 研究总时长
    arms = NULL           # 试验臂数量
) {
  set.seed(seed)
  # 初始化Clayton Copula
  clayton_cop <- claytonCopula(param = copula_param, dim = 2)
  
  results <- data.frame()
  
  for (j in 1:nsim) {
    # 生成各试验臂数据
    for (arm in 1:arms) {
      for (i in 1:n_trt) {
        subjid <- paste(j, arm, i, sep = "-")
        enrl_tm <- runif(1, 0, min(n_trt / enrl_rate, study_duration))
        
        # 用Copula生成相关的均匀随机数
        u <- rCopula(1, clayton_cop)
        # 转换为PFS和OS的生存时间(指数分布分位数函数)
        pfs_tm <- qexp(u[1], rate = log(2)/mt_pfs_trt[arm])
        os_tm <- qexp(u[2], rate = log(2)/mt_os_trt[arm])
        # 失访时间
        drpot_tm <- rexp(1, rate = drpot_hr)
        
        # 计算PFS的观测时间与删失状态
        if (pfs_tm > drpot_tm) {
          obs_pfs_tm <- drpot_tm
          cal_pfs_tm <- enrl_tm + obs_pfs_tm
          cnsr_pfs <- ifelse(cal_pfs_tm > study_duration, 1, 1)
        } else {
          obs_pfs_tm <- pfs_tm
          cal_pfs_tm <- enrl_tm + obs_pfs_tm
          cnsr_pfs <- ifelse(cal_pfs_tm > study_duration, 1, 0)
        }
        
        # 计算OS的观测时间与删失状态(OS不能早于PFS)
        os_actual_tm <- pfs_tm + os_tm
        if (os_actual_tm > drpot_tm) {
          obs_os_tm <- drpot_tm
          cal_os_tm <- enrl_tm + obs_os_tm
          cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 1)
        } else {
          obs_os_tm <- os_actual_tm
          cal_os_tm <- enrl_tm + obs_os_tm
          cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 0)
        }
        
        # 修正:PFS删失时,OS起始时间为观测到的PFS时间
        if (cnsr_pfs == 1) {
          os_actual_tm <- obs_pfs_tm + os_tm
          if (os_actual_tm > drpot_tm) {
            obs_os_tm <- drpot_tm
            cal_os_tm <- enrl_tm + obs_os_tm
            cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 1)
          } else {
            obs_os_tm <- os_actual_tm
            cal_os_tm <- enrl_tm + obs_os_tm
            cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 0)
          }
        }
        
        results <- rbind(results, data.frame(
          nsim = j,
          subjid = subjid,
          arm = as.character(arm),
          enrl_tm = enrl_tm,
          pfs_tm = pfs_tm,
          os_tm = os_actual_tm,
          drpot_tm = drpot_tm,
          obs_pfs_tm = obs_pfs_tm,
          cal_pfs_tm = cal_pfs_tm,
          cnsr_pfs = cnsr_pfs,
          obs_os_tm = obs_os_tm,
          cal_os_tm = cal_os_tm,
          cnsr_os = cnsr_os
        ))
      }
    }
    
    # 生成对照组数据
    for (i in 1:n_ctrl) {
      subjid <- paste(j, "CTRL", i, sep = "-")
      enrl_tm <- runif(1, 0, min(n_ctrl / enrl_rate, study_duration))
      
      u <- rCopula(1, clayton_cop)
      pfs_tm <- qexp(u[1], rate = log(2)/mt_pfs_ctrl)
      os_tm <- qexp(u[2], rate = log(2)/mt_os_ctrl)
      drpot_tm <- rexp(1, rate = drpot_hr)
      
      # PFS观测与删失
      if (pfs_tm > drpot_tm) {
        obs_pfs_tm <- drpot_tm
        cal_pfs_tm <- enrl_tm + obs_pfs_tm
        cnsr_pfs <- ifelse(cal_pfs_tm > study_duration, 1, 1)
      } else {
        obs_pfs_tm <- pfs_tm
        cal_pfs_tm <- enrl_tm + obs_pfs_tm
        cnsr_pfs <- ifelse(cal_pfs_tm > study_duration, 1, 0)
      }
      
      # OS观测与删失
      os_actual_tm <- pfs_tm + os_tm
      if (os_actual_tm > drpot_tm) {
        obs_os_tm <- drpot_tm
        cal_os_tm <- enrl_tm + obs_os_tm
        cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 1)
      } else {
        obs_os_tm <- os_actual_tm
        cal_os_tm <- enrl_tm + obs_os_tm
        cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 0)
      }
      
      if (cnsr_pfs == 1) {
        os_actual_tm <- obs_pfs_tm + os_tm
        if (os_actual_tm > drpot_tm) {
          obs_os_tm <- drpot_tm
          cal_os_tm <- enrl_tm + obs_os_tm
          cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 1)
        } else {
          obs_os_tm <- os_actual_tm
          cal_os_tm <- enrl_tm + obs_os_tm
          cnsr_os <- ifelse(cal_os_tm > study_duration, 1, 0)
        }
      }
      
      results <- rbind(results, data.frame(
        nsim = j,
        subjid = subjid,
        arm = "0",
        enrl_tm = enrl_tm,
        pfs_tm = pfs_tm,
        os_tm = os_actual_tm,
        drpot_tm = drpot_tm,
        obs_pfs_tm = obs_pfs_tm,
        cal_pfs_tm = cal_pfs_tm,
        cnsr_pfs = cnsr_pfs,
        obs_os_tm = obs_os_tm,
        cal_os_tm = cal_os_tm,
        cnsr_os = cnsr_os
      ))
    }
  }
  
  # 按模拟次数、PFS观测时间排序
  results <- results[order(results$nsim, results$cal_pfs_tm, results$cnsr_pfs), ]
  return(results)
}

三、函数调用示例

# 模拟参数设置
sim_data <- generate_multiple_survival_data(
  seed = 123,
  mt_pfs_trt = c(8, 10),    # 2个试验臂的PFS中位时间
  mt_os_trt = c(18, 22),    # 2个试验臂的OS中位时间
  mt_pfs_ctrl = 6,          # 对照组PFS中位时间
  mt_os_ctrl = 15,          # 对照组OS中位时间
  copula_param = 3,         # Copula参数,相关性较强
  n_trt = 100,              # 每个试验组100人
  n_ctrl = 200,             # 对照组200人
  enrl_rate = 20,           # 每月入组20人
  drpot_hr = log(2)/30,     # 失访中位时间30个月
  nsim = 5,                 # 模拟5次
  study_duration = 24,      # 研究时长24个月
  arms = 2
)

# 查看前5行数据
head(sim_data, 5)

四、后续log-rank检验示例

以第一次模拟的数据为例,分别对PFS和OS做log-rank检验:

# 提取第一次模拟的数据
sim1_data <- subset(sim_data, nsim == 1)

# PFS的log-rank检验
pfs_surv <- Surv(time = sim1_data$cal_pfs_tm, event = 1 - sim1_data$cnsr_pfs)
pfs_test <- survdiff(pfs_surv ~ arm, data = sim1_data)
print(pfs_test)

# OS的log-rank检验
os_surv <- Surv(time = sim1_data$cal_os_tm, event = 1 - sim1_data$cnsr_os)
os_test <- survdiff(os_surv ~ arm, data = sim1_data)
print(os_test)

五、关键说明

  • Copula选择:若需不同相关性结构,可替换为frankCopula或gumbelCopula,仅需修改Copula初始化代码;
  • 边缘分布:示例用指数分布,可根据需求替换为Weibull、对数正态等分布,修改对应分位数函数即可(如qweibull);
  • OS与PFS关联:示例假设OS是PFS之后的生存时间,可根据实际研究设计调整两者的关联逻辑。

内容的提问来源于stack exchange,提问作者sina wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:35:55