Skip to contents

Here, we’ll go over some examples of using per-protocol, censoring. First we need to load the library before getting in to some sample use cases.

Per-protocol, censoring, weights in pre-expanded data and no truncation, no excused conditions (i.e. static interventions)

options <- SEQopts(# tells SEQuential to create Kaplan-Meier curves
                   km.curves = TRUE,
                   # tells SEQuential to weight the outcome model
                   weighted = TRUE, 
                   # tells SEQuential to build weights from the pre-expanded data
                   weight.preexpansion = TRUE)

# use some example data in the package
data <- SEQdata                                
model <- SEQuential(data, 
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Non-required columns provided, pruning for efficiency
#> 
#> Pruned
#> 
#> Original dataset (eligible subjects): 9,203 observations, 9 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 15 variables
#> 
#> Expanded dataset (post-censoring): 102,749 observations, 15 variables
#>   entering outcome model (uncensored): 96,251
#>   artificially censored (treatment switch): 6,498
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 102,749 observations, 15 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Creating Survival curves
#> 
#> Completed

# retrieve risk plot
km_curve(model, plot.type = "risk")

Risk plot by treatment group.

# retrieve survival and risk data
survival_data <- km_data(model)
risk_data(model)
#> Index: <Followup>
#>       Method Followup      A      Risk
#>       <char>    <num> <char>     <num>
#> 1: censoring       60      0 0.6723717
#> 2: censoring       60      1 0.9243324
risk_comparison(model)
#>    Followup    A_x    A_y Risk Ratio Risk Difference
#>       <num> <fctr> <fctr>      <num>           <num>
#> 1:       60 risk_0 risk_1  1.3747342       0.2519607
#> 2:       60 risk_1 risk_0  0.7274133      -0.2519607

Per-protocol, censoring, weights in post-expanded data and no truncation, no excused conditions (i.e. static interventions)

options <- SEQopts(km.curves = TRUE,
                   weighted = TRUE, 
                   # tells SEQuential to build weights from the post-expanded data
                   weight.preexpansion = FALSE)

data <- SEQdata                                 
model <- SEQuential(data, 
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Non-required columns provided, pruning for efficiency
#> 
#> Pruned
#> 
#> Original dataset (eligible subjects): 9,203 observations, 9 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 18 variables
#> 
#> Expanded dataset (post-censoring): 102,749 observations, 18 variables
#>   entering outcome model (uncensored): 96,251
#>   artificially censored (treatment switch): 6,498
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 102,749 observations, 18 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Creating Survival curves
#> 
#> Completed

km_curve(model, plot.type = "risk")

Risk plot by treatment group.

risk_data(model)
#> Index: <Followup>
#>       Method Followup      A      Risk
#>       <char>    <num> <char>     <num>
#> 1: censoring       60      0 0.6533049
#> 2: censoring       60      1 0.9281893
risk_comparison(model)
#>    Followup    A_x    A_y Risk Ratio Risk Difference
#>       <num> <fctr> <fctr>      <num>           <num>
#> 1:       60 risk_0 risk_1  1.4207598       0.2748844
#> 2:       60 risk_1 risk_0  0.7038488      -0.2748844

Per-protocol, censoring, weights in pre-expanded data and no truncation, excused conditions for initiators and non-initiators (i.e. dynamic interventions)

options <- SEQopts(km.curves = TRUE,
                   weighted = TRUE,
                   weight.preexpansion = TRUE,
                   # tells SEQuential to run a dynamic intervention
                   excused = TRUE,                               
                   # excusedZero excuses switches from treatment 0 to 1,
                   # excusedOne switches from 1 to 0
                   excused.cols = c("excusedZero", "excusedOne"), 
                   # tells SEQuential to expect treatment levels 0, 1
                   # (mapping to the same positions as the list in excused.cols)
                   treat.level = c(0, 1))
data <- SEQdata                                
model <- SEQuential(data, 
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Original dataset (eligible subjects): 9,203 observations, 11 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 18 variables
#> 
#> Expanded dataset (post-censoring): 188,475 observations, 18 variables
#>   entering outcome model (uncensored): 186,259
#>   artificially censored (treatment switch): 2,216
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 188,475 observations, 18 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Creating Survival curves
#> 
#> Completed

km_curve(model, plot.type = "risk")

Risk plot by treatment group.

risk_data(model)
#> Index: <Followup>
#>       Method Followup      A      Risk
#>       <char>    <num> <char>     <num>
#> 1: censoring       60      0 0.8730463
#> 2: censoring       60      1 0.8855556
risk_comparison(model)
#>    Followup    A_x    A_y Risk Ratio Risk Difference
#>       <num> <fctr> <fctr>      <num>           <num>
#> 1:       60 risk_0 risk_1  1.0143283      0.01250924
#> 2:       60 risk_1 risk_0  0.9858741     -0.01250924

Per-protocol, censoring, a flexible baseline hazard in the weight models

By default the weight models are quadratic in time: followup, followup_sq, trial and trial_sq when weight.preexpansion = FALSE, and the time column and its square when weight.preexpansion = TRUE. A quadratic only lets the hazard of treatment rise or flatten off, which can fit poorly when the cumulative incidence of treatment moves through several phases over follow-up. weight.spline = TRUE replaces each of those quadratics with a natural cubic spline basis (splines::ns()) with weight.spline.df degrees of freedom, letting the baseline hazard take a flexible shape over time. This applies to the treatment weight models and, when cense or visit is set, to those models too.

options <- SEQopts(km.curves = TRUE,
                   weighted = TRUE,
                   weight.preexpansion = FALSE,
                   # model time in the weight models as a natural cubic spline
                   weight.spline = TRUE,
                   # 4 degrees of freedom, i.e. 3 interior knots at quantiles
                   weight.spline.df = 4L)

data <- SEQdata
model <- SEQuential(data,
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Non-required columns provided, pruning for efficiency
#> 
#> Pruned
#> 
#> Original dataset (eligible subjects): 9,203 observations, 9 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 18 variables
#> 
#> Expanded dataset (post-censoring): 102,749 observations, 18 variables
#>   entering outcome model (uncensored): 96,251
#>   artificially censored (treatment switch): 6,498
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 102,749 observations, 18 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Creating Survival curves
#> 
#> Completed

# the weight model formulas, with the spline knots fixed from the data
covariates(model)
#> $Outcome
#> [1] "outcome ~ tx_init_bas + followup + followup_sq + trial + trial_sq + sex + N_bas + L_bas + P_bas + tx_init_bas*followup"
#> 
#> $Numerator
#> [1] "tx_init ~ sex + N_bas + L_bas + P_bas + ns(followup, knots = c(2, 7, 14), Boundary.knots = c(0, 58)) + ns(trial, knots = c(8, 16, 26), Boundary.knots = c(0, 59))"
#> 
#> $Denominator
#> [1] "tx_init ~ sex + N + L + P + N_bas + L_bas + P_bas + ns(followup, knots = c(2, 7, 14), Boundary.knots = c(0, 58)) + ns(trial, knots = c(8, 16, 26), Boundary.knots = c(0, 59))"

For finer control - a spline in followup but not trial, a different number of knots per term, or a spline in a time-varying confounder - write the ns() terms into numerator and denominator yourself. Any ns(x, df = N) term in a model formula has its knots fixed from the data the model is fit on before fitting, so the basis is the same when the model is fit and when it is used to predict the weights, and is held constant across bootstrap resamples.

options <- SEQopts(weighted = TRUE,
                   weight.preexpansion = FALSE,
                   numerator = "sex+N_bas+L_bas+P_bas+ns(followup, df = 5)+trial+trial_sq",
                   denominator = "sex+N+L+P+N_bas+L_bas+P_bas+ns(followup, df = 5)+trial+trial_sq")

model <- SEQuential(data,
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Non-required columns provided, pruning for efficiency
#> 
#> Pruned
#> 
#> Original dataset (eligible subjects): 9,203 observations, 9 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 18 variables
#> 
#> Expanded dataset (post-censoring): 102,749 observations, 18 variables
#>   entering outcome model (uncensored): 96,251
#>   artificially censored (treatment switch): 6,498
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 102,749 observations, 18 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Completed

covariates(model)
#> $Outcome
#> [1] "outcome ~ tx_init_bas + followup + followup_sq + trial + trial_sq + sex + N_bas + L_bas + P_bas"
#> 
#> $Numerator
#> [1] "tx_init ~ sex + N_bas + L_bas + P_bas + ns(followup, knots = c(2, 5, 9, 16), Boundary.knots = c(0, 58)) + trial + trial_sq"
#> 
#> $Denominator
#> [1] "tx_init ~ sex + N + L + P + N_bas + L_bas + P_bas + ns(followup, knots = c(2, 5, 9, 16), Boundary.knots = c(0, 58)) + trial + trial_sq"

Per-protocol, censoring, weights in post-expanded data and no truncation, excused conditions for initiators and non-initiators (i.e. dynamic interventions)

options <- SEQopts(km.curves = TRUE,
                   weighted = TRUE,
                   weight.preexpansion = FALSE,
                   excused = TRUE,                               
                   excused.cols = c("excusedZero", "excusedOne"), 
                   treat.level = c(0, 1),
                   weight.p99 = TRUE)
data <- SEQdata                                
model <- SEQuential(data, 
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Original dataset (eligible subjects): 9,203 observations, 11 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 21 variables
#> 
#> Expanded dataset (post-censoring): 188,475 observations, 21 variables
#>   entering outcome model (uncensored): 186,259
#>   artificially censored (treatment switch): 2,216
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 188,475 observations, 21 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Creating Survival curves
#> 
#> Completed

km_curve(model, plot.type = "risk")

Risk plot by treatment group.

risk_data(model)
#> Index: <Followup>
#>       Method Followup      A      Risk
#>       <char>    <num> <char>     <num>
#> 1: censoring       60      0 0.8454007
#> 2: censoring       60      1 0.8691113
risk_comparison(model)
#>    Followup    A_x    A_y Risk Ratio Risk Difference
#>       <num> <fctr> <fctr>      <num>           <num>
#> 1:       60 risk_0 risk_1  1.0280466      0.02371061
#> 2:       60 risk_1 risk_0  0.9727186     -0.02371061

Per-protocol, censoring, weights in post-expanded data and no truncation, excused conditions for initiators and non-initiators (i.e. dynamic interventions) and a competing event

options <- SEQopts(km.curves = TRUE,
                   weighted = TRUE,
                   weight.preexpansion = FALSE,
                   excused = TRUE,                               
                   excused.cols = c("excusedZero", "excusedOne"), 
                   treat.level = c(0, 1),
                   # add a competing event
                   compevent = "LTFU")

data <- SEQdata.LTFU                                
model <- SEQuential(data, 
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 54,687 observations, 13 variables
#> 
#> Non-required columns provided, pruning for efficiency
#> 
#> Pruned
#> 
#> Original dataset (eligible subjects): 29,624 observations, 12 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 1,609,859 observations
#> 
#> Expanded dataset (pre-censoring): 1,119,229 observations, 22 variables
#> 
#> Expanded dataset (post-censoring): 814,689 observations, 22 variables
#>   entering outcome model (uncensored): 806,922
#>   artificially censored (treatment switch): 7,767
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 814,689 observations, 22 variables
#> 
#> Moving forward with censoring analysis
#> 
#> censoring model created successfully
#> 
#> Creating Survival curves
#> 
#> Completed

km_curve(model, plot.type = "risk")

Risk plot by treatment group.

risk_data(model)
#> Index: <Followup>
#>       Method Followup      A       Risk
#>       <char>    <num> <char>      <num>
#> 1: censoring       60      0 0.01707101
#> 2: censoring       60      1 0.02094034
risk_comparison(model)
#>    Followup    A_x    A_y Risk Ratio Risk Difference
#>       <num> <fctr> <fctr>      <num>           <num>
#> 1:       60  inc_0  inc_1  1.2266611     0.003869334
#> 2:       60  inc_1  inc_0  0.8152211    -0.003869334

Per-protocol, censoring, weights in post-expanded data and no truncation, excused conditions for initiators and non-initiators (i.e. dynamic interventions) and hazard ratio

options <- SEQopts(# tell SEQuential to run hazard ratios
                   hazard = TRUE,
                   weighted = TRUE,
                   weight.preexpansion = FALSE,
                   excused = TRUE,                               
                   excused.cols = c("excusedZero", "excusedOne"),
                   weight.p99 = TRUE)

data <- SEQdata                              
model <- SEQuential(data,
                    id.col = "ID",
                    time.col = "time",
                    eligible.col = "eligible",
                    treatment.col = "tx_init",
                    outcome.col = "outcome",
                    time_varying.cols = c("N", "L", "P"),
                    fixed.cols = "sex",
                    method = "censoring",
                    options = options)
#> 
#> Full dataset: 12,180 observations, 11 variables
#> 
#> Original dataset (eligible subjects): 9,203 observations, 11 variables
#> 
#> Expanding Data...
#> 
#> Pre-filter expansion: 310,080 observations
#> 
#> Expanded dataset (pre-censoring): 248,485 observations, 21 variables
#> 
#> Expanded dataset (post-censoring): 188,475 observations, 21 variables
#>   entering outcome model (uncensored): 186,259
#>   artificially censored (treatment switch): 2,216
#> 
#> Expansion Successful
#> 
#> Final analysis dataset: 188,475 observations, 21 variables
#> 
#> Moving forward with censoring analysis
#> 
#> Completed
hazard_ratio(model)
#> Hazard ratio          LCI          UCI 
#>     1.170182           NA           NA