Report

Author

Jeffrey Liang

Introductoin

Patients who survive critical illnesses often face long-term complications that can significantly impact their physical capacity and overall quality of life. Rural areas are generally characterized by poorer health conditions and more limited access to healthcare compared to urban areas.

Building on this context, the goal of this study is to investigate whether rurality and neighborhood deprivation are associated with adverse cognitive, disability, and quality of life outcomes among ICU survivors. This research aims to enhance our understanding of how geographic and socio-economic factors influence recovery trajectories in this vulnerable population.

Specifically, our main out come of interest is the association of RBANS score at 3 month and 12 month with rurality and deprivation.

Method

Data

The data used in this study is from the BrainMind study, a prospective cohort study of critically ill patients. The BrainMind study is a multi-center study that includes patients from the Vanderbilt University Medical Center. The cohort has been described else where, see ref. The data include patient’s demographic and ICU EHR summaries information.

And the follow-up data include RBANS, ADL, FAQ, and SF-36 scores. The RBANS is a brief cognitive assessment tool that measures cognitive function in five domains: immediate memory, visuospatial/constructional, language, attention, and delayed memory. The ADL and FAQ are disability assessment tools. The SF-36 is a quality of life assessment tool. We will use the RBANS global score as the primary outcome, and the ADL, FAQ, and SF-36 scores as secondary outcomes.

To represent the rurality, Rural-Urban Commuting Areas (RUCA) codes were used. The RUCA codes are a classification scheme that distinguishes metropolitan and non-metropolitan counties by the size of the urban population and the extent of commuting to larger urban areas. The RUCA codes were obtained from the 2000 Census data.

In addition, the Area Deprivation Index (ADI) was used to represent the neighborhood deprivation. The ADI is a composite measure of neighborhood socioeconomic disadvantage that is based on 17 variables from the US Census. The ADI was obtained from the 2010 Census data. National rank ADI is used as the data contained patient other than from TN.

Other covariates include Age at enrollment, The Charlson Comorbidity Index measures the severity and number of comorbid conditions. Cognitive baselines are assessed using the IQCODE, which typically ranges from 1 (much improved) to 5 (much worse). Disability models employ the Baseline Katz Index, scoring from 0 (independent) to 6 (dependent), and the Functional Activities Questionnaire, with scores from 0 (normal) to 30 (severe dysfunction). The Mean Modified Daily SOFA quantifies organ failure severity. The dataset also records days of delirium or coma and days under mechanical ventilation. Educational levels are noted, without a specified range, reflecting the highest education completed. The sex variable classifies individuals typically into male or female. Lastly, the dataset distinguishes between VA (Veterans Affairs) and civilian hospitals, with records from VA patients identified starting from code 14001

The inclusion criteria for the cohort specify that patients must be discharged from the hospital. At each time point, patients who are deceased, have withdrawn, or are lost to follow-up will be excluded starting from the earliest time point affected. Additionally, patients who lack RBANS scores across all categories will be excluded from the analysis at the latest time point where data is missing.

Code
cohort_id = read_rds("./data/inhosp_cohorts.rds") %>% 
  filter(hosp_survivor) %>% 
  select(id)

cohort_y = 
  cohort_id %>% 
  left_join(brainmind.fu %>% 
              filter(as.character(fu.period) %in% c('3 Month', 
                                                    '12 Month')) %>% 
              select(
                id,
                fu.period,
                starts_with("rbans"),
                rbans.global.score,
                trail.b.tscore,
                adl.cat,
                adl.totscore,
                faq.totscore,
                faq.cat,
                sf36.pcs,
                sf36.mcs,
                status
              ) %>% 
              mutate(id = as.numeric(id),
                     fu.period = as.character(fu.period),
                     fu.period = fct_relevel(fu.period,
                                             '3 Month'),
                     time = c(3,12)[as.numeric(fu.period)])
            ) %>% 
  drop_na(fu.period)


### 3 Month
cohort_y %>% 
  filter(
    fu.period == "3 Month",
         !str_detect(status,"Deceased|Withdrew|Closed")) %>% 
  select(id) %>% 
  left_join(cohort_y) -> cohort_y #608

### Drop no observations in 3 and 12
cohort_y %>% 
  select(id,fu.period,starts_with('rbans')) %>% 
  group_by(id,fu.period) %>% 
  mutate_all(is.na) %>% 
  summarise(observed =
              sum(c_across(starts_with("rbans")))<(ncol(.)-2)) %>%
  group_by(id) %>% 
  summarise(observed = sum(observed)) %>% 
  filter(observed>0) %>% 
  select(id) %>% 
  left_join(cohort_y) -> cohort_y

### 12 Month
cohort_y %>% 
  filter(!str_detect(status,"Deceased|Withdrew|Closed")) -> cohort_y
Code
cohort_x = 
  tibble(id = unique(cohort_y$id)) %>% 
  left_join(
    brainmind.oneobs %>%
      mutate(
        days.del.tot = days.septic.del
      ) %>% 
      select(id,
             age.enroll, 
             sex.pp, 
             edu, 
             iqcode.score.e,
             faq.cat.e,
             adl.cat.e,
             bmi, 
             charlson.score, 
             frailty,
             adl.e,
             faq.e,
             admit.dx,
             num.apache,
             sofa.mod,
             icudays.sevseptic.s,
             vent.los.tot.s,
             days.vent.hosp,
             days.del.tot,
             coma.s.imp,
             delcoma.s.imp,
             days.septic.coma,
             icu.los.tot,
             hospdis.loc
             ) %>% 
      mutate(
        id = as.numeric(id),
        va = if_else(id > 14001,"VA",'Civilian'),
        days.vent.hosp = as.numeric(days.vent.hosp),
        days.vent.hosp = replace_na(days.vent.hosp,0),
        vent.los.tot.s = as.numeric(vent.los.tot.s),
        vent.los.tot.s = replace_na(vent.los.tot.s,0),
        hospdis.loc = if_else(hospdis.loc == 'Home', 'Home', 'Facility'),
        admit.dx = as.character(admit.dx),
        admit.dx = case_match(admit.dx,
            c("Sepsis, ARDS due to infection or septic shock") ~
              "Sepsis or sepsis-associated acute respiratory distress syndrome",
            c("Acute MI","Arrhythmia","CHF/cardiogenic shock") ~
              "Cardiogenic shock, myocardial infarction, or dysrhythmia",
            c("ARDS without infection","COPD/asthma","Other pulmonary") ~
              "Acute respiratory failure",
            c("Airway protection/UAO") ~
              "Airway protection/upper airway obstruction",
            c("Cirrhosis/hepatic failure"," GI bleed") ~
              "Gastrointestinal disease or hepatic failure",
            c("Neurological disease","Seizures/status epilepticus") ~
              "Neurological disease or seizure",
            c("Renal failure","Metabolic/endocrine",
              "Metabolic acidosis, hypovolemia, or electrolyte disturbance") ~
              "Renal Failure and Endocrine/Metabolic Disturbances",
            c("Gastric Surgery","Hepatobiliary/pancreatic surgery",
               "Colonic surgery") ~
              "Abdominal Surgery",
            c("ENT Surgery","Obstetrical/gynecologic surgery","Orthopedic surgery",
              "Transplants (excluding liver)","Urologic surgery","Vascular surgery") ~
              "Other surgical procedure",
            c("Coagulopathy","Hemorrhagic shock","Malignancy",
              "Other","Other infectious disease") ~ "Other diagnosis"
            ),
        admit.dx = replace_na(admit.dx,"Other diagnosis")
      )
  ) %>% 
  left_join(adi %>% mutate(id = as.numeric(id)),by = "id") %>% 
  left_join(ruca %>% mutate(id = as.numeric(id)) %>% 
              select(id,ruca_cat), by = "id")

