R Under development (unstable) (2024-04-17 r86441) -- "Unsuffered Consequences"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: aarch64-unknown-linux-gnu

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> library(survival)
> # Test data set 1 for Fine-Gray regression
> fdata <- data.frame(time  =c(1,2,3,4,4,4,5,5,6,8,8, 9,10,12),
+                     status=factor(c(1,2,0,1,0,0,2,1,0,0,2, 0,1 ,0), 0:2,
+                              c("cen", "type1", "type2")),      
+                     x     =c(5,4,3,1,2,1,1,2,2,4,6,1,2, 0),
+                     id = 1:14)
> test1 <- finegray(Surv(time, status) ~., fdata, count="fgcount")
> test2 <- finegray(Surv(time, status) ~x, fdata, etype="type2")
> 
> # When creating the censoring time distribution remember that
> #  censors happen after deaths, so the distribution does not drop until
> #  time 3+, 4+, 6+, 8+ and 9+
> csurv <- list(time=c(0, 3, 4, 6, 8, 9),
+               p = cumprod(c(1, 11/12, 8/10, 5/6, 3/4, 2/3)))
> #
> # For estimation of event type 1, the first subject of event type
> #  2 will have weights of curve$p over (0,3], (3,4], (4,6], (6,8], (8,9] 
> #  and (9,12].  All that really matters is the weight at times 1, 4, 5,
> #  and 10, however, which are the points at which events of type 1 happen
> #  
> # The next subject of event type 2 occurs at time 5, and will have a
> #  weight of (9,12] /(4,5] = (5*4*2)/(7*5*3) = 8/21 at time 10.  The last
> #  censor at time 6 has a weight of 2/3 at time 10.
> 
> all.equal(test1$id, c(1, 2,2,2,2, 3:6, 7, 7, 8:11, 11, 12:14))
[1] TRUE
> twt <- c(1, csurv$p[c(1,2,3,6)], 1,1,1, 1, 1, 5/12, 1,1,1,  
+                          1, 1/2, 1,1,1)
> all.equal(test1$fgwt, twt)
[1] TRUE
> #extra obs will end at times found in csurv$time, or max(time)=12
> all.equal(test1$fgstop[test1$fgcount>0], c(4,6,12, 12,12))
[1] TRUE
> 
> #
> # Verify the data reproduces a multi-state curve
> #  censoring times may be different in the two setups so only 
> #  compare at the event times
> sfit <- survfit(Surv(time, status) ~1, fdata)
> sfit1<- survfit(Surv(fgstart, fgstop, fgstatus) ~1, test1, weights=fgwt)
> sfita<- sfit["type1"]
> i1 <- sfita$n.event > 0
> i2 <- sfit1$n.event > 0
> all.equal(sfita$pstate[i1], 1- sfit1$surv[i2])
[1] TRUE
> 
> sfitb <- sfit["type2"]
> sfit2 <- survfit(Surv(fgstart, fgstop, fgstatus) ~1, test2, weights=fgwt)
> i1 <- sfitb$n.event > 0
> i2 <- sfit2$n.event > 0
> all.equal(sfitb$pstate[i1], 1- sfit2$surv[i2])
[1] TRUE
> 
> # Test strata.  Make a single data set that has fdata for the first 19
> #  rows, then fdata with outcomes switched for the second 19.  It should
> #  reprise test1 and test2 in a single call.
> fdata2 <- rbind(fdata, fdata)
> fdata2$group <- rep(1:2, each=nrow(fdata))
> temp <- c(1,3,2)[as.numeric(fdata$status)]
> fdata2$status[fdata2$group==2] <- factor(temp, 1:3, levels(fdata$status))
> test3 <- finegray(Surv(time, status) ~ .+ strata(group), fdata2)
> vtemp <- c("fgstart", "fgstop", "fgstatus", "fgwt")
> all.equal(test3[1:19, vtemp], test1[,vtemp])
[1] TRUE
> all.equal(test3[20:38, vtemp], test2[,vtemp], check.attributes=FALSE)
[1] TRUE
> 
> #
> # Test data set 2: use the larger MGUS data set
> #  Time is in months which leads to lots of ties
> etime <- with(mgus2, ifelse(pstat==0, futime, ptime))
> event <- with(mgus2, ifelse(pstat==0, 2*death, 1))
> e2 <- factor(event, 0:2, c('censor', 'pcm', 'death'))
> edata <- finegray(Surv(etime, e2) ~ sex + id, mgus2, etype="pcm")
> 
> # Build G(t) = the KM of the censoring distribution
> # An event at time x is not "at risk" for censoring at time x (Geskus 2011)
> tt <- sort(unique(etime))  # all the times
> ntime <- length(tt)
> nrisk <- nevent <- double(ntime)
> for (i in 1:ntime) {
+     nrisk[i] <- sum((etime > tt[i] & event >0) | (etime >= tt[i] & event==0))
+     nevent[i] <- sum(etime == tt[i] & event==0)
+ }
> G <- cumprod(1- nevent/nrisk)
> 
> # The weight is defined as w(t)= G(t-)/G(s-) where s is the event time
> # for a subject who experiences an endpoint other then the one of interest
> type2 <- event[edata$id]==2  # the rows to be expanded
> # These rows are copied over as is: endpoint 1 and censors
> all(edata$fgstop[!type2] == etime[edata$id[!type2]])
[1] TRUE
> all(edata$fgstart[!type2] ==0) 
[1] TRUE
> all(edata$fgwt[!type2] ==1)
[1] TRUE
> 
> tdata <- edata[type2,]  #expanded rows
> first <- match(tdata$id, tdata$id)  #points to the first row for each subject
> Gwt <- c(1, G)[match(tdata$fgstop, tt)]  # G(t-)
> all.equal(tdata$fgwt, Gwt/Gwt[first])
[1] TRUE
> 
> # Test data 3, left truncation.
> # Ties are assumed to be ordered as event, censor, entry
> # H(t) = truncation distribution, and is calculated on a reverse time scale
> # Since there is only one row per subject every obs is a "start" event.
> # Per equation 5 and 6 of Geskus both G and H are right continuous functions
> #  (the value at t- epsilon is different than the value at t).
> fdata <- data.frame(time1 = c(0,0,0,3,2,0,0,1,0,7,5, 0, 0, 0),
+                     time2 = c(1,2,3,4,4,4,5,5,6,8,8, 9,10,12),
+                     status= c(1,2,0,1,0,0,2,1,0,0,2, 0, 1 ,0), 
+                     x     = c(5,4,3,1,2,1,1,2,2,4,6, 1, 2, 0),
+                     id = 1:14)
> tt <- sort(unique(c(fdata$time1, fdata$time2)))
> ntime <- length(tt)
> Grisk <- Gevent <- double(ntime)
> Hrisk <- Hevent <- double(ntime)
> for (i in 1:ntime) {
+     Grisk[i] <- with(fdata, sum((time2 > tt[i] & status >0 & time1 < tt[i]) | 
+                                 (time2 >= tt[i] & status ==0 & time1 < tt[i])))
+     Gevent[i]<- with(fdata, sum(time2 == tt[i] & status==0))
+     Hrisk[i] <- with(fdata, sum(time2 > tt[i] & time1 <= tt[i]))
+     Hevent[i]<- with(fdata, sum(time1 == tt[i]))
+ }
> G <- cumprod(1- Gevent/pmax(1,Grisk))
> G2 <- survfit(Surv(time1, time2 - .1*(status !=0), status==0) ~1, fdata)
> all.equal(G2$surv[G2$n.event>0], G[Gevent>0])
[1] TRUE
> 
> H <- double(ntime)
> # The loop below uses the definition of equation 6 in Geskus
> for (i in 1:ntime) 
+     H[i] <- prod((1- Hevent/pmax(1, Hrisk))[-(i:1)])
> H2 <- rev(cumprod(rev(1 - Hevent/pmax(1, Hrisk))))  #alternate form
> H3 <- survfit(Surv(-time2, -time1, rep(1,14)) ~1, fdata) # alternate 3
> # c(0,H) = H(t-), H2 = H(t-) already due to the time reversal
> i2 <- sort(match(unique(fdata$time1), tt))  #time points in H3
> all.equal(c(0, H), c(H2, 1))  
[1] TRUE
> all.equal(H2[i2], rev(H3$surv))
[1] TRUE
> 
> fg <- finegray(Surv(time1, time2, factor(status, 0:2)) ~ x, id=id, fdata)
> stat2 <- !is.na(match(fg$id, fdata$id[fdata$status==2]))  #expanded ids
> all(fg$fgwt[!stat2] ==1)  #ordinary rows are left alone
[1] TRUE
> all(fg$fgstart[!stat2] == fdata$time1[fdata$status !=2])
[1] TRUE
> all(fg$fgstop[!stat2]  == fdata$time2[fdata$status !=2])
[1] TRUE
> 
> tdata <- fg[stat2,]
> index <- match(tdata$id, tdata$id)   # points to the first row for each
> Gwt <- c(1, G)[match(tdata$fgstop, tt)]  # G(t-)
> Hwt <- c(0, H)[match(tdata$fgstop, tt)]  # H(t-)
> all.equal(tdata$fgwt,  Gwt*Hwt/(Gwt*Hwt)[index])
[1] TRUE
> 
> #
> # Test data 4: mgus2 data on age scale
> #  The answer is incorrect due to roundoff, but consistent
> #
> start <- mgus2$age  # age in years
> end   <- start + etime/12  #etime in months
> tt <- sort(unique(c(start, end)))  # all the times
> ntime <- length(tt)
> Grisk <- Gevent <-  double(ntime)
> Hrisk <- Hevent <-  double(ntime)
> for (i in 1:ntime) {
+     Grisk[i] <- sum(((end > tt[i] & event >0) | (end >= tt[i] & event==0)) &
+                       (tt[i] > start))
+     Gevent[i] <- sum(end == tt[i] & event==0)
+     Hrisk[i]  <- sum(start <= tt[i] & end > tt[i])
+     Hevent[i] <- sum(start == tt[i])
+ }
> G <- cumprod(1 - Gevent/pmax(1, Grisk))         # pmax to avoid 0/0
> H <- rev(cumprod(rev(1-Hevent/pmax(1,Hrisk))))
> H <- c(H[-1], 1)  #make it right continuous
> 
> wdata <- finegray(Surv(start, end, e2) ~ ., id=id, mgus2, timefix=FALSE)
> type2 <- event[wdata$id]==2  # the rows to be expanded
> tdata <- wdata[type2,]
> first <- match(tdata$id, tdata$id)
> 
> Gwt <- c(1, G)[match(tdata$fgstop, tt)]  # G(t-)
> Hwt <- c(0, H)[match(tdata$fgstop, tt)]  # H(t-)
> all.equal(tdata$fgwt,  (Gwt/Gwt[first]) * (Hwt / Hwt[first]))
[1] TRUE
> 
> 
> proc.time()
   user  system elapsed 
  0.486   0.024   0.507