---
title: "New Interface"
output: 
  rmarkdown::html_vignette:
    toc: true 
vignette: >
  %\VignetteIndexEntry{New Interface}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options: 
  chunk_output_type: console
---


```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(TrialEmulation)
```

To improve usability, we have implemented a new user interface. This allows a more structured specification of the 
target trial emulation process.

It also gives flexibility to add new methods and tools for parts of the analysis. For example, we now allow different
ways of storing the expanded data: as CSV files and in a DuckDB database. We also allow different weight fitting model
procedures: using `stats::glm` or `parglm::parglm`. New components can quickly and easily be specified for use with
this package.


## User Interface

A sequence of target trials analysis starts by specifying which estimand will be used:

```{r}
trial_pp <- trial_sequence(estimand = "PP") # Per-protocol
trial_itt <- trial_sequence(estimand = "ITT") # Intention-to-treat
```

Additionally it is useful to create a directory to save files for later inspection.
```{r}
trial_pp_dir <- file.path(tempdir(), "trial_pp")
dir.create(trial_pp_dir)
trial_itt_dir <- file.path(tempdir(), "trial_itt")
dir.create(trial_itt_dir)
```


### Observational Data

Next the user must specify the observational input data that will be used for the target trial emulation.
Here we need to specify which columns contain which values and how they should be used.
```{r}
data("data_censored")
trial_pp <- trial_pp |>
  set_data(
    data = data_censored,
    id = "id",
    period = "period",
    treatment = "treatment",
    outcome = "outcome",
    eligible = "eligible"
  )

# Function style without pipes
trial_itt <- set_data(
  trial_itt,
  data = data_censored,
  id = "id",
  period = "period",
  treatment = "treatment",
  outcome = "outcome",
  eligible = "eligible"
)
```

We can inspect our object by printing:

```{r}
trial_itt
```

We see the newly attached data. Some pre-processing has occurred: the id, period, treatment, outcome and eligible
columns are renamed to have those names, and some additional columns required for later processing have been created.
We also see some hints that other components of the analysis are not yet defined.

### Weight Models

To adjust for the effects of informative censoring, inverse probability of censoring weights (IPCW) can be applied. To
estimate these weights, we construct time-to-(censoring-)event models. Two sets of models are fit for the two censoring mechanisms which
may apply: censoring due to deviation from assigned treatment and other informative censoring.

#### Censoring due to treatment switching

We specify model formulas to be used for calculating the probability of receiving treatment in the current period.
Separate models are fitted for patients who had `treatment = 1` and those who had `treatment = 0` in the previous 
period. Stabilized weights are used by fitting numerator and denominator models.

There are optional arguments to specify columns which can include/exclude observations from the treatment models. These
are used in case it is not possible for a patient to deviate from a certain treatment assignment in that period.

```{r}
trial_pp <- trial_pp |>
  set_switch_weight_model(
    numerator = ~age,
    denominator = ~ age + x1 + x3,
    model_fitter = stats_glm_logit(save_path = file.path(trial_pp_dir, "switch_models"))
  )
trial_pp@switch_weights
```

This type of censoring is not used with an ITT estimand, so we cannot use `set_switch_weight_model()` with `trial_ITT`
objects.
Note that we calculated stabilised weights, so a numerator and denominator model is required. The numerator should
contain terms that are not time varying. These terms are later included in the final time-to-event model for the
outcome.

#### Other informative censoring

In case there is other informative censoring occurring in the data, we can create similar models to estimate the IPCW.
These can be used with all types of estimand. 
Compared to `set_switch_weight_model` there are additional required arguments:

 - `censor_event` which specifies the column containing the censoring indicator
 - `pool_models` which species that models may be fit separately (as in `set_switch_weight_model`) or pooled across the
treatments in the previous period. The choices are `"none"`, `"both"`, or `"numerator"` only. The default and allowed
choices depends on the estimand.


```{r}
trial_pp <- trial_pp |>
  set_censor_weight_model(
    censor_event = "censored",
    numerator = ~x2,
    denominator = ~ x2 + x1,
    pool_models = "none",
    model_fitter = stats_glm_logit(save_path = file.path(trial_pp_dir, "switch_models"))
  )
trial_pp@censor_weights
```

```{r}
trial_itt <- set_censor_weight_model(
  trial_itt,
  censor_event = "censored",
  numerator = ~x2,
  denominator = ~ x2 + x1,
  pool_models = "numerator",
  model_fitter = stats_glm_logit(save_path = file.path(trial_itt_dir, "switch_models"))
)
trial_itt@censor_weights
```

#### Calculate Weights

Next we need to fit the individual models and combine them into weights. This is done with `calculate_weights()`.

```{r}
trial_pp <- trial_pp |> calculate_weights()
trial_itt <- calculate_weights(trial_itt)
```

The full model objects are saved to disk in the directories we created above. The summaries are stored in the trial
sequence object and can be printed:

```{r}
show_weight_models(trial_itt)
```

### Specify Outcome Model

Now we can specify the outcome model. Here we can include adjustment terms for any variables in the dataset.
We can also specify how `followup_time` and `trial_period` terms should be included in the model.
As for the weight models, we can specify a `model_fitter`. The numerator terms from the stabilised weight
models are automatically included in the outcome model formula.

