SpliceImpactR Fiszbein Lab Logo

SpliceImpactR is an R package designed for studying the impact of alternative splicing on protein structure and function. It provides tools for analyzing RNA-seq data to identify differentially included splicing events and predict their consequences on the resulting protein products. SpliceImpactR output involves identifying key changes in proteins at various levels: primary sequence, domain content, and transcript-transcript interactions.

The suite of funcitons is designed to anaylyze the consequences of AFE, ALE, SE, MXE, A5SS, RI, and A3SS, along with hybrid exons. SpliceImpactR is built to take output from any source with custom functions to take data processed by from the HIT Index and rMATS.

The package is built to work with human and mouse data, primarily from GENCODE and biomaRt. We also allow for user-defined events and protein features for flexibility of use.

HIT Index data outputs, such as .exon files, are also incorporated into part of the process

Features

Identification of alternative splicing events from RNA-seq data. Analysis of the potential impact of splicing events on protein structure. Functional annotation of spliced isoforms to predict their biological impact. Integration with existing bioinformatics tools and databases for comprehensive analysis. Holistic analysis of how the use of different RNA processing events differs.

Installation

SpliceImpactR requires R (>= 4.5.0). Choose one installation path below.

BiocManager

if (!requireNamespace("BiocManager", quietly = TRUE)) {
  install.packages("BiocManager")
}
BiocManager::install("SpliceImpactR")

devtools (GitHub)

if (!requireNamespace("devtools", quietly = TRUE)) {
  install.packages("devtools")
}
devtools::install_github("fiszbein-lab/SpliceImpactR")

Load package

library(SpliceImpactR)

Usage

External tools and acronyms

Workflow map

The standard analysis path is:

  1. Load reference resources (annotations + protein features).
  2. Build sample manifest (sample_frame).
  3. (Optional) run the quick-start wrapper for end-to-end execution.
  4. Read splicing events for stepwise analysis.
  5. Run QC summaries and plots.
  6. Run differential inclusion (get_differential_inclusion) and significance filtering (keep_sig_pairs).
  7. Match significant events to annotated transcripts (get_ranked_pairs, the default; or the legacy get_matched_events_chunked and attach_sequences).
  8. Build case/control transcript pairs (returned by get_ranked_pairs; get_pairs in the legacy route).
  9. Compute sequence/frame consequences (compare_sequence_frame).
  10. Call domain changes (get_domains) and optional enrichment (enrich_*).
  11. Infer PPI rewiring (get_ppi_switches) and summarize (integrated_event_summary, plot_*).
  12. Build enrichment foreground gene sets from table or S4 inputs.
  13. Use S4 container workflows and accessors.
  14. Run optional custom-input entry points.

At a high level, each stage narrows from many sample-level rows to event-level effects: data (sample/form rows) -> res (DI rows) -> hits_final (paired event impact rows).

Load reference resources

Load GENCODE annotations

SpliceImpactR requires the accession of various genome annotations, accessed through biomaRt and directly through gencode, here we access the gencode files. SpliceImpactR is built to work with either human or mouse data. We will initially load a test set

annotation_df <- get_annotation(load = "test")

If we were looking to load the full annotations, we'd run the following

annotation_df <- get_annotation(load = "link", species = 'human', release = 45, base_dir = "./")

Or to load from cached annotations previously loaded:

annotation_df <- get_annotation(load="cached", base_dir="./path/")

By default, reference preparation keeps transcripts with TSL 1, 2, or 3. To retain transcripts regardless of TSL, including missing or unknown values, set filter_tsl = NULL:

annotation_df <- get_annotation(load = "link", filter_tsl = NULL)
annotation_df <- get_annotation(load = "cached", filter_tsl = NULL)

TSL-filtered and unfiltered references have separate processed cache entries. Use the same setting when preparing and reloading a reference. This option disables only the TSL filter; gene-biotype and incomplete-CDS tag filters still apply. The bundled test reference is loaded as stored.

Load protein features (BioMart / ELM / manual)

get_protein_features() supports:

For BioMart-backed features, attributes must follow: {feature}, {feature}_start, {feature}_end (for example pfam, pfam_start, pfam_end). Additional custom sources can be added with get_manual_features() (described in more detail in the custom input section below).