label(cohort_x$admit.dx) <- "Diagnosis at Enrollment"
label(cohort_x$days.del.tot) <- "Days of Delirium"
label(cohort_x$ADI) <- "Area Deprivation Index (ADI) "
label(cohort_x$ruca_cat) <- "Rural-Urban Commuting Areas (RUCA) Code"
label(cohort_x$va) <- "Discharge from a Veteran Hospital"
label(cohort_x$hospdis.loc) <- label(brainmind.oneobs$hospdis.loc)
label(cohort_x$days.vent.hosp) <- "Ventilation days"
label(cohort_x$vent.los.tot.s) <- label(brainmind.oneobs$vent.los.tot.s)

Statistical Analysis

The primary outcome of the study is the RBANS global score, while secondary outcomes include the Trail B score, ADL, FAQ, and SF-36 scores. The main exposure variables are RUCA codes, ADI, and their interaction. The model will adjust for age, Charlson Comorbidity Index, mean Modified Daily SOFA, days of delirium/coma, days of ventilation, education, sex, and hospital type (VA vs. civilian). Based on the specific outcomes of interest, the model will also adjust for IQ code at enrollment for the cognitive model and KATZ and FAQ scores at baseline for the disability model.

To enhance the analytical power, a longitudinal model will be used to analyze data from both the 3-month and 12-month follow-ups jointly. Generalized Estimating Equations (GEE) will be employed to account for the correlation between repeated measures. GEE is particularly suitable for this exploratory study due to its robustness against model and correlation structure misspecification. The model will be fitted using the ‘geepack’ package in R, and all inferences will be based on robust standard errors.

To address missing data, multiple imputation techniques will be implemented using the ‘mice’ package, with predictive mean matching as the method for imputing missing values. This method will target gaps primarily in demographic data, ICU electronic health record summaries, and follow-up data, provided that not all sub-scores are missing. If all main and sub-scores are absent, the subject will be excluded from the model. Models will be fitted for each imputation set and estimates will be pooled according to Rubin’s rules.

To mitigate attrition bias, inverse probability weighting will be applied. Weights will be calculated using logistic regression to estimate the probability of missingness based on the observed data, and these weights will be incorporated into the GEE model to adjust for missing data.

An additional sensitivity analysis will be conducted without the weights, using zip code as a clustering variable.

Result

Following the enrollment criteria, 550 unique patients are included in the cohort. with 550 patients at 3 months follow-up and 457 patients at 12 months follow-up.

Code
consort_p =
  add_box(txt = "Brainmind ICU cohort (N=1047)") %>% 
  add_side_box(txt = "Exclude:\n-Died in hospital (N=266)") %>% 
  add_box(txt = "Discharged Patient (N=781)") %>% 
    add_side_box(txt = "Exclude:\n-Deceased at 3 Month (N=115)\n-Withdrew (N=42)\n-Lost to Follow-up - Case Closed(N=5)\n-Does not have any RBANS scores\nin any categories (N=58)") %>% 
  add_box(txt = "3 Months Follow-up(N=550)") %>% 
  add_side_box(txt = "Exclude:\n-Deceased at 12 Month (N=58)\n-Withdrew (N=11)\n-Lost-to-Follow-Up Case Closed(N=11)\n-Does not have any RBANS scores\nin any categories (N=13)") %>% 
  add_box(txt = "12 Months Follow-up(N=457)")

plot(consort_p)

Table 1 presents the descriptive statistics of the covariates. The main exposures of the study, RUCA codes and ADI, exhibit a substantial number of missing values. Furthermore, the “Low commuting” categories within the RUCA codes have fewer than five observations. To preserve the statistical power of the analysis, the “Low commuting” categories will be merged with the “High commuting” categories. This decision is based on the spatial relationships observed on the map, ensuring a more robust analysis in subsequent studies.

Code
meanIQR = function(x,weights=rep(1,length(x)), ...){
  mean = mean(x,na.rm = T)
  p = quantile(x,c(0.25,0.75),na.rm = T)
  as.tbstat(c(mean,p),sep = "\t",sep2 = "- ",parens = c("[","]"))
}

cont = tableby.control(
  numeric.stats = c("Nmiss","meanIQR"),
  digits = 2,
  total = F
)

tableby(~.,
        data = cohort_x %>% 
          select(-id),
        control = cont
        ) %>% 
  summary() %>% 
  knitr::kable(caption = "Table.1 Cohort Characteristics")
Table.1 Cohort Characteristics
Overall (N=550)
Age at enrollment
   meanIQR 59.92 [51.37- 69.99]
Sex
   Male 327 (59.5%)
   Female 223 (40.5%)
Years of education
   N-Miss 4
   meanIQR 12.64 [12.00- 14.00]
IQCODE score at enrollment (missing -> 3)
   meanIQR 3.11 [3.00- 3.12]
FAQ category at enrollment
   N-Miss 5
   No impairment 502 (92.1%)
   Functional impairment 43 (7.9%)
Katz ADL category at enrollment
   N-Miss 19
   Independent 466 (87.8%)
   Partial disability 56 (10.5%)
   Total dependence 9 (1.7%)
Body Mass Index (BMI)
   N-Miss 3
   meanIQR 31.81 [24.96- 35.36]
Charlson score
   meanIQR 2.40 [1.00- 4.00]
CSHA Frailty Index
   1. Very fit 21 (3.8%)
   2. Well 90 (16.4%)
   3. Well with treated comorbid disease 192 (34.9%)
   4. Apparently vulnerable 120 (21.8%)
   5. Mildly frail 68 (12.4%)
   6. Moderately frail 48 (8.7%)
   7. Severely frail 11 (2.0%)
Katz ADL score at enrollment
   N-Miss 5
   meanIQR 0.73 [0.00- 1.00]
FAQ score at enrollment
   N-Miss 5
   meanIQR 2.29 [0.00- 2.00]
Diagnosis at Enrollment
   Abdominal Surgery 38 (6.9%)
   Acute respiratory failure 93 (16.9%)
   Airway protection/upper airway obstruction 54 (9.8%)
   Cardiogenic shock, myocardial infarction, or dysrhythmia 90 (16.4%)
   Gastrointestinal disease or hepatic failure 10 (1.8%)
   Neurological disease or seizure 7 (1.3%)
   Other diagnosis 45 (8.2%)
   Other surgical procedure 39 (7.1%)
   Renal Failure and Endocrine/Metabolic Disturbances 5 (0.9%)
   Sepsis or sepsis-associated acute respiratory distress syndrome 169 (30.7%)
APACHE II at ICU admission
   meanIQR 23.13 [17.00- 29.00]
Modified SOFA (omits GCS) at enrollment
   meanIQR 6.32 [4.00- 8.00]
Days severely septic in ICU during study period
   N-Miss 4
   meanIQR 3.91 [0.00- 5.00]
Length of time on vent during study period
   meanIQR 5.03 [0.67- 5.01]
Ventilation days
   meanIQR 5.46 [1.00- 6.00]
Days of Delirium
   meanIQR 1.96 [0.00- 3.00]
Days of coma within study period (imputed)
   meanIQR 1.99 [0.00- 2.00]
Days of delirium/coma within study period (imputed)
   meanIQR 5.06 [1.00- 7.00]
Days of septic coma during study period
   meanIQR 0.83 [0.00- 1.00]
ICU length of stay
   meanIQR 8.26 [2.49- 9.96]
Hospital discharge location
   Facility 195 (35.5%)
   Home 355 (64.5%)
Discharge from a Veteran Hospital
   Civilian 441 (80.2%)
   VA 109 (19.8%)
Area Deprivation Index (ADI)
   N-Miss 83
   meanIQR 66.42 [52.00- 85.00]
Rural-Urban Commuting Areas (RUCA) Code
   N-Miss 149
   Metropolitan Core 150 (37.4%)
   Metropolitan High Commuting 47 (11.7%)
   Metropolitan low Commuting 1 (0.2%)
   Micropolitan Core 69 (17.2%)
   Micropolitan High Commuting 32 (8.0%)
   Micropolitan low Commuting 2 (0.5%)
   Small Town Core 41 (10.2%)
   Small Town High Commuting 21 (5.2%)
   Small Town low Commuting 3 (0.7%)
   Rural 35 (8.7%)
