[R] Kaplan-Meier for left truncated data?

gb gb at stat.umu.se
Wed May 24 20:30:35 CEST 2000


On Sun, 21 May 2000, gb wrote:

> 
> Is there a function somewhere for estimating the survivor function
> (plus confidence limits) when data are both left truncated and right
> censored?  survfit in  survival5  doesn't like left truncation.

I got one answer, but a function for left censoring (for which I am 
grateful). So I wrote my own function. Here it is. Be careful, there
are a few nasty loops... and no serious testing yet.

Tips on improvements are welcome! (Note, it just plots, no return
values. Easy to change, though.)

Göran
------------------------------------------------------------------------
plot.surv <- function(enter = rep( 0, length(exit) ),
                      exit,
                      event = rep( 1, length(exit) ),
                      group = rep( 1, length(exit) ),
                      limits = F,
                      conf = 0.95)
  {
    ## Input data:
    ##
    ## enter : left truncation point
    ## exit  : exit time point
    ## event : if zero, a censored observation; otherwise an event.
    ## group : one curve for each distinct value in group.
    ## limits: if TRUE, and only one group, pointwise confidence
    ##         limits (Greenwoods formula, log(-log) type).
    ## conf  : confidence level. Can be given as a percentage.
    
    ## Check input data:
    n <- length(exit)
    if (length(enter) != n)stop("enter and exit must have equal length.")
    if (length(event) != n)
      stop("event and exit must have equal length.")
    if (length(group) != n)
      stop("group and exit must have equal length.")
    if (min(exit - enter) <= 0) stop("Interval length must be positive.")
    if (conf > 1) conf <- conf / 100 ## conf given as a percentage(?)
    if ( (conf < 0.5) | (conf >=1) ) stop("Bad conf value")

    grupp <- as.character(group)
   
    strata <- sort(unique(grupp))
    if (length(strata) > 9)
      stop("Too many groups. No more than 9 are allowed.")
    
    ##
    if (length(strata) > 1) limits <- F # No limits if multiple curves.
    gang <- 0
    for (stratum in strata)
      {
        atom <- table.events(enter[grupp == stratum],
                             exit[grupp == stratum],
                             event[grupp == stratum])
        
        gang <- gang + 1
        surv <- c( 1, cumprod(1 - atom$events / atom$riskset.sizes) )
        if (gang == 1)
          {
            plot(c(0, atom$times), surv, type = "s",
                 xlab = "duration", ylab = "Surviving fraction",
                 main = "Survivor function(s)", ylim = c(0, 1),
                 col = gang%%5)
            if (limits) ## Greenwood's formula,
                        ## Kalbfleisch & Prentice, p. 15 (note error!).
              {
                q.alpha <- abs(qnorm((1 - conf) / 2))
                survived <- (atom$riskset.size - atom$events)
                se <- sqrt(cumsum(atom$events /
                                   ( atom$riskset.sizes * survived )
                                   )
                           )/
                            cumsum(-log(survived / atom$riskset.sizes))
                upper <- surv ^ exp(q.alpha * c(0, se))
                lower <- surv ^ exp(-q.alpha * c(0, se))
                lines(c(0, atom$times), upper, type = "s",
                      col = gang%%5 + 1)
                lines(c(0, atom$times), lower, type = "s",
                      col = gang%%5 + 1)
              }
          }
        else
          {
            lines(c(0, atom$times), surv, type = "s", 
                  col = gang%%5)
          }
      }
    abline(h=0)
    if (length(strata) > 1)
      {
        ## legend.txt <- as.character(strata)
        colors <- (1:length(strata))%%5
        legend(0, 0.6, lty = 1, legend = strata, col = colors)
      }
  }

table.events <- function(enter = rep(0, length(exit)),
                         exit,
                         event)
{
  n <- length(exit)

  ## Check input data:
  if ( length(enter) != n ) stop("enter and exit must have equal length.")
  if ( length(event) != n ) stop("event and exit must have equal length.")
  ##
  
  event <- (event != 0) ## 0 (F) = censoring, else (T) = event

  times <- c(unique(sort(exit[event])))
  nn <- length(times)

  rs.size <- double(nn)
  n.events <- double(nn)

  for (i in 1:nn) ## Try to avoid this loop!
    {
      rs.size[i] <- sum( (enter < times[i]) &
                        (exit >= times[i]) )
      n.events[i] <- sum( (exit == times[i]) & event )
    }

  stop.at <- which(rs.size == n.events)
  if (length(stop.at))
    {
      stop.at <- min(stop.at) - 1
      if (stop.at <= 0) stop("Bad data. All died immediately!")
      times <- times[1:stop.at]
      n.events <- n.events[1:stop.at]
      rs.size <- rs.size[1:stop.at]
    }
      
  return ( list(times         = times,
                events        = n.events,
                riskset.sizes = rs.size)
          )
}

Have fun! And send bug reports to
-- 
 Göran Broström                    e-mail: gb at stat.umu.se
 Professor                           tel: +46 90 786 5223
 Department of Statistics            fax: +46 90 786 6614
 Umeå University
 SE-90187 Umeå, Sweden             http://www.stat.umu.se/egna/gb

-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
r-help mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html
Send "info", "help", or "[un]subscribe"
(in the "body", not the subject !)  To: r-help-request at stat.math.ethz.ch
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._



More information about the R-help mailing list