---
title: "Survival Analysis with causaldef"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Survival Analysis with causaldef}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
eval_surv_effect <- getRversion() >= "4.0.0" && requireNamespace("survival", quietly = TRUE)
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

**causaldef** supports survival analysis and a separate competing-risks workflow. This vignette focuses on a reproducible death-endpoint RMST analysis for the bundled HCT data, then shows how to switch to the dedicated competing-risks interface when relapse and death must be modeled separately.

The deficiency and regret-bound calculations below run on all supported runtimes. The effect-estimation chunk that relies on the survival runtime requires `R >= 4.0` in the current support matrix.

## Setup

```{r setup}
library(causaldef)
library(ggplot2)

plot_dag <- function(coords, edges, title = NULL) {
  edges_df <- merge(edges, coords, by.x = "from", by.y = "name")
  colnames(edges_df)[c(3, 4)] <- c("x_start", "y_start")
  edges_df <- merge(edges_df, coords, by.x = "to", by.y = "name")
  colnames(edges_df)[c(5, 6)] <- c("x_end", "y_end")

  ggplot2::ggplot(coords, ggplot2::aes(x = x, y = y)) +
    ggplot2::geom_segment(
      data = edges_df,
      ggplot2::aes(x = x_start, y = y_start, xend = x_end, yend = y_end),
      arrow = ggplot2::arrow(length = ggplot2::unit(0.3, "cm"), type = "closed"),
      color = "gray40",
      size = 1,
      alpha = 0.8
    ) +
    ggplot2::geom_point(
      size = 14,
      color = "white",
      fill = "#CD5C5C",
      shape = 21,
      stroke = 1.5
    ) +
    ggplot2::geom_text(
      ggplot2::aes(label = name),
      fontface = "bold",
      size = 3.5,
      color = "white"
    ) +
    ggplot2::ggtitle(title) +
    ggplot2::theme_void(base_size = 14) +
    ggplot2::theme(
      plot.title = ggplot2::element_text(
        hjust = 0.5,
        face = "bold",
        margin = ggplot2::margin(b = 10)
      )
    ) +
    ggplot2::coord_fixed()
}

data(hct_outcomes)
hct <- transform(
  hct_outcomes,
  event_death = as.integer(as.character(event_status) == "Death")
)

head(hct)
table(hct$event_status)
```

## Death-Endpoint Survival Workflow

We start with a simple survival specification where death is the binary event and the estimand is 24-month restricted mean survival time (RMST). This keeps the workflow aligned with the current survival API and with the manuscript example.

```{r dag}
coords <- data.frame(
  name = c("Covariates", "Conditioning", "Death"),
  x = c(0, -1.5, 1.5),
  y = c(1, 0, 0)
)
edges <- data.frame(
  from = c("Covariates", "Covariates", "Conditioning"),
  to = c("Conditioning", "Death", "Death")
)
plot_dag(coords, edges, title = "Death-Endpoint Survival Structure")
```

### Specification

```{r spec}
spec_hct <- causal_spec_survival(
  data = hct,
  treatment = "conditioning_intensity",
  time = "time_to_event",
  event = "event_death",
  covariates = c("age", "disease_status", "kps", "donor_type"),
  estimand = "RMST",
  horizon = 24
)

print(spec_hct)
```

### Deficiency Estimation

```{r deficiency}
results_hct <- estimate_deficiency(
  spec_hct,
  methods = c("unadjusted", "iptw"),
  n_boot = 0
)

print(results_hct)
plot(results_hct, type = "bar")
```

The PS-TV proxy is about `r formatC(results_hct$estimates["unadjusted"], digits = 3, format = "f")` before adjustment and about `r formatC(results_hct$estimates["iptw"], digits = 3, format = "f")` after IPTW. That is a meaningful reduction in the observational-to-interventional gap, but it is still large enough to remain decision-relevant on a 24-month utility scale.

In the HCT setting this matters because treatment assignment is confounded by indication: healthier patients are more likely to receive myeloablative conditioning. IPTW reduces that imbalance, but the remaining proxy shows that the observational comparison should not be treated as near-randomized.

### Regret Bounds on the Survival Scale

```{r regret}
horizon <- 24
bound <- policy_regret_bound(
  results_hct,
  utility_range = c(0, horizon),
  method = "iptw"
)

print(bound)
```

With a 24-month utility range, the current IPTW proxy implies a transfer penalty of about `r formatC(bound$transfer_penalty, digits = 2, format = "f")` months and a minimax safety floor of about `r formatC(bound$minimax_floor, digits = 2, format = "f")` months. This is a material amount of residual decision risk: even after observed confounding adjustment, the observational-to-interventional gap is not negligible relative to clinically meaningful survival differences.

### RMST Effect Estimation

```{r effect, eval = eval_surv_effect}
effect_iptw <- estimate_effect(
  results_hct,
  target_method = "iptw",
  contrast = c("Myeloablative", "Reduced")
)

print(effect_iptw)
```

```{r effect-note, results='asis', eval = !eval_surv_effect}
cat("Effect-estimation is skipped on runtimes without the required survival support. The deficiency and regret-bound calculations above still provide the main diagnostic quantities for this example.")
```

When this chunk is available, the RMST estimate should be interpreted together with the transfer penalty. A large estimated survival benefit can still be fragile if it is of the same order as the regret bound; conversely, a modest residual proxy is more reassuring when the treatment effect is much larger than the transfer penalty.

## Sensitivity Analysis via the Confounding Frontier

```{r frontier}
frontier <- confounding_frontier(
  spec_hct,
  alpha_range = c(-2, 2),
  gamma_range = c(-2, 2),
  grid_size = 30
)

print(frontier)
plot(frontier)
```

The confounding frontier maps how the deficiency proxy changes as hypothetical unmeasured confounding becomes stronger on the treatment and outcome paths. Use it as a sensitivity map, not as proof of robustness: if the plausible region of confounding strength overlaps higher-deficiency territory, the study conclusions should be reported as sensitive to hidden bias.

## Dedicated Competing-Risks Workflow

When relapse and death must be treated as distinct event types, prefer the competing-risks interface instead of collapsing them into a single binary endpoint:

```{r competing-risks, eval = FALSE}
spec_cr <- causal_spec_competing(
  data = hct_outcomes,
  treatment = "conditioning_intensity",
  time = "time_to_event",
  event = "event_status",
  covariates = c("age", "disease_status", "kps", "donor_type"),
  event_of_interest = "Relapse",
  horizon = 24
)

def_cr <- estimate_deficiency_competing(
  spec_cr,
  method = "cshr",
  n_boot = 100
)

print(def_cr)
```

This returns a competing-risks deficiency object for the selected event of interest and avoids treating competing events as ordinary censoring.

## Takeaway

For survival outcomes, `causal_spec_survival()` plus `estimate_deficiency()` gives a practical workflow for quantifying the observational-to-interventional gap on a clinically meaningful scale. For multi-state event processes such as relapse versus death, move to `causal_spec_competing()` and `estimate_deficiency_competing()` so the event structure is represented explicitly.
