---
title: "Filtering Subnetworks by Biological Context"
author: ""
date: "`r Sys.Date()`"
output:
  rmarkdown::html_vignette:
    toc: true
    toc_depth: 3
vignette: >
  %\VignetteIndexEntry{Filtering Subnetworks by Biological Context}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

## Overview

This vignette demonstrates how to use `filterSubnetworkByContext()` to filter a
protein interaction subnetwork by the contextual relevance of its supporting
literature. The function:

1. Retrieves evidence sentences from the INDRA database for each edge in the network
2. Fetches the corresponding PubMed abstracts
3. Scores each abstract against a user-supplied text query using TF-IDF cosine score
4. Returns only the nodes, edges, and evidence whose abstracts exceed a score cutoff

This is useful when a subnetwork contains many edges supported by literature from
unrelated biological contexts, and you want to focus on edges relevant to a
specific research question — in this case, **DNA damage repair in cancer**.

## Input Data

`filterSubnetworkByContext()` expects a nodes and edges dataframe, typically
produced by `getSubnetworkFromIndra()`. For this example we construct a small
representative input table directly, mimicking the structure of a proteomics
experiment centred on the DNA damage response kinase **CHK1**.

The input table contains one row per protein with columns for the UniProt
mnemonic identifier, the log2 fold-change, and the adjusted p-value from a
differential expression analysis.

```{r input-data}
input <- data.frame(
  Protein   = c("CHK1_HUMAN", "RFA1_HUMAN", "CLH1_HUMAN", "CRTC3_HUMAN"),
  log2FC    = c(2.31, 1.87, 1.45, 1.12),
  adj.pvalue = c(0.0021, 0.0089, 0.0310, 0.0490),
  stringsAsFactors = FALSE
)

input
```

```
##       Protein log2FC adj.pvalue
## 1  CHK1_HUMAN   2.31     0.0021
## 2  RFA1_HUMAN   1.87     0.0089
## 3  CLH1_HUMAN   1.45     0.0310
## 4 CRTC3_HUMAN   1.12     0.0490
```

All four proteins are up-regulated (positive log2FC) and statistically
significant (adj.pvalue < 0.05).

---

## Building the Subnetwork

### Step 1 — Annotate proteins with INDRA metadata

`annotateProteinInfoFromIndra()` maps UniProt mnemonics to HGNC gene identifiers
and other metadata used downstream by the INDRA query engine.

```{r annotate}
library(MSstatsBioNet)
annotated_df <- annotateProteinInfoFromIndra(input, "Uniprot_Mnemonic")
```

### Step 2 — Retrieve the interaction subnetwork

`getSubnetworkFromIndra()` queries the INDRA database for curated causal
interactions among the annotated proteins and returns a list containing `$nodes`
and `$edges` dataframes.

Key parameters used here:

- **`pvalueCutoff = 0.2`** — relaxed threshold to retain more candidate edges for
  downstream context filtering
- **`evidence_count_cutoff = 1`** — keep edges supported by at least one
  literature statement
- **`force_include_other = "HGNC:1925"`** — always include CHK1 (HGNC:1925)
  regardless of significance, as it is the focal protein of interest
- **`filter_by_curation = FALSE`** — include both curated and automatically
  extracted interactions

```{r subnetwork}
subnetwork <- getSubnetworkFromIndra(
  annotated_df,
  pvalueCutoff          = 0.2,
  logfc_cutoff          = NULL,
  evidence_count_cutoff = 1,
  sources_filter        = NULL,
  force_include_other   = "HGNC:1925",
  filter_by_curation    = FALSE
)

# Inspect the unfiltered network
nrow(subnetwork$nodes)
nrow(subnetwork$edges)
```

---

## Filtering by Context: Tag Count

### Defining the Query

The query string is compared against each PubMed abstract supporting the network
edges. A richer query — one that includes synonyms, abbreviations, and related
terms — improves recall under TF-IDF, which relies on exact token matching rather
than semantic understanding.

The expanded query below was produced with the help of a chatbot and
covers the major vocabulary used in the DNA damage repair and cancer literature.

```{r tags}
tags <- c(
  "dna damage repair",
  "cancer",
  "oncology",
  "dna repair",
  "genome integrity",
  "genomic instability",
  "double strand_break",
  "dsb",
  "single strand_break",
  "ssb",
  "base excision repair",
  "ber",
  "nucleotide excision repair",
  "ner",
  "mismatch repair",
  "mmr",
  "homologous recombination",
  "hr",
  "non homologous end joining",
  "nhej",
  "brca1",
  "brca2",
  "atm",
  "atr",
  "p53",
  "tp53",
  "parp",
  "tumor suppressor",
  "oncogene",
  "carcinogenesis",
  "tumorigenesis",
  "chemotherapy resistance",
  "radiation resistance",
  "genotoxic stress",
  "replication stress",
  "oxidative dna_damage",
  "somatic mutation",
  "tumor mutational burden",
  "tmb"
)
```

> **Tip:** You can iteratively refine `tags` by inspecting the
> scores in `filtered_network$evidence` and adding terms that appear frequently
> in high-scoring abstracts but are absent from your query.

`filterSubnetworkByContext()` ties everything together. The
`cutoff` parameter controls stringency — only edges whose supporting
abstracts score at or above this value are retained.

```{r filter}
filtered_network <- filterSubnetworkByContext(
  nodes             = subnetwork$nodes,
  edges             = subnetwork$edges,
  method            = "tag_count",
  cutoff = 3,
  query             = tags
)
```

The function prints a progress summary to the console:

