Longitudinal Ordinal Analysis for Violet2

Introduction

VIOLET 2 was a randomized clinical trial of seriously ill adults in ICUs to study the effectiveness of vitamin D vs. placebo. It has the advantage of having daily ordinal outcomes assessed for 28 days. The original NEJM paper focused on 1078 patients with confirmed baseline vitamin D deficiency. The analyses presented here use 1352 of the original 1360 randomized patients. Of the 1078 patients, 72 of 539 placebo patients died by day 28, and 92 of 531 vitamin D patients died (proportions of 0.13 and 0.17). Our starting dataset has 1359 patients, with death counts of 89 and 112 (proportions 0.13 and 0.16).

See this and related analyses of the ORCHID study and some additional analysis of VIOLET 2. See this for a study of modeling irregular time spacings with Markov models. Go here for a list of papers about Markov longitudinal modeling of binary and ordinal outcomes.

require(rmsb)
knitrSet(lang='markdown', w=7, h=7, dev='png', fig.path='png/stat-')
alrsimdone <- TRUE
Load(violet2)
d <- violet2
omit <- c('Home', 'Dead', 'In Post Hospital facility')
k <- with(d, sum(day == 1 & bstatus %in% omit))
d <- subset(d, bstatus %nin% omit)
oldstatus <- d$status
levels(d$status) <-
  list(Dead='Dead',
       'Vent/ARDS' = c('On Vent', 'ARDS'),
       'In Hospital/Facility' = c('In Hospital',
                                  'In Post Hospital Facility'),
       Home = 'Home')
table(oldstatus, d$status)
##                            
## oldstatus                    Dead Vent/ARDS In Hospital/Facility  Home
##   Dead                       3749         0                    0     0
##   ARDS                          0       377                    0     0
##   On Vent                       0      2966                    0     0
##   In Hospital                   0         0                 8191     0
##   In Post Hospital Facility     0         0                 2937     0
##   Home                          0         0                    0 19586
d$bstatus        <- droplevels(d$bstatus)
label(d$day)     <- 'Day'
label(d$bstatus) <- 'Baseline Status'
dcf <- d
saveRDS(dcf, 'dcf.rds')
dd <- datadist(d); options(datadist='dd')
options(prType='html')

Descriptive Statistics

7 patients with day 0 status of dead, at home, or in post hospital facility were dropped.

html(describe(d))
d

12 Variables   37856 Observations

id: Reference Id
nmissingdistinct
3785601352
lowest :A01-00208A01-00210A01-00261A01-00337A01-00354
highest:W05-00581W05-00589W05-00590W05-00591W05-00592

bstatus: Baseline Status
image
nmissingdistinct
37828283
 Value             ARDS     On Vent In Hospital
 Frequency         2828       10444       24556
 Proportion       0.075       0.276       0.649
 

day: Day
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
378560280.99914.59.322 2.00 3.00 7.7514.5021.2526.0027.00
lowest : 1 2 3 4 5 , highest: 24 25 26 27 28
status
image
nmissingdistinct
37806504
 Value                      Dead            Vent/ARDS In Hospital/Facility
 Frequency                  3749                 3343                11128
 Proportion                0.099                0.088                0.294
                                
 Value                      Home
 Frequency                 19586
 Proportion                0.518
 

age: SCR age enrolled
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
37856078156.3118.1427.0033.0046.0058.0067.2576.0082.00
lowest : 18 19 20 21 22 , highest: 91 92 93 94 96
treatment
nmissingdistinct
3785602
 Value        Placebo Vitamin D
 Frequency      18676     19180
 Proportion     0.493     0.507
 

lips: Baseline LIPS Score
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
378560280.9965.1753.399 0 0 3 5 7 910
lowest : 0.0 1.0 1.5 2.0 2.5 , highest: 12.0 12.5 13.0 13.5 14.5
charlson: Baseline Charlson Score
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
366801176170.9873.8243.2420023589
lowest : 0 1 2 3 4 , highest: 12 13 14 15 18
 Value          0     1     2     3     4     5     6     7     8     9    10    11
 Frequency   4816  4312  4648  4704  4760  4312  2800  2072  1708   896   644   280
 Proportion 0.131 0.118 0.127 0.128 0.130 0.118 0.076 0.056 0.047 0.024 0.018 0.008
                                         
 Value         12    13    14    15    18
 Frequency    392   168   112    28    28
 Proportion 0.011 0.005 0.003 0.001 0.001
 

sofa: SOFA Total Score
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
378560190.9935.3824.028 0 1 3 5 81012
lowest : 0 1 2 3 4 , highest: 14 15 16 17 18
 Value          0     1     2     3     4     5     6     7     8     9    10    11
 Frequency   2436  3444  3136  3808  4424  3752  3780  2968  2492  2128  1820  1540
 Proportion 0.064 0.091 0.083 0.101 0.117 0.099 0.100 0.078 0.066 0.056 0.048 0.041
                                                     
 Value         12    13    14    15    16    17    18
 Frequency    896   476   308   168   140    84    56
 Proportion 0.024 0.013 0.008 0.004 0.004 0.002 0.001
 

vitD: LCMS Day 0
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
364001456554113.748.279 3.898 5.163 8.04712.60017.80023.31027.300
lowest : 0.0 0.6 0.8 1.3 1.5 , highest: 48.3 56.2 57.0 67.6 68.0
ddeath: Day of Death (29 if alive)
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
378560280.37526.234.918 5132929292929
lowest : 1 2 3 4 5 , highest: 25 26 27 28 29
dhome: Day Discharged to Home (NA if never)
image
nmissingdistinctInfoMeanGmd.05.10.25.50.75.90.95
2679611060280.9948.1996.851 2 2 4 6111825
lowest : 1 2 3 4 5 , highest: 24 25 26 27 28

Summarize deaths occurring after a patient arrives at home (there were no other events).

u <- subset(d, ! is.na(dhome) & day >= dhome)
np <- length(unique(u$id))
ud <- subset(u, status == 'Dead')
nd <- length(unique(ud$id))
cat(np, 'patients discharged to home\n',
    nd, 'died post discharge\n')
957 patients discharged to home
 29 died post discharge

Event Chart for Random Sample of Patients

A status timeline is shown for each of 65 randomly chosen patients. The patients are sorted so that the worst outcomes at the last follow-up day are at the top. One day is subtract from the time variable to make follow-up start at day 0.

ssamp <- sample(unique(d$id), 65, FALSE)
dr        <- subset(d, id %in% ssamp)
dr        <- subset(dr, day <= ddeath)
dr$id     <- as.integer(as.factor(dr$id))
dr$status <- factor(dr$status, levels=rev(levels(dr$status)))
dr$day    <- dr$day - 1
multEventChart(status ~ day + id, data=dr,
               absorb='Dead', sortbylast = TRUE) +
  theme_classic() +
  theme(legend.position='bottom')

Summary of Successive State Transitions

propsTrans(status ~ day + id, data=d, maxsize=6) +
  theme(legend.position='bottom', axis.text.x=element_text(angle=90, hjust=1))
# For a nice event chart for this type of data see https://livefreeordichotomize.com/2020/05/21/survival-model-detective-1