Code
ruca_name = c(
  "Metropolitan Core",
  "Metropolitan High Commuting",
  "Metropolitan low Commuting",
  "Micropolitan Core",
  "Micropolitan High Commuting",
  "Micropolitan low Commuting",
  "Small Town Core",
  "Small Town High Commuting",
  "Small Town low Commuting",
  "Rural"
)

ruca_map = ruca_map %>% 
  mutate(ruca_cat = ruca_name[ruca_primary],
         ruca_cat = forcats::fct_reorder(ruca_cat,ruca_primary))

ggplot(TN_acs %>% 
  left_join(ruca_map))+
  geom_sf(aes(fill = ruca_cat),size=0.1) + 
  guides(fill = guide_legend(title = "RUCA",nrow = 3))+
  theme(legend.position = "top")+
  labs(title = "Figure 1. RUCA Codes in Tennessee") +
  scale_fill_viridis_d()

The descriptive statsitcs of outcome of interest are shown in Table.2.

Code
tableby(fu.period~.,
        data = cohort_y %>% 
          select(everything(),-id,-starts_with('rbans'),rbans.global.score, -time) %>% 
          relocate(rbans.global.score),
        control = cont) %>% 
  summary() %>% 
  knitr::kable(caption = "Table.2 Descriptives of outcomes")
Table.2 Descriptives of outcomes
3 Month (N=550) 12 Month (N=457) p value
RBANS global composite score 0.084
   N-Miss 86 60
   meanIQR 79.25 [71.00- 87.00] 80.83 [72.00- 90.00]
Trails B t-score 0.004
   N-Miss 70 55
   meanIQR 39.99 [33.00- 48.00] 42.46 [35.00- 50.00]
ADL dependence category 0.020
   N-Miss 27 8
   No dependence (score = 0) 330 (63.1%) 315 (70.2%)
   Some dependence (score >=1) 193 (36.9%) 134 (29.8%)
ADL total score 0.010
   N-Miss 27 8
   meanIQR 1.26 [0.00- 1.50] 0.90 [0.00- 1.00]
FAQ total score 0.089
   N-Miss 32 10
   meanIQR 5.69 [0.00- 9.00] 4.98 [0.00- 8.00]
FAQ impairment category 0.243
   N-Miss 32 10
   No impairment (score 0-8) 383 (73.9%) 345 (77.2%)
   Some impairment (score >=9) 135 (26.1%) 102 (22.8%)
SF-36 physical component score < 0.001
   N-Miss 35 18
   meanIQR 30.46 [22.07- 37.40] 33.37 [24.41- 41.77]
SF-36 mental component score 0.613
   N-Miss 35 18
   meanIQR 49.98 [39.72- 61.74] 50.45 [41.74- 60.46]
Status at followup
   Deceased 0 (0.0%) 0 (0.0%)
   Living 11 (2.0%) 1 (0.2%)
   Living-Active in the study 524 (95.3%) 456 (99.8%)
   Living-Withdrew from the study 0 (0.0%) 0 (0.0%)
   Lost to Follow-Up - Case Closed 0 (0.0%) 0 (0.0%)
   Visit Not Completed 13 (2.4%) 0 (0.0%)
   Still Trying to Contact 2 (0.4%) 0 (0.0%)

Noted that 25% of missing of RUCA code and 15% missing of ADI of our main exposure of interest. 15% of the RBANS scores are missing, with the highest missing rate of RBANS score at 12%.

Code
par(mfrow = c(2,2))
obj = naclus(cohort_x)
naplot(obj)

Code
par(mfrow = c(2,2))
obj = naclus(cohort_y)
naplot(obj)

Imputation

Code
# Fix Ruca
ruca_name = c(
  "Metropolitan Core",
  "Metropolitan High/Low Commuting",
  "Metropolitan High/Low Commuting",
  "Micropolitan Core",
  "Micropolitan High/Low Commuting",
  "Micropolitan High/Low Commuting",
  "Small Town Core",
  "Small Town High/Low Commuting",
  "Small Town High/Low Commuting",
  "Rural"
)

## Keep only some rbans missing
cohort_y %>% 
  filter(!all(across(starts_with("rbans"),is.na),
              fu.period=="12 Month")) %>% 
  left_join(cohort_x %>% 
              mutate(ruca_cat = factor(ruca_name[as.numeric(ruca_cat)],
                                       levels = unique(ruca_name))),
            by = "id") -> cohort

missing_rbans = rowSums(!is.na(cohort_y %>% select(starts_with("rbans"))))<1
Code
mimp = read_rds("mimp.rds")

cohort_imp = complete(mimp,1)

With multiple imputation to estimate the attrition weight for missing main outcome of interst RBANS. The pooled weight is shown in Figure.3, the highest weight is around 6, which is not too extreme. Most of the weight is around 1 to 2.

Code
y_miss = rowSums(is.na(cohort_y)) > 0
w = list()
for (i in 1:10) {
  dt = complete(mimp, i)
  w[[i]] = lrm(as.formula(
    paste0(
      "y_miss~rbans.global.score +",
      paste0(colnames(cohort_x %>%
                        select(-id)), collapse = "+")
    )
  ),
  data = dt)$linear.predictor
}

w = do.call(cbind,w)
w = rowMeans(w)
w = (1 + exp(w))

tibble(IPW.weight = w) %>% 
  ggplot(aes(x = IPW.weight))+
  geom_density()+
  geom_rug()

IPW-weight

Figure 4 illustrates the association between the main effects and RBANS total scores. After adjusting for longitudinal effects, there appears to be no significant association between RUCA levels and RBANS scores. Similarly, ADI scores also show no significant association with RBANS scores.

However, the interaction between RUCA and ADI scores may modify the non-linear association with RBANS scores, although the confidence bands significantly overlap, indicating uncertainty.

This suggests that further exploration of the non-linear associations between ADI and RBANS, as well as the interaction terms involving RUCA and RBANS, may be warranted.

Figure 5 provides little graphical evidence that the associations between RUCA and ADI with RBANS scores are modified by the follow-up period. Figure 6 suggests that the covariates are not all linearly associated with RBANS scores, indicating potential complexity in their relationships.

Code
cohort_imp$res = lm(rbans.global.score ~ fu.period, cohort_imp)$res

p1 = cohort_imp %>% 
  ggplot(aes(y = res, x = time, color = ruca_cat))+
  geom_line(aes(group = id),alpha = 0.05) +
  geom_smooth(method = "loess",se = F) + 
  scale_x_continuous(breaks = c(3,12))+
  scale_color_viridis_d() +
  labs(color = "",
       y = "RBANS Residual",
       x = "Follow-up")+
  guides(color = guide_legend(nrow=3))+
  theme(legend.position = "top")
Code
p2 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = rbans.global.score))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period))+
  scale_color_fivethirtyeight()+
  guides(color = guide_legend(nrow=3))+
  labs(color = "",
       y = "RBANS Global Score")
Code
p3 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = rbans.global.score))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = ruca_cat),method = "lm",
              formula = y ~ poly(x,2))+
  scale_color_viridis_d()+
  theme(legend.position = "right")+
  labs(color = "",
       y = "RBANS Global Score")+
  theme(legend.position = "none")
Code
 p1 + (p2 / p3) + plot_layout(guides = "collect") 

Main Exposure vs RBANS
Code
cohort_imp %>% 
  ggplot(aes(x = ADI, y = rbans.global.score))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period),
              method = "lm",
              formula = y ~poly(x,2))+
  scale_color_fivethirtyeight()+
  labs(color = "",
       y = "RBANS Global Score")+
  facet_wrap(~ruca_cat,nrow = 5)

Code
s3 = summary(rbans.global.score ~ 
              age.enroll +charlson.score + iqcode.score.e +
              # adl.cat.e + faq.e + 
              sofa.mod + delcoma.s.imp + vent.los.tot.s+
              edu + sex.pp + va,
        data = cohort_imp,
        subset = fu.period == "3 Month")