```
Processing N unique statement hashes...
Fetching M abstracts...
Progress: M/M (100.0%)
Done fetching abstracts!

X / M abstracts passed score cutoff (>= 0.10)
Retained: A edges (of B), C nodes (of D), E evidence rows (of F)
```


### Filtered nodes

```{r nodes}
filtered_network$nodes
```

Only proteins connected by at least one contextually relevant edge are retained.

### Filtered edges

```{r edges}
filtered_network$edges
```

Each row represents a causal interaction (e.g. phosphorylation, activation)
supported by literature that passed the score threshold.

### Evidence with scores

```{r evidence}
filtered_network$evidence
```

The evidence dataframe contains the following columns:

| Column | Description |
|---|---|
| `source` | Source protein / gene |
| `target` | Target protein / gene |
| `interaction` | Interaction type (e.g. Phosphorylation) |
| `site` | Modification site if applicable |
| `evidenceLink` | URL to the INDRA evidence viewer |
| `stmt_hash` | Unique INDRA statement identifier |
| `text` | Sentence extracted from the supporting paper |
| `pmid` | PubMed ID of the source article |
| `score` | Cosine score of the abstract vs. query |

You can sort by score to identify the most on-topic supporting evidence:

```{r sort-evidence}
filtered_network$evidence[
  order(filtered_network$evidence$score, decreasing = TRUE), 
]
```

### Defining a cutoff

```{r explore-cutoff}
# Run with permissive cutoff to see full score distribution
exploratory <- filterSubnetworkByContext(
  nodes             = subnetwork$nodes,
  edges             = subnetwork$edges,
  cutoff = 0.0,
  query             = tags
)

summary(exploratory$evidence$score)
hist(exploratory$evidence$score,
     breaks = 30,
     main   = "Distribution of abstract scores",
     xlab   = "Number of tags matched",
     col    = "steelblue")
```

---

## Filtering by Context: Cosine score

### Defining the Query

The query string is compared against each PubMed abstract supporting the network
edges. A richer query — one that includes synonyms, abbreviations, and related
terms — improves recall under TF-IDF, which relies on exact token matching rather
than semantic understanding.

The expanded query below was produced with the help of a chatbot and
covers the major vocabulary used in the DNA damage repair and cancer literature.

```{r query}
my_query <- "DNA damage repair cancer oncology DNA repair genome integrity
  genomic instability double strand break DSB single strand break SSB
  base excision repair BER nucleotide excision repair NER mismatch repair MMR
  homologous recombination HR non-homologous end joining NHEJ BRCA1 BRCA2
  ATM ATR p53 TP53 PARP tumor suppressor oncogene carcinogenesis tumorigenesis
  chemotherapy resistance radiation resistance genotoxic stress replication stress
  oxidative DNA damage somatic mutation tumor mutational burden TMB"
```

> **Tip:** You can iteratively refine `my_query` by inspecting the
> scores in `filtered_network$evidence` and adding terms that appear frequently
> in high-scoring abstracts but are absent from your query.

`filterSubnetworkByContext()` ties everything together. The
`cutoff` parameter controls stringency — only edges whose supporting
abstracts score at or above this value are retained.

```{r}
filtered_network <- filterSubnetworkByContext(
  nodes             = subnetwork$nodes,
  edges             = subnetwork$edges,
  method            = "cosine",
  cutoff = 0.10,
  query             = my_query
)
```

The function prints a progress summary to the console:

```
Processing N unique statement hashes...
Fetching M abstracts...
Progress: M/M (100.0%)
Done fetching abstracts!

X / M abstracts passed score cutoff (>= 0.10)
Retained: A edges (of B), C nodes (of D), E evidence rows (of F)
```


### Filtered nodes

```{r}
filtered_network$nodes
```

Only proteins connected by at least one contextually relevant edge are retained.

### Filtered edges

```{r}
filtered_network$edges
```

Each row represents a causal interaction (e.g. phosphorylation, activation)
supported by literature that passed the score threshold.

### Evidence with scores

```{r}
filtered_network$evidence
```

The evidence dataframe contains the following columns:

| Column | Description |
|---|---|
| `source` | Source protein / gene |
| `target` | Target protein / gene |
| `interaction` | Interaction type (e.g. Phosphorylation) |
| `site` | Modification site if applicable |
| `evidenceLink` | URL to the INDRA evidence viewer |
| `stmt_hash` | Unique INDRA statement identifier |
| `text` | Sentence extracted from the supporting paper |
| `pmid` | PubMed ID of the source article |
| `score` | Relevance score (tag count or cosine similarity) |

You can sort by score to identify the most on-topic supporting evidence:

```{r}
filtered_network$evidence[
  order(filtered_network$evidence$score, decreasing = TRUE), 
]
```

### Choosing a score Cutoff

The right cutoff depends on how broadly the query overlaps with the literature
in your network. As a rough guide:

| Cutoff | Effect |
|---|---|
| `0.05` | Permissive — removes only completely off-topic abstracts |
| `0.10` | Recommended default for domain-specific queries |
| `0.20` | Stringent — retains only highly on-topic edges |
| `> 0.30` | Very stringent — use only with highly specific queries |

To explore the score distribution before committing to a cutoff, run the
function at a low threshold and inspect the scores:

```{r}
# Run with permissive cutoff to see full score distribution
exploratory <- filterSubnetworkByContext(
  nodes             = subnetwork$nodes,
  edges             = subnetwork$edges,
  cutoff = 0.0,
  method = "cosine",
  query             = my_query
)

summary(exploratory$evidence$score)
hist(exploratory$evidence$score,
     breaks = 30,
     main   = "Distribution of abstract scores",
     xlab   = "Cosine score to query",
     col    = "steelblue")
```

---

## Session Info

```{r session-info, eval=TRUE}
sessionInfo()
```