Category Proportions Over Time and PO Assumption

The following plot shows how the mix of outcomes changes over time stratified also by treatment.

propsPO(status ~ treatment + day, data=subset(d, day < 15), nrow=1) +
  theme(legend.position='bottom', axis.text.x=element_text(angle=90, hjust=1))

propsPO(status ~ treatment + day, data=subset(d, day > 14), nrow=1) +
  theme(legend.position='bottom', axis.text.x=element_text(angle=90, hjust=1))

Ignoring treatment, compare the observed patterns with what would be expected had the proportional odds assumption been satisfied, fit a PO model containing only day as a predictor, and allow the day effect to be a flexible spline function. Then compute odds ratios against day 1 and apply those odds ratios to the observed proportions from day 1. This is for the original dataset with death carried forward.

f <- lrm(status ~ rcs(day, 5), data=d)
g <- Function(f)   # derive predicted log odds as a function of day
# Make another function that gets the OR vs. day=1
dor <- function(day) exp(g(day) - g(1))
propsPO(status ~ day, odds.ratio=dor, data=d) +
  theme(legend.position='bottom')

Carrying death forward results in a devastating violation of the PO assumption with respect to time. For example, the incidence of death keeps increasing while the incidence of vent/ARDS is decreasing. Now we repeat the process but terminating follow-up at death.

u <- subset(d, day <= ddeath)
f <- lrm(status ~ rcs(day, 5), data=u)
g <- Function(f)   # derive predicted log odds as a function of day
dor <- function(day) exp(g(day) - g(1))
propsPO(status ~ day, odds.ratio=dor, data=u) +
  theme(legend.position='bottom')

The non-PO with respect to time is less problematic when follow-up stops at death but continues for nonfatal events. Note that in the limiting case where death is the only event and the time intervals are small, such truncation results in a binary logistic odds ratio for treatment being almost exactly the hazard ratio in a Cox model analysis when interval risks are small. [However the absolute risk estimates from such a model as a function of time are hard to interpret.].

Note: The lower panel of the above graph is dependent on the choice of reference day. The proportions were estimated from day 1 alone. Instead let’s estimate them from day 4 and repeat.

dor <- function(day) exp(g(day) - g(4))
propsPO(status ~ day, odds.ratio=dor, ref=4, data=u) +
  theme(legend.position='bottom')

Now proportional odds holds with respect to time.

Now do a similar analysis for the second most important predictor: baseline status, still truncating at death. ARDS is taken as the reference group for computing proportions in the follow-up status categories for the purpose of applying the PO assumption.

f <- lrm(status ~ bstatus, data=u)
g <- Function(f)   # derive predicted log odds as a function of bstatus
dor <- function(bstatus) exp(g(bstatus) - g('ARDS'))
propsPO(status ~ bstatus, odds.ratio=dor, ref='ARDS', data=u) 

Proportional odds is reasonably well satisfied with regard to baseline status except that for patients in hospital at baseline, without ARDS or vent, there is an overprediction of the proportion in the vent/ARDS category. Because these summaries are confounded with time, let’s repeat for day 3 only.

u3 <- subset(u, day == 3)
f <- lrm(status ~ bstatus, data=u3)
g <- Function(f)   # derive predicted log odds as a function of bstatus
dor <- function(bstatus) exp(g(bstatus) - g('ARDS'))
propsPO(status ~ bstatus, odds.ratio=dor, ref='ARDS', data=u3) 

See here for assessment of PO for other variables.

GEE-Type Proportional Odds Modeling

This analysis uses a working independence proportional odds model, then the variance-covariance matrix is corrected for within-patient correlation using the robust cluster sandwhich covariance estimator. Covariates are allowed to have nonlinear effects through the use of restricted cubic spline functions with 4 knots. A dot charts shows the relative importance of the predictors. Note that the lrm function in the R rms package frames the model in terms of \(P(Y \geq y | X)\) whereas most software uses \(P(Y \leq y | X)\).

There are several patients excluded in the analysis below due to missing baseline vitamin D levels.

f <- lrm(status ~ bstatus + rcs(day, 5) + rcs(age, 5) + rcs(lips, 5) +
             rcs(charlson, 4) + rcs(sofa, 5) + rcs(vitD, 5) + treatment,
         data=d, tol=1e-13, x=TRUE, y=TRUE)
g <- robcov(f, d$id)
plot(anova(g), trans=sqrt)

We reallocate degrees of freedom for the terms in the model so that the predictors with the most potential from the dot chart are given more parameters. This is because complex relationships that are week will not suffer from underfitting. The likelihood ratio and score \(\chi^2\) statistics in the output below should be ignored because they are not adjusted for intra-cluster correlation.

f <- lrm(status ~ bstatus + rcs(day, 5) + rcs(age, 3) + rcs(lips, 3) +
             rcs(charlson, 4) + rcs(sofa, 4) + rcs(vitD, 3) + treatment,
         data=d, x=TRUE, y=TRUE)
g <- robcov(f, d$id)
g

Logistic Regression Model

 lrm(formula = status ~ bstatus + rcs(day, 5) + rcs(age, 3) + 
     rcs(lips, 3) + rcs(charlson, 4) + rcs(sofa, 4) + rcs(vitD, 
     3) + treatment, data = d, x = TRUE, y = TRUE)
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                 3423                 3043                10467 
                 Home 
                18353 
 

Frequencies of Missing Values Due to Each Variable

Model Likelihood
Ratio Test
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 35286 LR χ2 9851.12 R2 0.271 C 0.749
Cluster on d$id d.f. 19 g 1.208 Dxy 0.498
Clusters 1261 Pr(>χ2) <0.0001 gr 3.348 γ 0.498
max |∂log L/∂β| 1×10-10 gp 0.158 τa 0.311
Brier 0.122
β S.E. Wald Z Pr(>|Z|)
y≥Vent/ARDS   0.5629  0.4329 1.30 0.1935
y≥In Hospital/Facility  -0.3008  0.4376 -0.69 0.4918
y≥Home  -2.1195  0.4404 -4.81 <0.0001
bstatus=On Vent   0.4439  0.2116 2.10 0.0359
bstatus=In Hospital   1.4338  0.2179 6.58 <0.0001
day   0.1833  0.0100 18.39 <0.0001
day’  -0.3621  0.0446 -8.12 <0.0001
day’’   0.5972  0.1172 5.10 <0.0001
day’’’  -0.1138  0.1323 -0.86 0.3897
age   0.0013  0.0081 0.16 0.8703
age’  -0.0153  0.0075 -2.04 0.0414
lips  -0.0397  0.0456 -0.87 0.3837
lips’  -0.0151  0.0469 -0.32 0.7483
charlson  -0.1797  0.1106 -1.63 0.1041
charlson’   0.3888  0.5018 0.77 0.4384
charlson’’  -0.7933  0.9951 -0.80 0.4253
sofa   0.0212  0.0756 0.28 0.7791
sofa’  -0.1943  0.2564 -0.76 0.4485
sofa’’   0.4016  1.0363 0.39 0.6984
vitD   0.0595  0.0175 3.41 0.0007
vitD’  -0.0584  0.0201 -2.91 0.0037
treatment=Vitamin D   0.1169  0.0963 1.21 0.2249
anova(g)
Wald Statistics for status
χ2 d.f. P
bstatus 79.00 2 <0.0001
day 482.46 4 <0.0001
Nonlinear 282.09 3 <0.0001
age 12.77 2 0.0017
Nonlinear 4.16 1 0.0414
lips 9.91 2 0.0071
Nonlinear 0.10 1 0.7483
charlson 26.63 3 <0.0001
Nonlinear 0.66 2 0.7175
sofa 27.70 3 <0.0001
Nonlinear 4.75 2 0.0932
vitD 12.27 2 0.0022
Nonlinear 8.44 1 0.0037
treatment 1.47 1 0.2249
TOTAL NONLINEAR 293.51 10 <0.0001
TOTAL 786.13 19 <0.0001