plot(s3,width.factor=0.5,xlab = "RBANS 3 Month")

Figure 6. Covariate vs RBANS
Code
s12 = summary(rbans.global.score ~ 
              age.enroll +charlson.score + iqcode.score.e +
              # adl.cat.e + faq.e + 
              sofa.mod + delcoma.s.imp + vent.los.tot.s+
              edu + sex.pp + va,
        data = cohort_imp,
        subset = fu.period == "12 Month")
plot(s12,width.factor=0.5,xlab = "RBANS 12 Month")

Figure 6. Covariate vs RBANS

We can reach similar observation for main exposure and Trail B score.

Code
cohort_imp$res = lm(trail.b.tscore ~ fu.period, cohort_imp)$res

p1 = cohort_imp %>% 
  ggplot(aes(y = res, x = time, color = ruca_cat))+
  geom_line(aes(group = id),alpha = 0.05) +
  geom_smooth(method = "loess",se = F) + 
  scale_x_continuous(breaks = c(3,12))+
  scale_color_viridis_d() +
  labs(color = "",
       y = "Trail.b Residual",
       x = "Follow-up")+
  guides(color = guide_legend(nrow=3))
Code
p2 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = trail.b.tscore))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period))+
  scale_color_fivethirtyeight()+
  guides(color = guide_legend(nrow=3))+
  labs(color = "",
       y = "Trail.B Score")
Code
p3 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = trail.b.tscore))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = ruca_cat),method = "lm",
              formula = y ~ poly(x,2))+
  scale_color_viridis_d()+
  theme(legend.position = "none")+
  labs(color = "",
       y = "Trail.B Score")
Code
 p1 + (p2 / p3) + plot_layout(guides = "collect") 

Main Exposure vs Trail.B

Katz and FAQ score follows a right skewed distribution. The main exposures and the outcome of interestes in original scale show similar observation as RBANS.

Code
cohort_imp$res = lm(adl.totscore ~ fu.period, cohort_imp)$res

p1 = cohort_imp %>% 
  ggplot(aes(y = res, x = time, color = ruca_cat))+
  geom_line(aes(group = id),alpha = 0.05) +
  geom_smooth(method = "loess",se = F) + 
  scale_x_continuous(breaks = c(3,12))+
  scale_color_viridis_d() +
  labs(color = "",
       y = "RBANS Residual",
       x = "Follow-up")+
  guides(color = guide_legend(nrow=3))
Code
p2 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = adl.totscore))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period))+
  scale_color_fivethirtyeight()+
  labs(color = "",
       y = "Kats Score") +
  guides(color = guide_legend(nrow=3))
Code
p3 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = adl.totscore))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = ruca_cat),method = "lm",
              formula = y ~ poly(x,2))+
  scale_color_viridis_d()+
  theme(legend.position = "none")+
  labs(color = "",
       y = "Kats Score")
Code
 p1 + (p2 / p3) + plot_layout(guides = "collect") 

Main Exposure vs Katz
Code
s3 = summary(faq.totscore ~ 
              age.enroll +charlson.score + 
               # iqcode.score.e +
              adl.cat.e + faq.e +
              sofa.mod + delcoma.s.imp + vent.los.tot.s+
              edu + sex.pp + va,
        data = cohort_imp,
        subset = fu.period == "3 Month")
plot(s3,width.factor=0.5,xlab = "FAQ 3 Month")

Code
s12 = summary(faq.totscore ~ 
              age.enroll +charlson.score + 
                # iqcode.score.e +
              adl.cat.e + faq.e +
              sofa.mod + delcoma.s.imp + vent.los.tot.s+
              edu + sex.pp + va,
        data = cohort_imp,
        subset = fu.period == "12 Month")
plot(s12,width.factor=0.5,xlab = "FAQ 12 Month")

Code
cohort_imp$res = lm(faq.totscore ~ fu.period, cohort_imp)$res

p1 = cohort_imp %>% 
  ggplot(aes(y = res, x = time, color = ruca_cat))+
  geom_line(aes(group = id),alpha = 0.05) +
  geom_smooth(method = "loess",se = F) + 
  scale_x_continuous(breaks = c(3,12))+
  scale_color_viridis_d() +
  labs(color = "",
       y = "FAQ Residual",
       x = "Follow-up")+
  guides(color = guide_legend(nrow=3))
Code
p2 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = faq.totscore))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period))+
  scale_color_fivethirtyeight()+
  guides(color = guide_legend(nrow=3))+
  labs(color = "",
       y = "FAQ Score")
Code
p3 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = faq.totscore))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = ruca_cat),method = "lm",
              formula = y ~ poly(x,2))+
  scale_color_viridis_d()+
  theme(legend.position = "none")+
  labs(color = "",
       y = "FAQ Score")
Code
 p1 + (p2 / p3) + plot_layout(guides = "collect") 

Main Exposure vs FAQ
Code
cohort_imp$res = lm(sf36.pcs ~ fu.period, cohort_imp)$res

p1 = cohort_imp %>% 
  ggplot(aes(y = res, x = time, color = ruca_cat))+
  geom_line(aes(group = id),alpha = 0.05) +
  geom_smooth(method = "loess",se = F) + 
  scale_x_continuous(breaks = c(3,12))+
  scale_color_viridis_d() +
  labs(color = "",
       y = "SF-36 Residual",
       x = "Follow-up")+
  guides(color = guide_legend(nrow=3))
Code
p2 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = sf36.pcs))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period))+
  scale_color_fivethirtyeight()+
  labs(color = "",
       y = "SF-36 - PCS Score")+
  guides(color = guide_legend(nrow=3))
Code
p3 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = sf36.pcs))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = ruca_cat),method = "lm",
              formula = y ~ poly(x,2))+
  scale_color_viridis_d()+
  theme(legend.position = "none")+
  labs(color = "",
       y = "SF-36 - PCS Score")
Code
 p1 + (p2 / p3) + plot_layout(guides = "collect") 

Main Exposure vs SF-36 - PCS Score
Code
cohort_imp$res = lm(sf36.mcs ~ fu.period, cohort_imp)$res

p1 = cohort_imp %>% 
  ggplot(aes(y = res, x = time, color = ruca_cat))+
  geom_line(aes(group = id),alpha = 0.05) +
  geom_smooth(method = "loess",se = F) + 
  scale_x_continuous(breaks = c(3,12))+
  scale_color_viridis_d() +
  labs(color = "",
       y = "SF-36 Residual",
       x = "Follow-up")+
  guides(color = guide_legend(nrow=3))
Code
p2 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = sf36.mcs))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = fu.period))+
  scale_color_fivethirtyeight()+
  labs(color = "",
       y = "SF-36 - MCS Score")+
  guides(color = guide_legend(nrow=3))
Code
p3 = cohort_imp %>% 
  ggplot(aes(x = ADI, y = sf36.pcs))+
  geom_jitter(alpha = 0.1)+
  geom_smooth(aes(color = ruca_cat),method = "lm",
              formula = y ~ poly(x,2))+
  scale_color_viridis_d()+
  theme(legend.position = "none")+
  labs(color = "",
       y = "SF-36 - MCS Score")
Code
 p1 + (p2 / p3) + plot_layout(guides = "collect") 

Main Exposure vs SF-36 - MCS Score

Model

From the exploratory data analysis (EDA), there is no significant evidence of a relationship between the outcome of interest and factors such as ADI and RUCA codes. Furthermore, there is no clear evidence to suggest that this relationship is modified by time. However, there might be an interaction between ADI and RUCA, and the relationship appears to be non-linear.

To leverage the advantages of longitudinal data and ensure robust estimation, a generalized estimating equation (GEE) has been selected for the model. Noted that Katz and FAQ might not be normal distributed, GEE however is robust to residuals distribution as long as it is not too extreme.

The model includes the main effects and their interactions, and adjustments are made for covariates. To avoid assuming linearity for the covariates, splines are utilized.

For the cognitive outcomes of interest (RBANS, SF-36-MCS), the main effect model is defined as follows:

