title | author | date | output | classoption | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
A Practical Introduction to Propensity Score Analysis using R |
Ehsan Karim [http://ehsank.com/] |
30 Sept 2020: SPPH, UBC |
|
aspectratio=169 |
\newcommand{\indep}{\perp !!! \perp}
-
TI Methods Speaker Series page: Therapeutics Initiative
- Dr. Carole Lunny
-
SARGC page: Students and Recent Graduates Committee (SARGC) coordinate activities for the Statistical Society of Canada (SSC)'s student and recent graduate members
- Md. Erfanul Hoque
- Janie Coulombe
-
Target audience:
- Familiar with regression
- Familiar with R
- will explain some necessary package / functions / arguments
- have no/minimal idea about propensity score
-
Topics covered
- Not covering any new research
- Not covering statistical theory
- implementation being the goal here
- Not attempting to reach any clinical conclusion
-
Presentation format
- Rather informal
- 1 hr vs. 2 hr
- Q/A at
- 45 min and
- at the end
-
Webinar Materials
- All reproducible codes provided
- Necessary references cited in respective slides
- [1] Data and Regression
- (Diagnostics)
- [2] Exact matching
- (motivation)
- [3] Propensity score matching
- (4 steps)
- [4] Propensity score Reviews in different disease areas
- (brief)
The dataset that we will use today is from Connors et al. (1996).
\includegraphics[width=0.4\linewidth]{images/citeRHC} \includegraphics[width=0.4\linewidth]{images/rhcvars}
- Outcome
Death
($Y$ )- Death at any time up to 180 Days
- Treatment
swang1
($A$ : Swan-Ganz catheter)- Whether or not a patient received a RHC
-
Covariate list:
$L$ (age
,sex
,race
,$\ldots$ ) - Analysis strategy: matching RHC patients with non-RHC patients
-
RHC
is helpful in guiding therapy decision- Helps determine the pressures within the heart
- Popularly beleived that
RHC
is benefitial - Conducting RCT is hard (ethical reasons)
- Benefit of
RHC
was not shown earlier (1996)
-
SUPPORT data has 2 phases
- phase 1: prospective observational study
- phase 2: cluster RCT
- Data in this study is combined
\includegraphics[width=0.2\linewidth]{images/RHC}
# Load the cleaned up data.
# Reproducible codes:
# https://ehsanx.github.io/SARGC-TIMethods/
analytic.data <- readRDS("data/RHC.Rds")
# Data size and number of variables
dim(analytic.data)
## [1] 4767 23
# variable names
names(analytic.data)
## [1] "age" "sex" "race"
## [4] "Disease.category" "DNR.status" "APACHE.III.score"
## [7] "Pr.2mo.survival" "No.of.comorbidity" "DASI.2wk.prior"
## [10] "Temperature" "Heart.rate" "Blood.pressure"
## [13] "Respiratory.rate" "WBC.count" "PaO2.by.FIO2"
## [16] "PaCO2" "pH" "Creatinine"
## [19] "Albumin" "GComa.Score" "RHC"
## [22] "Death" "ID"
require(tableone)
# 2 x 2 table
tab0 <- CreateTableOne(vars = "RHC",
data = analytic.data,
strata = "Death")
print(tab0, showAllLevels = TRUE)
## Stratified by Death
## level No Yes p test
## n 2013 2754
## RHC (%) No RHC 1315 (65.3) 1268 (46.0) <0.001
## RHC 698 (34.7) 1486 (54.0)
baselinevars <- c("age","sex", "race")
# Table 1
tab1 <- CreateTableOne(vars = baselinevars,
data = analytic.data,
strata = "Death", includeNA = TRUE,
test = TRUE, smd = FALSE)
print(tab1, showAllLevels = FALSE, smd = FALSE)
## Stratified by Death
## No Yes p test
## n 2013 2754
## age (%) <0.001
## [-Inf,50) 713 (35.4) 400 (14.5)
## [50,60) 351 (17.4) 452 (16.4)
## [60,70) 426 (21.2) 789 (28.6)
## [70,80) 382 (19.0) 750 (27.2)
## [80, Inf) 141 ( 7.0) 363 (13.2)
## sex = Female (%) 919 (45.7) 865 (31.4) <0.001
## race (%) 0.004
## white 1554 (77.2) 2225 (80.8)
## black 332 (16.5) 403 (14.6)
## other 127 ( 6.3) 126 ( 4.6)
# adjust the exposure variable (primary interest)
fit0 <- glm(I(Death=="Yes")~RHC,
family=binomial, data = analytic.data)
require(Publish)
publish(fit0)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.21 [1.96;2.49] <1e-04
# adjust the exposure variable + demographics
fit1 <- glm(I(Death=="Yes")~RHC + age + sex + race,
family=binomial, data = analytic.data)
publish(fit1)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.71 [2.38;3.08] < 1e-04
## age [-Inf,50) Ref
## [50,60) 3.56 [3.01;4.20] < 1e-04
## [60,70) 0.74 [0.64;0.87] 0.0001274
## [70,80) 1.33 [1.15;1.54] < 1e-04
## [80, Inf) 1.06 [0.93;1.21] 0.3633526
## sex Male Ref
## Female 0.49 [0.43;0.56] < 1e-04
## race white Ref
## black 1.10 [0.93;1.31] 0.2666157
## other 0.97 [0.73;1.28] 0.8068800
- treated group
$A=1$ (RHC
) - control group
$A=0$ (no RHC
)
Treatment effect =
- Would only work if 2 groups are comparable / exchangeable / ignorable treatment assignment
- Randomization with enough sample size is one
Treatment effect =
In absence of randomization,
includes
- Treatment effect
- Systematic differences in 2 groups (‘confounding’)
- Doctors may prescribe tx more to frail and older age patients.
- In here,
$L$ = age is a confounder.
In absence of randomization, if age is a known issue
-
$E[Y|A=1, L =$ younger age
$]$ -$E[Y|A=0, L =$ younger age
$]$
-
$E[Y|A=1, L =$ older age
$]$ -$E[Y|A=0, L =$ older age
$]$
Conditional exchangeability; only works if
\includegraphics[width=0.5\linewidth]{images/dag1}
This was not a completely randomized data; some observational data was combined.
# adjust the exposure variable + adjustment variables
baselinevars <- c("age","sex", "race","Disease.category",
"DNR.status", "APACHE.III.score",
"Pr.2mo.survival","No.of.comorbidity",
"DASI.2wk.prior","Temperature",
"Heart.rate", "Blood.pressure",
"Respiratory.rate", "WBC.count",
"PaO2.by.FIO2","PaCO2","pH",
"Creatinine","Albumin","GComa.Score")
out.formula <- as.formula(paste("I(Death=='Yes')", "~ RHC +",
paste(baselinevars,
collapse = "+")))
out.formula
## I(Death == "Yes") ~ RHC + age + sex + race + Disease.category +
## DNR.status + APACHE.III.score + Pr.2mo.survival + No.of.comorbidity +
## DASI.2wk.prior + Temperature + Heart.rate + Blood.pressure +
## Respiratory.rate + WBC.count + PaO2.by.FIO2 + PaCO2 + pH +
## Creatinine + Albumin + GComa.Score
fit2 <- glm(out.formula,
family=binomial, data = analytic.data)
publish(fit2)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.55 [2.18;2.97] < 1e-04
## age [-Inf,50) Ref
## [50,60) 1.70 [1.39;2.07] < 1e-04
## [60,70) 0.77 [0.65;0.91] 0.0019296
## [70,80) 1.18 [1.01;1.38] 0.0396871
## [80, Inf) 1.13 [0.98;1.30] 0.0957860
## sex Male Ref
## Female 0.43 [0.38;0.50] < 1e-04
## race white Ref
## black 1.14 [0.94;1.38] 0.1917093
## other 0.91 [0.67;1.24] 0.5634739
## Disease.category ARF Ref
## CHF 1.30 [0.98;1.73] 0.0702401
## MOSF 0.94 [0.79;1.12] 0.4786581
## Other 1.29 [1.04;1.59] 0.0196439
## DNR.status No Ref
## Yes 2.44 [1.85;3.22] < 1e-04
## APACHE.III.score 1.01 [1.00;1.01] 0.0903495
## Pr.2mo.survival 0.01 [0.01;0.02] < 1e-04
## No.of.comorbidity 1.22 [1.14;1.30] < 1e-04
## DASI.2wk.prior 0.95 [0.94;0.97] < 1e-04
## Temperature 0.94 [0.90;0.99] 0.0095914
## Heart.rate 1.00 [1.00;1.00] 0.0975692
## Blood.pressure 1.00 [1.00;1.00] 0.5146500
## Respiratory.rate 1.00 [1.00;1.01] 0.1459102
## WBC.count 1.01 [1.00;1.01] 0.0259773
## PaO2.by.FIO2 1.00 [1.00;1.00] 0.0003039
## PaCO2 1.00 [0.99;1.00] 0.5339307
## pH 1.03 [0.46;2.33] 0.9407749
## Creatinine 1.03 [0.99;1.07] 0.1781145
## Albumin 1.00 [0.92;1.10] 0.9337588
## GComa.Score 1.00 [0.99;1.00] 0.0441504
plot(fit2, which =1)
\includegraphics[width=0.5\linewidth]{slidePS_files/figure-beamer/reg3bc1-1}
- curvilinear trends?
- logistic regression IS curvilinear by nature
plot(fit2, which =3)
\includegraphics[width=0.5\linewidth]{slidePS_files/figure-beamer/reg3bc3-1}
- red line is approximately horizontal?
- points have approximately equal spread around the red line?
- more about detecting heteroscedasticity?
plot(fit2, which =4)
\includegraphics[width=0.5\linewidth]{slidePS_files/figure-beamer/reg3bc3jr-1}
- Cook's D estimates the influence of data points
How sure are you about the model-specification?
-
Interaction?
-
Polynomial?
-
Potential solution?
- Exact Matching
var.comb <- do.call('paste0',
analytic.data[, c('race', 'sex')])
length(table(var.comb))
## [1] 6
table(var.comb)
## var.comb
## blackFemale blackMale otherFemale otherMale whiteFemale whiteMale
## 331 404 113 140 1340 2439
table(analytic.data$RHC,var.comb)
## var.comb
## blackFemale blackMale otherFemale otherMale whiteFemale whiteMale
## No RHC 161 239 50 61 667 1405
## RHC 170 165 63 79 673 1034
require(MatchIt)
# exact match by sex and race
m.out = matchit (RHC=="RHC" ~ sex + race,
data = analytic.data,
method = "exact")
m.out
##
## Call:
## matchit(formula = RHC == "RHC" ~ sex + race, data = analytic.data,
## method = "exact")
##
## Exact Subclasses: 6
##
## Sample sizes:
## Control Treated
## All 2583 2184
## Matched 2583 2184
## Unmatched 0 0
var.comb <- do.call('paste0',
analytic.data[, c('race', 'sex', 'age')])
length(table(var.comb))
## [1] 30
table(analytic.data$RHC,var.comb=="otherMale[80, Inf)")
##
## FALSE TRUE
## No RHC 2580 3
## RHC 2183 1
table(analytic.data$RHC,var.comb=="otherFemale[80, Inf)")
##
## FALSE TRUE
## No RHC 2581 2
## RHC 2184 0
# exact match by age, sex and race
m.out = matchit (RHC=="RHC" ~ age + sex + race,
data = analytic.data,
method = "exact")
m.out
##
## Call:
## matchit(formula = RHC == "RHC" ~ age + sex + race, data = analytic.data,
## method = "exact")
##
## Exact Subclasses: 29
##
## Sample sizes:
## Control Treated
## All 2583 2184
## Matched 2581 2184
## Unmatched 2 0
matched.data <- match.data(m.out)
dim(matched.data)
## [1] 4765 25
nrow(analytic.data)-nrow(matched.data) # subjects deleted
## [1] 2
# Not taking into account of matched sets
fit1m <- glm(I(Death=="Yes")~RHC,
family=binomial, data = matched.data)
publish(fit1m)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.21 [1.96;2.49] <1e-04
m.out = matchit (RHC=="RHC" ~ age + sex + race +
Disease.category + DNR.status,
data = analytic.data,
method = "exact")
m.out
##
## Call:
## matchit(formula = RHC == "RHC" ~ age + sex + race + Disease.category +
## DNR.status, data = analytic.data, method = "exact")
##
## Exact Subclasses: 137
##
## Sample sizes:
## Control Treated
## All 2583 2184
## Matched 2524 2150
## Unmatched 59 34
matched.data <- match.data(m.out)
dim(matched.data)
## [1] 4674 25
fit2m <- glm(I(Death=="Yes")~RHC,
family=binomial, data = matched.data)
publish(fit2m)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.23 [1.98;2.51] <1e-04
m.out = matchit (RHC=="RHC" ~ age + sex + race +
Disease.category + DNR.status+
Heart.rate, # continuous
data = analytic.data,
method = "exact")
m.out
##
## Call:
## matchit(formula = RHC == "RHC" ~ age + sex + race + Disease.category +
## DNR.status + Heart.rate, data = analytic.data, method = "exact")
##
## Exact Subclasses: 504
##
## Sample sizes:
## Control Treated
## All 2583 2184
## Matched 929 947
## Unmatched 1654 1237
m.out = matchit (RHC=="RHC" ~ age + sex + race +
Disease.category + DNR.status+
Heart.rate + Blood.pressure +
Temperature,
data = analytic.data,
method = "exact")
m.out
##
## Call:
## matchit(formula = RHC == "RHC" ~ age + sex + race + Disease.category +
## DNR.status + Heart.rate + Blood.pressure + Temperature, data = analytic.data,
## method = "exact")
##
## Exact Subclasses: 3
##
## Sample sizes:
## Control Treated
## All 2583 2184
## Matched 3 3
## Unmatched 2580 2181
matched.data <- match.data(m.out)
dim(matched.data)
## [1] 6 25
nrow(analytic.data)-nrow(matched.data) # subjects deleted
## [1] 4761
fit3m <- glm(I(Death=="Yes")~RHC,
family=binomial, data = matched.data)
publish(fit3m)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 1.00 [0.03;29.81] 1
- Conditional Probability of getting treatment, given the observed covariates
- Prob(treatment:
A
= 1 | baseline or pre-treatment covariates:L
)- Prob(
RHC
= treated/RHC group |age
,sex
,race
, etc.) - f(L) = Prob(A=1|L)
- Prob(
baselinevars
## [1] "age" "sex" "race"
## [4] "Disease.category" "DNR.status" "APACHE.III.score"
## [7] "Pr.2mo.survival" "No.of.comorbidity" "DASI.2wk.prior"
## [10] "Temperature" "Heart.rate" "Blood.pressure"
## [13] "Respiratory.rate" "WBC.count" "PaO2.by.FIO2"
## [16] "PaCO2" "pH" "Creatinine"
## [19] "Albumin" "GComa.Score"
\includegraphics[width=0.5\linewidth]{images/citePS}
Rosenbaum, Rubin (1983) showed:
-
For potential outcomes
$(Y^0, Y^1)$ , if you have sufficient observed covariate list$L$ to reduce confounding (`strong ignoribility'):$A$ being treatment assignment here:- i.e., if
$(Y^0, Y^1) \indep A | L$ (Note that is this NOT$Y \indep A | L$ )
- i.e., if
-
then
-
$(Y^0, Y^1) \indep A | PS$ and $A \indep L | PS$
-
- no unmeasured confounding
- positivity ($ 0 < PS < 1 $)
- well-defined treatment
- sufficient overlap
- model-specification
\includegraphics[width=0.5\linewidth]{images/psvar} \includegraphics[width=0.5\linewidth]{images/psdesign}
-
Observed covariates are used to fix design
-
Which covariates should be selected:
- known to be a confounder (causes of
Death
andRHC
) - known to be a cause of the outcome (risk factors of
Death
) - avoid known instruments or noise variables: SE suffers
- mediating factors should be avoided (total effect = goal)
- known to be a confounder (causes of
-
Stepwise (p-value or criterion based) not recommended
- depending on sample size, different values can get selected
- may select variables highly associated with
$A$
-
Don't look at the outcome (
Death
) in your data to select covariates
Many ways to use propensity scores (PS) in the analysis
- PS matching [our focus today: intuitive!]
- PS weighting
- PS stratification
- PS used as a covariate
\includegraphics[width=0.5\linewidth]{images/citeaustin}
- Stage 1: exposure modelling:
$PS = Prob(A=1|L)$ - Stage 2: Match by
$PS$ - Stage 2: Assess balance and overlap (
$PS$ and$L$ ) - Stage 4: outcome modelling:
$Prob(Y=1|A=1)$
\includegraphics[width=0.5\linewidth]{images/citeaustin0}
- Assessment of Balance in the whole data
- balance = similarity of the covariate distributions
-
$d$ or$SMD > 0.1$ can be considered as imbalance
\includegraphics[width=0.2\linewidth]{images/d1}
\includegraphics[width=0.4\linewidth]{images/d2}
tab1e <- CreateTableOne(vars = baselinevars,
data = analytic.data, strata = "RHC",
includeNA = TRUE,
test = FALSE, smd = TRUE)
print(tab1e, smd = TRUE)
## Stratified by RHC
## No RHC RHC SMD
## n 2583 2184
## age (%) 0.181
## [-Inf,50) 573 (22.2) 540 (24.7)
## [50,60) 432 (16.7) 371 (17.0)
## [60,70) 638 (24.7) 577 (26.4)
## [70,80) 603 (23.3) 529 (24.2)
## [80, Inf) 337 (13.0) 167 ( 7.6)
## sex = Female (%) 878 (34.0) 906 (41.5) 0.155
## race (%) 0.098
## white 2072 (80.2) 1707 (78.2)
## black 400 (15.5) 335 (15.3)
## other 111 ( 4.3) 142 ( 6.5)
## Disease.category (%) 0.557
## ARF 1206 (46.7) 909 (41.6)
## CHF 194 ( 7.5) 209 ( 9.6)
## MOSF 515 (19.9) 858 (39.3)
## Other 668 (25.9) 208 ( 9.5)
## DNR.status = Yes (%) 317 (12.3) 155 ( 7.1) 0.176
## APACHE.III.score (mean (SD)) 50.06 (18.28) 60.74 (20.27) 0.553
## Pr.2mo.survival (mean (SD)) 0.63 (0.18) 0.57 (0.20) 0.301
## No.of.comorbidity (mean (SD)) 1.51 (1.17) 1.48 (1.13) 0.022
## DASI.2wk.prior (mean (SD)) 20.72 (5.68) 20.70 (5.03) 0.003
## Temperature (mean (SD)) 37.66 (1.73) 37.59 (1.83) 0.035
## Heart.rate (mean (SD)) 112.29 (40.13) 118.93 (41.47) 0.163
## Blood.pressure (mean (SD)) 85.13 (38.54) 68.20 (34.24) 0.464
## Respiratory.rate (mean (SD)) 28.93 (13.69) 26.65 (14.17) 0.163
## WBC.count (mean (SD)) 15.29 (11.22) 16.27 (12.55) 0.082
## PaO2.by.FIO2 (mean (SD)) 236.55 (113.17) 192.43 (105.54) 0.403
## PaCO2 (mean (SD)) 40.13 (14.23) 36.79 (10.97) 0.263
## pH (mean (SD)) 7.39 (0.11) 7.38 (0.11) 0.133
## Creatinine (mean (SD)) 1.94 (2.06) 2.47 (2.05) 0.260
## Albumin (mean (SD)) 3.18 (0.65) 2.98 (0.93) 0.251
## GComa.Score (mean (SD)) 20.53 (30.22) 18.97 (28.26) 0.053
Specify the propensity score model to estimate propensity scores, and fit the model
ps.formula <- as.formula(paste("I(RHC == 'RHC')", "~",
paste(baselinevars, collapse = "+")))
ps.formula
## I(RHC == "RHC") ~ age + sex + race + Disease.category + DNR.status +
## APACHE.III.score + Pr.2mo.survival + No.of.comorbidity +
## DASI.2wk.prior + Temperature + Heart.rate + Blood.pressure +
## Respiratory.rate + WBC.count + PaO2.by.FIO2 + PaCO2 + pH +
## Creatinine + Albumin + GComa.Score
- Coef of PS model fit is not of concern
- Model can be rich: to the extent that prediction is better
- But look for multi-collinearity issues
- SE too high?
While PS has balancing property, PS is unknown and needs to be estimated:
# fit logistic regression to estimate propensity scores
PS.fit <- glm(ps.formula,family="binomial",
data=analytic.data)
# extract estimated propensity scores from the fit
analytic.data$PS <- predict(PS.fit,
newdata = analytic.data, type="response")
- Other machine learning alternatives are possible to use instead of logistic regression.
- tree based methods have better ability to detect non-linearity / non-additivity (model-specification aspect)
- shrinkage methods - lasso / elastic net may better deal with multi-collinearity
- ensemble learners / super learners were successfully used
- shallow/deep learning!
- Don't loose sight that better balance is the ultimate goal for propensity score
- Prediction of
$A$ is just a means to that end (as true PS is unknown). - May attract variables highly associated with
$A$
\includegraphics[width=0.5\linewidth]{images/citesuper0} \includegraphics[width=0.5\linewidth]{images/citesuper} \includegraphics[width=0.5\linewidth]{images/psalt} \includegraphics[width=0.5\linewidth]{images/psml}
# summarize propensity scores
summary(analytic.data$PS)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.009182 0.268112 0.454924 0.458150 0.640362 0.975476
# summarize propensity scores by exposure group
tapply(analytic.data$PS, analytic.data$RHC, summary)
## $`No RHC`
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.009182 0.184909 0.330687 0.357838 0.504012 0.974095
##
## $RHC
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.05156 0.42874 0.59400 0.57679 0.74044 0.97548
logitPS <- -log(1/analytic.data$PS - 1)
# logit of the propensity score
.2*sd(logitPS) # suggested in the literature
## [1] 0.2382708
0.1*sd(logitPS) # we are using this
## [1] 0.1191354
# choosing too strict PS has unintended consequences
\includegraphics[width=0.5\linewidth]{images/citecapiler} \includegraphics[width=0.5\linewidth]{images/pscal}
Match using estimates propensity scores
- nearest-neighbor (NN) matching
- without replacement
- with caliper = .1*SD of logit of propensity score
- with 1:1 ratio (pair-matching)
\includegraphics[width=0.3\linewidth]{images/nn}
Match using estimates propensity scores
set.seed(123)
match.obj <- matchit(ps.formula, data = analytic.data,
distance = analytic.data$PS,
method = "nearest", replace=FALSE,
caliper = .1*sd(logitPS), ratio = 1)
# see matchit function options here
# https://www.rdocumentation.org/packages/MatchIt/versions/1.0-1/topics/matchit
analytic.data$PS <- match.obj$distance
summary(match.obj$distance)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.009182 0.268112 0.454924 0.458150 0.640362 0.975476
match.obj
##
## Call:
## matchit(formula = ps.formula, data = analytic.data, method = "nearest",
## distance = analytic.data$PS, replace = FALSE, caliper = 0.1 *
## sd(logitPS), ratio = 1)
##
## Sample sizes:
## Control Treated
## All 2583 2184
## Matched 1519 1519
## Unmatched 1064 665
## Discarded 0 0
Step 1 and 2 can be done together by specifying distance
match.obj <- matchit(ps.formula, data = analytic.data,
distance = 'logit',
method = "nearest",
replace=FALSE,
caliper = .1*sd(logitPS),
ratio = 1)
analytic.data$PS <- match.obj$distance
summary(match.obj$distance)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.009182 0.268112 0.454924 0.458150 0.640362 0.975476
# Ref: https://lists.gking.harvard.edu/pipermail/matchit/2013-October/000559.html
matches <- as.data.frame(match.obj$match.matrix)
colnames(matches)<-c("matched_unit")
matches$matched_unit<-as.numeric(
as.character(matches$matched_unit))
matches$treated_unit<-as.numeric(rownames(matches))
matches.only<-matches[!is.na(matches$matched_unit),]
head(matches.only)
## matched_unit treated_unit
## 5 438 5
## 10 2385 10
## 12 4177 12
## 13 4429 13
## 17 5228 17
## 22 1009 22
analytic.data[analytic.data$ID %in%
as.numeric(matches.only[1,]),]
## age sex race Disease.category DNR.status APACHE.III.score
## 5 [60,70) Male white MOSF Yes 72
## 438 [80, Inf) Male white ARF Yes 93
## Pr.2mo.survival No.of.comorbidity DASI.2wk.prior Temperature Heart.rate
## 5 0.43699980 0 21.05078 34.79688 125
## 438 0.01399994 2 15.95312 34.89844 0
## Blood.pressure Respiratory.rate WBC.count PaO2.by.FIO2 PaCO2 pH
## 5 65 27 29.699219 478.0000 17 7.229492
## 438 46 0 8.699219 138.0938 82 7.019531
## Creatinine Albumin GComa.Score RHC Death ID PS
## 5 3.599609 3.500000 41 RHC Yes 5 0.4213309
## 438 3.500000 2.799805 100 No RHC Yes 438 0.4125774
analytic.data[analytic.data$ID %in%
as.numeric(matches.only[2,]),]
## age sex race Disease.category DNR.status APACHE.III.score
## 10 [-Inf,50) Female white ARF No 48
## 2385 [70,80) Female white ARF No 36
## Pr.2mo.survival No.of.comorbidity DASI.2wk.prior Temperature Heart.rate
## 10 0.6689453 1 23.25781 38.5 141
## 2385 0.6219997 1 18.35156 36.5 120
## Blood.pressure Respiratory.rate WBC.count PaO2.by.FIO2 PaCO2 pH
## 10 73 40 20.597656 68.0000 30 7.349609
## 2385 67 9 3.199707 168.5625 34 7.429688
## Creatinine Albumin GComa.Score RHC Death ID PS
## 10 0.500000 2.5 0 RHC No 10 0.6565211
## 2385 1.599854 3.5 0 No RHC No 2385 0.6292985
Balance is more important than prediction!
- Criteria to assess success of step 2: PS estimation
- better balance
- better overlap [no extrapolation!]
- PS = 0 or PS = 1 needs close inspection
boxplot(PS ~ RHC=='RHC', data = analytic.data,
lwd = 2, ylab = 'PS')
stripchart(PS ~ RHC=='RHC', vertical = TRUE,
data = analytic.data, method = "jitter",
add = TRUE, pch = 20, col = 'blue')
\includegraphics[width=0.3\linewidth]{slidePS_files/figure-beamer/ps3-1}
Vizualization
plot(match.obj, type = "jitter")
\includegraphics[width=0.5\linewidth]{slidePS_files/figure-beamer/ps8-1}
## [1] "To identify the units, use first mouse button; to stop, use second."
## integer(0)
Vizualization for assessing overlap issues
plot(match.obj, type = "hist")
\includegraphics[width=0.5\linewidth]{slidePS_files/figure-beamer/ps9-1}
Assessment of Balance: Better than regression diagnostics!
matched.data <- match.data(match.obj)
tab1m <- CreateTableOne(vars = baselinevars,
data = matched.data, strata = "RHC",
includeNA = TRUE,
test = TRUE, smd = TRUE)
Compare the similarity of baseline characteristics between treated and untreated subjects in a the propensity score-matched sample.
- In this case, we will compare SMD < 0.1 or not.
- In some literature, other generous values (0.25) are proposed.
\includegraphics[width=0.5\linewidth]{images/citeaustin0} \includegraphics[width=0.5\linewidth]{images/smdcut}
print(tab1m, showAllLevels = FALSE, smd = TRUE, test = FALSE)
## Stratified by RHC
## No RHC RHC SMD
## n 1519 1519
## age (%) 0.042
## [-Inf,50) 367 (24.2) 365 (24.0)
## [50,60) 255 (16.8) 272 (17.9)
## [60,70) 385 (25.3) 395 (26.0)
## [70,80) 368 (24.2) 351 (23.1)
## [80, Inf) 144 ( 9.5) 136 ( 9.0)
## sex = Female (%) 560 (36.9) 572 (37.7) 0.016
## race (%) 0.017
## white 1212 (79.8) 1212 (79.8)
## black 229 (15.1) 234 (15.4)
## other 78 ( 5.1) 73 ( 4.8)
## Disease.category (%) 0.043
## ARF 709 (46.7) 689 (45.4)
## CHF 172 (11.3) 162 (10.7)
## MOSF 452 (29.8) 479 (31.5)
## Other 186 (12.2) 189 (12.4)
## DNR.status = Yes (%) 123 ( 8.1) 126 ( 8.3) 0.007
## APACHE.III.score (mean (SD)) 55.09 (18.55) 56.41 (19.33) 0.070
## Pr.2mo.survival (mean (SD)) 0.61 (0.19) 0.59 (0.19) 0.059
## No.of.comorbidity (mean (SD)) 1.53 (1.18) 1.52 (1.16) 0.005
## DASI.2wk.prior (mean (SD)) 20.75 (5.62) 20.75 (5.12) 0.001
## Temperature (mean (SD)) 37.67 (1.85) 37.65 (1.70) 0.010
## Heart.rate (mean (SD)) 115.75 (41.06) 115.73 (39.83) 0.001
## Blood.pressure (mean (SD)) 75.41 (36.03) 73.74 (35.75) 0.047
## Respiratory.rate (mean (SD)) 28.25 (13.69) 27.74 (14.13) 0.036
## WBC.count (mean (SD)) 15.93 (11.99) 15.76 (12.15) 0.014
## PaO2.by.FIO2 (mean (SD)) 214.17 (110.19) 210.06 (108.71) 0.038
## PaCO2 (mean (SD)) 37.57 (10.98) 37.47 (11.47) 0.009
## pH (mean (SD)) 7.39 (0.11) 7.39 (0.11) 0.029
## Creatinine (mean (SD)) 2.24 (2.41) 2.28 (1.88) 0.019
## Albumin (mean (SD)) 3.07 (0.68) 3.05 (0.97) 0.029
## GComa.Score (mean (SD)) 18.50 (28.53) 18.68 (28.31) 0.006
Possible to get p-values to check balance: but strongly discouraged
- P-value based balance assessment can be influenced by sample size
\includegraphics[width=0.5\linewidth]{images/citeaustin}
print(tab1m, showAllLevels = FALSE, smd = FALSE, test = TRUE)
## Stratified by RHC
## No RHC RHC p test
## n 1519 1519
## age (%) 0.859
## [-Inf,50) 367 (24.2) 365 (24.0)
## [50,60) 255 (16.8) 272 (17.9)
## [60,70) 385 (25.3) 395 (26.0)
## [70,80) 368 (24.2) 351 (23.1)
## [80, Inf) 144 ( 9.5) 136 ( 9.0)
## sex = Female (%) 560 (36.9) 572 (37.7) 0.680
## race (%) 0.896
## white 1212 (79.8) 1212 (79.8)
## black 229 (15.1) 234 (15.4)
## other 78 ( 5.1) 73 ( 4.8)
## Disease.category (%) 0.707
## ARF 709 (46.7) 689 (45.4)
## CHF 172 (11.3) 162 (10.7)
## MOSF 452 (29.8) 479 (31.5)
## Other 186 (12.2) 189 (12.4)
## DNR.status = Yes (%) 123 ( 8.1) 126 ( 8.3) 0.895
## APACHE.III.score (mean (SD)) 55.09 (18.55) 56.41 (19.33) 0.055
## Pr.2mo.survival (mean (SD)) 0.61 (0.19) 0.59 (0.19) 0.102
## No.of.comorbidity (mean (SD)) 1.53 (1.18) 1.52 (1.16) 0.889
## DASI.2wk.prior (mean (SD)) 20.75 (5.62) 20.75 (5.12) 0.985
## Temperature (mean (SD)) 37.67 (1.85) 37.65 (1.70) 0.786
## Heart.rate (mean (SD)) 115.75 (41.06) 115.73 (39.83) 0.989
## Blood.pressure (mean (SD)) 75.41 (36.03) 73.74 (35.75) 0.200
## Respiratory.rate (mean (SD)) 28.25 (13.69) 27.74 (14.13) 0.315
## WBC.count (mean (SD)) 15.93 (11.99) 15.76 (12.15) 0.703
## PaO2.by.FIO2 (mean (SD)) 214.17 (110.19) 210.06 (108.71) 0.301
## PaCO2 (mean (SD)) 37.57 (10.98) 37.47 (11.47) 0.801
## pH (mean (SD)) 7.39 (0.11) 7.39 (0.11) 0.425
## Creatinine (mean (SD)) 2.24 (2.41) 2.28 (1.88) 0.593
## Albumin (mean (SD)) 3.07 (0.68) 3.05 (0.97) 0.425
## GComa.Score (mean (SD)) 18.50 (28.53) 18.68 (28.31) 0.868
Assessment of balance in the matched data
smd.res <- ExtractSmd(tab1m)
t(round(smd.res,2))
## age sex race Disease.category DNR.status APACHE.III.score
## 1 vs 2 0.04 0.02 0.02 0.04 0.01 0.07
## Pr.2mo.survival No.of.comorbidity DASI.2wk.prior Temperature Heart.rate
## 1 vs 2 0.06 0.01 0 0.01 0
## Blood.pressure Respiratory.rate WBC.count PaO2.by.FIO2 PaCO2 pH
## 1 vs 2 0.05 0.04 0.01 0.04 0.01 0.03
## Creatinine Albumin GComa.Score
## 1 vs 2 0.02 0.03 0.01
- Variance ratios
$\sim$ 1 means: - equal variances in groups
- group balance
- could vary from 1/2 to 2
- other cut-points are suggested as well (0.8 to 1.2)
\includegraphics[width=0.5\linewidth]{images/psbal} \includegraphics[width=0.5\linewidth]{images/vr}
require(cobalt)
baltab.res <- bal.tab(x = match.obj, data = analytic.data,
treat = analytic.data$RHC,
disp.v.ratio = TRUE)
## Note: 's.d.denom' not specified; assuming pooled.
baltab.res$Balance$V.Ratio.Adj
## [1] 1.0990553 NA NA NA NA NA NA
## [8] NA NA NA NA NA NA NA
## [15] NA 1.0867497 0.9714495 0.9605864 0.8305596 0.8395535 0.9408913
## [22] 0.9841995 1.0655834 1.0262382 0.9733399 1.0919443 1.0916685 0.6100881
## [29] 2.0325397 0.9847091
- Some flexibility in choosing outcome model
- considered independent of exposure modelling
- some propose double robust approach
- adjusting imbalanced covariates only?
Estimate the effect of treatment on outcomes using propensity score-matched sample
fit3 <- glm(I(Death=="Yes")~RHC,
family=binomial, data = matched.data)
publish(fit3)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.08 [1.80;2.41] <1e-04
out.formula
## I(Death == "Yes") ~ RHC + age + sex + race + Disease.category +
## DNR.status + APACHE.III.score + Pr.2mo.survival + No.of.comorbidity +
## DASI.2wk.prior + Temperature + Heart.rate + Blood.pressure +
## Respiratory.rate + WBC.count + PaO2.by.FIO2 + PaCO2 + pH +
## Creatinine + Albumin + GComa.Score
fit3b <- glm(out.formula,
family=binomial, data = matched.data)
publish(fit3b)
## Variable Units OddsRatio CI.95 p-value
## RHC No RHC Ref
## RHC 2.55 [2.14;3.03] < 1e-04
## age [-Inf,50) Ref
## [50,60) 1.72 [1.33;2.23] < 1e-04
## [60,70) 0.76 [0.61;0.95] 0.015909
## [70,80) 1.12 [0.92;1.37] 0.263369
## [80, Inf) 1.27 [1.06;1.52] 0.008216
## sex Male Ref
## Female 0.43 [0.36;0.52] < 1e-04
## race white Ref
## black 1.14 [0.89;1.46] 0.310211
## other 0.86 [0.57;1.29] 0.460330
## Disease.category ARF Ref
## CHF 1.49 [1.05;2.10] 0.025167
## MOSF 1.00 [0.81;1.24] 0.981457
## Other 1.34 [0.98;1.82] 0.068593
## DNR.status No Ref
## Yes 2.60 [1.73;3.90] < 1e-04
## APACHE.III.score 1.00 [1.00;1.01] 0.297336
## Pr.2mo.survival 0.01 [0.00;0.01] < 1e-04
## No.of.comorbidity 1.19 [1.10;1.30] < 1e-04
## DASI.2wk.prior 0.95 [0.93;0.96] < 1e-04
## Temperature 0.95 [0.90;1.01] 0.081292
## Heart.rate 1.00 [1.00;1.00] 0.089442
## Blood.pressure 1.00 [1.00;1.00] 0.207696
## Respiratory.rate 1.00 [1.00;1.01] 0.175634
## WBC.count 1.00 [1.00;1.01] 0.417667
## PaO2.by.FIO2 1.00 [1.00;1.00] 0.005573
## PaCO2 1.00 [0.99;1.01] 0.907590
## pH 0.86 [0.30;2.43] 0.770447
## Creatinine 1.03 [0.99;1.08] 0.155861
## Albumin 1.02 [0.91;1.15] 0.699519
## GComa.Score 1.00 [0.99;1.00] 0.013497
The above analysis do not take matched pair into consideration while regressing. Literature proposes different strategies:
- do not control for pairs / clusters
- use
glm
as is
- use
- control for pairs / clusters
- use
cluster
option or GEE or conditional logistic
- use
- Bootstrap for matched pairfor WOR
- may not be appropriate for WR
\includegraphics[width=0.5\linewidth]{images/boot}
- The example compared
RHC
(a treated group; target) vsNo RHC
(untreated). - Thc corresponding treatment effect estimate is known as
- Average Treatment Effects on the Treated (ATT)
- Other estimates from PS analysis are possible that compared the whole population
- what if everyone treated vs. what if nobody was treated (ATE)
- Optimal
- genetic matching
- CEM
- variable ratio NN
- MatchIt
- Matching
Other useful packages
- cobalt
- twang
Outdated package
- nonrandom
- Propensity score matching most popular
- Cardiovascular / Infective endocarditis / Intensive care
- Critical care / anesthesiology / Sepsis / Psychology
- Cancer / Multiple sclerosis
- Not meta-analysis; but reviews of usage of PS methods in different disciplines
\includegraphics[width=0.3\linewidth]{images/r1} \includegraphics[width=0.3\linewidth]{images/r2} \includegraphics[width=0.3\linewidth]{images/r3} \includegraphics[width=0.3\linewidth]{images/r4} \includegraphics[width=0.3\linewidth]{images/r5} \includegraphics[width=0.3\linewidth]{images/r6} \includegraphics[width=0.3\linewidth]{images/r7} \includegraphics[width=0.3\linewidth]{images/r8} \includegraphics[width=0.3\linewidth]{images/r9}
- Be specific about population of interest
- ATT vs. ATE
- exclusion criteria
- Be specific about exposure
- no multiple version of treatment
- no interference
- comparator
- Report clearly about missing data
- how handled
- Why PS matching (or other approach) was selected?
- Software
- How variables selected
- Any important variables not measured
- proxy
- Model selection
- interaction or polynomials
- logistic vs. machine learning
- Overlap vs. balance
- numeric and visual
\includegraphics[width=0.3\linewidth]{images/books}
- Reduction % of the matched data: main objection against this method!
- Residual imbalance
- refit PS model
- Subgroup analysis
- Refit within each group for matching
- Sensitivity analysis
- unmeasured confounder / hdPS
- any positivity issue? Deleting extremes has consequences!
- ad-hoc methods: truncation / trimming: bias-variance trade-off
\includegraphics[width=0.5\linewidth]{images/sub} \includegraphics[width=0.5\linewidth]{images/hdps}
\includegraphics[width=0.2\linewidth]{images/book1}
\includegraphics[width=0.5\linewidth]{images/book}
Companion site: study.sagepub.com/leite
[ehsank.com/workshops/](https://ehsank.com/workshops/)