--- title: "ORF-aware transcript-pair selection" author: "SpliceImpactR" output: BiocStyle::html_document vignette: > %\VignetteIndexEntry{ORF-aware transcript-pair selection} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(SpliceImpactR) BiocParallel::register(BiocParallel::SerialParam()) ``` ## Purpose and scope An alternative splicing event describes local RNA processing. It usually does not identify the complete transcripts expressed in either condition. The aim of matching is to select plausible annotated transcripts for the two forms, then compare the proteins they encode. A transcript that only overlaps a small part of the event is not sufficient evidence for that form. `get_ranked_pairs()` is the default matcher in `get_splicing_impact()`; the legacy independent matcher remains available with `matching = "legacy"`. It first checks AS-form compatibility, then ranks **pairs** of compatible transcripts. Coding status (an annotated CDS) and transcript support remain priorities. Within a support tier, similarity of coding sequence outside the event precedes exact TSL. This protocol ranks plausible representatives. It does not estimate transcript abundance, phase distant splicing events, establish actual isoform expression, or assign probabilities that a particular pair is expressed. Protein features and interaction consequences remain predictions conditional on the selected pair and the supplied reference. ## Step 1: validate the input and establish the comparison Supply canonical differential rows with `event_id`, `event_type`, `form`, `gene_id`, `chr`, `strand`, `inc`, and `exc`. Coordinates must be **one-based, closed**; `101-200` contains 100 bases. Multiple intervals use semicolons. The rMATS import functions perform their zero-based-start conversion before matching. Do not convert their standardized output a second time. Each event ID must refer to one gene, chromosome, strand, and event type. Each event/form/interval definition must identify one differential row. Malformed coordinates or conflicting metadata are errors raised before any matching; the message names the affected events. IDs must already agree with the reference; matching does not guess gene aliases or mix releases. In `source = "multi"`, each positive-delta-PSI form is compared with each negative-delta-PSI form in the same event. Positive is case; negative is control. Thus a terminal event with two increasing and three decreasing sites has six form comparisons, recorded as `n_event_comparisons` on each selected pair. The ORF protocol does not replace those comparisons with a single gene-wide winner. In `source = "paired"`, one INC and one EXC are required. If delta PSI is absent entirely, INC is assigned to case, preserving the existing form-based convention. Rows without an opposing form are reported. Significance is separate from matching. Filter with `keep_sig_pairs()` before calling `get_ranked_pairs()` if that is the desired analysis population. The pipeline wrapper performs this filtering using its existing thresholds. ## Step 2: build the reference index once Index the relevant transcript metadata and exon structures. A candidate must have the event's **gene, chromosome and strand**. Exons are ordered by genomic position for junction comparisons and by transcript direction when extracting coding sequence. Exon IDs never determine biological order. Conflicting transcript metadata, overlapping exons within a transcript, and conflicting sequence records for one transcript ID stop the calculation. Identical duplicate records are harmless. A failed match remains visible in the event diagnostics; it is not interpreted as evidence for a noncoding or missing biological isoform. ## Step 3: require the candidate to represent its assigned form Any overlap between an explicit forbidden interval and a candidate exon rejects the candidate, and every event-defining boundary in the table below must match exactly. There is no 5% eligibility threshold in this protocol. A candidate that also contains every required interval completely is an `exact` match. The outer ends of flanking exons, the shared boundary of an A5SS/A3SS exon and the TSS/PAS end of a terminal exon are not part of the event, so a candidate that differs only there is a `relaxed` match. The `structural_match` column of `candidates` and the selected `pairs` records which applied. Relaxed matches must still contain their form's alternative region (otherwise the pair is rejected as `event_region_not_covered`), and they lose ties to exact matches. For AFE/HFE/ALE/HLE they are considered only when no transcript matches the terminal exon exactly, so two sites that share a splice site remain distinguishable. | Event | Additional eligibility rule | |---|---| | SE inclusion | With three intervals, the upstream, included and downstream exons must be adjacent in the transcript and have the specified inner splice boundaries. The flanks' outer ends may differ (relaxed). | | SE exclusion | The two flanking exons must be adjacent, with the specified skip junction; the skipped exon must be absent. The flanks' outer ends may differ (relaxed). | | MXE | Each three-interval branch must follow its specified adjacent-exon chain and exclude the other branch's forbidden exon. The flanks' outer ends may differ (relaxed). | | RI inclusion | The intron must lie in a **single annotated exon** that reaches into both flanks; exact matches contain both flanks completely. Separate exons do not establish retention. | | RI exclusion | The flanks must form the specified adjacent-exon junction, with the intron absent from exons. The flanks' outer ends may differ (relaxed). | | A5SS/A3SS | The variable donor/acceptor boundary must match exactly on the appropriate strand, and the variable exon must be adjacent to the partner exon with the specified junction. The exon's shared boundary and the partner's outer end may differ (relaxed). | | AFE/HFE | The transcript's first exon, in transcript direction, must match the specified interval exactly; without such a transcript, a first exon with the same donor and another TSS is accepted as relaxed. | | ALE/HLE | The transcript's last exon, in transcript direction, must match the specified interval exactly; without such a transcript, a last exon with the same acceptor and another PAS is accepted as relaxed. | | Generic event | Full required-interval coverage and forbidden-interval exclusion; labelled `interval_only`. | The rMATS converters define each A3SS/A5SS form by the variable exon and the flanking exon it is spliced to (rMATS's `flankingES`/`flankingEE`), so the junction is verified (`junction_chain`). A definition with the variable exon alone, as in DI tables saved by earlier versions or some user imports, is labelled `splice_site_only`: the variable boundary can be verified, but the partner cannot. The matcher does not invent missing context. For generic imports labelled `SITE`, SE skip and RI retained/spliced branches can be recognized from their supplied interval topology. Original form labels are preserved in the output; event grouping still comes from the supplied IDs. A single internal exon supplied for SE inclusion or MXE is accepted only with exact exon boundaries and is labelled `partial_event`; its omitted flanking junctions are unverified. RI supplied as one continuous retained span is also labelled `partial_event`. A single-exon transcript can satisfy a terminal rule at either end. Hybrid first/last events use their terminal role for the chosen form; they do not require the alternative terminal exon to be absent from all internal positions in the other transcript. These rules can still reduce the number of matched events. Novel junctions, annotation-version differences, or partial event definitions may leave no representative pair. That is an explicit unresolved result. Relaxation never moves an event-defining boundary, and every relaxed match is labelled. ## Step 4: construct candidate pairs For each form comparison, cross the compatible case and control candidates. A transcript is never paired with itself. A pair is also rejected if both transcripts satisfy both form definitions: those candidates cannot distinguish the requested forms. A pair is also rejected if either transcript lacks part of its own form's alternative region (`event_region_not_covered`). These cases are recorded in `pair_rejections`. Nothing is selected independently before considering its partner. `max_candidates` limits each form to 100 candidates per comparison, so at most 10,000 pairs are scored. A form with more keeps its best-supported candidates, in the ranking's own order: annotated CDS, TSL tier, exact structure, numeric TSL, then transcript ID. Dropped candidates are counted in `candidates_dropped_case` and `candidates_dropped_control`, and a message reports how many comparisons were affected. In the default human GENCODE v45 annotation (TSL 1--3) the largest gene has 58 transcripts, so nothing is dropped; the limit matters with `filter_tsl = NULL`, where some genes have several hundred transcripts. ## Step 5: identify the event-specific region Reduce the required intervals for each form to genomic ranges. The event mask is their symmetric difference, plus the explicit forbidden intervals from either form. Shared flanking sequence is retained as context. For SE this normally masks the skipped exon. For MXE it masks both alternative exons. For RI it masks the retained intron. For A3SS/A5SS it masks the extension between the alternative boundaries. For terminal events it masks the regions unique to the alternative terminal intervals. Positions in this mask are excluded from the context comparison in Step 6, so the score describes how the two transcripts differ *outside* the event. ## Step 6: compare the coding region outside the event Exon-level `cds_gen_start` and `cds_gen_stop` define the coding bases. Each pair is compared within a coding window: - When both transcripts are coding, the window is the span coded by either of them (`context_window = "pair_cds_span"`). - When a member is noncoding, every such pair of the comparison shares one window, the span coded by any candidate (`"event_cds_span"`), or the candidates' full span when none is coding (`"event_exon_span"`). A shared window keeps those pairs comparable with each other. `context_similarity` is the base-pair Jaccard overlap of the two transcripts' exonic positions in the window, outside the event mask: \[ S = \frac{\text{exonic positions shared by both transcripts}} {\text{exonic positions in either transcript}}. \] Positions in one genome determine sequence, so this is a direct measure of how much coding-region structure the two transcripts share. It deliberately does not depend on where each annotated reading frame ends. Consider an exon skip that shifts the frame: - the plain skip isoform has the same exons as the inclusion isoform outside the event, but its annotated CDS stops early at the premature stop; - another isoform also skips the exon but restores the frame with a second change, such as a 2-nt shorter acceptor downstream, so its CDS stays long. Comparing annotated CDS sequences would favour the second isoform and hide the frameshift. Comparing exonic positions scores the plain skip 1 and the rescue slightly lower, because of its extra change. `orf_similarity` is the same score including the event region and is a later tie-breaker. The diagnostics also give the shared fraction of each transcript's compared positions and the number of positions compared. The sequence table is still checked, because the selected pairs carry sequences into the downstream protein analyses. `transcript_seq` may contain CDS or full RNA sequence: 1. A sequence whose length equals the annotated CDS length is used as CDS. 2. An additional terminal TAA/TAG/TGA is removed when it accounts for exactly three extra bases. This accommodates the package's GENCODE FASTA workflow. 3. A sequence whose length equals the total exon length is sliced to its CDS using exon coordinates and strand. 4. Other length mismatches are reported; the sequence is not silently trimmed or assumed to represent the ORF. Length agreement is a consistency check, not proof that independently supplied FASTA and GTF files describe the same genome. Use a coherent reference bundle. The evidence label records what supports each pair: `cds_sequences` (both CDS sequences usable downstream), `cds_annotation` (both coding, a sequence unusable), `exon_annotation` (a member without CDS), or `no_outside_event_context` (nothing to compare outside the event). Protein sequence identity is **not** used to rank pairs. A legitimate event-induced frameshift can replace much of the downstream protein. Ranking on protein identity, or on annotated CDS sequence, could preferentially hide that consequence. The existing downstream protein alignment, frame/rescue classification and feature analyses still run on the selected pair. ## Step 7: choose the pair transparently Sort by the following rules, in this exact order: 1. More coding transcripts, meaning transcripts with an annotated CDS: two, then one, then zero. 2. Better worst-member TSL tier: both within 1–3, then a member at 4–5, then a member with unknown TSL. 3. Stronger evidence: `cds_sequences`, `cds_annotation`, `exon_annotation`, then `no_outside_event_context`. 4. Higher `context_similarity`. 5. Higher `orf_similarity`. 6. Fewer members that are not exact structural matches (`n_inexact`): exact outer boundaries win ties. 7. Better worst numeric TSL, then lower summed TSL. Unknown TSL is ranked as 6. 8. Stable case and control transcript IDs for reproducible ties. Coding status comes from the annotated CDS, not the biotype, so a transcript annotated as nonsense-mediated decay counts as coding. When an event shifts the frame, the transcript that carries it faithfully is often annotated that way, like the plain skip in Step 6. Requiring the `protein_coding` biotype would hand such comparisons to a frame-restoring isoform before similarity is considered. The biotype is still reported: both matchers add `transcript_type_case` and `transcript_type_control`, and `compare_sequence_frame()` labels a pair with an NMD-annotated member `"NMD"`, since that transcript is taken to make no protein. The 1–3 grouping is a **policy choice for this implementation**, consistent with the package's default reference filter. It deliberately allows a TSL-2 or TSL-3 transcript to beat a TSL-1 transcript when its coding background fits substantially better. It does not equate their underlying evidence. TSL describes support for the transcript model; it is not a measurement of expression in the samples under study. See the [GENCODE FAQ](https://www.gencodegenes.org/pages/faq.html). `n_tied_best` counts ties across all substantive criteria, before transcript-ID ordering. `context_score_margin` compares the winner's context score with the next pair in the same coding, support and evidence tiers. A zero margin can still be resolved by full ORF similarity or TSL. A missing margin means no comparable runner-up or no usable context score. Neither quantity is a p-value or calibrated confidence estimate. ## Step 7b: fall back to an approximate pair If no structural pair exists for a comparison, and `fallback = TRUE` (the default; `matching_fallback` in `get_splicing_impact()`), the comparison is retried with the legacy matcher's gates: each required interval must overlap one exon by at least 5% of its width, and no exon may overlap a forbidden interval by 5%. Event boundaries are not checked. A pair must still use two different transcripts and must differ at the event in the expected direction. `event_agreement` adds, for each form, how much more of that form's event-specific bases its own transcript covers than its partner does. It ranges up to 2 and must be positive. Approximate pairs are ranked by `event_agreement` first, then by the Step 7 criteria. They are labelled `matching_tier = "approximate"`, and a member whose structure was not verified is labelled `structural_match = "approximate"`. For example, when the annotation lacks the exact alternative exon, the fallback can choose a transcript with a slightly different version of it; the frame call is then a consequence of that version. Report or filter approximate pairs separately where exact event structure matters. Comparisons with no approximate pair remain in `unmatched`, whose `fallback` column records why. ## Step 8: inspect and analyze the result This runnable usage example uses the bundled reference and splicing inputs. The comparison against the legacy method is a separate review exercise. ```{r bundled-inputs} ex <- load_example_data(c("sample_frame", "annotation_df", "exon_features", "ppi")) annotation_df <- ex$annotation_df protein_feature_total <- ex$protein_feature_total exon_features <- ex$exon_features ppi <- ex$ppi data <- get_rmats_hit(ex$sample_frame) res <- get_differential_inclusion( data, parallel_glm = FALSE, BPPARAM = BiocParallel::SerialParam(), verbose = FALSE ) res_di <- keep_sig_pairs(res) ``` ```{r standalone} ranked <- get_ranked_pairs( res_di, annotation_df$annotations, annotation_df$sequences, source = "multi", verbose = FALSE ) names(ranked) # ranked$pairs: selected pairs, ready for downstream functions # ranked$rankings: every scored pair, all ranking components and ties # ranked$candidates: every same-locus candidate, including rejection reasons # ranked$pair_rejections: self-pairs, form-indistinguishable combinations and # approximate combinations without an event difference # ranked$events: every input form, including forms without matches # ranked$unmatched: form comparisons without a pair in any tier # ranked$settings: protocol version and scoring parameters seq_compare <- compare_sequence_frame(ranked$pairs, annotation_df$annotations) hits_domain <- get_domains(seq_compare, exon_features) ``` `ranked$matched` holds the selected form rows qualified by `matching_group`. The same form can select different transcript representatives against different opposing forms. Use `ranked$pairs` directly for consequences; running ordinary `get_pairs()` on all those selected form rows would cross them again. ```{r wrapper} out <- get_splicing_impact( res = res, annotation_df = annotation_df, protein_feature_total = protein_feature_total, ppi = ppi, matching = "orf", debug_steps = TRUE, verbose = FALSE ) names(out$matching) ``` In S4 mode, the audit tables are in `out@metadata$matching_diagnostics`; `get_ranked_pairs(return_class = "S4")` stores its results the same way for a stepwise S4 workflow. `matching_pair_id` includes the selected transcript IDs and is preserved as the S4 pair key. Legacy results keep their established key convention. The wrapper does not load unused reference resources when no significant forms remain; the matching diagnostics are then empty tables, or `NULL` when no `annotation_df` is given. ## Differences from the legacy protocol | Aspect | Legacy | ORF | |---|---|---| | Eligibility | At least 5% of each required segment overlaps an exon by default; forbidden-overlap veto at the same threshold | Exact event boundaries and structure, any forbidden overlap rejected; outer flank ends may differ (`relaxed`); legacy gates only as a labelled `approximate` fallback | | Selection unit | One best transcript independently for each form | One best transcript pair for each form comparison | | Exon class | A ranking preference | Defining boundaries and junction/terminal roles are eligibility requirements where context is supplied | | Coding preference | `protein_coding` biotype, as a tie-breaker after fit | An annotated CDS, so NMD-annotated transcripts count | | Transcript support | Breaks the last ties, after fit and coding status | Worst TSL tier first; similarity can distinguish candidates within TSL 1–3; exact TSL breaks later ties | | Similarity at selection | Not used | Shared exonic positions in the coding window, outside the event first, including it second | | Missing sequence | Appears downstream after transcript selection | Explicit evidence label during ranking | | Alternative candidates | Discarded at transcript selection | Retained with eligibility reasons and complete pair scores | | Ambiguity | Implicit in ordering | Tied-best counts, comparable score margin and stable IDs | | Cost | Overlap search and per-form sorting | Additional candidate cross-products, bounded explicitly; no sequence alignment | The ORF protocol should reduce cross-context comparisons and prevent structurally incompatible isoforms from winning through TSL or overlap preferences. It also makes disagreements inspectable. Its main costs are approximate pairs where the annotation lacks a transcript with the exact event boundaries, greater computation, and dependence on a correct reference/sequence pairing. Structural differences in the coding window outside the event, such as a different coding start exon, still lower context similarity; a premature stop alone does not. The evidence tiers can favour a pair with usable sequences over one with missing sequence data. There are no expression weights, new transcript assembly, ORF prediction, machine-learned probabilities or changes to differential testing in this implementation. Before adopting it as the default, inspect retained and rejected real events across all supported classes, review ties and missing context, and confirm that the TSL-tier policy fits the intended analysis. Event definitions follow the package's standardized inputs; the upstream file conventions are described in the [rMATS documentation](https://github.com/Xinglab/rmats-turbo#output). ## Session information ```{r session-information} sessionInfo() ```