\[\begin{align*} \mu_i = E[Y_{cognitive}|X_i] = \beta_0 &+ \beta_1 \times \text{fu.period} \\ &+ \vec\beta_{RUCA}*\text{RUCA} + \beta_{ADI}*\text{(ADI-69)} + \beta_{ADI^2}*\text{(ADI-69)}^2\\ &+ \vec\beta_{RUCA*ADI}*\text{RUCA}*\text{(ADI-69)} +\\ & +\vec\beta_{RUCA*ADI^2}*\text{RUCA}*\text{(ADI-69)}^2 +\\ &+ f_{\text{age.enroll}}(\text{age.enroll}) + f_{\text{charlson.score}}(\text{charlson.score}) \\ &+ f_{\text{iqcode.score.e}}(\text{iqcode.score.e}) + f_{\text{sofa.mod}}(\text{sofa.mod}) \\ &+ f_{\text{delcoma.s.imp}}(\text{delcoma.s.imp}) + f_{\text{vent.los.tot.s}}(\text{vent.los.tot.s}) \\ &+ \beta_{\text{edu}} \times \text{edu} + \beta_{\text{sex.pp}} \times \text{sex.pp} + \beta_{\text{va}} \times \text{va} \end{align*}\]

For functional outcome of interest, the main effect model show as follow:

\[\begin{align*} \mu_i = E[Y_{functional}|X_i] = \beta_0 &+ \beta_1 \times \text{fu.period} \\ &+ \vec\beta_{RUCA}*\text{RUCA} + \beta_{ADI}*\text{(ADI-69)} + \beta_{ADI^2}*\text{(ADI-69)}^2\\ &+ \vec\beta_{RUCA*ADI}*\text{RUCA}*\text{(ADI-69)} +\\ & +\vec\beta_{RUCA*ADI^2}*\text{RUCA}*\text{(ADI-69)}^2 +\\ &+ f_{\text{age.enroll}}(\text{age.enroll}) + f_{\text{charlson.score}}(\text{charlson.score}) \\ &+ f_{\text{adl.enroll}}(\text{adl.enroll}) +f_{\text{faq.enroll}}(\text{faq.enroll}) +\\ & + f_{\text{sofa.mod}}(\text{sofa.mod}) \\ &+ f_{\text{delcoma.s.imp}}(\text{delcoma.s.imp}) + f_{\text{vent.los.tot.s}}(\text{vent.los.tot.s}) \\ &+ \beta_{\text{edu}} \times \text{edu} + \beta_{\text{sex.pp}} \times \text{sex.pp} + \beta_{\text{va}} \times \text{va} \end{align*}\]

\[ H_0: \vec\beta_{RUCA} = \vec\beta_{ADI} = \vec\beta_{RUCA*ADI} = 0 \]

The null hypothesis for each model posits that the outcome of interest is not associated with RUCA, ADI, or their interaction.

Three models are fitted for hypothesis testing: The null model, which excludes RUCA, ADI, and their interaction; the main effect model, which includes these variables; and an additional OLS model fitted with the same formula to provide a clear ANOVA table and contrast summary, supplementing the GEE main model’s estimations.

An ANOVA test using Wald statistics is conducted to test the main hypothesis concerning the outcome of interest (RBANS). This approach helps to rigorously examine the relationship between the variables and the outcome.

Code
replace_ols = function(ols_object,mira_object){
  pool_est = Reduce("+",lapply(mira_object$analyses,function(x) coef(x)))/10
  pool_var = Reduce("+",lapply(mira_object$analyses,function(x) vcov(x)))/10
  
  ols_object$coefficients = pool_est
  ols_object$var = pool_var
  
  return(ols_object)
}
Code
### Null model
m0 = with(mimp,
          geeglm(rbans.global.score ~ 
           fu.period +
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3) + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

### GLM

m1 = ols(rbans.global.score ~ 
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3) + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
         weights = w,
         data = cohort_imp)

### Main effect model

m2 = with(mimp,
          geeglm(rbans.global.score ~ 
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3) + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

m1 = replace_ols(m1,m2)

D1(m2,m0)$result %>% 
  as.data.frame() %>% 
  knitr::kable(caption = "ANOVA Test",digits = 2)
ANOVA Test
F.value df1 df2 P(>F) RIV
0.35 20 427.25 1 0.9
Code
m2.summary = summary(m1, ADI = quantile(cohort$ADI,c(0.25,0.75),na.rm = T),
        ruca_cat = ruca_name[1],est.all = F) %>% 
  as.data.frame() %>% 
  select(-Diff.,-S.E.,-Type) %>% 
  mutate(across(where(is.numeric),round,2))

m2.summary %>% 
  knitr::kable(caption = "Contrast Table",digits = 2)
Contrast Table
Low High Effect Lower 0.95 Upper 0.95
ADI 52 85 0.98 -2.68 4.63
ruca_cat - Metropolitan High/Low Commuting:Metropolitan Core 1 2 -0.70 -4.24 2.85
ruca_cat - Micropolitan Core:Metropolitan Core 1 3 0.96 -2.42 4.34
ruca_cat - Micropolitan High/Low Commuting:Metropolitan Core 1 4 3.17 -0.41 6.76
ruca_cat - Small Town Core:Metropolitan Core 1 5 -1.44 -5.27 2.38
ruca_cat - Small Town High/Low Commuting:Metropolitan Core 1 6 -0.37 -5.43 4.68
ruca_cat - Rural:Metropolitan Core 1 7 -1.15 -5.89 3.59

With a ANOVA test with pooled Wald statistics, we do not have enough evidence to reject the null hypothesis that neither rurality nor area deprevation is associated with RBANS score.

Note that the following interpretation is based on the weighted adjusted main effect model, which all are NOT statistical significant.

  • Increase of rurality within metropolitan area with a median ADI(69) is associated with -0.7 change in expected population RBANS score, but with a 95% CI of (-4.24,2.85)

  • Compared to metropolitan core, a population in micropolitan core and High/Low commuting area with a median ADI is associated with 0.96, 3.17 change in expected RBANS scores respectably, but with 95% CI of ( -2.42,4.34) and ( -0.41,6.76).

  • Compared to metropolitan core, a population in small town core and High/Low commuting area with a median ADI is associated with -1.44, -0.37 change in expected RBANS scores respectably, but with 95% CI of ( -5.27,2.38 ) and ( -5.43,4.68 ).

  • Compared to metropolitan core, a population in rural area with a median ADI is associated with -1.15 change in RBANS, but with a 95% CI of ( -5.89,3.59 )

  • For a population in metropolitan core, there is a 0.98 change in expected RBANS score comparing 75-percentile to 25-percentile ADI area, but with a 95% CI of ( -2.68,4.63 )

Code
zipcode = geoid %>% 
  mutate(id = as.numeric(id),
         zip = str_sub(geoid,1,5)) %>%
  right_join(cohort_imp) %>% 
  pull(zip)

weight.result = list()
zip.result = list()
Code
m2.w = with(mimp,
          geeglm(rbans.global.score ~ 
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3) + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           corstr = "exchangeable",
         family = gaussian
           )
          )

weight.result[["rbans"]] = filter(summary(pool(m2.w))[,c(1,2,3,6)],
                                  str_detect(term,"ADI|ruca")
)