Partial effect plots below show the relationship between each predictor and log odds, holding other predictors to median or mode.

ggplot(Predict(g))

A nomogram representation of the model is below. The nomogram can be enhanced to show the estimated probability or outcome better than any given level.

n <- nomogram(g, vnames='names')
plot(n)

Fit With Only Linear Effects and Only for Day 14

d14 <- subset(d, day == 14)
f <- lrm(status ~ bstatus + age + lips +
             charlson + sofa + treatment, data=d14, x=TRUE, y=TRUE)
f

Logistic Regression Model

 lrm(formula = status ~ bstatus + age + lips + charlson + sofa + 
     treatment, data = d14, x = TRUE, y = TRUE)
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  142                   77                  303 
                 Home 
                  786 
 
Frequencies of Missing Values Due to Each Variable
    status   bstatus       age      lips  charlson      sofa treatment 
         2         1         0         0        42         0         0 
 
Model Likelihood
Ratio Test
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 1308 LR χ2 252.94 R2 0.200 C 0.712
max |∂log L/∂β| 9×10-7 d.f. 7 g 1.050 Dxy 0.424
Pr(>χ2) <0.0001 gr 2.858 γ 0.424
gp 0.139 τa 0.242
Brier 0.116
β S.E. Wald Z Pr(>|Z|)
y≥Vent/ARDS   3.1451  0.3644 8.63 <0.0001
y≥In Hospital/Facility   2.5659  0.3605 7.12 <0.0001
y≥Home   1.1570  0.3551 3.26 0.0011
bstatus=On Vent   0.4889  0.2205 2.22 0.0266
bstatus=In Hospital   1.3809  0.2197 6.28 <0.0001
age  -0.0115  0.0045 -2.57 0.0103
lips  -0.0487  0.0196 -2.49 0.0129
charlson  -0.1248  0.0241 -5.19 <0.0001
sofa  -0.0947  0.0180 -5.28 <0.0001
treatment=Vitamin D   0.2483  0.1156 2.15 0.0317
anova(f)
Wald Statistics for status
χ2 d.f. P
bstatus 65.58 2 <0.0001
age 6.59 1 0.0103
lips 6.19 1 0.0129
charlson 26.89 1 <0.0001
sofa 27.85 1 <0.0001
treatment 4.61 1 0.0317
TOTAL 225.54 7 <0.0001

Evaluation of Baseline Measurement \(\times\) Time Interactions

In the following, allow time (simplified to 3 knots in the spline) to interact with each of the baseline variables, using fewer knots than before because of the much larger number of parameters being fitted.

f <- lrm(status ~ rcs(day, 3) * (bstatus + rcs(age, 3) + rcs(lips, 3) +
             rcs(charlson, 3) + rcs(sofa, 3) + treatment),
                 data=d, tol=1e-13, x=TRUE, y=TRUE)
g <- robcov(f, d$id)
anova(g)
Wald Statistics for status
χ2 d.f. P
day (Factor+Higher Order Factors) 560.38 24 <0.0001
All Interactions 124.64 22 <0.0001
Nonlinear (Factor+Higher Order Factors) 295.79 12 <0.0001
bstatus (Factor+Higher Order Factors) 238.83 6 <0.0001
All Interactions 4.41 4 0.3529
age (Factor+Higher Order Factors) 11.07 6 0.0861
All Interactions 8.80 4 0.0663
Nonlinear (Factor+Higher Order Factors) 4.21 3 0.2400
lips (Factor+Higher Order Factors) 22.96 6 0.0008
All Interactions 2.65 4 0.6174
Nonlinear (Factor+Higher Order Factors) 1.40 3 0.7059
charlson (Factor+Higher Order Factors) 35.46 6 <0.0001
All Interactions 25.45 4 <0.0001
Nonlinear (Factor+Higher Order Factors) 4.35 3 0.2265
sofa (Factor+Higher Order Factors) 53.37 6 <0.0001
All Interactions 16.99 4 0.0019
Nonlinear (Factor+Higher Order Factors) 7.09 3 0.0692
treatment (Factor+Higher Order Factors) 5.64 3 0.1303
All Interactions 4.60 2 0.1001
day × bstatus (Factor+Higher Order Factors) 4.41 4 0.3529
Nonlinear 1.71 2 0.4261
Nonlinear Interaction : f(A,B) vs. AB 1.71 2 0.4261
day × age (Factor+Higher Order Factors) 8.80 4 0.0663
Nonlinear 7.32 3 0.0624
Nonlinear Interaction : f(A,B) vs. AB 7.32 3 0.0624
f(A,B) vs. Af(B) + Bg(A) 3.49 1 0.0618
Nonlinear Interaction in day vs. Af(B) 6.75 2 0.0343
Nonlinear Interaction in age vs. Bg(A) 4.12 2 0.1277
day × lips (Factor+Higher Order Factors) 2.65 4 0.6174
Nonlinear 1.72 3 0.6329
Nonlinear Interaction : f(A,B) vs. AB 1.72 3 0.6329
f(A,B) vs. Af(B) + Bg(A) 0.86 1 0.3528
Nonlinear Interaction in day vs. Af(B) 1.27 2 0.5309
Nonlinear Interaction in lips vs. Bg(A) 1.30 2 0.5230
day × charlson (Factor+Higher Order Factors) 25.45 4 <0.0001
Nonlinear 5.33 3 0.1495
Nonlinear Interaction : f(A,B) vs. AB 5.33 3 0.1495
f(A,B) vs. Af(B) + Bg(A) 0.49 1 0.4833
Nonlinear Interaction in day vs. Af(B) 5.18 2 0.0752
Nonlinear Interaction in charlson vs. Bg(A) 0.70 2 0.7042
day × sofa (Factor+Higher Order Factors) 16.99 4 0.0019
Nonlinear 14.80 3 0.0020
Nonlinear Interaction : f(A,B) vs. AB 14.80 3 0.0020
f(A,B) vs. Af(B) + Bg(A) 1.38 1 0.2400
Nonlinear Interaction in day vs. Af(B) 12.02 2 0.0025
Nonlinear Interaction in sofa vs. Bg(A) 6.19 2 0.0453
day × treatment (Factor+Higher Order Factors) 4.60 2 0.1001
Nonlinear 4.01 1 0.0451
Nonlinear Interaction : f(A,B) vs. AB 4.01 1 0.0451
TOTAL NONLINEAR 305.41 20 <0.0001
TOTAL INTERACTION 124.64 22 <0.0001
TOTAL NONLINEAR + INTERACTION 366.25 27 <0.0001
TOTAL 927.04 35 <0.0001