Full runs query Ensembl BioMart for release (default 111, the Ensembl release of the default GENCODE v45 annotation); pass the Ensembl release that matches your get_annotation() release. Ensembl 116 (June 2026) is the last release with BioMart, so each release is queried at its own address (for example https://e111.ensembl.org, which Ensembl forwards to the 111 archive), and ensembl_host selects another BioMart host. Ensembl BioMart is sometimes temporarily unavailable; try again later or choose another release.

We're loading test data here, but set test = FALSE to get the full set.

interpro_features <- get_protein_features(c("interpro"), annotation_df$annotations, test = TRUE)
signalp_features <- get_protein_features(c("signalp"), annotation_df$annotations, test = TRUE)
elm_features <- get_protein_features(c("elm"), annotation_df$annotations, test = TRUE)

We can also load user-defined protein features by transcript or peptide Ensembl ID, with the feature's amino-acid start and stop. These illustrative features sit on transcripts in the test annotation; the last is given by its peptide ID only.

user_df <- data.frame(
  ensembl_transcript_id = c("ENST00000253925", "ENST00000297268",
                            "ENST00000371730", NA),
  ensembl_peptide_id = c("ENSP00000253925", "ENSP00000297268",
                         "ENSP00000360795", "ENSP00000305503"),
  name = c("Coiled-coil", "Signal peptide", "Low complexity", "Low complexity"),
  start = c(100L, 1L, 600L, 1L),
  stop  = c(400L, 22L, 650L, 300L),
  database = c("ncoils", "signalp", "seg", "seg")
)
user_features <- get_manual_features(user_df, gtf_df = annotation_df$annotations)

We use this function to combine multiple protein features and the user-defined features. If no user_features are added, remove user_features from get_comprehensive_annotations() We also get the exon-level protein features from the prior overall features.

protein_feature_total <- get_comprehensive_annotations(list(signalp_features, interpro_features, user_features))
exon_features <- get_exon_features(annotation_df$annotations, protein_feature_total)

Build sample manifest

For the standard workflow, each row in sample_frame represents one sample. The sample directory should contain outputs from rMATS and/or HITindex. rMATS readers look for {AS}.MATS.JC/JCEC.txt, and HITindex readers look for .AFEPSI, .ALEPSI, and .exon files (.gz-compressed copies are read too).

Required sample manifest columns:

Manifest expectations:

For the sake of this intro, we use toy versions (limited to a handful of genes). The data files should be organized as such for each sample:

print(list.files(file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/control_S5/")))

Standard combined-directory example:

sample_frame <- data.frame(path = c(file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/control_S5/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/control_S6/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/control_S7/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/control_S8/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/case_S1/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/case_S2/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/case_S3/"),
                                    file.path(system.file("extdata", package = "SpliceImpactR"), "rawData/case_S4/")),
                           sample_name  = c("S5", "S6", "S7", "S8", "S1", "S2", "S3", "S4"),
                           condition    = c("control", "control", "control", "control", "case",  "case",  "case",  "case"),
                           stringsAsFactors = FALSE)

Quick start wrapper (get_splicing_impact)

If your inputs already follow the standard layout, run the full workflow with the wrapper:

# data.table-first return
out <- get_splicing_impact(
  sample_frame = sample_frame,
  source_data = "both",  # "hitindex" | "rmats" | "both"
  annotation_df = annotation_df,
  protein_feature_total = protein_feature_total,
  return_class = "data.table"
)

data <- out$data
res <- out$res
hits_final <- out$hits_final

# S4-first return
obj <- get_splicing_impact(
  sample_frame = sample_frame,
  source_data = "both",
  annotation_df = annotation_df,
  protein_feature_total = protein_feature_total,
  return_class = "S4"
)

The wrapper selects transcript pairs with the ORF-aware matcher by default (matching = "orf", described under Matching and pairing); matching = "legacy" runs the previous matcher. With the ORF matcher, out$matching holds the candidate, ranking and unresolved-comparison diagnostics.

Read splicing events (rmats + hit index example)

If you want stepwise control, load splicing events directly:

data <- get_rmats_hit(
  sample_frame,
  event_types = c("ALE", "AFE", "MXE", "SE", "A3SS", "A5SS", "RI")
)

DT <- data.table::as.data.table(data)
DT[, .(
  n_rows = .N,
  n_events = data.table::uniqueN(event_id),
  n_genes = data.table::uniqueN(gene_id)
)]

Coordinates are one-based and closed, as in GENCODE: rMATS's 0-based starts are shifted by +1, the retained-intron (RI) interval excludes the flanking exon ends, and A3SS/A5SS forms list the variable exon together with its partner (flanking) exon. Differential-inclusion tables saved with versions before 1.1.1 used the old conversion and should be regenerated.

Alternative when rMATS and HITindex are in separate directory trees:

sample_frame_rmats <- sample_frame
sample_frame_hit <- sample_frame

# In real usage, set different path columns:
# sample_frame_rmats$path <- c("/rmats/S5/", "/rmats/S6/", ...)
# sample_frame_hit$path <- c("/hitindex/S5/", "/hitindex/S6/", ...)

rmats_only <- get_rmats(
  load_rmats(
    sample_frame_rmats,
    use = "JCEC",
    event_types = c("MXE", "SE", "A3SS", "A5SS", "RI")
  )
)

hit_only <- get_hitindex(
  sample_frame_hit,
  keep_annotated_first_last = TRUE
)

shared_cols <- intersect(names(rmats_only), names(hit_only))
data <- data.table::rbindlist(
  list(
    rmats_only[, ..shared_cols],
    hit_only[, ..shared_cols]
  ),
  use.names = TRUE,
  fill = TRUE
)

Sample-Level QC and Exploration

Compare HITindex Between Conditions

This summary compares event-level mean HIT values across conditions.

hit_compare <- compare_hit_index(
  sample_frame,
  condition_map = c(control = "control", test = "case")
)

hit_compare$plot
head(hit_compare$results[order(fdr)], 6)

PSI Distribution Overview

This panel summarizes event count depth-normalized metrics and PSI eCDF.

overview_plot <- overview_spicing_comparison(
  events = data,
  sample_df = sample_frame,
  depth_norm = "exon_files",
  event_type = "AFE"
)
overview_plot

Differential Inclusion

We then perform differential inclusion analysis. This uses a quasibinomial glm and subsequent F test to identify significant changes in PSI across condition. The default here is 10 minimum read count, at least present (nonzero) in half of the samples within either of the conditions. This step does various filtering and with verbose = TRUE prints wrapped, readable progress lines (no horizontal scrolling needed).

We filter for fdr < 0.05 and delta_psi > 0.1 and output a volcano plot If using real data, this may take a long time. To speed it up, set parallel_glm = TRUE and pass a BiocParallel backend via BPPARAM (for example BiocParallel::MulticoreParam(workers = 4) on Linux/macOS or BiocParallel::SnowParam(workers = 4) on Windows). If needed, load the package explicitly with library(BiocParallel). Using keep_sig_pairs we filter for significance/threshold and plot with plot_di_volcano

res <- get_differential_inclusion(
  data,
  min_total_reads = 10,
  parallel_glm = TRUE,
  BPPARAM = BiocParallel::SerialParam()
)
res_di <- keep_sig_pairs(res)
volcano_plot <- plot_di_volcano_dt(res)
volcano_plot

Matching and pairing

Then we select, for each significant comparison, one case and one control transcript that represent the two forms and differ as little as possible outside the event. get_ranked_pairs() does this in one step (the default matcher in get_splicing_impact()):

  1. Validate the input: missing metadata, strands other than +/-, unknown forms and malformed intervals stop the run, naming the affected events.
  2. Pair forms: positive delta_psi rows are the case and negative rows the control, for each event_id.
  3. Keep, for each form, same-gene transcripts that use the event's defining splice sites exactly and have no exon in the excluded region. Outer flank ends and TSS/PAS ends may differ (labelled relaxed). A form with more than max_candidates (100) candidates keeps the best supported, and the number dropped is reported.
  4. Rank candidate pairs by coding status (an annotated CDS, so NMD-annotated transcripts count), transcript support tier (TSL 1-3, 4-5, unknown), how much coding-region structure the two transcripts share outside the event (a frameshifted isoform is not penalised for its early stop), and then exact TSL. A transcript is never paired with itself.
  5. When no structural pair exists, choose a pair with the legacy overlap rules and label it approximate (fallback = FALSE disables this). Comparisons still without a pair are listed in unmatched with a reason.

For an event with several sites, such as an AFE with three first exons, each rising x falling pair of sites is its own comparison, so a site can appear in several rows. keep_sig_pairs() keeps every site of an event in which any site passes; site_significant_case/_control show which sites passed themselves, and n_event_comparisons gives the number of comparisons the event defines. Pair counts, including domain enrichment, count comparisons rather than events.

ranked <- get_ranked_pairs(res_di, annotation_df$annotations, annotation_df$sequences)
pairs <- ranked$pairs
table(pairs$matching_tier)   # "structural" or "approximate"
head(ranked$unmatched)       # comparisons without a pair, with reasons

With the small test annotation, many comparisons lack a transcript with the exact event structure, so about half the pairs are approximate; with a full GENCODE annotation most pairs are structural.

ranked also holds every candidate with its eligibility, every scored pair and the settings. See vignette("transcript_pair_ranking", package = "SpliceImpactR") for the step-by-step protocol and its tradeoffs; scores and ties are descriptive diagnostics, not expression probabilities.

Legacy matcher

The previous matcher (matching = "legacy" in get_splicing_impact()) picks each form's transcript independently. It is faster, but it does not check the event's exact structure and can pair a transcript with itself. It works through a strict hierarchy:

  1. Prefilter by chr, strand, and gene_id to keep only compatible annotation intervals.
  2. Match inclusion (inc) coordinates to exons and require all inclusion parts to be covered for a transcript candidate.
  3. Remove candidates that overlap exclusion (exc) coordinates.
  4. Prioritize transcript/exon choices by event-type-consistent exon class (first, internal, last), then reciprocal overlap and intersection width; protein-coding status and then TSL only break remaining ties.
  5. Build case/control pairs in get_pairs(source = "multi") by joining all positive delta_psi rows (case) with all negative rows (control) for each event_id, then ordering by strongest |delta_psi|. Forms that match no transcript are left out.
matched <- get_matched_events_chunked(res_di, annotation_df$annotations, chunk_size = 2000)
hits_sequences <- attach_sequences(matched, annotation_df$sequences)
pairs <- get_pairs(hits_sequences, source="multi")

We can also perform analysis looking at how events impact proximal/distal use of terminal exons

proximal_output <- get_proximal_shift_from_hits(pairs)

Inspect PSI for a single event

Use probe_individual_event() to visualize PSI distributions for a specific event across samples. For terminal exon events (AFE/ALE), PSI is separated by the inc entry to highlight proximal vs distal choices.

Identify an event of interest from the differential inclusion results

event_to_probe <- res$event_id[1]

Plot PSI by sample/condition; missing combinations are filled with zeros by default

probe <- probe_individual_event(data, event = event_to_probe)
probe

If you want to start from transcript pairs directly (instead of DI/matching), see the Advanced / custom input workflows section at the end.

Primary sequence comparisons

Here, we compare sequence using protein-coding status, sequence alignment percent identity, protein length, and whether frame shifts / rescues are produced. And make a summary plot

seq_compare <-compare_sequence_frame(pairs, annotation_df$annotations)
alignment_summary <- plot_alignment_summary(seq_compare)
alignment_summary

Key labels used in sequence/frame outputs:

We can also perform analysis looking at how events impact protein length

length_output <- plot_length_comparison(seq_compare)
length_output

Get background

We next must get a background set for domain enrichment analysis. We can do this through all annotated transcripts (the default), a given set of possible transcripts, or the hit-index's .exon files. Use the annotated background for domain enrichment: a hit-index background keeps one transcript per observed exon, so it usually lacks the matched pairs and enrich_domains_hypergeo() rejects it. Its gene universes still work for gene-level enrichment.

bg <- get_background(source = "annotated",
                     annotations = annotation_df$annotations,
                     protein_features = protein_feature_total)

Get domain changes

Here we identify when the alternative RNA processing event drives a change in protein features, then identify enriched domains using the backgrond set First get the domains that change across pairs

hits_domain <- get_domains(seq_compare, exon_features)

Then we can probe for any enriched domains that are changing and plot

enriched_domains <- enrich_domains_hypergeo(hits_domain, bg, db_filter = 'interpro')
domain_plot <- plot_enriched_domains_counts(enriched_domains, top_n = 20)
domain_plot

Enrichment counts transcript pairs that change a tested domain: in both the foreground and the background, only pairs whose domain differences include a domain from db_filter count, and repeated transcript pairs count once. The odds ratio compares the foreground with the rest of the background. Every foreground pair and its domains must be in the background, so use the annotated background.

And we're able to search for A) specific events enrichment (AFE, ALE, etc) or by database (Interpro, SignalP, etc)

enriched_domains <- enrich_by_event(hits_domain, bg, events = 'AFE', db_filter = 'interpro')
enriched_domains <- enrich_by_db(hits_domain, bg, dbs = 'interpro')

Isoform-Isoform interaction network (Only available for human data currently)

For PPI analysis, we first obtain protein-protein interactions from a pre-derived network built from biogrid, ppidm, and elm And use these to show when a change in domain / SLiM changes ppi

ppi <- get_ppi_interactions()             
hits_final <- get_ppi_switches(hits_domain, ppi, protein_feature_total)
ppi_plot <- plot_ppi_summary(hits_final)
ppi_plot

Gene Enrichment Foreground Probes (table and S4)

get_gene_enrichment() supports both table and S4 input.

fg_di <- get_gene_enrichment(
  mode = "di",
  res = res,
  padj_threshold = 0.05,
  delta_psi_threshold = 0.1
)
fg_domain <- get_gene_enrichment(mode = "domain", hits = hits_domain)
fg_ppi <- get_gene_enrichment(mode = "ppi", hits = hits_final)

If DI enrichment returns all NA statistics, the usual issue is sparse Ensembl-to-Entrez mapping relative to the selected background. In practice, increase foreground size (relax DI cutoffs), broaden background, and/or lower min_size in get_enrichment().

The background carries two gene universes: attr(bg, "gene_universe") holds the genes with eligible transcript pairs, and attr(bg, "feature_gene_universe") the genes with at least one eligible pair whose features differ. Intersect them with the genes actually tested; use gene_universe for DI foregrounds and feature_gene_universe for domain and PPI foregrounds.

enrichment_di <- get_enrichment(
  foreground = fg_di,
  background = intersect(attr(bg, "gene_universe"),
                         res$gene_id[is.finite(res$padj)]),
  species = "human",
  gene_id_type = "ensembl",
  sources = "GO:BP",
  min_size = 5
)

enrichment_domain <- get_enrichment(
  foreground = fg_domain,
  background = intersect(attr(bg, "feature_gene_universe"),
                         res$gene_id[is.finite(res$padj)]),
  species = "human",
  gene_id_type = "ensembl",
  sources = "GO:BP",
  min_size = 5
)

Visualize specific transcript changes

You can visualize one paired event in transcript-centric and protein-centric views.

viz_pair <- hits_final[
  !is.na(transcript_id_case) &
    !is.na(transcript_id_control) &
    transcript_id_case != "" &
    transcript_id_control != ""
][1]

tx_pair <- c(viz_pair$transcript_id_case, viz_pair$transcript_id_control)

transcript_centric <- plot_two_transcripts_with_domains_unified(
  transcripts = tx_pair,
  gtf_df = annotation_df$annotations,
  protein_features = protein_feature_total,
  feature_db = c("interpro"),
  combine_domains = TRUE,
  view = "transcript"
)

protein_centric <- plot_two_transcripts_with_domains_unified(
  transcripts = tx_pair,
  gtf_df = annotation_df$annotations,
  protein_features = protein_feature_total,
  feature_db = c("interpro"),
  combine_domains = TRUE,
  view = "protein"
)

Integrative visualization

Use integrated_event_summary() for a multi-panel overview across DI, sequence, domain, and PPI layers.

Panel guide:

int_summary <- integrated_event_summary(hits_final, res)
int_summary$plot
int_summary$summaries$relative_use

Understanding output columns (hits_final, data, res)

The pipeline returns three core tables in data.table mode:

Suffix convention used throughout:

hits_final (integrated event-level output)

Use this table for biological interpretation and downstream plotting.

1) Event and isoform identifiers

2) Event coordinates and differential statistics

3) Sequence content and coding context