# weight.result[['rbans']] = as.data.frame(
#   anova(replace_ols(m1,m2.w),ADI,ruca_cat)
# )
Code
### Null model
m0 = with(mimp,
          geeglm(trail.b.tscore ~
           fu.period +
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$trail.b.tscore),
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

### GLM

m1 = ols(trail.b.tscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
         weights = w,
         data = cohort_imp)

### Main effect model

m2 = with(mimp,
          geeglm(trail.b.tscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$trail.b.tscore),
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

m1 = replace_ols(m1,m2)

D1(m2,m0)$result %>% 
  as.data.frame() %>% 
  knitr::kable(caption = "ANOVA Test",digits = 2)
ANOVA Test
F.value df1 df2 P(>F) RIV
0.34 20 423.74 1 0.81
Code
m2.summary = summary(m1, ADI = quantile(cohort$ADI,c(0.25,0.75),na.rm = T),
        ruca_cat = ruca_name[1],est.all = F) %>% 
  as.data.frame() %>% 
  select(-Diff.,-S.E.,-Type) %>% 
  mutate(across(where(is.numeric),round,2))

m2.summary %>% 
  knitr::kable(caption = "Contrast Table",digits = 2)
Contrast Table
Low High Effect Lower 0.95 Upper 0.95
ADI 52 85 0.37 -3.71 4.46
ruca_cat - Metropolitan High/Low Commuting:Metropolitan Core 1 2 1.14 -2.46 4.74
ruca_cat - Micropolitan Core:Metropolitan Core 1 3 0.49 -2.82 3.79
ruca_cat - Micropolitan High/Low Commuting:Metropolitan Core 1 4 0.33 -4.15 4.80
ruca_cat - Small Town Core:Metropolitan Core 1 5 -0.99 -5.70 3.72
ruca_cat - Small Town High/Low Commuting:Metropolitan Core 1 6 5.05 -0.62 10.72
ruca_cat - Rural:Metropolitan Core 1 7 -0.43 -6.38 5.52

Note that the following interpretation is based on the weighted adjusted main effect model, which all are NOT statistical significant.

  • Increase of rurality within metropolitan area with a median ADI(69) is associated with 1.14 change in expected population Trail.B score, but with a 95% CI of (-2.46,4.74)

  • Compared to metropolitan core, a population in micropolitan core and High/Low commuting area with a median ADI is associated with 0.49, 0.33 change in expected Trail.B scores respectably, but with 95% CI of ( -2.82,3.79) and ( -4.15,4.8).

  • Compared to metropolitan core, a population in small town core and High/Low commuting area with a median ADI is associated with -0.99, 5.05 change in expected Trail.B scores respectably, but with 95% CI of ( -5.7,3.72 ) and ( -0.62,10.72 ).

  • Compared to metropolitan core, a population in rural area with a median ADI is associated with -0.43 change in Trail.B, but with a 95% CI of ( -6.38,5.52 )

  • For a population in metropolitan core, there is a 0.37 change in expected Trail.B score comparing 75-percentile to 25-percentile ADI area, but with a 95% CI of ( -3.71,4.46 )

Code
m2.w = with(mimp,
          geeglm(trail.b.tscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$trail.b.tscore),
           corstr = "exchangeable",
         family = gaussian
           )
          )

weight.result[["trail.b"]] = filter(summary(pool(m2.w))[,c(1,2,3,6)],
                                  str_detect(term,"ADI|ruca")
)
Code
### Null model
m0 = with(mimp,
          geeglm(adl.totscore ~
           fu.period +
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$adl.totscore),
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

### GLM

m1 = ols(adl.totscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
         weights = w,
         data = cohort_imp)

### Main effect model

m2 = with(mimp,
          geeglm(adl.totscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$adl.totscore),
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

m1 = replace_ols(m1,m2)

D1(m2,m0)$result %>% 
  as.data.frame() %>% 
  knitr::kable(caption = "ANOVA Test",digits = 2)
ANOVA Test
F.value df1 df2 P(>F) RIV
0.49 20 373.41 0.97 1.1
Code
m2.summary = summary(m1, ADI = quantile(cohort$ADI,c(0.25,0.75),na.rm = T),
        ruca_cat = ruca_name[1],est.all = F) %>% 
  as.data.frame() %>% 
  select(-Diff.,-S.E.,-Type) %>% 
  mutate(across(where(is.numeric),round,2))

m2.summary %>% 
  knitr::kable(caption = "Contrast Table",digits = 2)
Contrast Table
Low High Effect Lower 0.95 Upper 0.95
ADI 52 85 0.20 -0.44 0.84
ruca_cat - Metropolitan High/Low Commuting:Metropolitan Core 1 2 0.75 0.12 1.39
ruca_cat - Micropolitan Core:Metropolitan Core 1 3 0.36 -0.20 0.93
ruca_cat - Micropolitan High/Low Commuting:Metropolitan Core 1 4 0.71 0.07 1.34
ruca_cat - Small Town Core:Metropolitan Core 1 5 0.79 -0.05 1.63
ruca_cat - Small Town High/Low Commuting:Metropolitan Core 1 6 0.06 -0.87 1.00
ruca_cat - Rural:Metropolitan Core 1 7 0.99 0.10 1.88

Note that the following interpretation is based on the weighted adjusted main effect model, which all are NOT statistical significant.

  • Increase of rurality within metropolitan area with a median ADI(69) is associated with 0.75 change in expected population Katz score, but with a 95% CI of (0.12,1.39)

  • Compared to metropolitan core, a population in micropolitan core and High/Low commuting area with a median ADI is associated with 0.36, 0.71 change in expected Katz scores respectably, but with 95% CI of ( -0.2,0.93) and ( 0.07,1.34).

  • Compared to metropolitan core, a population in small town core and High/Low commuting area with a median ADI is associated with 0.79, 0.06 change in expected Katz scores respectably, but with 95% CI of ( -0.05,1.63 ) and ( -0.87,1 ).

  • Compared to metropolitan core, a population in rural area with a median ADI is associated with 0.99 change in Katz, but with a 95% CI of ( 0.1,1.88 )

  • For a population in metropolitan core, there is a 0.2 change in expected Katz score comparing 75-percentile to 25-percentile ADI area, but with a 95% CI of ( -0.44,0.84 )

Code
m2.w = with(mimp,
          geeglm(adl.totscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$adl.totscore),
           corstr = "exchangeable",
         family = gaussian
           )
          )

weight.result[["katz"]] = filter(summary(pool(m2.w))[,c(1,2,3,6)],
                                  str_detect(term,"ADI|ruca")
)
Code
### Null model
m0 = with(mimp,
          geeglm(faq.totscore ~
           fu.period +
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           subset = is.finite(cohort$faq.totscore),
           corstr = "exchangeable",
         family = gaussian
           )
          )

### GLM

m1 = ols(faq.totscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
         weights = w,
         data = cohort_imp)

### Main effect model

m2 = with(mimp,
          geeglm(faq.totscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$faq.totscore),
           weights = w,
           corstr = "exchangeable",
         family = gaussian
           )
          )

m1 = replace_ols(m1,m2)

D1(m2,m0)$result %>% 
  as.data.frame() %>% 
  knitr::kable(caption = "ANOVA Test",digits = 2)
ANOVA Test
F.value df1 df2 P(>F) RIV
0.27 20 476.37 1 0.71
Code
m2.summary = summary(m1, ADI = quantile(cohort$ADI,c(0.25,0.75),na.rm = T),
        ruca_cat = ruca_name[1],est.all = F) %>% 
  as.data.frame() %>% 
  select(-Diff.,-S.E.,-Type) %>% 
  mutate(across(where(is.numeric),round,2))

m2.summary %>% 
  knitr::kable(caption = "Contrast Table",digits = 2)
Contrast Table
Low High Effect Lower 0.95 Upper 0.95
ADI 52 85 -0.15 -2.24 1.94
ruca_cat - Metropolitan High/Low Commuting:Metropolitan Core 1 2 -0.04 -1.88 1.80
ruca_cat - Micropolitan Core:Metropolitan Core 1 3 0.20 -1.56 1.97
ruca_cat - Micropolitan High/Low Commuting:Metropolitan Core 1 4 0.41 -1.63 2.45
ruca_cat - Small Town Core:Metropolitan Core 1 5 1.56 -0.93 4.04
ruca_cat - Small Town High/Low Commuting:Metropolitan Core 1 6 0.54 -2.34 3.41
ruca_cat - Rural:Metropolitan Core 1 7 1.57 -1.14 4.27

Note that the following interpretation is based on the weighted adjusted main effect model, which all are NOT statistical significant.

  • Increase of rurality within metropolitan area with a median ADI(69) is associated with -0.04 change in expected population FAQ score, but with a 95% CI of (-1.88,1.8)

  • Compared to metropolitan core, a population in micropolitan core and High/Low commuting area with a median ADI is associated with 0.2, 0.41 change in expected FAQ scores respectably, but with 95% CI of ( -1.56,1.97) and ( -1.63,2.45).

  • Compared to metropolitan core, a population in small town core and High/Low commuting area with a median ADI is associated with 1.56, 0.54 change in expected FAQ scores respectably, but with 95% CI of ( -0.93,4.04 ) and ( -2.34,3.41 ).

  • Compared to metropolitan core, a population in rural area with a median ADI is associated with 1.57 change in FAQ, but with a 95% CI of ( -1.14,4.27 )

  • For a population in metropolitan core, there is a -0.15 change in expected FAQ score comparing 75-percentile to 25-percentile ADI area, but with a 95% CI of ( -2.24,1.94 )

Code
m2.w = with(mimp,
          geeglm(faq.totscore ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$faq.totscore),
           corstr = "exchangeable",
         family = gaussian
           )
          )