From the ANOVA above you can see strong evidence for some form of interactions. Interactions between time and baseline SOFA score and time and the Charlson index stand out. The interactions indicate that the effect of these two baseline variables dissipates at later times. Let’s estimate the baseline covariate effects for days 1, 8, 15, 22 from the interaction model.

days <- c(1, 8, 15, 22)
ggplot(Predict(g, bstatus, day=days))
ggplot(Predict(g, age, day=days))
ggplot(Predict(g, lips, day=days))
ggplot(Predict(g, charlson, day=days))
ggplot(Predict(g, sofa, day=days))
ggplot(Predict(g, treatment, day=days))
ggplot(Predict(g, day, treatment))

Checking Proportional Odds Assumption

Before getting into a more detailed analysis concerning the proportional odds assumption involving multiple predictors, let’s take a different look at time alone (the approach described earlier is more intuitive). For each day, compute all cumulative proportions, take their logit, and see how the spacing between successive logit cumulative proportions varies across days. Plotting symbols are defined in the code. This may be easier to see in a table of simple cell frequencies that is also shown.

d$yn <- as.integer(d$status)   # numeric Y for easy dichotomization
table(d$status, d$yn)
                      
                           1     2     3     4
  Dead                  3749     0     0     0
  Vent/ARDS                0  3343     0     0
  In Hospital/Facility     0     0 11128     0
  Home                     0     0     0 19586
ql <- function(y) {
  p <- min(0.9999, max(0.0001, mean(y, na.rm=TRUE)))
  qlogis(p)
}
sf <- function(y) c(
  'y>=2'=ql(y >= 2),  # box      (alive)
  'y>=3'=ql(y >= 3),  # circle   (not dead or Vent/ARDS)
  'y>=4'=ql(y == 4))  # triangle (discharged)
s <- summary(yn ~ day, fun=sf, continuous=99, data=d)
plot(s, which=1:3, pch=0:4, xlab='logit Cumulative Prob', main='')
# g <- function(y)
#   apply(y, 2, function(u) qlogis(mean(u)))
# s <- summary(cumcategory(yn) ~ day, continuous=99, data=d)
# plot(s, which=2:3, pch=1:5, xlab='logit Cumulative Prob.', main='')
with(d, print(table(day, status)))
    status
day  Dead Vent/ARDS In Hospital/Facility Home
  1    16       421                  877   37
  2    31       319                  860  141
  3    49       258                  809  235
  4    63       226                  727  335
  5    76       202                  634  439
  6    84       169                  586  512
  7    96       153                  521  580
  8   107       130                  469  644
  9   113       123                  423  691
  10  121       115                  405  709
  11  125       104                  387  734
  12  135        93                  357  765
  13  140        87                  334  789
  14  147        82                  311  810
  15  151        79                  297  823
  16  157        78                  280  835
  17  162        72                  275  841
  18  166        69                  266  849
  19  167        67                  258  858
  20  170        66                  251  863
  21  170        66                  247  867
  22  173        63                  241  873
  23  178        62                  232  878
  24  182        62                  223  883
  25  186        61                  217  886
  26  193        59                  211  887
  27  195        57                  204  894
  28  196         0                  226  928

In the following we ignore intra-patient correlation and use an all-linear (except for time) PO model for purposes of assessing the PO assumption on all covariates including time. The assessment is done by comparing \(\beta\)s with those from the PO model overall fit and those from all dichotomizations of Y. The analysis is done twice. First all patient records are considered. Then days after the day of death are removed. We pay more attention to the more important variables: day, baseline status, SOFA, and Charlson (in descending order of outcome variation explained). The day effect is extremely nonlinear in the PO model, and day is modeled as a quadratic effect here so it has two \(\beta\)s.

for(inclall in c(TRUE, FALSE)) {
  cat(if(inclall) '\nAll records\n\n' else '\nRecords until death\n\n')
  u <- if(inclall) d else subset(d, day <= ddeath)
  f <- lrm(status ~ bstatus + pol(day, 2) + age + lips + charlson + sofa +
             treatment, data=u)
  k <- matrix(NA, nrow=4, ncol=9)
  poc <- coef(f)[-(1:3)]
  colnames(k) <- names(poc)
  rownames(k) <- c('PO', paste0('>=', 2:4))
  k[1, ] <- poc
  for(cut in c(2 : 4)) {
    g <- lrm(yn >= cut ~ bstatus + pol(day, 2) + age + lips + charlson +
               sofa + treatment, data=u)
    k[cut, ] <- coef(g)[-1]
  }
  print(round(k, 3))
}

All records

    bstatus=On Vent bstatus=In Hospital    day  day^2    age   lips charlson
PO            0.464               1.465  0.171 -0.004 -0.009 -0.051   -0.113
>=2           0.582               1.053 -0.183  0.004 -0.004 -0.106   -0.207
>=3           0.410               2.334  0.096 -0.003 -0.001 -0.047   -0.160
>=4           0.364               1.170  0.322 -0.008 -0.014 -0.042   -0.105
      sofa treatment=Vitamin D
PO  -0.089               0.154
>=2 -0.123               0.022
>=3 -0.076               0.046
>=4 -0.098               0.205

Records until death

    bstatus=On Vent bstatus=In Hospital   day  day^2    age   lips charlson
PO            0.326               1.600 0.284 -0.006 -0.015 -0.021   -0.045
>=2           0.490               0.772 0.080 -0.001 -0.006 -0.098   -0.177
>=3           0.271               3.868 0.237 -0.005 -0.003 -0.003   -0.044
>=4           0.193               1.001 0.345 -0.008 -0.018 -0.021   -0.049
      sofa treatment=Vitamin D
PO  -0.075               0.217
>=2 -0.100              -0.035
>=3 -0.034               0.102
>=4 -0.085               0.255

Constancy of the \(\beta\)s vertically in the above tables, and hence equality with the PO \(\beta\)s in the first row, means the PO assumption is satisfied. Not that PO is not strictly satisfied for any dataset we encounter, and even when PO is not satisfied, the PO model can still work better than alternative approaches.

When all records are analyzed (even those with duplicated death status post death), the coefficients are slightly more constant, except for the coefficient of day. When death-carried-forward is not used, the coefficients are slightly less constant except for the effect of day. To help explore that, here is an analysis of when deaths are occuring (patients not dying are removed):

w <- subset(d, day == 1 & ddeath < 29)
hist(w$ddeath, breaks=0:28, main='', xlab='Day of Death')

GEE-Like Analysis Repeated Without Carrying Death Forward

The last analysis with cluster correlation correction is repeated after deletion of patient records beyond the day of death. In other words, the death status is not repeated up to day 28 but the patient data are truncated at death.