4) Alignment and frame classification

5) Domain-level change annotations

6) Predicted interaction rewiring (PPI/DDI/DMI-aware)

7) Transcript-pair selection (ORF matcher)

data (raw sample-level input table)

Use data to inspect per-sample evidence feeding differential inclusion.

Core columns:

Often present depending on import path:

res (differential inclusion output)

Use res to rank significant events before downstream pairing/domain/PPI steps.

Core columns:

S4 Applications and Accessors

You can run the full pipeline with get_splicing_impact() and choose either compact data.table outputs or a single S4 object. This is the canonical place to initialize obj for S4 workflows.

SpliceImpactResult is a custom S4 container that keeps all major pipeline parts synchronized:

# End-to-end (combined HITindex + rMATS)
out <- get_splicing_impact(
  sample_frame = sample_frame,
  source_data = "both",                 # "hitindex" | "rmats" | "both"
  event_types = c("ALE", "AFE", "MXE", "SE", "A3SS", "A5SS", "RI"),
  annotation_df = annotation_df,
  protein_feature_total = protein_feature_total,
  return_class = "data.table"
)

# Compact returns in data.table mode
data <- out$data
res <- out$res
hits_final <- out$hits_final
# Return a single S4 object
obj <- get_splicing_impact(
  sample_frame = sample_frame,
  source_data = "both",
  annotation_df = annotation_df,
  protein_feature_total = protein_feature_total,
  return_class = "S4"
)

