4  Effect Modification Analysis within the Propensity score Framework

Author
Affiliations

Mohammad Ehsanul Karim

University of British Columbia

Observational comparative effectiveness studies often adopt propensity score analysis to adjust for confounding. Although this approach is relatively straightforward to implement, careful thought is needed when treatment effect heterogeneity is present. This chapter illustrates the estimation of subgroup-specific treatment effects using (traditional) covariate adjustment methods, propensity score matching, propensity score weighting, propensity score stratification, and covariate adjustment using propensity scores.

4.1 Simulation

We will use the following data-generation model:

require(simcausal)
D <- DAG.empty()
D <- D + 
  node("age", distr = "rnorm", 
       mean = 2, sd = 4) + 
  node("gender", distr = "rbern", 
       prob = plogis(4)) +
  node("education", distr = "rbern", 
       prob = plogis(3 + 5 * age)) +
  node("diet", distr = "rbern", 
       prob = plogis(1 - 3 * education)) +
  node("income", distr = "rbern", 
       prob = plogis(2 - 5 * education - 4 * age)) +
  node("smoking", distr = "rbern", 
       prob = plogis(1 + 1.2 * gender + 2 * age)) +
  node("hypertension", distr = "rbern", 
       prob = plogis(1 + log(3) * diet + 
                       log(1.3) * age + 
                       log(3.5) * smoking + 
                       log(0.5) * gender))
Dset <- set.DAG(D)

Below is the diagram, with pink lines representing open backdoor path.

using the following vertex attributes: 
NAdarkbluenone100.50
using the following edge attributes: 
black0.210.60.5

We can now generate an example dataset:

Obs.Data <- sim(DAG = Dset, n = 50000, rndseed = 123)
Obs.Data$smoking <- as.character(Obs.Data$smoking)
Obs.Data$income <- as.factor(Obs.Data$income)
Obs.Data$income <- relevel(Obs.Data$income, ref = "1")

Sample data from the hypothetical example of association between hypertension and smoking, where other variables such as income, age [centered], gender, education and diet also plays a role in the data generation process.

age gender education diet income smoking hypertension
34901 12.29 1 1 1 0 1 1
149 10.40 1 1 0 0 1 1
10060 2.99 1 1 0 0 1 0
22220 -4.31 0 0 0 1 0 1
9979 -6.44 0 0 0 1 0 1

4.2 Covariate adjustment

4.2.1 Interaction approach

Below, we estimate a logistic regression model to assess whether the effect of smoking (the exposure) on hypertension is modified by income levels. This model considers the following variables:

  • Outcome: hypertension
  • Exposure variables: smoking and income
  • Confounders: age and gender
require(jtools)

fit.w.em <- glm(hypertension ~ smoking * income + age + gender, 
                family = binomial(link = "logit"), 
                data = Obs.Data)

results.model <- summ(fit.w.em, exp = TRUE)
exp(Est.) 2.5% 97.5% z val. p
(Intercept) 5.46 4.37 6.82 14.97 0.00
smoking1 2.93 2.60 3.30 17.69 0.00
income0 0.48 0.41 0.57 -8.28 0.00
age 1.29 1.27 1.31 36.77 0.00
gender 0.54 0.43 0.67 -5.55 0.00
smoking1:income0 1.27 1.04 1.56 2.33 0.02

Results indicate that the interaction between smoking status and income level is statistically significant (p = 0.02).

If we expand previous model to adjust for an additional confounder education, we have:

fit.w.int <- glm(hypertension ~ smoking * income + age + gender + education, 
                 family = binomial(link = "logit"), 
                 data = Obs.Data)

results.int.model <- summ(fit.w.int, exp = TRUE)
exp(Est.) 2.5% 97.5% z val. p
(Intercept) 5.69 4.56 7.11 15.31 0.00
smoking1 3.35 2.95 3.79 18.85 0.00
income0 1.09 0.85 1.40 0.68 0.49
age 1.30 1.28 1.32 37.32 0.00
gender 0.54 0.43 0.67 -5.58 0.00
education 0.42 0.35 0.51 -8.87 0.00
smoking1:income0 1.10 0.90 1.35 0.93 0.35

