---
title: "6. Case study: small cohorts"
author: "Abdelkarim Hani Ghrieb"
date: "`r Sys.Date()`"
output:
  BiocStyle::html_document:
    toc: true
    toc_float: true
vignette: >
  %\VignetteIndexEntry{6. Case study: small cohorts}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

# Status note

This is a synthetic-data walkthrough demonstrating how the
`events_per_parameter` guardrail and fold-level reporting expose unstable
signatures. It is a vignette, not a study. The narrative follows the
*three-tier* structure used across the case studies: a **naive analysis**
first, then **rnaSentry standing guard**, then the **counterfactual**.

# Setup

We build a 30-sample cohort with 23 events and a real (if weak) survival
signal driven by five genes, then ask for a 5-gene signature. This is 4.6
events per parameter -- below the guardrail of 5.

```{r build-signature, warning = TRUE}
library(rnaSentry)
library(SummarizedExperiment)

set.seed(30)
n_sm <- 30
counts <- matrix(rpois(60 * n_sm, lambda = 500), nrow = 60, ncol = n_sm,
                 dimnames = list(paste0("gene", 1:60), paste0("S", 1:n_sm)))
sig_expr <- colMeans(counts[1:5, , drop = FALSE])
risk <- scale(sig_expr)[, 1] * 0.5
event_time <- rexp(n_sm, rate = 0.05 * exp(0.9 * risk))
censor_time <- rexp(n_sm, rate = 0.02)
cd <- S4Vectors::DataFrame(time = pmin(event_time, censor_time),
                           event = as.integer(event_time < censor_time),
                           row.names = colnames(counts))
se <- SummarizedExperiment(assays = list(counts = counts), colData = cd)

sig <- build_signature(se, "time", "event", top_n = 5,
                       repeats = 5, folds = 5, seed = 30)
```

# Tier 1: the naive analysis

A standard analysis reports the single number the summary returns and stops
there:

```{r naive-summary}
sig$cv_summary
```

The mean cross-validated concordance is 0.74 -- which a careless reader would
call "decent." A naive manuscript would report *"cross-validated C = 0.74"*
as evidence that the signature is prognostic, with no sense of how much that
single mean is hiding.

# Tier 2: rnaSentry standing guard

## The guardrail fires

```{r guardrail-flags}
sig$flags
```

The `events_per_parameter` warning fires immediately -- 23 events for 5 genes
is 4.6 per parameter, below the guardrail of 5 -- and is recorded in the
stage's flag ledger so it propagates into the rendered report.

## The mean hides the spread

```{r per-fold-results}
sig$cv_results
```

The per-fold concordances range from **0.50 to 1.00** across repeats and
folds. With only a handful of held-out samples per fold, single-fold C
estimates are nearly coin flips, and the standard deviation (0.14) is
enormous relative to the mean. A signature like this is unstable and should
be treated as hypothesis-generating only.

```{r small-fold-spread, fig.width = 7, fig.height = 5, fig.align = "center", fig.cap = "Per-fold cross-validated concordance across repeats and folds (one point per fold). The vertical dashed line marks the mean; the spread from 0.50 to 1.00 is the instability the mean hides."}
set.seed(30)
cv <- sig$cv_results
stripchart(cv$c_index, method = "jitter", jitter = 0.12, pch = 19, cex = 1.1,
           ylim = c(0, 1.05), ylab = "Cross-validated C-index (per fold)",
           main = sprintf("Per-fold C-index: mean %.2f, sd %.2f",
                          mean(cv$c_index), stats::sd(cv$c_index)))
abline(v = mean(cv$c_index), lty = 2, col = "grey40")
```

# Tier 3: the counterfactual

| | Naive analysis (Tier 1) | rnaSentry (Tier 2) |
|---|---|---|
| Reported statistic | mean CV C = 0.74, quoted alone | mean + fold-level distribution (range 0.50-1.00, sd 0.14) |
| Events per parameter | not computed | 4.6 < 5, `events_per_parameter` warning in the ledger |
| Scientific status | "a decent prognostic signature" | "hypothesis-generating only; unstable" |

**What a guarded analysis can claim:** the exact same cross-validated
concordance that a naive analysis would headline as 0.74 is shown by
`build_signature()` to be an average over near-coin-flip folds -- and the
guardrail warns rather than silently proceeding. This is the documented
"small-cohort trap," quantified in our simulation analysis
(`validation/simulate_study.R`): with fewer than ~5 events per signature
gene, neither the concordance nor the hazard ratios are trustworthy, and a
*mean* C-index hides the instability that the fold-level table exposes.

# Reading

The takeaway is exactly the documented "small-cohort trap": with fewer than
~5 events per signature gene, neither the concordance nor the hazard ratios
are trustworthy. This is why `build_signature()` reports the per-fold
distribution, not just the mean, and why the guardrail warns rather than
silently proceeding.

# Session info

```{r session-info}
sessionInfo()
```
