Structure-aware Graph Learning Predicts RNA Editability Across Tissues and Species
This repository, together with the linked external datasets, contains the model code, processed data splits, trained best checkpoints, analysis inputs and outputs, and one self-documenting folder per analysis. All reported numerical results are linked to supplied artifacts and reproducible analysis workflows; external or controlled-access inputs needed for upstream reconstruction are identified explicitly.
The central protocol. All results use a strict global pair-disjoint split: every Alu inverted-repeat pair — and every adenosine site derived from it — is assigned to exactly one of train / validation / test (64:16:20), shared across all five tissues, with zero pair overlap between partitions. Class balancing is performed before partitioning; after the split is fixed, the test partition is not used for model, epoch or threshold selection.
Every model in this repository is one of two graph neural networks over the predicted dsRNA secondary structure. They appear throughout as Baseline GAT and Bio-aware GNN.
- Baseline GAT (
code/gnnadar_verb_compact.py) — a compact graph attention network. Each nucleotide is a node with 8 features (one-hot base A/G/C/U/N, a paired/unpaired flag, relative distance to the target adenosine, and a target-site flag); edges connect sequential (backbone) neighbours and base-paired nucleotides, and carry no features — the model receives only graph topology and must learn structural editing rules from data alone. - Bio-aware GNN (
code/bioaware_gnn.py) — the same graph enriched with explicit biochemistry: 22-dimensional node features (adds 5′/3′ neighbour identity, stem/loop geometry, and base-pairing energy), typed edges that distinguish Watson–Crick (A-U, G-C) from wobble (G-U) and backbone edges (each with a learned embedding and scalar attributes), and a parallel sequence-CNN branch capturing local motifs. It tests whether hand-provided biological annotation improves prediction beyond the minimal baseline.
The joint multi-label model (one network predicting all five tissues at once)
comes in the same two flavours — Joint Baseline and Joint Bio-aware —
using these same two encoders (see analyses/joint_model/).
conda env create -f environment.yml # creates the 'adaredit' env (Python 3.10)
conda activate adaredit
# or manually:
pip install torch==2.7.0 torch-geometric==2.6.1 scikit-learn==1.6.1 \
numpy==2.2.5 pandas==2.2.3 matplotlib \
networkx shap seaborn pysam scipy xgboost
# ViennaRNA (RNAfold) is only needed to regenerate secondary structures from scratch:
# conda install -c bioconda viennarnaA GPU is recommended for (re)training; all evaluation, the classical baselines and every figure script run on CPU.
ADAREDIT_repro/
├── README.md ← this file
├── environment.yml ← conda environment
├── code/ ← model + trainer (self-contained)
│ ├── train_strict_long.py ← exact per-context training pipeline used for the supplied checkpoints
│ ├── gnnadar_verb_compact.py ← exact Baseline GAT implementation used for training
│ ├── bioaware_gnn.py ← exact Bio-aware GNN implementation used for training
│ └── rna_attention_analysis.py ← attention extraction utilities
├── data_construction/ ← how data/ was built, + split verifier (see §3.1)
│ ├── human_alu/ ← Alu substrates from hg38 + GTEx editing levels
│ ├── species/ ← non-Alu substrates for the three species (7 steps)
│ ├── split/ ← the global pair-disjoint 64:16:20 protocol
│ └── verify_split.py ← verifies pair/site disjointness across splits
├── data/
│ ├── human/{Artery,Brain,Liver,MuscleSkeletal,Combined}/{train,valid,test}.jsonl (+ .metadata.csv)
│ ├── species/{Octopus,Ptychodera,Strongylocentrotus}/{train,valid,test}.jsonl
│ └── raw/
│ ├── alu_pairs/Pair_Alu_withStrand.bed ← 905 Alu-pair duplex definitions (hg38)
│ └── editing_levels/ ← GTEx per-site editing tables — HOSTED ON GOOGLE DRIVE
│ (see §3; too large to commit, ~700 MB)
├── checkpoints/ ← BEST checkpoint + summary.json per model (see §7)
├── results/ ← long-format results, manuscript table, matrices and predictions
├── analyses/ ← one self-contained folder per analysis (see §6)
│ ├── threshold_relaxation/ ├── substrate_stability/
│ ├── gtex_tissue_selection/
│ ├── rnaatlas_external_cohort/ ├── component_ablation/
│ ├── snp_audit/ ├── triplet_baseline/
│ ├── species_sensitivity/ ├── coding_targets/
│ ├── attention_interpretability/ ├── insilico_mutagenesis/
│ ├── joint_model/ ├── joint_vs_pertissue/
│ └── minor_training_dynamics/
└── manuscript/ ← the figures that appear in the paper (main + supplementary)
Each analyses/<name>/ folder holds scripts/ (code), data/ and/or raw_data/ (inputs and
derived tables), figures/ (outputs) and usually its own README.md.
Processed data (in this repo). Each data/**/*.jsonl line is one candidate adenosine as a
chat-style record. The user message encodes L:<left seq>, A:<target base>, R:<right seq>, Alu Vienna Structure:<RNAfold dot-bracket>; the assistant message is yes/no (edited ≥15% /
non-edited <1%; intermediate 1–15% excluded). The full substrate is L + A + R and its length
equals the dot-bracket length.
Human *.metadata.csv files carry one row per site, aligned to the .jsonl, with
columns pair_id, pair_prefix, yes_no, split. pair_id is the substrate key — one
integer per distinct L + A + R duplex, shared across tissues, so a substrate has the same
id wherever it appears. Use it for any disjointness or grouping check. pair_prefix is the
first 24 nt of the duplex, useful for eyeballing but not unique: Alu elements share a
conserved 5' end, so 55 of the 884 substrates collide on it.
Species *.metadata.csv files are likewise aligned row-for-row to their JSONL files and
record the species, genomic region_id, source row, label, split, genomic coordinate,
strand, local position, editing level and source table. Use region_id to verify that
complete genomic regions remain disjoint across train, validation and test.
Pair_Alu_withStrand.bed contains 905 candidate inverted Alu pair loci. After
coverage/label selection and removal of 156 terminal-site records whose
serialized L+A+R length did not match the dot-bracket length,
884 graph-buildable full substrate structures remain. These are assigned stable
pair_id values 1..884 in all shipped human metadata files.
Test-split unique-site counts: Artery 4,792 · Brain 4,566 · Liver 4,150 ·
Muscle Skeletal 1,425 · Combined 4,864. All 884 retained substrates are
graph-buildable (dot-bracket length equals sequence length), and the global
pair-disjoint split assignment in the shipped metadata and
data_construction/split/global_pair_split.json contains exactly these 884
(566 train / 139 valid / 179 test).
Raw data.
data/raw/alu_pairs/Pair_Alu_withStrand.bed— the 905 Alu-pair duplex loci (hg38, stranded).- GTEx per-site editing tables
{Tissue}_Site_in_PairAlu_cov100.csv(raw editing level and coverage for every candidate adenosine, the source from which the binary labels were derived). These are ~700 MB and are hosted on Google Drive: https://drive.google.com/drive/folders/1KkGElOF-Peg0xzJWehONI5JfRKEAAdT_?usp=drive_link Download them intodata/raw/editing_levels/to rebuild the.jsonlfrom scratch. The Combined dataset was derived from the GTEx-wide table pooling data across all 47 tissues; its precomputed per-site levels are provided inCombined_Site_in_PairAlu_cov100.csv. - Analysis-specific raw inputs live inside each analysis folder's
raw_data/(e.g. the RNAAtlas editing table, the donor-SNP overlap table) — see §6.
Controlled-access / external data (NOT redistributed here).
- GTEx v8 donor genotype VCF
GTEx_..._838Indiv_..._SHAPEIT2_phased.vcf.gz(dbGaP/AnVIL) — used by the SNP donor cascade; (https://drive.google.com/file/d/1jVDpQ_AJ_X55TkCkZ4P5Z72uJ1FPuGPL/view?usp=drive_link) - hg38 reference for coordinate/strand checks.
- UCSC Common SNPs 150 track (public; ~143 MB) — the duplex SNP-burden script
(
analyses/snp_audit/scripts/duplex_snp_burden.py) reads it fromdata/raw/dbsnp/ucscHg38CommonGenomicSNPs150.bed.gz; download the track from the UCSC Genome Browser (hg38snp150Common) into that folder to regenerate the panel-a numbers.
The upstream pipeline is included so the benchmark is auditable end to end
(data_construction/README.md has the full step-by-step):
| stage | folder | what it produces |
|---|---|---|
| Human Alu substrates | data_construction/human_alu/ |
labels the five downloaded per-site tables (yes ≥15 %, no <1 %, intermediate excluded), verifies the exact balanced pre-split site selection using the shipped manifest, and reports/removes 156 non-buildable terminal-site serializations |
| Cross-species substrates | data_construction/species/ |
seven steps from the Zhang et al. editing tables: parse → cluster (1 kb, >5 sites) → fold and pick the dsRNA segment → merge levels → filter (len ≥200, cov ≥100) → label and balance → region-disjoint split |
| Pair-disjoint split | data_construction/split/ |
applies the exact shipped pair_id map to whole Alu substrates at 64:16:20, one assignment reused across all five contexts |
The cross-species stages, including the final region-disjoint 64:16:20 split, can be run with one command:
bash data_construction/run_species_construction.sh \
data_construction/species_manifest.csv \
/tmp/adaredit_species_reconstructed \
--num-processes 8 --merge-workers 8Copy data_construction/species_manifest.example.csv and replace the editing
table and genome paths. The complete instructions, outputs and focal-adenosine
safety check are documented in data_construction/README.md. This workflow
also requires bpRNA.pl; provide it through PATH, BPRNA_PL, or the
--bprna option as described there.
To reconstruct the human benchmark after downloading the five tables:
bash data_construction/run_human_construction.sh \
data/raw/editing_levels \
/tmp/adaredit_human_reconstructedThe command writes only to the requested output and temporary directories. It
verifies the exact published site selection against the downloaded raw tables,
applies the immutable global pair assignment, compares every reconstructed
partition with data/human/, and then runs the split-overlap verifier.
Verify the split yourself — no external input needed:
python data_construction/verify_split.pyIt asserts, directly from the shipped JSONL: 884 substrates with 0 appearing in more than one split; 0 cross-tissue site overlap across all 25 ordered tissue pairs (no site in one tissue's train/valid is in another tissue's test); and 64.0/15.7/20.2 % partition proportions. It also reports the per-split positive rate (45.6–56.5 %), which varies because balancing was applied to each dataset as a whole before splitting.
Split provenance. The exact split assignment is shipped as data — the split column
of every data/human/<tissue>/*.metadata.csv and
data_construction/split/global_pair_split.json — so the partition behind every
number in the paper is available directly.
split/build_human_global_split.py applies this exact map to reconstructed
balanced pools. split/build_global_split.py can create an alternative
protocol-equivalent random assignment, but it must not replace the manuscript
map. verify_split.py confirms that the shipped partition satisfies the
whole-pair, cross-context disjointness claims.
# One model (for example, Liver bio-aware). The output directory contains
# checkpoints/, history.csv, summary.json and held-out test predictions.
python code/train_strict_long.py \
--variant bioaware \
--context Liver \
--data-root data/human \
--cache-root cache/single \
--out-root runs \
--epochs 1000 \
--batch-size 256 \
--num-workers 0 \
--num-threads 8 \
--checkpoint-every 100 \
--seed 42 \
--resumeUse --variant baseline|bioaware; for species, replace --data-root data/human with --data-root data/species and set --context to the species
directory name. A CUDA device is required by default; pass --allow-cpu only
for a CPU smoke test or an intentionally slow CPU training run.
This is the same validation-selected training implementation used to produce
the supplied per-context checkpoints. It records the input hashes, environment,
graph version and RNG state; writes checkpoints atomically; and refuses to
resume from an incompatible graph version. The default --grad-clip 0.0
matches the reported training recipe. The test split is evaluated only after
the requested final epoch, never for checkpoint or threshold selection.
To score with a provided checkpoint instead of retraining, load
checkpoints/<model>/best.pth (state under model_state). Each
checkpoints/<model>/summary.json holds the held-out test_metrics reported
in the paper.
Within-tissue (diagonal), Baseline GAT / Bio-aware GNN:
| Tissue | Base F1 | Base AUROC | Bio F1 | Bio AUROC |
|---|---|---|---|---|
| Artery | 0.867 | 0.924 | 0.858 | 0.918 |
| Brain | 0.869 | 0.930 | 0.863 | 0.927 |
| Liver | 0.854 | 0.909 | 0.854 | 0.910 |
| Muscle Skeletal | 0.814 | 0.869 | 0.816 | 0.878 |
| Combined | 0.867 | 0.933 | 0.836 | 0.903 |
Joint multi-label model (one model, all five tissue outputs): bio-aware
macro F1 0.855 / AUROC 0.917, baseline 0.851 / 0.911. The no-Combined
control is documented in analyses/joint_model/. Triplet-SVM baseline (Liver):
F1 0.785–0.793 / AUROC 0.784–0.787 across its three classifier heads.
Every folder is self-contained. Below: what it does, what to run, and what to expect. Each script
resolves its paths relative to its own location (Path(__file__).resolve().parents[...]), so the
analyses run in place with no path edits; controlled-access external inputs (donor VCF, hg38, UCSC
dbSNP, the large editing tables) are marked and documented in §3.
- Run: from the repository root,
bash analyses/threshold_relaxation/run_all.sh. - Inputs: the five GTEx per-site editing tables under
data/raw/editing_levels/, the authoritative human split, and the per-context checkpoints plus their canonical test predictions. The workflow derives the complete 1–15% held-out cohorts directly from these inputs. - Outputs:
data/run0_distribution.csv,run1_score_by_bin.csv,run2_threshold_auroc.csv;data/analysis_metadata.json,figures/threshold_relaxation.*,figures/intermediate_site_scores.*, andmanuscript/figS1_combined.png. - Validation: all 30,488 context-specific intermediate records must map to held-out substrates; all 60,976 model-site scores must align with their editing levels; local inference must reproduce canonical checkpoint predictions; and the ≥15% column must exactly equal each within-context test AUROC.
- Run:
bash analyses/gtex_tissue_selection/run_all.sh. - Input: a frozen 54-tissue snapshot of GTEx v8 median TPM for
ADAR(ADAR1) andADARB1(ADAR2), with API endpoint and Ensembl identifiers documented in the analysis README. - Outputs:
figures/gtex_tissues.png/pdfandmanuscript/gtex_tissues.png. - Validation: verifies the four plotted values against the frozen table,
that Artery Tibial has the highest
ADARB1across all GTEx v8 tissues, and that Muscle Skeletal has the lowestADAR.
- Run:
bash analyses/rnaatlas_external_cohort/run_all.sh. - Input (raw):
raw_data/Combined_GTEx_RNAatlas.csv— GTEx vs RNAAtlas editing index per site. - Outputs:
figures/rnaatlas_concordance.png/pdf. - Expect: Pearson r = 0.967, Spearman ρ = 0.932 and n = 16,028 co-measured sites. Of these, 1,678 fall in the intermediate RNAAtlas range; no site changes from GTEx non-edited to RNAAtlas edited, and one changes from GTEx edited to RNAAtlas non-edited.
- Run (figure from shipped summaries):
bash analyses/snp_audit/run_all.sh. - Optional upstream panel-a recomputation:
python scripts/duplex_snp_burden.py→data/duplex_snp_burden_summary.json(requires the public UCSC dbSNP track). - Panel b (donor cascade): computed against the controlled-access GTEx v8 donor VCF; the derived
summary tables are provided in
donor_gtex_analysis/(see its README for the numbers). - Figure:
python scripts/make_snp_figure.py. - Input (raw):
raw_data/gtex_empirical_snp_site_overlaps.csv(100,377 site×donor overlaps). - Expect: ~3.5 common SNPs per ~586-nt duplex, 92.5% carry ≥1; only 0.151% of labelled sites carry a strand-aware editing-mimicking donor variant (0.041% at AF≥1%, 0.030% at AF≥5%).
- Run:
bash analyses/species_sensitivity/run_all.sh. - Inputs:
data/species/*,data/species_prebalancing/*.csv.gz, and the within-species checkpoints and test predictions. - Outputs:
data/sensitivity.json;figures/species_sensitivity.*;raw_data/species_inter_site_distances.json; andmanuscript/species_sensitivity.png. - Expect: excluding operational negatives near benchmark-positive sites raises F1 (octopus baseline 0.809→0.835; bio-aware 0.831→0.864), while AUROC remains stable or increases modestly, supporting robustness to alternative minimum-distance criteria.
- Run:
bash analyses/substrate_stability/run_all.sh. - Inputs:
data/human/*+ checkpoints;raw_data/context_folding_results.csv,raw_data/Pair_Alu_withStrand.bed. - Outputs:
data/stability_performance.json;figures/substrate_stability.png/pdf;manuscript/fig_stability.png. - Expect: paired-fraction Q1/median/Q3 = 0.776/0.812/0.844 over 884 substrates; genomic-context dsRNA fraction median 0.810 with 94.6% ≥0.5; least-stable-quartile AUROC ≈0.88–0.90 in most contexts (Muscle Skeletal: 0.826–0.839).
- Run from shipped outputs:
bash analyses/component_ablation/run_all.sh. - Retrain on GPU:
bash analyses/component_ablation/scripts/run_ablations.sh. - Inputs:
data/human/Liver;checkpoints/bioaware_Liver/summary.json;checkpoints/ablations/Liver/*/summary.json. - Outputs:
ablation_summary.{csv,json};figures/component_ablation.*;manuscript/figS_ablation.png. - Expect (ΔAUROC vs full 0.9100): neighbouring-base context −0.0540, pairing partner −0.0241, base-pair edges −0.0188, edge typing −0.0018, sequence-CNN −0.0054, and stem-loop geometry +0.0118. These are descriptive single-seed comparisons of separately trained models.
- Run:
bash analyses/triplet_baseline/scripts/run_all.sh Liver(builds metadata and trains all three heads). - Input:
data/human/Liver. - Outputs:
results/Liver_{logreg,linear_svm,rbf_svm}.json(+ test predictions). - Expect: F1 0.785–0.793 / AUROC 0.784–0.787 — below the graph models (AUROC 0.909–0.910).
- Run:
bash analyses/coding_targets/run_all.sh. - Inputs: seven sequence/structure records and their Brain, Combined and Liver editing-level tables; six per-tissue checkpoints and two joint checkpoints.
- Outputs: 84 per-adenosine prediction files;
results/auroc_summary.{csv,json}; joint-only and four-model AUROC panels; the FLNA structure panel; andfigures/coding_targets_full.{png,pdf}. - Expect: Joint Bio-aware AUROC is 0.86/0.83/0.85 for AJUBA, 0.92/1.00/0.98 for BLCAP, 1.00/1.00/0.96 for FLNA, and 0.90/0.91/0.93 for GRIA2 (Brain/Combined/Liver). The sparse NEIL1 and TTYH2 cells remain descriptive. Interpret these per-gene AUROCs as a scope/generalization check rather than a definitive coding-target benchmark.
- Environment: this analysis uses its own pinned software environment. From
the repository root, create and activate it once with
conda env create -f analyses/attention_interpretability/environment.ymlfollowed byconda activate adaredit-attention. Use this environment rather than the repository-level environment for the attention workflow. - Run: after activating
adaredit-attention, executebash analyses/attention_interpretability/run_all.sh. - Protocol: extracts last-layer attention from the frozen Baseline Combined GAT across positions -50 to +50; fits and SHAP-ranks XGBoost probes on the validation split; evaluates them on the duplex-disjoint test split; and summarizes attention by editing label, nucleotide, and structural status across all test sites.
- Inputs: the checkpoint and Combined validation/test JSONL files included within the analysis directory.
- Outputs:
figures/attention_interpretability.png, standalone panels B-G, complete metrics and predictions, node-level attention, SHAP data, and serialized XGBoost models.
- Run:
cd analyses/insilico_mutagenesis && bash run_all.sh. - Inputs/model: the self-contained Combined validation JSONL and Baseline Combined checkpoint stored in the analysis directory.
- Outputs: sample-level and summary CSV files under
data/, individual A--D panels, andfigures/insilico_mutagenesis_full.{png,pdf}.
- Validate supplied outputs:
bash analyses/joint_model/run_all.sh. - Retrain:
bash analyses/joint_model/scripts/run_joint.sh. - Models: Baseline and Bio-aware, each with and without the Combined output.
- Outputs: four joint checkpoint directories under
checkpoints/and the with/without-Combined control underanalyses/joint_model/results/combined_supervision_control.csv. - Expect: with-Combined macro F1 0.855 (bio-aware) / 0.851 (baseline); no-Combined macro F1 0.856 / 0.858.
- Run:
bash analyses/joint_vs_pertissue/run_all.sh. - Inputs: supplied per-tissue prediction NPZ files and joint summaries;
data/comparison_data.csvis generated rather than manually entered. - Outputs:
figures/joint_comparison_*(slope + stacked). - Expect: Bio-aware joint training improves the five-tissue macro F1 by 0.009 and AUROC by 0.010; Baseline effects are mixed across tissues.
- Run:
bash analyses/minor_training_dynamics/run_all.sh. - Inputs: the 16 per-context
history.csv,summary.jsonandrun_config.jsonfiles undercheckpoints/. - Outputs:
training_dynamics.png/pdf,training_dynamics_summary.csv/json, andmanuscript/training_dynamics.png. - Expect: Baseline GAT threshold median 0.4625 (range 0.350--0.600); Bio-aware GNN median 0.2875 (range 0.100--0.400).
checkpoints/
├── {baseline,bioaware}_{Artery,Brain,Liver,MuscleSkeletal,Combined}/best.pth (10 human)
├── {baseline,bioaware}_{Octopus,Ptychodera,Strongylocentrotus}/best.pth (6 species)
├── joint_{baseline,bioaware}/best.pth
└── joint_{baseline,bioaware}_noCombined/best.pth
Single-output summaries carry test_metrics; joint summaries carry
test_by_tissue, macro and pooled metrics. All use validation-selected
thresholds and report held-out test performance.
- All 20 best checkpoints are supplied: 16 single-output models and four joint multi-label models. Their summaries and saved predictions match the reported metrics.
- The Triplet-SVM baseline reproduces end-to-end from the data in this repo (F1 0.785 / AUROC 0.784).
- Every supplementary number was traced to the run-output file of its specific analysis (comprehensive metrics table, threshold relaxation, RNAAtlas concordance, SNP audit, cross-species sensitivity, substrate stability, component ablation).
- Precision/recall/AUPRC for off-diagonal cells live in the per-cell prediction files under the
matrix package; the diagonal + thresholds are in each
checkpoints/*/summary.json.