d <- dnc <- subset(d, day <= ddeath)
f <- lrm(status ~ bstatus + rcs(day, 5) + rcs(age, 3) + rcs(lips, 3) +
             rcs(charlson, 4) + rcs(sofa, 4) + rcs(vitD, 3) + treatment,
         data=d, x=TRUE, y=TRUE)
g <- robcov(f, d$id)
g

Logistic Regression Model

 lrm(formula = status ~ bstatus + rcs(day, 5) + rcs(age, 3) + 
     rcs(lips, 3) + rcs(charlson, 4) + rcs(sofa, 4) + rcs(vitD, 
     3) + treatment, data = d, x = TRUE, y = TRUE)
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  180                 3043                10467 
                 Home 
                18353 
 

Frequencies of Missing Values Due to Each Variable

Model Likelihood
Ratio Test
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 32043 LR χ2 11418.02 R2 0.354 C 0.787
Cluster on d$id d.f. 19 g 1.506 Dxy 0.574
Clusters 1261 Pr(>χ2) <0.0001 gr 4.506 γ 0.575
max |∂log L/∂β| 1×10-7 gp 0.119 τa 0.319
Brier 0.069
β S.E. Wald Z Pr(>|Z|)
y≥Vent/ARDS   2.7293  0.4924 5.54 <0.0001
y≥In Hospital/Facility  -0.6462  0.5038 -1.28 0.1996
y≥Home  -3.2094  0.4970 -6.46 <0.0001
bstatus=On Vent   0.3845  0.2626 1.46 0.1430
bstatus=In Hospital   1.6675  0.2571 6.49 <0.0001
day   0.3020  0.0127 23.72 <0.0001
day’  -0.5754  0.0647 -8.89 <0.0001
day’’   0.9879  0.1814 5.45 <0.0001
day’’’  -0.2407  0.2103 -1.14 0.2523
age   0.0018  0.0094 0.19 0.8477
age’  -0.0187  0.0083 -2.26 0.0240
lips  -0.0468  0.0519 -0.90 0.3676
lips’   0.0213  0.0469 0.45 0.6492
charlson  -0.1732  0.1384 -1.25 0.2106
charlson’   0.6995  0.7050 0.99 0.3211
charlson’’  -1.6179  1.6182 -1.00 0.3174
sofa   0.0392  0.1066 0.37 0.7129
sofa’  -0.3083  0.3890 -0.79 0.4280
sofa’’   0.6236  0.9470 0.66 0.5102
vitD   0.0413  0.0193 2.14 0.0322
vitD’  -0.0494  0.0229 -2.16 0.0309
treatment=Vitamin D   0.1920  0.1063 1.81 0.0708
anova(g)
Wald Statistics for status
χ2 d.f. P
bstatus 108.29 2 <0.0001
day 1401.68 4 <0.0001
Nonlinear 391.54 3 <0.0001
age 17.32 2 0.0002
Nonlinear 5.09 1 0.0240
lips 1.86 2 0.3949
Nonlinear 0.21 1 0.6492
charlson 6.47 3 0.0908
Nonlinear 1.00 2 0.6063
sofa 19.39 3 0.0002
Nonlinear 1.91 2 0.3856
vitD 4.79 2 0.0913
Nonlinear 4.66 1 0.0309
treatment 3.26 1 0.0708
TOTAL NONLINEAR 403.82 10 <0.0001
TOTAL 1621.75 19 <0.0001
ggplot(Predict(g))

Let’s also look at the proportional odds assumption using these truncated records (death not carried forward).

s <- summary(yn ~ day, fun=sf, continuous=99, data=d)
plot(s, which=1:3, pch=0:4, xlab='logit Cumulative Prob', main='')

When the absorbing state is not carried forward, the problem with non-proportional odds almost vanishes.

Finally, look at interactions with time in this model.

f <- lrm(status ~ rcs(day, 3) * (bstatus + rcs(age, 3) + rcs(lips, 3) +
             rcs(charlson, 3) + rcs(sofa, 3) + treatment),
                 data=d, tol=1e-13, x=TRUE, y=TRUE)
g <- robcov(f, d$id)
anova(g)
Wald Statistics for status
χ2 d.f. P
day (Factor+Higher Order Factors) 1195.35 24 <0.0001
All Interactions 87.91 22 <0.0001
Nonlinear (Factor+Higher Order Factors) 400.49 12 <0.0001
bstatus (Factor+Higher Order Factors) 365.19 6 <0.0001
All Interactions 19.95 4 0.0005
age (Factor+Higher Order Factors) 23.69 6 0.0006
All Interactions 20.58 4 0.0004
Nonlinear (Factor+Higher Order Factors) 5.92 3 0.1154
lips (Factor+Higher Order Factors) 26.36 6 0.0002
All Interactions 10.98 4 0.0268
Nonlinear (Factor+Higher Order Factors) 2.62 3 0.4542
charlson (Factor+Higher Order Factors) 15.46 6 0.0170
All Interactions 6.11 4 0.1914
Nonlinear (Factor+Higher Order Factors) 2.17 3 0.5381
sofa (Factor+Higher Order Factors) 46.36 6 <0.0001
All Interactions 1.64 4 0.8011
Nonlinear (Factor+Higher Order Factors) 4.72 3 0.1931
treatment (Factor+Higher Order Factors) 4.66 3 0.1984
All Interactions 1.91 2 0.3852
day × bstatus (Factor+Higher Order Factors) 19.95 4 0.0005
Nonlinear 12.96 2 0.0015
Nonlinear Interaction : f(A,B) vs. AB 12.96 2 0.0015
day × age (Factor+Higher Order Factors) 20.58 4 0.0004
Nonlinear 8.33 3 0.0397
Nonlinear Interaction : f(A,B) vs. AB 8.33 3 0.0397
f(A,B) vs. Af(B) + Bg(A) 4.22 1 0.0399
Nonlinear Interaction in day vs. Af(B) 6.98 2 0.0305
Nonlinear Interaction in age vs. Bg(A) 5.87 2 0.0532
day × lips (Factor+Higher Order Factors) 10.98 4 0.0268
Nonlinear 2.81 3 0.4222
Nonlinear Interaction : f(A,B) vs. AB 2.81 3 0.4222
f(A,B) vs. Af(B) + Bg(A) 0.01 1 0.9409
Nonlinear Interaction in day vs. Af(B) 0.25 2 0.8805
Nonlinear Interaction in lips vs. Bg(A) 2.41 2 0.2997
day × charlson (Factor+Higher Order Factors) 6.11 4 0.1914
Nonlinear 4.78 3 0.1888
Nonlinear Interaction : f(A,B) vs. AB 4.78 3 0.1888
f(A,B) vs. Af(B) + Bg(A) 0.06 1 0.8138
Nonlinear Interaction in day vs. Af(B) 3.92 2 0.1405
Nonlinear Interaction in charlson vs. Bg(A) 1.31 2 0.5196
day × sofa (Factor+Higher Order Factors) 1.64 4 0.8011
Nonlinear 1.46 3 0.6922
Nonlinear Interaction : f(A,B) vs. AB 1.46 3 0.6922
f(A,B) vs. Af(B) + Bg(A) 0.01 1 0.9297
Nonlinear Interaction in day vs. Af(B) 1.42 2 0.4911
Nonlinear Interaction in sofa vs. Bg(A) 0.04 2 0.9792
day × treatment (Factor+Higher Order Factors) 1.91 2 0.3852
Nonlinear 1.86 1 0.1728
Nonlinear Interaction : f(A,B) vs. AB 1.86 1 0.1728
TOTAL NONLINEAR 420.41 20 <0.0001
TOTAL INTERACTION 87.91 22 <0.0001
TOTAL NONLINEAR + INTERACTION 455.33 27 <0.0001
TOTAL 1637.55 35 <0.0001
days <- c(1, 8, 15, 22)
ggplot(Predict(g, bstatus, day=days))
ggplot(Predict(g, age, day=days))
ggplot(Predict(g, lips, day=days))
ggplot(Predict(g, charlson, day=days))
ggplot(Predict(g, sofa, day=days))
ggplot(Predict(g, treatment, day=days))
ggplot(Predict(g, day, treatment))