# Convert slots back to data.table
raw_dt <- as_dt_from_s4(obj, "raw_events")
di_dt <- as_dt_from_s4(obj, "di_events")
hits_dt <- as_dt_from_s4(obj, "paired_hits")

When to prefer S4:

For paired-hit summaries, use fast accessors:

# Generic subset accessor
core_dt <- get_hits_final_view(obj, col_subset = "core")
dom_dt <- get_hits_final_view(obj, col_subset = "domain")
ppi_dt <- get_hits_final_view(obj, col_subset = "ppi")
seq_dt <- get_hits_final_view(obj, col_subset = "sequence")

# Tiny wrappers
core_dt <- get_hits_core(obj)
dom_dt <- get_hits_domain(obj)
ppi_dt <- get_hits_ppi(obj)
seq_dt <- get_hits_sequence(obj)

To inspect S4 schema and slot usage:

spliceimpact_s4_schema()
spliceimpact_s4_guide()

Filtering an S4 object by hits_final columns

Use filter_spliceimpact_hits() to subset by any paired_hits column and keep all event-linked slots synchronized (paired_hits, segments, res_di, di_events, matched, raw_events).

# keep one event
obj_one <- filter_spliceimpact_hits(obj, event_id == "A3SS:44")

# keep one gene with PPI change
obj_gene <- filter_spliceimpact_hits(obj, gene_id == "ENSG00000142599", n_ppi > 0)

