--- title: "relievED - marginal means models" author: "S.L. van der Pas" date: "`r Sys.Date()`" output: pdf_document --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) knitr::opts_chunk$set(tidy.opts=list(width.cutoff=60), tidy = TRUE) library(readxl) #voor inlezen data library(lubridate) library(survival) library(mstate) library(mets) loc.data <- "/Users/stephanie/Documents/Consultatie Amsterdam UMC/2201_Selder/Data/" ``` # Inlezen en controleren data ```{r} df <- read_excel(paste(loc.data, "220412_Data_relievED.xlsx", sep = "")) df <- as.data.frame(df) dim(df) head(df) ``` We controleren het aantal deelnemers per groep. ```{r} table(df$`Group (1 = kardia, 2 = control)`, useNA = "ifany") ``` 123 in de kardia-groep, 81 in de controlegroep. Een tabel van het aantal ER visites, per groep. ```{r} table(df$`Group (1 = kardia, 2 = control)`, df$`ER visits`, useNA = "ifany") ``` We voegen de einde follow-up datum voor iedereen toe, 1 jaar na inclusie. ```{r} df$EndFollowUp <- df$`Datum PVI` %m+% years(1) ``` # Data herstructureren - voor Possible/Definite rhythm disorder De data moet worden geherstructureerd, zodat er 1 regel is per event. Dit doen we voor elke uitkomst apart. We schrijven een speciale functie om per patient de juiste structuur te vinden. Eerst voor het bekijken van een specifieke uitkomst (Possible of Definite rhythm disorder). ```{r} #Voor uitkomst: Possible rhythm disorder of Definite rhythm disorder create.mini.long.df <- function(pt.index, string.reden, data.name){ #pt.index: regelnummer dataframe voor patient #string.reden: kan "Possible" of "Definite" zijn pt.df <- data.name[pt.index, ] df.reden.cols <- pt.df[, grep( string.reden , names(pt.df) ) ] ind.reden <- which(df.reden.cols == 1) visits <- substring(colnames(df.reden.cols[ind.reden]), 1, 3) #extraheer of het 'ER1', 'ER2' etc was nr.visits <- length(visits) if(nr.visits > pt.df$`ER visits`){print(pt.df$StudyId)} #sanity check if(nr.visits == 0){ df.tmp <- data.frame( id = pt.df$StudyID, tstart = 0, tstop = 365, status = 0, group = pt.df$`Group (1 = kardia, 2 = control)` ) } #end if if(nr.visits > 0){ #We berekenen het aantal dagen tussen elke visite col.names.dates <- paste(visits, "Datum") all.dates <- c(pt.df$`Datum PVI`, subset(pt.df, select= col.names.dates), pt.df$EndFollowUp) diff.days <- as.numeric(difftime(all.dates, pt.df$`Datum PVI`, units = c("days"))) #verschil in dagen voor elk event sinds inclusie if(sum(diff.days > 365)){print(pt.df$StudyId)} #sanity check df.tmp <- data.frame( id = rep(pt.df$StudyID, (nr.visits+1)), tstart = diff.days[1:(nr.visits+1)], tstop = diff.days[2:length(diff.days)], status = c(rep(1, nr.visits), 0), group = rep(pt.df$`Group (1 = kardia, 2 = control)`, (nr.visits+1)) ) } #end if return(df.tmp) } #end create.mini.long.df ``` We testen de functie voor een aantal patienten. ```{r} which(df$`ER visits` > 0) df[1, ] create.mini.long.df(1, "Possible", df) #0 visites create.mini.long.df(1, "Definite", df) #0 visites df[5, ] create.mini.long.df(5, "Possible", df) #3 visites create.mini.long.df(5, "Definite", df) #2 visites df[190, ] create.mini.long.df(190, "Possible", df) #1 visite create.mini.long.df(190, "Definite", df) #1 visite df[135, ] create.mini.long.df(135, "Possible", df) #3 visites create.mini.long.df(135, "Definite", df) #0 visites ``` Dit lijkt goed te werken. Na het omvormen van de hele data set in het 'start-stop' format, zullen we nog wat controles doen. # Data herstructureren - voor visite 'any reason' De data moet worden geherstructureerd, zodat er 1 regel is per event. Een event is nu een bezoek om wat voor reden dan ook. Dit is terug te vinden door te kijken of er een datum staat in 'ERX Datum', met X een getal. We schrijven een speciale functie om per patient de juiste structuur te vinden. ```{r} #Voor uitkomst: ER bezoek create.mini.long.df.any.reason <- function(pt.index, data.name){ #pt.index: regelnummer dataframe voor patient pt.df <- data.name[pt.index, ] df.datum.cols <- pt.df[, grep( "Datum" , names(pt.df) ) ] ind.bezoek <- which(!is.na(df.datum.cols)) #ind.bezoek kan ook Dat bevatten wegens "Datum PVI", deze verwijderen we ind.bezoek.zonder.pvi <- grep("ER", names(df.datum.cols[ind.bezoek])) visits <- substring(colnames(df.datum.cols[ind.bezoek.zonder.pvi]), 1, 3) #extraheer of het 'ER1', 'ER2' etc was nr.visits <- length(visits) if(nr.visits != pt.df$`ER visits`){print(pt.df$StudyId)} #sanity check if(nr.visits == 0){ df.tmp <- data.frame( id = pt.df$StudyID, tstart = 0, tstop = 365, status = 0, group = pt.df$`Group (1 = kardia, 2 = control)` ) } #end if if(nr.visits > 0){ #We berekenen het aantal dagen tussen elke visite col.names.dates <- paste(visits, "Datum") all.dates <- c(pt.df$`Datum PVI`, subset(pt.df, select= col.names.dates), pt.df$EndFollowUp) diff.days <- as.numeric(difftime(all.dates, pt.df$`Datum PVI`, units = c("days"))) #verschil in dagen voor elk event sinds inclusie if(sum(diff.days > 365)){print(pt.df$StudyId)} #sanity check df.tmp <- data.frame( id = rep(pt.df$StudyID, (nr.visits+1)), tstart = diff.days[1:(nr.visits+1)], tstop = diff.days[2:length(diff.days)], status = c(rep(1, nr.visits), 0), group = rep(pt.df$`Group (1 = kardia, 2 = control)`, (nr.visits+1)) ) } #end if return(df.tmp) } #end create.mini.long.df.any.reason ``` We testen de functie weer voor een aantal patienten. ```{r} which(df$`ER visits` > 0) df[1, ] create.mini.long.df.any.reason(1, df) #0 visites df[5, ] create.mini.long.df.any.reason(5, df) #3 visites df[190, ] create.mini.long.df.any.reason(190, df) #1 visite df[135, ] create.mini.long.df.any.reason(135, df) #4 visites ``` Dit lijkt goed te werken. Na het omvormen van de hele data set in het 'start-stop' format, zullen we nog wat controles doen. # Analyse voor Possible rhythm disorder We gebruiken de net geschreven functie om de dataset in het juiste format te maken, met uitkomst "Possible rhythm disorder". ```{r} df.possible <- create.mini.long.df(pt.index = 1, string = "Possible", df) for(i in 2:dim(df)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Possible", df) df.possible <- rbind(df.possible, df.current.pt) } ``` We voeren controles uit op de net gemaakte data set. ```{r} head(df.possible) tail(df.possible) table( table(df.possible$id) - df$`ER visits` ) #df.possible mag hooguit 1 regel meer hebben dan ER visits ``` Deze check is geslaagd. We kijken nu of de stop-tijd wel altijd groter is dan de start-tijd. ```{r} ind.time.issue <- which(df.possible$tstop - df.possible$tstart <= 0) df.possible[ind.time.issue, ] ``` Deze check is geslaagd. ```{r} survcheck(Surv(tstart, tstop, status) ~ 1, id=id, data=df.possible) ``` We gaan verder met de analyse. In deze setting is de cumulative hazard gelijk aan de mean cumulative function. ```{r} model.possible = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.possible) summary(model.possible) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.possible, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Possible rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia"), col = c("red", "black"), lwd = 2, bty='n') ``` Het plotten van de standard errors is hier niet mogelijk. We plotten de cumulative hazard ook met behulp van \url{https://cran.r-project.org/web/packages/mets/vignettes/recurrent-events.html}. Hier zit geen aanname van een Cox-model in, daarom zien de mean functions er net wat anders uit. Hier kunnen de confidence intervals wel zichtbaar worden gemaakt. ```{r} xr <- phreg(Surv(tstart,tstop,status) ~ strata(group) + cluster(id), data = df.possible) bplot(xr, se=TRUE, las = 1, main = "Possible rhythm disorder", ylim = c(0, 0.8), legend = F, lty = 1) legend("topleft", c("Control", "Kardia"), col = c("red", "black"), lwd = 1, bty = 'n') ``` # Analyse voor Definite rhythm disorder We gebruiken de net geschreven functie om de dataset in het juiste format te maken, met uitkomst "Definite rhythm disorder". ```{r} df.definite <- create.mini.long.df(pt.index = 1, string = "Definite", df) for(i in 2:dim(df)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Definite", df) df.definite <- rbind(df.definite, df.current.pt) } ``` We voeren controles uit op de net gemaakte data set. ```{r} head(df.definite) tail(df.definite) table( table(df.definite$id) - df$`ER visits` ) #df.possible mag hooguit 1 regel meer hebben dan ER visits ``` Deze check is geslaagd. We kijken nu of de stop-tijd wel altijd groter is dan de start-tijd. ```{r} ind.time.issue <- which(df.definite$tstop - df.definite$tstart <= 0) df.definite[ind.time.issue, ] #geen problemen ``` ```{r} survcheck(Surv(tstart, tstop, status) ~ 1, id=id, data=df.definite) ``` We gaan verder met de analyse. In deze setting is de cumulative hazard gelijk aan de mean cumulative function. ```{r} model.definite = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.definite) summary(model.definite) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.definite, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Definite rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia"), col = c("red", "black"), lwd = 2, bty='n') ``` Het plotten van de standard errors is hier niet mogelijk. We plotten de cumulative hazard ook met behulp van \url{https://cran.r-project.org/web/packages/mets/vignettes/recurrent-events.html}. Hier zit geen aanname van een Cox-model in, daarom zien de mean functions er net wat anders uit. Hier kunnen de confidence intervals wel zichtbaar worden gemaakt. ```{r} xr <- phreg(Surv(tstart,tstop,status) ~ strata(group) + cluster(id), data = df.definite) bplot(xr, se=TRUE, las = 1, main = "Definite rhythm disorder", ylim = c(0, 0.8), legend = F, lty = 1) legend("topleft", c("Control", "Kardia"), col = c("red", "black"), lwd = 1, bty = 'n') ``` # Analyse voor Any reason We gebruiken de net geschreven functie om de dataset in het juiste format te maken, met uitkomst "ER visite any reason" ```{r} df.any <- create.mini.long.df.any.reason(pt.index = 1, df) for(i in 2:dim(df)[1]){ df.current.pt <- create.mini.long.df.any.reason(pt.index = i, df) df.any <- rbind(df.any, df.current.pt) } ``` We voeren controles uit op de net gemaakte data set. ```{r} head(df.any) tail(df.any) table( table(df.any$id) - df$`ER visits` ) #df.any moet 1 regel meer hebben dan ER visits ``` Deze check is geslaagd. We kijken nu of de stop-tijd wel altijd groter is dan de start-tijd. ```{r} ind.time.issue <- which(df.any$tstop - df.any$tstart <= 0) df.any[ind.time.issue, ] ``` ```{r} survcheck(Surv(tstart, tstop, status) ~ 1, id=id, data=df.any) ``` We gaan verder met de analyse. In deze setting is de cumulative hazard gelijk aan de mean cumulative function. ```{r} model.any = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.any) summary(model.any) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.any, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "ER visit (any reason)", col = c("black", "red")) legend("topleft", c("Control", "Kardia"), col = c("red", "black"), lwd = 2, bty='n') ``` Het plotten van de standard errors is hier niet mogelijk. We plotten de cumulative hazard ook met behulp van \url{https://cran.r-project.org/web/packages/mets/vignettes/recurrent-events.html}. Hier zit geen aanname van een Cox-model in, daarom zien de mean functions er net wat anders uit. Hier kunnen de confidence intervals wel zichtbaar worden gemaakt. ```{r} xr <- phreg(Surv(tstart,tstop,status) ~ strata(group) + cluster(id), data = df.any) bplot(xr, se=TRUE, las = 1, main = "ER visit (any reason)", ylim = c(0, 0.8), legend = F, lty = 1) legend("topleft", c("Control", "Kardia"), col = c("red", "black"), lwd = 1, bty = 'n') ``` ## Sensitiviteitsanalyse ivm corona De follow-up van de Kardia-groep overlapt deels met de corona pandemie. In de voorgaande, primaire analyse, nemen we aan dat de corona pandemie geen invloed heeft gehad op de uitkomst (ER bezoek). In deze sectie voeren we daar een sensitiviteitsanalyse op uit, door de Kardia-groep in te perken zodat er minder overlap is met de corona-periode. Daartoe herhalen we bovenstaande analyses met telkens de volledige controlegroep, en de Kardia-groep ingeperkt tot: * Inclusiedatum uiterlijk 31-12-2019 (ca 3 maanden voor start pandemie) * Inclusiedatum uiterlijk 30-9-2019 (ca 6 maanden voor start pandemie) * Inclusiedatum uiterlijk 30-6-2019 (ca 9 maanden voor start pandemie) ```{r} df$DateInclusion <- as.Date(df$`Datum PVI`, "%y-%m-%d") df.dec19 <- subset(df, DateInclusion <= as.Date("2019-12-31")) df.sep19 <- subset(df, DateInclusion <= as.Date("2019-09-30")) df.jun19 <- subset(df, DateInclusion <= as.Date("2019-06-30")) dim(df.dec19) table(df.dec19$`Group (1 = kardia, 2 = control)`, useNA = "ifany") dim(df.sep19) table(df.sep19$`Group (1 = kardia, 2 = control)`, useNA = "ifany") dim(df.jun19) table(df.jun19$`Group (1 = kardia, 2 = control)`, useNA = "ifany") ``` Er worden dan resp. 87, 65 en 42 deelnemers uit de Kardia-groep meegenomen (en 81 uit de controlegroep). ### Inclusie uiterlijk 31-12-2019 ```{r} df.possible.dec19 <- create.mini.long.df(pt.index = 1, string = "Possible", df.dec19) for(i in 2:dim(df.dec19)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Possible", df.dec19) df.possible.dec19 <- rbind(df.possible.dec19, df.current.pt) } model.possible.dec19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.possible.dec19) summary(model.possible.dec19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.possible.dec19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Possible rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-12-31)"), col = c("red", "black"), lwd = 2, bty='n') ``` ```{r} df.definite.dec19 <- create.mini.long.df(pt.index = 1, string = "Definite", df.dec19) for(i in 2:dim(df.dec19)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Definite", df.dec19) df.definite.dec19 <- rbind(df.definite.dec19, df.current.pt) } model.definite.dec19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.definite.dec19) summary(model.definite.dec19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.definite.dec19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Definite rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-12-31)"), col = c("red", "black"), lwd = 2, bty='n') ``` ```{r} df.any.dec19 <- create.mini.long.df.any.reason(pt.index = 1, df.dec19) for(i in 2:dim(df.dec19)[1]){ df.current.pt <- create.mini.long.df.any.reason(pt.index = i, df.dec19) df.any.dec19 <- rbind(df.any.dec19, df.current.pt) } model.any.dec19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.any.dec19) summary(model.any.dec19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.any.dec19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "ER visit (any reason)", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-12-31)"), col = c("red", "black"), lwd = 2, bty='n') ``` ### Inclusie uiterlijk 30-09-2019 ```{r} df.possible.sep19 <- create.mini.long.df(pt.index = 1, string = "Possible", df.sep19) for(i in 2:dim(df.sep19)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Possible", df.sep19) df.possible.sep19 <- rbind(df.possible.sep19, df.current.pt) } model.possible.sep19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.possible.sep19) summary(model.possible.sep19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.possible.sep19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Possible rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-09-30)"), col = c("red", "black"), lwd = 2, bty='n') ``` ```{r} df.definite.sep19 <- create.mini.long.df(pt.index = 1, string = "Definite", df.sep19) for(i in 2:dim(df.sep19)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Definite", df.sep19) df.definite.sep19 <- rbind(df.definite.sep19, df.current.pt) } model.definite.sep19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.definite.sep19) summary(model.definite.sep19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.definite.sep19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Definite rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-09-30)"), col = c("red", "black"), lwd = 2, bty='n') ``` ```{r} df.any.sep19 <- create.mini.long.df.any.reason(pt.index = 1, df.sep19) for(i in 2:dim(df.sep19)[1]){ df.current.pt <- create.mini.long.df.any.reason(pt.index = i, df.sep19) df.any.sep19 <- rbind(df.any.sep19, df.current.pt) } model.any.sep19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.any.sep19) summary(model.any.sep19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.any.sep19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "ER visit (any reason)", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-09-30)"), col = c("red", "black"), lwd = 2, bty='n') ``` ### Inclusie uiterlijk 30-06-2019 ```{r} df.possible.jun19 <- create.mini.long.df(pt.index = 1, string = "Possible", df.jun19) for(i in 2:dim(df.jun19)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Possible", df.jun19) df.possible.jun19 <- rbind(df.possible.jun19, df.current.pt) } model.possible.jun19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.possible.jun19) summary(model.possible.jun19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.possible.jun19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Possible rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-06-30)"), col = c("red", "black"), lwd = 2, bty='n') ``` ```{r} df.definite.jun19 <- create.mini.long.df(pt.index = 1, string = "Definite", df.jun19) for(i in 2:dim(df.jun19)[1]){ df.current.pt <- create.mini.long.df(pt.index = i, string = "Definite", df.jun19) df.definite.jun19 <- rbind(df.definite.jun19, df.current.pt) } model.definite.jun19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.definite.jun19) summary(model.definite.jun19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.definite.jun19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "Definite rhythm disorder", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-06-30)"), col = c("red", "black"), lwd = 2, bty='n') ``` ```{r} df.any.jun19 <- create.mini.long.df.any.reason(pt.index = 1, df.jun19) for(i in 2:dim(df.jun19)[1]){ df.current.pt <- create.mini.long.df.any.reason(pt.index = i, df.jun19) df.any.jun19 <- rbind(df.any.jun19, df.current.pt) } model.any.jun19 = coxph(Surv(tstart, tstop, status) ~ group + cluster(id), method = "breslow", data = df.any.jun19) summary(model.any.jun19) #Plot van de cumulative hazard volgens het model df.plot <- data.frame(group = c(1, 2)) plot.cumhaz <- survfit(model.any.jun19, newdata = df.plot) plot(plot.cumhaz, fun = "cumhaz", ylim = c(0, 0.8), las = 1, main = "ER visit (any reason)", col = c("black", "red")) legend("topleft", c("Control", "Kardia (inclusion <= 2019-06-30)"), col = c("red", "black"), lwd = 2, bty='n') ```