weight.result[["faq"]] = filter(summary(pool(m2.w))[,c(1,2,3,6)],
                                  str_detect(term,"ADI|ruca")
)
Code
### Null model
m0 = with(mimp,
          geeglm(sf36.pcs ~
           fu.period +
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           subset = is.finite(cohort$sf36.pcs),
           corstr = "exchangeable",
         family = gaussian
           )
          )

### GLM

m1 = ols(sf36.pcs ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
         weights = w,
         data = cohort_imp)

### Main effect model

m2 = with(mimp,
          geeglm(sf36.pcs ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           subset = is.finite(cohort$sf36.pcs),
           corstr = "exchangeable",
         family = gaussian
           )
          )

m1 = replace_ols(m1,m2)

D1(m2,m0)$result %>% 
  as.data.frame() %>% 
  knitr::kable(caption = "ANOVA Test",digits = 2)
ANOVA Test
F.value df1 df2 P(>F) RIV
0.55 20 474.72 0.94 0.71
Code
m2.summary = summary(m1, ADI = quantile(cohort$ADI,c(0.25,0.75),na.rm = T),
        ruca_cat = ruca_name[1],est.all = F) %>% 
  as.data.frame() %>% 
  select(-Diff.,-S.E.,-Type) %>% 
  mutate(across(where(is.numeric),round,2))

m2.summary %>% 
  knitr::kable(caption = "Contrast Table",digits = 2)
Contrast Table
Low High Effect Lower 0.95 Upper 0.95
ADI 52 85 0.92 -2.34 4.19
ruca_cat - Metropolitan High/Low Commuting:Metropolitan Core 1 2 -0.61 -3.79 2.58
ruca_cat - Micropolitan Core:Metropolitan Core 1 3 -0.72 -3.70 2.26
ruca_cat - Micropolitan High/Low Commuting:Metropolitan Core 1 4 -0.72 -4.54 3.09
ruca_cat - Small Town Core:Metropolitan Core 1 5 -3.16 -6.37 0.04
ruca_cat - Small Town High/Low Commuting:Metropolitan Core 1 6 -3.72 -8.22 0.79
ruca_cat - Rural:Metropolitan Core 1 7 -1.83 -5.73 2.08

Note that the following interpretation is based on the weighted adjusted main effect model, which all are NOT statistical significant.

  • Increase of rurality within metropolitan area with a median ADI(69) is associated with -0.61 change in expected population SF36-PCS score, but with a 95% CI of (-3.79,2.58)

  • Compared to metropolitan core, a population in micropolitan core and High/Low commuting area with a median ADI is associated with -0.72, -0.72 change in expected SF36-PCS scores respectably, but with 95% CI of ( -3.7,2.26) and ( -4.54,3.09).

  • Compared to metropolitan core, a population in small town core and High/Low commuting area with a median ADI is associated with -3.16, -3.72 change in expected SF36-PCS scores respectably, but with 95% CI of ( -6.37,0.04 ) and ( -8.22,0.79 ).

  • Compared to metropolitan core, a population in rural area with a median ADI is associated with -1.83 change in SF36-PCS, but with a 95% CI of ( -5.73,2.08 )

  • For a population in metropolitan core, there is a 0.92 change in expected SF36-PCS score comparing 75-percentile to 25-percentile ADI area, but with a 95% CI of ( -2.34,4.19 )