Assessment of Interaction Between Baseline Vitamin D and Treatment

We repeat the model above but allowing baseline vitamin D to interact with treatment.

f <- lrm(status ~ bstatus + rcs(day, 5) + rcs(age, 3) + rcs(lips, 3) +
             rcs(charlson, 4) + rcs(sofa, 4) + rcs(vitD, 4) * treatment,
         data=d, x=TRUE, y=TRUE)
g <- robcov(f, d$id)
g

Logistic Regression Model

 lrm(formula = status ~ bstatus + rcs(day, 5) + rcs(age, 3) + 
     rcs(lips, 3) + rcs(charlson, 4) + rcs(sofa, 4) + rcs(vitD, 
     4) * treatment, data = d, x = TRUE, y = TRUE)
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  180                 3043                10467 
                 Home 
                18353 
 

Frequencies of Missing Values Due to Each Variable

Model Likelihood
Ratio Test
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 32043 LR χ2 11633.44 R2 0.360 C 0.790
Cluster on d$id d.f. 23 g 1.524 Dxy 0.580
Clusters 1261 Pr(>χ2) <0.0001 gr 4.592 γ 0.580
max |∂log L/∂β| 1×10-7 gp 0.120 τa 0.322
Brier 0.069
β S.E. Wald Z Pr(>|Z|)
y≥Vent/ARDS   2.3634  0.5685 4.16 <0.0001
y≥In Hospital/Facility  -1.0222  0.5765 -1.77 0.0762
y≥Home  -3.6015  0.5723 -6.29 <0.0001
bstatus=On Vent   0.4329  0.2622 1.65 0.0987
bstatus=In Hospital   1.7149  0.2571 6.67 <0.0001
day   0.3041  0.0128 23.70 <0.0001
day’  -0.5816  0.0650 -8.94 <0.0001
day’’   1.0016  0.1823 5.49 <0.0001
day’’’  -0.2506  0.2113 -1.19 0.2355
age   0.0029  0.0094 0.31 0.7600
age’  -0.0193  0.0083 -2.33 0.0197
lips  -0.0507  0.0521 -0.97 0.3312
lips’   0.0260  0.0472 0.55 0.5823
charlson  -0.1829  0.1393 -1.31 0.1893
charlson’   0.7225  0.7116 1.02 0.3099
charlson’’  -1.6651  1.6355 -1.02 0.3086
sofa   0.0355  0.1074 0.33 0.7410
sofa’  -0.2841  0.3896 -0.73 0.4659
sofa’’   0.5528  0.9472 0.58 0.5595
vitD   0.0686  0.0505 1.36 0.1742
vitD’  -0.0298  0.2232 -0.13 0.8937
vitD’’  -0.1455  0.5537 -0.26 0.7928
treatment=Vitamin D   0.7066  0.5397 1.31 0.1905
vitD × treatment=Vitamin D  -0.0303  0.0757 -0.40 0.6894
vitD’ × treatment=Vitamin D  -0.2233  0.3288 -0.68 0.4971
vitD’’ × treatment=Vitamin D   0.8314  0.8141 1.02 0.3071
anova(g)
Wald Statistics for status
χ2 d.f. P
bstatus 110.06 2 <0.0001
day 1405.28 4 <0.0001
Nonlinear 388.96 3 <0.0001
age 17.09 2 0.0002
Nonlinear 5.44 1 0.0197
lips 1.84 2 0.3994
Nonlinear 0.30 1 0.5823
charlson 7.13 3 0.0678
Nonlinear 1.04 2 0.5954
sofa 20.01 3 0.0002
Nonlinear 2.05 2 0.3594
vitD (Factor+Higher Order Factors) 19.97 6 0.0028
All Interactions 13.72 3 0.0033
Nonlinear (Factor+Higher Order Factors) 19.95 4 0.0005
treatment (Factor+Higher Order Factors) 17.48 4 0.0016
All Interactions 13.72 3 0.0033
vitD × treatment (Factor+Higher Order Factors) 13.72 3 0.0033
Nonlinear 13.68 2 0.0011
Nonlinear Interaction : f(A,B) vs. AB 13.68 2 0.0011
TOTAL NONLINEAR 407.50 13 <0.0001
TOTAL NONLINEAR + INTERACTION 410.86 14 <0.0001
TOTAL 1634.16 23 <0.0001

There is evidence for interaction. Plot the interaction effect.

ggplot(Predict(g, vitD, treatment))

To get the right confidence bands, form contrasts.

k <- contrast(g, list(treatment='Vitamin D', vitD=0:65),
                 list(treatment='Placebo', vitD=0:65))
k <- as.data.frame(k[c('vitD', 'Contrast', 'Lower', 'Upper')])
ggplot(k, aes(x=vitD, y=Contrast)) + geom_point() + 
  geom_errorbar(aes(ymin=Lower, ymax=Upper), width=0)

There seems to be benefit of treatment in the very low range of baseline vitamin D and for those patients with high baseline vitamin D, but not in the middle.

Bayesian Random Effects Model Without Carrying Death Forward

Repeat the last model but using a Bayesian approach with normally distributed random effects for patient level intercepts. This is a compound symmetry type of correlation structure within patient. Before proceeding witih the full model try to find the smallest model that exhibits convergence problems that will be seen later with the full model.

stanSet()
# This is fine:
bnocsnoc <- blrm(status ~ age, data=d, loo=FALSE, refresh=100,
                 file='bnocsnoc.rds')
stanDx(bnocsnoc)
Iterations: 2000 on each of 4 chains, with 4000 posterior distribution samples saved

For each parameter, n_eff is a crude measure of effective sample size
and Rhat is the potential scale reduction factor on split chains
(at convergence, Rhat=1)
                        n_eff  Rhat
y>=Vent/ARDS             4146 1.000
y>=In Hospital/Facility  4713 1.000
y>=Home                  4700 0.999
age                      4419 0.999
stanDxplot(bnocsnoc, which='ALL')

# Problem with this one if rsdmean=0.3 or 0.08, minor prob if 0.005
bnocs <- blrm(status ~ age + cluster(id), psigma=1, rsdsd=0.075,
              data=d, loo=FALSE, refresh=100, file='bnocs.rds')