# keep coding events with domain and frame criteria
obj_focus <- filter_spliceimpact_hits(
  obj,
  pc_class == "protein_coding",
  diff_n > 0,
  frame_call %in% c("Match", "PartialMatch")
)

Quick validation and extraction after filtering:

# confirm retained events
as_dt_from_s4(obj_focus, "paired_hits")[, .N, by = event_type][order(-N)]

# get compact outputs
get_hits_core(obj_focus)
get_hits_domain(obj_focus)
get_hits_ppi(obj_focus)

Notes:

Common S4 applications

Use the same S4 object directly in downstream helpers without converting to tables.

fg_di_s4 <- get_gene_enrichment(
  mode = "di",
  x = obj,
  padj_threshold = 0.05,
  delta_psi_threshold = 0.1
)

fg_ppi_s4 <- get_gene_enrichment(mode = "ppi", x = obj)

fg_ppi_focus <- get_gene_enrichment(mode = "ppi", x = obj_focus)
fg_di_focus <- get_gene_enrichment(
  mode = "di",
  x = obj_focus,
  padj_threshold = 0.05,
  delta_psi_threshold = 0.1
)

int_summary_focus <- integrated_event_summary(obj_focus, obj_focus)
int_summary_focus$plot

S4-first main workflow (code only)