Code
m2.w = with(mimp,
          geeglm(sf36.pcs ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(adl.e,3) + rcs(faq.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           corstr = "exchangeable",
           subset = is.finite(cohort$sf36.pcs),
         family = gaussian
           )
          )

weight.result[["sf36.pcs"]] = filter(summary(pool(m2.w))[,c(1,2,3,6)],
                                  str_detect(term,"ADI|ruca")
)
Code
### Null model
m0 = with(mimp,
          geeglm(sf36.mcs ~
           fu.period +
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           subset = is.finite(cohort$sf36.mcs),
           corstr = "exchangeable",
         family = gaussian
           )
          )

### GLM

m1 = ols(sf36.mcs ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
         weights = w,
         data = cohort_imp)

### Main effect model

m2 = with(mimp,
          geeglm(sf36.mcs ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           weights = w,
           subset = is.finite(cohort$sf36.mcs),
           corstr = "exchangeable",
         family = gaussian
           )
          )

m1 = replace_ols(m1,m2)

D1(m2,m0)$result %>% 
  as.data.frame() %>% 
  knitr::kable(caption = "ANOVA Test",digits = 2)
ANOVA Test
F.value df1 df2 P(>F) RIV
0.53 20 465.21 0.95 0.74
Code
m2.summary = summary(m1, ADI = quantile(cohort$ADI,c(0.25,0.75),na.rm = T),
        ruca_cat = ruca_name[1],est.all = F) %>% 
  as.data.frame() %>% 
  select(-Diff.,-S.E.,-Type) %>% 
  mutate(across(where(is.numeric),round,2))

m2.summary %>% 
  knitr::kable(caption = "Contrast Table",digits = 2)
Contrast Table
Low High Effect Lower 0.95 Upper 0.95
ADI 52 85 -3.78 -8.31 0.74
ruca_cat - Metropolitan High/Low Commuting:Metropolitan Core 1 2 0.25 -3.80 4.31
ruca_cat - Micropolitan Core:Metropolitan Core 1 3 2.42 -1.32 6.17
ruca_cat - Micropolitan High/Low Commuting:Metropolitan Core 1 4 2.12 -2.81 7.05
ruca_cat - Small Town Core:Metropolitan Core 1 5 0.74 -4.27 5.75
ruca_cat - Small Town High/Low Commuting:Metropolitan Core 1 6 4.53 -0.99 10.05
ruca_cat - Rural:Metropolitan Core 1 7 -2.81 -9.20 3.58

Note that the following interpretation is based on the weighted adjusted main effect model, which all are NOT statistical significant.

  • Increase of rurality within metropolitan area with a median ADI(69) is associated with 0.25 change in expected population SF36-MCS score, but with a 95% CI of (-3.8,4.31)

  • Compared to metropolitan core, a population in micropolitan core and High/Low commuting area with a median ADI is associated with 2.42, 2.12 change in expected SF36-MCS scores respectably, but with 95% CI of ( -1.32,6.17) and ( -2.81,7.05).

  • Compared to metropolitan core, a population in small town core and High/Low commuting area with a median ADI is associated with 0.74, 4.53 change in expected SF36-MCS scores respectably, but with 95% CI of ( -4.27,5.75 ) and ( -0.99,10.05 ).

  • Compared to metropolitan core, a population in rural area with a median ADI is associated with -2.81 change in SF36-MCS, but with a 95% CI of ( -9.2,3.58 )

  • For a population in metropolitan core, there is a -3.78 change in expected SF36-MCS score comparing 75-percentile to 25-percentile ADI area, but with a 95% CI of ( -8.31,0.74 )

Code
m2.w = with(mimp,
          geeglm(sf36.mcs ~
            fu.period + pol(I(ADI-69),2)* ruca_cat+
           rcs(age.enroll,3) + rcs(charlson.score,3) +
           rcs(iqcode.score.e,3)  + rcs(sofa.mod,3) +
           rcs(delcoma.s.imp,3) + rcs(vent.los.tot.s,3) +
           edu + sex.pp + va,
           id = id,
           subset = is.finite(cohort$sf36.mcs),
           corstr = "exchangeable",
         family = gaussian
           )
          )

weight.result[["sf36.mcs"]] = filter(summary(pool(m2.w))[,c(1,2,3,6)],
                                  str_detect(term,"ADI|ruca")
)

Sensitivity analysis was performed on each model without the inclusion of Inverse Probability Weighting (IPW). When compared to the models that included IPW weighting, all non-weighted models showed no changes conclusion of the main hypothesis and the conlusions of individual models.

Code
do.call(cbind,weight.result) %>% 
  select(-ends_with('term'),term = rbans.term) %>% 
  janitor::clean_names() %>% 
  relocate(term) %>% 
  knitr::kable(caption = "Sensitivity Analysis: IPW",
               col.names = c("term",rep(c("Estimate ","SE ","p.value "),6)),
              align = "c",
               digits = 2) %>% 
  kableExtra::add_header_above(
    c(" "=1, "RBANS"=3, "Trail.B"=3,"Katz"=3, "FAQ"=3, "SF36-PCS"=3, "SF36-MCS"=3)
  )
Sensitivity Analysis: IPW
RBANS
Trail.B
Katz
FAQ
SF36-PCS
SF36-MCS
term Estimate SE p.value Estimate SE p.value Estimate SE p.value Estimate SE p.value Estimate SE p.value Estimate SE p.value
pol(I(ADI - 69), 2)ADI 0.03 0.07 0.71 0.01 0.07 0.93 0.01 0.01 0.59 -0.01 0.03 0.82 0.02 0.07 0.78 -0.12 0.08 0.12
pol(I(ADI - 69), 2)ADI^2 0.00 0.00 0.52 0.00 0.00 0.99 0.00 0.00 0.22 0.00 0.00 0.89 0.00 0.00 0.78 0.00 0.00 0.60
ruca_catMetropolitan High/Low Commuting -0.70 2.49 0.78 1.40 2.18 0.52 0.70 0.36 0.06 -0.11 1.10 0.92 -0.43 1.84 0.82 0.58 2.45 0.81
ruca_catMicropolitan Core 0.56 2.11 0.79 0.60 2.03 0.77 0.36 0.32 0.26 0.14 1.14 0.90 -0.54 1.86 0.77 2.21 2.08 0.29
ruca_catMicropolitan High/Low Commuting 3.17 2.18 0.15 0.57 2.60 0.83 0.70 0.35 0.05 0.28 1.32 0.83 -0.89 2.14 0.68 2.36 2.84 0.41
ruca_catSmall Town Core -1.21 2.17 0.58 -0.94 2.58 0.72 0.72 0.39 0.07 1.19 1.43 0.41 -2.88 2.20 0.20 2.48 3.02 0.42
ruca_catSmall Town High/Low Commuting -0.56 2.77 0.84 5.15 3.18 0.11 0.04 0.43 0.93 0.61 1.62 0.71 -4.06 2.68 0.13 4.21 3.45 0.23
ruca_catRural -0.87 2.53 0.73 -0.86 3.59 0.81 0.95 0.48 0.05 1.19 1.46 0.42 -1.99 2.80 0.48 -1.99 3.34 0.55
pol(I(ADI - 69), 2)ADI:ruca_catMetropolitan High/Low Commuting -0.04 0.17 0.80 -0.07 0.14 0.62 0.00 0.03 0.93 -0.05 0.11 0.67 -0.02 0.11 0.84 0.15 0.15 0.30
pol(I(ADI - 69), 2)ADI^2:ruca_catMetropolitan High/Low Commuting 0.00 0.00 0.62 0.00 0.00 0.53 0.00 0.00 0.47 0.00 0.00 0.95 0.00 0.00 0.79 0.00 0.00 0.75
pol(I(ADI - 69), 2)ADI:ruca_catMicropolitan Core -0.11 0.09 0.26 0.00 0.09 0.98 0.00 0.01 0.89 0.04 0.05 0.40 -0.08 0.09 0.34 0.11 0.10 0.28
pol(I(ADI - 69), 2)ADI^2:ruca_catMicropolitan Core 0.00 0.00 0.86 0.00 0.00 0.98 0.00 0.00 0.57 0.00 0.00 0.70 0.00 0.00 0.76 0.00 0.00 0.83
pol(I(ADI - 69), 2)ADI:ruca_catMicropolitan High/Low Commuting -0.10 0.12 0.40 -0.16 0.15 0.29 -0.01 0.02 0.41 0.05 0.05 0.37 -0.09 0.13 0.51 0.20 0.15 0.18
pol(I(ADI - 69), 2)ADI^2:ruca_catMicropolitan High/Low Commuting -0.01 0.01 0.41 0.00 0.01 0.91 0.00 0.00 0.36 0.00 0.00 0.75 0.00 0.01 0.56 0.00 0.01 0.95
pol(I(ADI - 69), 2)ADI:ruca_catSmall Town Core -0.13 0.21 0.53 -0.06 0.27 0.83 -0.03 0.06 0.64 0.01 0.13 0.93 -0.04 0.16 0.80 0.00 0.25 0.99
pol(I(ADI - 69), 2)ADI^2:ruca_catSmall Town Core 0.00 0.01 0.70 0.00 0.01 0.65 0.00 0.00 0.78 0.00 0.00 0.72 0.00 0.01 0.75 0.00 0.01 0.67
pol(I(ADI - 69), 2)ADI:ruca_catSmall Town High/Low Commuting -0.02 0.18 0.90 0.08 0.23 0.74 -0.02 0.04 0.54 -0.07 0.10 0.47 0.09 0.21 0.66 0.21 0.23 0.37
pol(I(ADI - 69), 2)ADI^2:ruca_catSmall Town High/Low Commuting 0.00 0.01 0.81 -0.01 0.01 0.69 0.00 0.00 0.63 0.00 0.01 0.83 0.00 0.01 0.89 0.00 0.01 0.83
pol(I(ADI - 69), 2)ADI:ruca_catRural -0.13 0.14 0.36 0.06 0.19 0.77 -0.02 0.02 0.34 -0.02 0.08 0.83 0.03 0.14 0.81 -0.16 0.26 0.56
pol(I(ADI - 69), 2)ADI^2:ruca_catRural 0.00 0.01 0.82 0.00 0.01 0.94 0.00 0.00 0.74 0.00 0.00 0.95 0.00 0.01 0.67 0.01 0.01 0.30

Summary

This study found no evidence that rurality and area deprivation are associated with cognitive and functional outcomes. However, these findings are significantly constrained by missing data in the RUCA codes, with 25% of the data unavailable. This substantial amount of missing data undermines the statistical power of the study, a limitation that imputation methods cannot adequately address. Additionally, small sample sizes in the “Low Commute” categories restrict our ability to examine potential associations between these areas and the outcomes of interest. The criteria for grouping these categories are based on mapping rules, which are also subject to scrutiny. Nonetheless, even with complete data, there is limited evidence to suggest that rurality influences cognitive and functional outcomes.

Futhermore, notice the degree of freedom used in each model, which limit the power to detect the effect of interest. One alternative is to limit the flexibility of ADI and limit the interaction to linear interaction. But it is out of the scope of this report.

Another limitation involves the use of Inverse Probability Weighting (IPW) alongside the Generalized Estimating Equation (GEE). There is no guarantee, to my knowledge, that if both the model specification and the IPW weights are incorrect, the estimated equation will remain unbiased. Although GEE is known for its robustness to model misspecification, the introduction of IPW complicates this robustness. However, the sensitivity analysis indicated that there were no changes in the significance of the estimated exposures of interest. And most importantly we increases the power by analysising the data longitudinally.

Supplementary