---
title: "diagFDR quick start: scope, calibration, stability and entrapment on a simulated list"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{diagFDR quick start}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4)
```

This vignette runs end to end without external data. It uses the ground-truth
simulator of the diagFDR paper (Supplementary Note S6) so that every
diagnostic can be compared with the *realised* false discovery proportion,
which is known exactly in simulation and unknown on real data.

```{r}
library(diagFDR)
sim <- sim_tdc_example          # 20,000 units, Gaussian nulls, correct specification
sim
```

## 1. Headline support and the FDP-CV benchmark

An FDR claim at a threshold $\alpha$ should come with the accepted target and
decoy counts, $T_\alpha$ and $D_\alpha$, the recomputed decoy ratio
$D_\alpha/T_\alpha$, the decoy-support index $D_\alpha^{-1/2}$ and the FDP-CV
benchmark $\sqrt2\,D_\alpha^{-1/2}$ (Proposition 1).

```{r}
h <- dfdr_headline(sim, alpha = 0.01)
h[, c("alpha", "T_alpha", "D_alpha", "FDR_hat", "support_index", "CV_benchmark")]
```

In simulation the realised FDP is known:

```{r}
dfdr_realised_fdp(sim, alpha = c(0.05, 0.01, 0.002))
```

The benchmark says how much the realised FDP is expected to vary *across
experiments* with this decoy support. `dfdr_check_cv_benchmark()` checks it by
Monte Carlo (the paper uses 1000 replicates of $2\times10^5$ units; here a
small run suffices):

```{r}
dfdr_check_cv_benchmark(B = 30, n = 20000, alpha = 0.01)[, c("alpha", "CV_FDP", "mean_D", "kappa")]
```

`kappa` is the ratio of the observed CV to $D_\alpha^{-1/2}$; Proposition 1
predicts $\sqrt2\approx1.41$ under independence.

## 2. Stability across thresholds

```{r}
alphas <- c(0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05)
dfdr_curve_stability(sim, alphas = alphas)[, c("alpha", "T_alpha", "D_alpha", "support_index")]
dfdr_local_tail(sim, alphas = alphas, win_rel = 0.2)[, c("alpha", "D_alpha_win")]
dfdr_elasticity(sim, alphas = alphas, eps = 0.2)[, c("alpha", "jaccard")]
```

## 3. Calibration: PEP concordance and equal chance

The simulated list carries the true PEP of every target, so the IPE should
be close to zero under the target--decoy comparator $D_b/T_b$:

```{r}
dfdr_ipe(sim)$IPE
dfdr_equal_chance_qbands(sim)$pooled
```

## 4. What a misspecified decoy model looks like

Decoys that are under-dispersed (scenario S2 of the paper) inflate the realised
FDP while the reported decoy ratio stays at $\alpha$:

```{r}
bad <- dfdr_simulate_tdc(n = 20000, decoy_scale = 0.85, seed = 2)
dfdr_realised_fdp(bad, alpha = 0.01)
```

This is why external validation (entrapment) is a distinct requirement.

## 5. Label propagation at protein-group level

When several exported rows collapse into one reported unit and the rows do
not all carry the same target/decoy label, the rule that assigns the unit's
label must be stated. The four rules are compared by
`dfdr_label_rule_audit()`:

```{r}
set.seed(1)
rep <- data.frame(Protein.Group = rep(paste0("P", 1:2000), each = 3),
                  Decoy = rbinom(6000, 1, 0.12),
                  Global.PG.Q.Value = c(runif(6000, 0, 0.3)))
label_is_intrinsic(rep, "Protein.Group")
dfdr_label_rule_audit(rep, "Protein.Group", "Global.PG.Q.Value", alpha = 0.05)
```

Picked-protein competition is the procedural alternative that needs no rule:

```{r}
pg <- diann_global_proteingroup(rep, rule = "split", two_pass = FALSE)
pk <- dfdr_picked_protein(pg, alpha = 0.05)
c(T = pk$T_alpha, D = pk$D_alpha, ratio = pk$decoy_ratio, pairs = pk$n_pairs)
```

## 6. External validation by entrapment

Entrapment matches are accepted identifications known to be false because
they map only to a proteome absent from the sample. On a real DIA-NN
entrapment search the ids come from `diann_entrapment_ids(rep, accessions)`
with the accessions read by `read_fasta_accessions()`; here we label some
incorrect targets as entrapment to show the output.

```{r}
truth <- attr(sim, "truth")
ent_ids <- sim$id[!sim$is_decoy & truth$is_null & runif(nrow(sim)) < 0.3]
dfdr_entrapment(sim, ent_ids, alpha = c(0.05, 0.01), r = 0.43)
```

`fdp_raw` is $E_\alpha/T_\alpha$ with a Wilson interval; `fdp_corrected` is
the size-corrected estimate of Wen et al. (2025), which needs the
entrapment-to-target size ratio `r` (`dfdr_entrapment_size_ratio()` computes
it from the FASTA files at protein and peptide level).

Two scope choices that overlap almost entirely can still differ sharply where
they disagree. `dfdr_entrapment_discordant()` computes the entrapment rate on
the shared core and on the discordant set:

```{r}
a1 <- dfdr_accepted(sim, 0.01)
a2 <- dfdr_accepted(sim, 0.03)      # a looser list standing in for a mis-scoped one
d  <- dfdr_entrapment_discordant(a1, a2, ent_ids)
d
attr(d, "ratio"); attr(d, "jaccard")
```

## 7. Report

`dfdr_run_all()` runs the standard suite on a named list of `dfdr_tbl`
objects and `dfdr_render_report()` writes the HTML report with alerts tagged
by their evidential basis; see the DIA-NN, Spectronaut, MaxQuant, mokapot and
mzIdentML vignettes for the adapters.