This mirrors the table workflow but keeps all updates inside one SpliceImpactResult object. The same core functions accept S4 input and return an updated S4 object when return_class = "S4" is set. get_ranked_pairs() stores the selected pairs and form rows in the object and its diagnostics in metadata$matching_diagnostics. For the legacy matcher, replace that step with get_matched_events_chunked(), attach_sequences() and get_pairs(source = "multi"), each with return_class = "S4".

obj_flow <- as_splice_impact_result(
  data = data,
  sample_frame = sample_frame
)

obj_flow <- get_differential_inclusion(
  obj_flow,
  min_total_reads = 10,
  parallel_glm = TRUE,
  BPPARAM = BiocParallel::SerialParam(),
  return_class = "S4"
)
obj_flow <- keep_sig_pairs(obj_flow, return_class = "S4")

obj_flow <- get_ranked_pairs(
  obj_flow,
  annotation_df$annotations,
  annotation_df$sequences,
  return_class = "S4"
)
obj_flow <- compare_sequence_frame(
  obj_flow,
  annotation_df$annotations,
  return_class = "S4"
)
obj_flow <- get_domains(obj_flow, exon_features, return_class = "S4")
obj_flow <- get_ppi_switches(
  obj_flow,
  ppi,
  protein_feature_total,
  return_class = "S4"
)