The interaction term between income and smoking is no longer statistically significant (p = 0.35).

We can generate a summary report from aforementioned effect modification analysis.

library(interactionR)

em.object <- interactionR(fit.w.em, 
                          exposure_names = c("income0", "smoking1"), 
                          ci.type = "mover", ci.level = 0.95, 
                          em = TRUE, recode = FALSE)

The table below depicts the adjusted odds ratios for income levels (high = 0, and low = 1). The variables CI.ll and CI.ul depict the lower and upper limits of the 95 percent confidence intervals, OR11 = \(OR_{A = 1, M = 1}\) , OR10 = \(OR_{A = 1}\), OR01 = \(OR_{M = 1}\) and OR00 captures the reference.

Table 4.1: Summary report from an interaction analysis when investigating association between two exposure variables (smoking and income) and hypertension.
Measures Estimates CI.ll CI.ul
OR00 1.00 NA NA
OR01 2.93 2.60 3.30
OR10 0.48 0.41 0.57
OR11 1.80 1.63 1.98
OR(smoking1 on outcome [income0==0] 2.93 2.60 3.30
OR(smoking1 on outcome [income0==1] 3.72 3.14 4.41
Multiplicative scale 1.27 1.04 1.56
RERI -0.61 -0.98 -0.29

Similarly, for the analysis adjusting for an additional confounder education, we have:

Table 4.2: Summary report from an interaction analysis when investigating association between two exposure variables (smoking and income) and hypertension.
Measures Estimates CI.ll CI.ul
OR00 1.00 NA NA
OR01 1.09 0.85 1.40
OR10 3.35 2.95 3.79
OR11 4.02 3.29 4.92
OR(income0 on outcome [smoking1==0] 1.09 0.85 1.40
OR(income0 on outcome [smoking1==1] 1.20 1.00 1.45
OR(smoking1 on outcome [income0==0] 3.35 2.95 3.79
OR(smoking1 on outcome [income0==1] 3.69 3.11 4.37
Multiplicative scale 1.10 0.90 1.35
RERI 0.59 0.03 1.27
AP 0.15 0.00 0.26
SI 1.24 1.01 1.53
# test run with additive model
Obs.Data$smoking <- as.numeric(as.character(Obs.Data$smoking))
Obs.Data$income <- as.numeric(as.character(Obs.Data$income))
fit.w.int.add <- glm(hypertension ~ smoking * income + age + gender + education, 
                     family = gaussian(link = "identity"), data = Obs.Data)
interactions::sim_slopes(fit.w.int.add, 
                         pred = smoking, 
                         modx = income,
                         exp = TRUE, 
                         robust = TRUE,
                         confint = TRUE, 
                         data = Obs.Dat)
JOHNSON-NEYMAN INTERVAL 

When income is INSIDE the interval [-3.27, 16.87], the slope of smoking is
p < .05.

Note: The range of observed values of income is [0.00, 1.00]

SIMPLE SLOPES ANALYSIS 

Slope of smoking when income = 0.00 (0): 

  Est.   S.E.   2.5%   97.5%   t val.      p
------ ------ ------ ------- -------- ------
  0.25   0.02   1.24    1.34    12.76   0.00

Slope of smoking when income = 1.00 (1): 

  Est.   S.E.   2.5%   97.5%   t val.      p
------ ------ ------ ------- -------- ------
  0.28   0.01   1.30    1.34    34.53   0.00

4.2.2 Stratification

This approach involves estimating a regression model in different strata of the discrete effect modifier income:

# Estimate the prognostic effect of smoking in low income individuals
fit.income1 <- glm(hypertension ~ smoking + age + gender, 
            family = binomial(link = "logit"), 
            data = subset(Obs.Data, income == 1))

# Estimate the prognostic effect of smoking in high income individuals
fit.income0 <- glm(hypertension ~ smoking + age + gender, 
            family = binomial(link = "logit"), 
            data = subset(Obs.Data, income == 0))

The table below summarizes the adjusted odds ratios for smoking across the different income levels (low = 1, and high = 0) as obtained using the stratified approach.

Value of income Estimate 2.5 % 97.5 % z value p value
1 3.07 2.71 3.47 17.65 0
0 3.59 3.02 4.26 14.57 0

Note that we can obtain the same results by estimating a regression model with an interaction term between the modifier and all covariates:

fit.all.int <- glm(hypertension ~ income * (smoking + age + gender), 
                   family = binomial(link = "logit"), data = Obs.Data)

# Odds ratio for smoking in individuals with low income 
exp(coef(fit.all.int)["smoking"])
smoking 
3.59026 
# Odds ratio for smoking in individuals with high income
exp(coef(fit.all.int)["smoking"] + coef(fit.all.int)["income:smoking"])
 smoking 
3.066878 

4.3 Propensity score matching

4.3.1 Stratification with exact matching within subgroups

We simulate another example dataset using aforementioned DAG, but restrict the sample size to 5000 individuals to reduce computational burden.

set.seed(123)
Obs.Data <- sim(DAG = Dset, n = 5000, rndseed = 123)

We first estimate the propensity of smoking in the high-income group (income == 0):

require(MatchIt)

match.income.0 <- matchit(smoking ~ age + gender, 
                          data = subset(Obs.Data, income == 0),
                          method = "full", distance = "glm", link = "logit")
data.income.0 <- match.data(match.income.0)

Below, we draw a sample from the high-income group based on the hypothetical example of an association between hypertension and smoking. Here age [centered], gender, education, and diet are covariates.

            age gender education diet income smoking hypertension  distance
657   6.0810120      0         1    1      0       1            1 0.9999874
4932  1.6109860      1         1    0      0       1            0 0.9943155
252  -0.2475055      1         1    1      0       0            1 0.8525107
2693 -0.2511048      1         1    0      0       1            1 0.8516785
1646 -0.2836155      1         0    1      0       1            1 0.8439843
        weights subclass
657  1.00000000       33
4932 1.00000000       33
252  0.04944134       23
2693 1.00000000       23
1646 1.00000000        4

Now, we do the same for the low-income group (income == 1):

match.income.1 <- matchit(smoking ~ age + gender, 
                          data = subset(Obs.Data, income == 1),
                          method = "full", distance = "glm", link = "logit")
data.income.1 <- match.data(match.income.1)

We estimated the exposure effect from a weighted outcome model for the matched data. While the weights are essential for estimating the point estimate from the outcome model, the subclass variable assists in calculating the robust variance of the exposure effect estimate.

# Treatment effect estimation
fit.income.0 <- glm(hypertension ~ smoking + age + gender, 
                   data = data.income.0, weights = weights,
                   family = quasibinomial("logit"))
fit.income.1 <- glm(hypertension ~ smoking + age + gender, 
                   data = data.income.1, weights = weights,
                   family = quasibinomial("logit"))
# Robust variance calculation
fit.nexp.adj.res1 <- summ(fit.income.1,  
                          robust = TRUE,
                          cluster = "subclass",
                          confint = TRUE)
fit.nexp.adj.res0 <- summ(fit.income.0, 
                          robust = TRUE,
                          cluster = "subclass",
                          confint = TRUE)
Table 4.3: Subgroup-specific treatment effect estimates (expressed in log-OR) from the hypothetical example using the stratified approach.
Value of income Est. 2.5% 97.5% t val. p
0 3.76 -43.15 50.66 0.16 0.88
1 1.36 0.90 1.81 5.78 0.00

4.3.2 Joint approach without exact matching within subgroups

Here, entire cohort data is used to estimate the propensity scores, and the effect modifier income is considered as a covariate in the propensity score model:

ps.formula <- as.formula("smoking ~ age + gender + income")
match.obj.j <- matchit(ps.formula, data = Obs.Data,
                      method = "full", 
                      distance = "glm",
                      link = "logit")
match.data.j <- match.data(match.obj.j)
fit.joint.no.exact <- glm(hypertension ~ smoking*income + age + gender, 
                          data = match.data.j, 
                          weights = weights,
                          family = binomial("logit"))

nem.nexp.adj.res <- interactions::sim_slopes(fit.joint.no.exact, 
                                             pred = smoking, 
                                             modx = income,
                                             robust = "HC1", 
                                             cluster = "subclass",
                                             johnson_neyman = TRUE, 
                                             confint = TRUE,
                                             data = match.data.j)
Table 4.4: Subgroup-specific treatment effect estimates (expressed in log-OR) from the hypothetical example using the joint approach.
Value of income Est. S.E. 2.5% 97.5% z val. p
0 3.77 1.02 1.77 5.76 3.70 0
1 1.34 0.28 0.79 1.89 4.78 0

4.3.3 Joint approach with exact matching within subgroups

We specify the moderator variable’s name in the exact argument of the matchit function.

ps.formula.no.mod <- as.formula("smoking ~ age + gender")
match.obj.js <- matchit(ps.formula.no.mod, data = Obs.Data,
                        method = "full", distance = "glm",link = "logit",
                        exact = "income")
match.data.js <- match.data(match.obj.js)
fit.joint.exact <- glm(hypertension ~ smoking*income + age + gender, 
                       data = match.data.js, weights = weights,
                       family = binomial("logit"))
js.nexp.adj.res <- interactions::sim_slopes(fit.joint.exact, 
                                            pred = smoking, 
                                            modx = income, # effect modifier
                                            robust = "HC1", 
                                            cluster = "subclass",
                                            johnson_neyman = FALSE, 
                                            confint = TRUE,
                                            data = match.data.js)
Table 4.5: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using the Joint model, separate matching approach.
Value of income Est. S.E. 2.5% 97.5% z val. p
0 3.81 1.02 1.81 5.80 3.74 0
1 1.34 0.28 0.79 1.89 4.76 0

4.3.4 Interaction approach without exact matching within subgroups

Analysts incorporate relevant moderator-covariate interactions into the propensity score model that align with biological plausibility. For instance, in the case study we considered an interaction between age (a covariate) and income (a moderator), but did not include other interactions terms.

ps.formula.with.int <- formula("smoking ~ age*income + gender")
match.obj.i <- matchit(ps.formula.with.int, data = Obs.Data,
                       method = "full", distance = "glm",link = "logit")
match.data.i <- match.data(match.obj.i)
fit.int.no.exact <- glm(hypertension ~ smoking*income + age + gender, 
                        data = match.data.i, weights = weights,
                        family = binomial("logit"))
i.nexp.adj.res <- interactions::sim_slopes(fit.int.no.exact, 
                                           pred = smoking, 
                                           modx = income,
                                           robust = "HC1", 
                                           cluster = "subclass",
                                           johnson_neyman = FALSE, 
                                           confint = TRUE,
                                           data = match.data.i)
Table 4.6: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using the interaction approach.
Value of income Est. S.E. 2.5% 97.5% z val. p
0 3.77 1.02 1.77 5.77 3.70 0
1 1.35 0.28 0.80 1.91 4.77 0

4.3.5 Interaction approach with exact matching within subgroups

This method bears resemblance to the interaction approach for propensity score estimation. However, when it comes to matching, researchers match within each moderator subgroup.

match.obj.is <- matchit(ps.formula.with.int, data = Obs.Data,
                      method = "full", distance = "glm",link = "logit",
                      exact = "income")
match.data.is <- match.data(match.obj.is)
fit.int.exact <- glm(hypertension ~ smoking*income + age + gender, 
                     data = match.data.is, weights = weights,
                     family = binomial("logit"))
is.nexp.adj.res <- interactions::sim_slopes(fit.int.exact, 
                                            pred = smoking, 
                                            modx = income,
                                            robust = "HC1", 
                                            cluster = "subclass",
                                            johnson_neyman = FALSE, 
                                            confint = TRUE,
                                            data = match.data.is)
Table 4.7: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using the interaction model, separate matching approach.
Value of income Est. S.E. 2.5% 97.5% z val. p
0 3.75 1.02 1.75 5.75 3.68 0
1 1.37 0.28 0.82 1.92 4.88 0

4.4 Propensity Score Weighting

4.4.1 Common model

This approach adds confounder-moderator interactions in the common weight model.

require(WeightIt)
library(survey)
library(interactions)
W.out <- weightit(ps.formula.with.int, 
                  data = Obs.Data,
                  method = "ps", 
                  estimand = "ATT")
d.w <- svydesign(~1, weights = W.out$weights, data = Obs.Data)
fit2w <- svyglm(hypertension ~ smoking*income, design = d.w,
                family = quasibinomial("logit"))
w.nexp.adj.res <- interactions::sim_slopes(fit2w, 
                                           pred = smoking, 
                                           modx = income, 
                                           confint = TRUE)
Table 4.8: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using the weighting approach.
Value of income Est. S.E. 2.5% 97.5% t val. p
0 2.66 0.63 1.42 3.89 4.23 0
1 1.32 0.25 0.83 1.82 5.24 0

We can adjust previous analysis model to adopt stabilized weights for the propensity score (stabilize = TRUE):

W.out.st <- weightit(ps.formula.with.int, data = Obs.Data,
                     method = "ps", 
                     estimand = "ATT", 
                     stabilize = TRUE)
d.sw <- svydesign(~1, weights = W.out.st$weights, data = Obs.Data)
fit2sw <- svyglm(hypertension ~ smoking*income + age + gender, 
                  design = d.sw,
                  family = binomial("logit"))
ws.nexp.adj.res <- interactions::sim_slopes(fit2sw, 
                                            pred = smoking, 
                                            modx = income, 
                                            confint = TRUE)
Table 4.9: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using stabilized propensity score weights.
Value of income Est. S.E. 2.5% 97.5% t val. p
0 2.27 0.73 0.84 3.69 3.12 0
1 1.32 0.25 0.83 1.82 5.23 0

4.4.2 Separate models

Propensity score weighting approach with weights estimated separately from each subgroup:

ps.formula.with.no.int <- formula("smoking ~ age + gender")
W.out1 <- weightit(ps.formula.with.no.int, 
                   data = subset(Obs.Data, income == 1),
                   method = "ps", 
                   estimand = "ATT")
trimmed.weight.1.percent1 <- trim(W.out1$weights, 
                                  at = 1, lower = TRUE)
Table 4.10: Weight summaries before and after truncation.
Weight Min. 1st Qu. Median Mean 3rd Qu. Max.
Raw weights 0 0.01 0.11 0.45 1 11.69
1% truncated weights 0 0.01 0.11 0.44 1 7.61
# Outcome model for income = 1
d.w1 <- svydesign(~1, weights = trimmed.weight.1.percent1, 
                  data = subset(Obs.Data, income == 1))

fit2unadj1 <- svyglm(hypertension ~ smoking, 
                     design = d.w1,
                     family = binomial("logit"))

# weight model for income = 0
W.out0 <- weightit(ps.formula, data = subset(Obs.Data, income == 0),
                  method = "ps", estimand = "ATT")
trimmed.weight.1.percent0 <- trim(W.out0$weights, at = 1, lower = TRUE)

# Outcome model for income = 0
d.w0 <- svydesign(~1, weights = trimmed.weight.1.percent0, 
                  data = subset(Obs.Data, income == 0))

fit2unadj0 <- svyglm(hypertension ~ smoking, 
                     design = d.w0,
                     family = binomial("logit"))

fit.exp.adj.res1 <- summ(fit2unadj1, confint = TRUE)
fit.exp.adj.res0 <- summ(fit2unadj0, confint = TRUE)
Table 4.11: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using the propensity score weighting approach (Separate weight models).
Value of income Est. 2.5% 97.5% t val. p
0 2.21 1.27 3.15 4.60 0
1 1.34 0.85 1.83 5.36 0

4.4.3 Weights from the subgroup balancing propensity scores

Subgroup balancing propensity scores for propensity score weighting:

w.out <- weightit(smoking ~ age + gender + income, 
                  data = Obs.Data,
                  method = "ps", 
                  estimand = "ATT")

w.out.sb <- sbps(w.out, moderator = "income")
d.w.sb <- svydesign(~1, weights = w.out.sb$weights, data = Obs.Data)

fit2unadj.sb <- svyglm(hypertension ~ smoking*income, 
                       design = d.w.sb,
                       family = binomial("logit"))

sb.w.nexp.adj.res <- interactions::sim_slopes(fit2unadj.sb, 
                                              pred = smoking, 
                                              modx = income, 
                                              confint = TRUE,
                                              johnson_neyman = FALSE)
Table 4.12: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using the subgroup balancing weighting approach.
Value of income Est. S.E. 2.5% 97.5% t val. p
0 2.68 0.64 1.44 3.92 4.22 0
1 1.32 0.25 0.82 1.82 5.22 0

4.5 Covariate adjustment for the propensity score

4.5.1 As continuous covariate

An implementation of propensity scores as a continuous covariate in the outcome model:

# Separate models for each subgroup

# For subgroup income = 1 
Obs.Data$ps[Obs.Data$income == 1] <- glm(ps.formula, 
                                         data = subset(Obs.Data, income == 1), 
                                         family = "binomial")$fitted.values

fit2adj1 <- glm(hypertension ~ smoking + age + gender, 
                family = binomial("logit"), 
                data = subset(Obs.Data, income == 1))

# For subgroup income = 0
Obs.Data$ps[Obs.Data$income == 0] <- glm(ps.formula, 
                                         data = subset(Obs.Data, income == 0), 
                                         family = "binomial")$fitted.values

fit2adj0 <- glm(hypertension ~ smoking + age + gender, 
                family = binomial("logit"), 
                data = subset(Obs.Data, income == 0))

fit.nexp.adj.res1 <- summ(fit2adj1, robust = TRUE, confint = TRUE)
fit.nexp.adj.res0 <- summ(fit2adj0, robust = TRUE, confint = TRUE)
Table 4.13: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using Propensity Score as a covariate adjustment approach (considering separate models for each subgroup).
Value of income Est. 2.5% 97.5% z val. p
0 1.16 0.56 1.75 3.83 0
1 1.37 0.96 1.77 6.61 0
# Common model
Obs.Data$ps <- glm(ps.formula.with.int, data = Obs.Data,
                       family = "binomial")$fitted.values
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
fit2adjc <- glm(hypertension ~ smoking*income + age + gender + ps, 
                family = binomial("logit"), 
                data = Obs.Data)

c.nexp.adj.res <- interactions::sim_slopes(fit2adjc,
                                           pred = smoking, 
                                           modx = income,
                                           confint = TRUE,
                                           data = Obs.Data)
Table 4.14: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using Propensity Score as a covariate adjustment approach (considering a common model).
Value of income Est. S.E. 2.5% 97.5% z val. p
0 1.17 0.29 0.61 1.74 4.07 0
1 1.43 0.23 0.98 1.87 6.30 0

4.5.2 As quantiles

The propensity scores as a categorical covariate, broken by quintiles, in the outcome model.

Obs.Data$ps <- glm(ps.formula.with.int, 
                   data = Obs.Data, 
                   family = "binomial")$fitted.values
quintiles <- quantile(Obs.Data$ps, 
                      prob = seq(from = 0, to = 1, by = 0.2), 
                      na.rm = T)
Obs.Data$psq <- cut(Obs.Data$ps, breaks = quintiles, 
                   labels = seq(1,5), include.lowest = T)
Obs.Data$psq <- as.factor(Obs.Data$psq)

fit2adjq <- glm(hypertension ~ (smoking*psq)*income, 
                family = binomial("logit"),
                data = Obs.Data)

cq.nexp.adj.res <- interactions::sim_slopes(fit2adjq, 
                                            pred = smoking, 
                                            modx = income, 
                                            confint = TRUE,
                                            data = Obs.Data)
Table 4.15: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using Propensity Score as a covariate adjustment approach (as quintiles).
Value of income Est. S.E. 2.5% 97.5% z val. p
0 3.08 0.63 1.85 4.32 4.91 0
1 2.60 0.47 1.68 3.51 5.56 0

4.6 Propensity Score Stratification

Here is an implementation of propensity score stratification approach by using the marginal mean weighting through stratification (MMWS):

match.obj <- matchit(ps.formula, data = Obs.Data,
                      method = "subclass", subclass = 3, 
                      estimand = "ATT", min.n = 10)

data.subclass <- match.data(match.obj)

subclass.fit <- glm(hypertension ~ smoking*income, 
                    family = binomial("logit"),
                    data = data.subclass,
                    weights = weights)

subclass.nexp.adj.res <- interactions::sim_slopes(subclass.fit, 
                                                  pred = smoking, 
                                                  modx = income, 
                                                  confint = TRUE,
                                                  robust = "HC3",
                                                  johnson_neyman = FALSE,
                                                  data = data.subclass)
Table 4.16: Subgroup-specific exposure effect estimates (expressed in log-OR) from the hypothetical example using propensity score stratification approach.
Value of income Est. S.E. 2.5% 97.5% z val. p
0 2.21 0.47 1.29 3.13 4.71 0
1 1.89 0.19 1.51 2.26 9.78 0

4.7 Summary

The marginal odds ratios for smoking are summarized below

Version info

This chapter was rendered using the following version of R and its packages:

R version 4.2.3 (2023-03-15)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
 [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
[10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] interactions_1.1.5 survey_4.4-1       survival_3.5-3     Matrix_1.6-5      
 [5] interactionR_0.1.7 simcausal_0.5.6    scales_1.3.0       ggplot2_3.5.0     
 [9] xtable_1.8-4       dplyr_1.1.4        kableExtra_1.4.0   knitr_1.45        
[13] cowplot_1.1.3      broom_1.0.5        MatchIt_4.5.5      jtools_2.2.2      
[17] sandwich_3.1-0     lmtest_0.9-40      zoo_1.8-12         optmatch_0.10.7   
[21] WeightIt_0.14.2    cobalt_4.5.4       table1_1.4.3      

loaded via a namespace (and not attached):
 [1] fontquiver_0.2.1        tools_4.2.3             backports_1.4.1        
 [4] utf8_1.2.4              R6_2.5.1                DBI_1.2.2              
 [7] colorspace_2.1-0        withr_3.0.0             tidyselect_1.2.1       
[10] curl_5.2.1              compiler_4.2.3          textshaping_0.3.7      
[13] cli_3.6.2               expm_0.999-9            flextable_0.9.5        
[16] xml2_1.3.6              officer_0.6.5           fontBitstreamVera_0.1.1
[19] labeling_0.4.3          mvtnorm_1.2-4           askpass_1.2.0          
[22] systemfonts_1.0.6       stringr_1.5.1           digest_0.6.35          
[25] rmarkdown_2.26          svglite_2.1.3           gfonts_0.2.0           
[28] pkgconfig_2.0.3         htmltools_0.5.7         highr_0.10             
[31] fastmap_1.1.1           htmlwidgets_1.6.4       rlang_1.1.3            
[34] rstudioapi_0.15.0       httpcode_0.3.0          shiny_1.8.0            
[37] farver_2.1.1            generics_0.1.3          jsonlite_1.8.8         
[40] car_3.1-2               zip_2.3.1               magrittr_2.0.3         
[43] Formula_1.2-5           Rcpp_1.0.12             munsell_0.5.0          
[46] fansi_1.0.6             abind_1.4-5             gdtools_0.3.7          
[49] lifecycle_1.0.4         chk_0.9.1               stringi_1.8.3          
[52] yaml_2.3.8              carData_3.0-5           promises_1.2.1         
[55] crayon_1.5.2            lattice_0.20-45         splines_4.2.3          
[58] pander_0.6.5            pillar_1.9.0            igraph_2.0.3           
[61] uuid_1.2-0              codetools_0.2-19        crul_1.4.0             
[64] glue_1.7.0              evaluate_0.23           msm_1.7.1              
[67] mitools_2.4             fontLiberation_0.1.0    data.table_1.15.2      
[70] vctrs_0.6.5             httpuv_1.6.14           gtable_0.3.4           
[73] openssl_2.1.1           purrr_1.0.2             tidyr_1.3.1            
[76] assertthat_0.2.1        xfun_0.42               mime_0.12              
[79] later_1.3.2             ragg_1.3.0              viridisLite_0.4.2      
[82] tibble_3.2.1            ellipsis_0.3.2          rlemon_0.2.1           

References