# Previous: psigma=2 rsdmean=0.005; psigma=1 rsdmean=0.08
stanDx(bnocs)
Iterations: 2000 on each of 4 chains, with 4000 posterior distribution samples saved

For each parameter, n_eff is a crude measure of effective sample size
and Rhat is the potential scale reduction factor on split chains
(at convergence, Rhat=1)
                        n_eff  Rhat
y>=Vent/ARDS              421 1.003
y>=In Hospital/Facility   194 1.011
y>=Home                   173 1.011
age                       199 1.009
sigmag                    385 1.008
stanDxplot(bnocs, which='ALL')
# Problematic even if binary for Home, but got slightly better with
# t(4, 0, 1) prior for sigma SD
# adding conc=0.5 rsdmean made things significanty worse
# b <- blrm(status ~ age + cluster(id), data=d, iter=1000, refresh=100)
# stanDx(b); stanDxplot(b, which='ALL')

bnocs

Bayesian Proportional Odds Ordinal Logistic Model

Dirichlet Priors With Concentration Parameter 0.455 for Intercepts

 blrm(formula = status ~ age + cluster(id), data = d, psigma = 1, 
     rsdsd = 0.075, refresh = 100, loo = FALSE, file = "bnocs.rds")
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  196                 3343                11128 
                 Home 
                19586 
 
Frequencies of Missing Values Due to Each Variable
      status         age cluster(id) 
          50           0           0 
 
Mixed Calibration/
Discrimination Indexes
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 34253 B 0.006 [0.006, 0.006] g 0.742 [0.574, 0.952] C 0.582 [0.582, 0.582]
Draws 4000 gp 0 [0, 0] Dxy 0.163 [0.163, 0.163]
Chains 4 EV 0 [0, 0]
p 1 v 0.435 [0.252, 0.7]
Cluster on id vp 0 [0, 0]
Clusters 1351
σγ 3.2259 [3.0911, 3.3747]
Mean β Median β S.E. Lower Upper Pr(β>0) Symmetry
y≥Vent/ARDS  11.4263  11.4129  0.3425  10.7745  12.1094  1.0000  1.07
y≥In Hospital/Facility   6.3262   6.3142  0.3260   5.6944   6.9784  1.0000  1.04
y≥Home   1.9749   1.9653  0.3233   1.3710   2.6387  1.0000  1.03
age  -0.0410  -0.0408  0.0057  -0.0533  -0.0313  0.0000  0.95
bnocarry <- blrm(status ~ bstatus + rcs(day, 5) + rcs(age, 3) +
                 rcs(lips, 3) + rcs(charlson, 4) + rcs(sofa, 4) + treatment +
                         cluster(id), data=d, loo=FALSE, refresh=50, file='bnocarry.rds')
bnocarry

Bayesian Proportional Odds Ordinal Logistic Model

Dirichlet Priors With Concentration Parameter 0.455 for Intercepts

 blrm(formula = status ~ bstatus + rcs(day, 5) + rcs(age, 3) + 
     rcs(lips, 3) + rcs(charlson, 4) + rcs(sofa, 4) + treatment + 
     cluster(id), data = d, refresh = 50, loo = FALSE, file = "bnocarry.rds")
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  189                 3169                10862 
                 Home 
                18988 
 
Frequencies of Missing Values Due to Each Variable
      status     bstatus         day         age        lips    charlson 
          50          28           0           0           0        1045 
        sofa   treatment cluster(id) 
           0           0           0 
 
Mixed Calibration/
Discrimination Indexes
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 33208 B 0.006 [0.006, 0.006] g 4.077 [3.892, 4.272] C 0.773 [0.77, 0.776]
Draws 4000 gp 0 [0, 0.001] Dxy 0.546 [0.54, 0.553]
Chains 4 EV 0.098 [0.015, 0.183]
p 17 v 13.183 [11.97, 14.401]
Cluster on id vp 0 [0, 0]
Clusters 1309
σγ 4.1683 [3.9875, 4.3536]
Mean β Median β S.E. Lower Upper Pr(β>0) Symmetry
y≥Vent/ARDS   8.2537   8.2419  0.9665   6.5110  10.2707  1.0000  1.01
y≥In Hospital/Facility  -0.2323  -0.2381  0.9608  -2.0608   1.6383  0.4040  1.02
y≥Home  -7.7042  -7.7114  0.9637  -9.6311  -5.9360  0.0000  1.00
bstatus=On Vent   0.9488   0.9359  0.5272  -0.0824   1.9521  0.9635  1.00
bstatus=In Hospital   4.7748   4.7693  0.5249   3.7904   5.8431  1.0000  1.07
day   0.6975   0.6970  0.0169   0.6649   0.7316  1.0000  0.98
day’  -1.1754  -1.1746  0.1039  -1.3757  -0.9721  0.0000  1.01
day’’   1.8060   1.8027  0.3214   1.1876   2.4208  1.0000  1.00
day’’’  -0.0027   0.0056  0.4316  -0.8599   0.8159  0.5042  1.00
age  -0.0003   0.0000  0.0186  -0.0371   0.0366  0.4990  1.03
age’  -0.0287  -0.0291  0.0166  -0.0595   0.0053  0.0423  0.98
lips  -0.0262  -0.0269  0.1091  -0.2349   0.1944  0.3985  1.07
lips’  -0.0787  -0.0782  0.1009  -0.2673   0.1185  0.2272  0.97
charlson  -0.4306  -0.4217  0.2920  -1.0024   0.1340  0.0685  0.99
charlson’   1.5518   1.5217  1.4831  -1.5054   4.4033  0.8562  1.06
charlson’’  -3.6542  -3.5987  3.4116  -9.9032   3.5933  0.1395  0.94
sofa   0.0069   0.0120  0.1872  -0.3533   0.3709  0.5212  0.89
sofa’  -0.3965  -0.4190  0.7343  -1.8090   1.0533  0.2828  1.11
sofa’’   0.3921   0.4177  1.8166  -3.0990   4.0762  0.5980  0.90
treatment=Vitamin D   0.4395   0.4486  0.2436  -0.0398   0.9269  0.9575  0.98
a <- anova(bnocarry)
a
Relative Explained Variation for status. Approximate total model Wald χ2 used in denominators of REV:6411.9 [6129.9, 6754.7].
REV Lower Upper d.f.
bstatus 0.029 0.023 0.039 2
day 0.977 0.968 0.983 4
Nonlinear 0.246 0.226 0.265 3
age 0.002 0.000 0.004 2
Nonlinear 0.000 0.000 0.002 1
lips 0.001 0.000 0.003 2
Nonlinear 0.000 0.000 0.001 1
charlson 0.002 0.000 0.005 3
Nonlinear 0.000 0.000 0.001 2
sofa 0.007 0.004 0.011 3
Nonlinear 0.001 0.000 0.003 2
treatment 0.001 0.000 0.002 1
TOTAL NONLINEAR 0.248 0.229 0.269 9
TOTAL 1.000 1.000 1.000 17
stanDx(bnocarry)
Iterations: 2000 on each of 4 chains, with 4000 posterior distribution samples saved