hits_core_flow <- get_hits_core(obj_flow)
hits_domain_flow <- get_hits_domain(obj_flow)
hits_ppi_flow <- get_hits_ppi(obj_flow)
hits_sequence_flow <- get_hits_sequence(obj_flow)

Advanced / custom input workflows (optional)

Use these entry points when your data starts outside the default get_rmats_hit() to get_differential_inclusion() flow.

Add user-defined protein features (get_manual_features)

Rows may give ensembl_transcript_id, or only ensembl_peptide_id, which is placed through the annotation's protein IDs. Rows that cannot be placed (an ID not in the annotation, or a transcript without an annotated CDS) are dropped and reported in a message.

ann_dt <- data.table::as.data.table(annotation_df$annotations)
coding_tx <- unique(ann_dt[type == "exon" & cds_has == TRUE, transcript_id])
n_manual <- min(3L, length(coding_tx))
stopifnot(n_manual >= 1L)

manual_df <- data.frame(
  ensembl_transcript_id = coding_tx[seq_len(n_manual)],
  ensembl_peptide_id = rep(NA_character_, n_manual),
  name = paste0("demo_feature_", seq_len(n_manual)),
  start = c(20L, 45L, 80L)[seq_len(n_manual)],
  stop = c(35L, 58L, 92L)[seq_len(n_manual)],
  database = rep("manual", n_manual),
  alt_name = rep(NA_character_, n_manual),
  feature_id = rep(NA_character_, n_manual),
  stringsAsFactors = FALSE
)

manual_features <- get_manual_features(
  manual_features = manual_df,
  gtf_df = annotation_df$annotations
)

Bring your own pre-DI event table

example_df <- data.frame(
  event_id = rep("A3SS:1", 8),
  event_type = "A3SS",
  form = rep(c("INC", "EXC"), each = 4),
  gene_id = "ENSG00000158286",
  chr = "chrX",
  strand = "-",
  inc = c(rep("149608626-149608834", 4), rep("149608626-149608829", 4)),
  exc = c(rep("", 4), rep("149608830-149608834", 4)),
  inclusion_reads = c(30, 32, 29, 31, 2, 3, 4, 3),
  exclusion_reads = c(1, 1, 2, 1, 28, 27, 26, 30),
  sample = c("S1", "S2", "S3", "S4", "S1", "S2", "S3", "S4"),
  condition = rep(c("case", "case", "control", "control"), 2),
  stringsAsFactors = FALSE
)
example_df$psi <- example_df$inclusion_reads /
  (example_df$inclusion_reads + example_df$exclusion_reads)