```{r}
trial_pp <- set_outcome_model(trial_pp)
trial_itt <- set_outcome_model(trial_itt, adjustment_terms = ~x2)
```

It is necessary to specify the outcome model at this stage because we need to know which columns should be
retained and expanded in the construction of the sequence of trials data set. After expansion it is possible
to set the outcome model again to modify how covariates are modelled, e.g. to add an interaction or squared
term. To add a term for a variable not in the expanded data, the expansion procedure will need to be repeated.
 

### Expand Trials

Now we are ready to create the data set with all of the sequence of target trials. First we specify some options for the
expansion and then expand.

#### Set Expansion Options

There are two options to set

 - output: specifies how and where the expanded data will be saved. As it can be very large, we may need to save it to
 disk with CSV files or DuckDB, using a `save_to_*` function.
 - chunk_size: if the expanded data is too large to fit in memory, we need to process it in chunks by specifying how
 many patients are processed at one time.

```{r}
trial_pp <- set_expansion_options(
  trial_pp,
  output = save_to_datatable(),
  chunk_size = 500
)
trial_itt <- set_expansion_options(
  trial_itt,
  output = save_to_datatable(),
  chunk_size = 500
)
```

Other options for big data are to save to csv or DuckDB:

```{r, eval = FALSE}
trial_pp <- trial_pp |>
  set_expansion_options(
    output = save_to_csv(file.path(trial_pp_dir, "trial_csvs")),
    chunk_size = 500
  )

trial_itt <- set_expansion_options(
  trial_itt,
  output = save_to_csv(file.path(trial_itt_dir, "trial_csvs")),
  chunk_size = 500
)

trial_itt <- set_expansion_options(
  trial_itt,
  output = save_to_duckdb(file.path(trial_itt_dir, "trial_duckdb")),
  chunk_size = 500
)

# For the purposes of this vignette the previous `save_to_datatable()` output
# option is used in the following code.
```


#### Create Sequence of Trials Data

Now we are ready to construct the sequence of trials dataset using the `expand_trials()` method.
This can take some time for large input data.

```{r}
trial_pp <- expand_trials(trial_pp)
trial_itt <- expand_trials(trial_itt)
```

The resulting object shows the settings used for the expansion and where the expanded data has been 
saved.

```{r}
trial_pp@expansion
```


### Sample or Load from Expanded Data

Now that the expanded data has been created, we can prepare the data to fit the outcome model.
For data that can fit comfortably in memory, this is a trivial step using `load_expanded_data`.
For large datasets, it may be necessary to sample from the expanded by setting the `p_control` argument. This
sets the probability that an observation with`outcome == 0` will be included in the loaded data. A seed can be set for
reproducibility.
Additionally, a vector of periods to include can be specified, eg `period = 1:60`, and/or a subsetting condition,
`subset_condition = "age > 65"`.


```{r}
trial_itt <- load_expanded_data(trial_itt, seed = 1234, p_control = 0.5)
```

The loaded data can be accessed and/or modified with `outcome_data()`.
```{r}
x2_sq <- outcome_data(trial_itt)$x2^2
outcome_data(trial_itt)$x2_sq <- x2_sq
head(outcome_data(trial_itt))
```


### Fit Marginal Structural Model

To fit the outcome model we use `fit_msm()`. There are two options to specify how weights are used in the model.
First we can select which weights columns are used, by default the product of columns `weight` and `sample_weight` is
used. We can also apply a modifier function, for example, to trim large weights to some fixed value or a percentile.


```{r}
trial_itt <- fit_msm(
  trial_itt,
  weight_cols = c("weight", "sample_weight"),
  modify_weights = function(w) {
    q99 <- quantile(w, probs = 0.99)
    pmin(w, q99)
  }
)
```

The summary of the model fit is shown:

```{r}
trial_itt@outcome_model
```

Depending on the model fitter used, we can also access the model object.
For the default `stats::glm` logistic model, we have the `glm` object as well as the `sandwich` variance-covariance
matrix.
```{r}
trial_itt@outcome_model@fitted@model$model
trial_itt@outcome_model@fitted@model$vcov
```

The complete object shows all the specifications:
```{r}
trial_itt
```


### Inference

We use the `predict()` method to estimate survival probabilities or cumulative incidences for different values of
`assigned_treatment`. The `predict` method takes the baseline of the provided `newdata`, i.e. observations with 
`followup_time == 0` and constructs data with `followup_time` for the given `predict_times`.
It is important to specify `newdata` correctly for a meaningful interpretation of the differences in survival.

```{r}
preds <- predict(
  trial_itt,
  newdata = outcome_data(trial_itt)[trial_period == 1, ],
  predict_times = 0:10,
  type = "survival",
)
plot(preds$difference$followup_time, preds$difference$survival_diff,
  type = "l", xlab = "Follow up", ylab = "Survival difference"
)
lines(preds$difference$followup_time, preds$difference$`2.5%`, type = "l", col = "red", lty = 2)
lines(preds$difference$followup_time, preds$difference$`97.5%`, type = "l", col = "red", lty = 2)
```

### Flowchart

This flow chart helps visualise the complete workflow.

![Flowchart](img/trial_sequence_functions_overview_solid.png){ width=80% }