For each parameter, n_eff is a crude measure of effective sample size
and Rhat is the potential scale reduction factor on split chains
(at convergence, Rhat=1)
                        n_eff  Rhat
y>=Vent/ARDS              336 1.008
y>=In Hospital/Facility   138 1.026
y>=Home                   101 1.038
bstatus=On Vent           110 1.042
bstatus=In Hospital       159 1.010
day                      1357 1.000
day'                     2714 1.001
day''                    3691 1.001
day'''                   4264 1.000
age                       141 1.032
age'                      163 1.022
lips                      121 1.038
lips'                      52 1.076
charlson                  126 1.027
charlson'                 141 1.015
charlson''                122 1.028
sofa                      143 1.024
sofa'                     140 1.024
sofa''                    159 1.017
treatment=Vitamin D       130 1.039
sigmag                    264 1.005
stanDxplot(bnocarry)
ggplot(Predict(bnocarry))

plot(a)

Compute the posterior probability that treatment raises the chance of a patient having a given outcome or better (this is already in the coefficient table above).

P <- PostF(bnocarry, name='orig')
P(`treatment=Vitamin D` > 0)
[1] 0.9575

For comparison, fit a similar Bayesian model adjusting only for time, and one not adjusting for any covariates.

bnocarry2 <- blrm(status ~ rcs(day, 5) + treatment +
                  cluster(id), data=d, loo=FALSE, refresh=50, file='bnocarry2.rds')
bnocarry2

Bayesian Proportional Odds Ordinal Logistic Model

Dirichlet Priors With Concentration Parameter 0.455 for Intercepts

 blrm(formula = status ~ rcs(day, 5) + treatment + cluster(id), 
     data = d, refresh = 50, loo = FALSE, file = "bnocarry2.rds")
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  196                 3343                11128 
                 Home 
                19586 
 
Frequencies of Missing Values Due to Each Variable
      status         day   treatment cluster(id) 
          50           0           0           0 
 
Mixed Calibration/
Discrimination Indexes
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 34253 B 0.006 [0.006, 0.006] g 2.627 [2.547, 2.691] C 0.711 [0.71, 0.711]
Draws 4000 gp 0 [0, 0] Dxy 0.422 [0.42, 0.423]
Chains 4 EV 0 [0, 0]
p 5 v 5.784 [5.503, 6.074]
Cluster on id vp 0 [0, 0]
Clusters 1351
σγ 5.0107 [4.7918, 5.2634]
Mean β Median β S.E. Lower Upper Pr(β>0) Symmetry
y≥Vent/ARDS   9.2229   9.2242  0.2474   8.7477   9.7103  1.0000  0.99
y≥In Hospital/Facility   0.7985   0.7963  0.2049   0.4063   1.1985  1.0000  0.94
y≥Home  -6.5560  -6.5525  0.2136  -6.9970  -6.1586  0.0000  0.93
day   0.6986   0.6988  0.0163   0.6660   0.7290  1.0000  1.02
day’  -1.2033  -1.2027  0.1006  -1.4030  -1.0111  0.0000  0.96
day’’   1.8840   1.8821  0.3113   1.2824   2.4896  1.0000  1.02
day’’’  -0.0628  -0.0655  0.4169  -0.8835   0.7454  0.4395  1.02
treatment=Vitamin D   0.2469   0.2403  0.2630  -0.3019   0.7672  0.8315  1.11
bnocarry3 <- blrm(status ~ treatment + cluster(id),
                  data=d, loo=FALSE, refresh=50, file='bnocarry3.rds')
bnocarry3

Bayesian Proportional Odds Ordinal Logistic Model

Dirichlet Priors With Concentration Parameter 0.455 for Intercepts

 blrm(formula = status ~ treatment + cluster(id), data = d, refresh = 50, 
     loo = FALSE, file = "bnocarry3.rds")
 

Frequencies of Responses

                 Dead            Vent/ARDS In Hospital/Facility 
                  196                 3343                11128 
                 Home 
                19586 
 
Frequencies of Missing Values Due to Each Variable
      status   treatment cluster(id) 
          50           0           0 
 
Mixed Calibration/
Discrimination Indexes
Discrimination
Indexes
Rank Discrim.
Indexes
Obs 34253 B 0.006 [0.006, 0.006] g 0.088 [0, 0.226] C 0.511 [0.473, 0.527]
Draws 4000 gp 0 [0, 0] Dxy 0.023 [-0.053, 0.053]
Chains 4 EV 0 [0, 0]
p 1 v 0.012 [0, 0.051]
Cluster on id vp 0 [0, 0]
Clusters 1351
σγ 3.2963 [3.1478, 3.4361]
Mean β Median β S.E. Lower Upper Pr(β>0) Symmetry
y≥Vent/ARDS   9.0687   9.0673  0.1694   8.7362   9.3921  1.0000  1.00
y≥In Hospital/Facility   3.9605   3.9591  0.1361   3.7138   4.2406  1.0000  1.06
y≥Home  -0.3899  -0.3909  0.1324  -0.6394  -0.1336  0.0008  0.99
treatment=Vitamin D   0.1152   0.1155  0.1948  -0.2774   0.4813  0.7225  0.94

Adjusting only for time and not other baseline covariates, the between-patient outcome heterogeneity is greater as makes sense, and the treatment effect remains diminished. When not even adjusting for time, the value of \(\sigma_\gamma\) ironically becomes lower than from the model with full covariate adjustment, and the treatment effect becomes very small.

Bayesian PO Random Effects Model With Time by Treatment Interaction

We go back to adjusting for the full set of covariates, and allow time to nonlinearly interact with treatment.

btxtime <- blrm(status ~ bstatus + treatment * rcs(day, 4) + rcs(age, 3) +
                 rcs(lips, 3) + rcs(charlson, 4) + rcs(sofa, 4) +
                         cluster(id), data=d, loo=FALSE, refresh=50, file='btxtime.rds')
stanDx(btxtime)
Iterations: 2000 on each of 4 chains, with 4000 posterior distribution samples saved

For each parameter, n_eff is a crude measure of effective sample size
and Rhat is the potential scale reduction factor on split chains
(at convergence, Rhat=1)
                            n_eff  Rhat
y>=Vent/ARDS                  309 1.013
y>=In Hospital/Facility       149 1.022
y>=Home                       120 1.023
bstatus=On Vent               142 1.027
bstatus=In Hospital           143 1.020
treatment=Vitamin D           154 1.011
day                          1979 1.005
day'                         3281 1.000
day''                        3943 1.001
age                           131 1.026
age'                          119 1.031
lips                           67 1.064
lips'                         149 1.012
charlson                      144 1.038
charlson'                     155 1.033
charlson''                     79 1.066
sofa                          168 1.017
sofa'                         164 1.011
sofa''                        104 1.053
treatment=Vitamin D * day    3919 1.000
treatment=Vitamin D * day'   3880 1.000
treatment=Vitamin D * day''  4018 1.000
sigmag                        298 1.010
stanDxplot(btxtime)