user_data <- get_user_data(example_df)

Bring your own post-DI table

event_id is required: it groups each event's forms, which must be INC and EXC, or SITE alone. Events without a valid set of forms stop with an error that names them.

example_user_data <- data.frame(
  event_id = rep("A3SS:1", 8),
  event_type = "A3SS",
  gene_id = "ENSG00000158286",
  chr = "chrX",
  strand = "-",
  form = rep(c("INC", "EXC"), each = 4),
  inc = c(rep("149608626-149608834", 4), rep("149608626-149608829", 4)),
  exc = c(rep("", 4), rep("149608830-149608834", 4)),
  stringsAsFactors = FALSE
)

user_res <- get_user_data_post_di(example_user_data)

Import rMATS post-DI results directly

rMATS reports IncLevelDifference as mean(IncLevel1) - mean(IncLevel2), where group 1 holds the --b1 samples. SpliceImpactR reads it as case - control, so by default rMATS group 1 is the case. If your case samples were --b2, set case_group = 2; with a file table you can also name the case group, for example case_group = "KO". Each import reports which group it treated as the case. Events are numbered across files, and an event type listed under two different grp1/grp2 comparisons stops with an error.

Multiple files:

input <- data.frame(
  path = c("/path/A3SS.MATS.JC.txt", "/path/A5SS.MATS.JC.txt"),
  grp1 = c("WT", "WT"),
  grp2 = c("KO", "KO"),
  event_type = c("A3SS", "A5SS"),
  stringsAsFactors = FALSE
)

# res_rmats_di <- get_rmats_post_di(input, case_group = "KO")

Single preloaded rMATS table. Inclusion is higher in group 1, the case, so the inclusion form gets delta_psi = 1:

rmats_df <- data.frame(
  ID = 1L,
  GeneID = "ENSG00000182871",
  geneSymbol = "COL18A1",
  chr = "chr21",
  strand = "+",
  longExonStart_0base = 45505834L,
  longExonEnd = 45505966L,
  shortES = 45505837L,
  shortEE = 45505966L,
  flankingES = 45505357L,
  flankingEE = 45505431L,
  ID.2 = 2L,
  IJC_SAMPLE_1 = "5,5,5",
  SJC_SAMPLE_1 = "0,0,0",
  IJC_SAMPLE_2 = "0,0,0",
  SJC_SAMPLE_2 = "5,5,5",
  IncFormLen = 52L,
  SkipFormLen = 49L,
  PValue = 0.6967562,
  FDR = 1,
  IncLevel1 = "1.0,1.0,1.0",
  IncLevel2 = "0.0,0.0,0.0",
  IncLevelDifference = 1.0,
  stringsAsFactors = FALSE
)

res_rmats_di <- get_rmats_post_di(rmats_df, event_type = "A3SS")

Start from transcript pairs instead of event matching

tx_ids <- unique(annotation_df$annotations$transcript_id)
tx_ids <- tx_ids[!is.na(tx_ids) & tx_ids != ""]
stopifnot(length(tx_ids) >= 4L)

transcript_pairs <- data.frame(
  transcript1 = tx_ids[1:2],
  transcript2 = tx_ids[3:4],
  stringsAsFactors = FALSE
)

user_matched <- compare_transcript_pairs(
  transcript_pairs = transcript_pairs,
  annotations = annotation_df$annotations
)

Contributing

Contributions to SpliceImpactR are welcome, including bug reports, feature requests, and pull requests. Please see CONTRIBUTING.md for guidelines on how to contribute.

Support

If you encounter any problems or have suggestions, please file an issue on the GitHub issue tracker. Or contact zachpw@bu.edu

Citation

If you use SpliceImpactR in your research, please cite:

Zachary Peters Wakefield, Ana Fiszbein
SpliceImpactR maps alternative RNA processing events driving protein functional diversity
2025
https://www.biorxiv.org/content/10.1101/2025.06.20.660706v1
https://github.com/fiszbein-lab/SpliceImpactR