IsoEnds
Poly(A) tail length, alternative polyadenylation and transcription start sites from IsoForge long-read RNA-seq results, tested over biological replicates.
IsoEnds reads an IsoForge run as it is. The per-read table of IsoForge holds each read's transcript, start site, poly(A) site and tail length, so IsoEnds needs no alignments. It compares two conditions and reports, per gene:
| Analysis | What is tested |
|---|---|
| Poly(A) tails | Change in the median tail length of genes, transcripts, transcript categories and gene sets |
| Alternative polyadenylation | Tandem 3′UTR sites (PDUI), intronic sites on composite and skipped exons, internal exonic sites, 5′UTR sites, and tandem sites of non-coding genes |
| Transcription start sites | Promoter switches (DTUI), upstream promoters, alternative first exons and internal exonic starts |
Every test uses replicates as its unit, never reads. The figures on this page are IsoEnds output for a simulated experiment of 10 units with three replicate pairs each.
isoends init --out config.yaml # write a template, then edit it
isoends all --config config.yaml -j 4 # tails, APA, TSS and the report
import isoends as ie
exp = ie.Experiment.from_isoforge(
runs={"my_run": "isoforge/results"}, samples="samples.tsv", control="control", treatment="treated",
reference_gtf="gencode.primary_assembly.annotation.gtf", outdir="isoends_out")
res = exp.run(jobs=4)
res.volcano("tandem")
Contents
- Install
- Input
- Poly(A) tails
- Alternative polyadenylation
- Transcription start sites
- Statistics
- Output
- Python API
- Command line
- Development
Install
pip install isoends
Python 3.11 or later. pip install "isoends[bam]" adds pysam, for tails read from BAM files. R with limma is used
for the moderated t-test when present; without it the built-in Python implementation gives the same results.
isoends demo makes a synthetic experiment and analyses it.
Input
| Input | Where | Needed for |
|---|---|---|
| IsoForge results folder | runs: {name: {results: ...}} |
everything. IsoEnds reads <prefix>_isoforge_read_annot.parquet and <prefix>_isoforge.gtf, so run IsoForge with --read_annot_format parquet or both |
| Sample sheet | samples: |
everything |
Reference GTF with Ensembl_canonical tags (GENCODE) |
reference: {gtf: ...} |
APA and TSS |
Characterised GTF of isoforge-characterize |
found in the results folder or a sibling SQANTI3 folder |
categories of novel transcripts (NMD, retained intron) |
| Genome BAMs | runs: {name: {bams: "{dataset}.bam"}} |
IsoTracks views of chosen genes |
IsoEnds uses the supported reads of the Parquet read table: reads marked cluster_supported whose transcript is in
the IsoForge GTF. These are the reads of IsoForge's filtered read TSV. Tail lengths come from their polya_length:
a read without a call has -1 and is left out, a length of 0 is a call (tails.min_tail_nt: 1 leaves it out). A
per-read tail table or BAMs with the pt:i tag can be given instead (tails:, tail_bams:).
Sample sheet
A tab- or comma-separated table with one row per dataset of the read table:
dataset unit condition pair
A_control_1 line_A control 1
A_treated_1 line_A treated 1
A_control_2 line_A control 2
A_treated_2 line_A treated 2
B_control_1 line_B control 1
...
| Column | Meaning |
|---|---|
dataset |
The dataset name in the IsoForge run |
unit |
The experiment a sample belongs to: a cell line, a donor, a time point. One unit is enough; with several, IsoEnds also tests across them |
condition |
The two conditions compared are named by control and treatment in the config. Datasets with any other condition are left out, so one sheet can hold several treatments |
pair |
Optional. Give it when each control sample has a matched treated sample; the tests within the unit are then paired. Leave the column out for independent replicates |
run |
Optional. The IsoForge run of the dataset, when several runs are configured and units are not named after them |
A unit needs two or more replicates of each condition to be tested on its own. To compare another pair of
conditions from the same sheet, run isoends all --control A --treatment C --outdir out_C.
Poly(A) tails
Per sample, the tail of a gene or transcript is the median over its reads, and the overall tail of the sample is the median over its transcripts with more than 20 reads. The change between conditions is tested within each unit and across units.
res.volcano("tail_genes") # genes; "tail_transcripts" for transcripts
res.get("tail_genes", level="within") # the table behind it, per unit
exp.plot_tails("GENE", unit="line_A") # the tail distribution of one gene
(A) Per-transcript median tails of each unit by condition, with their medians. (B) Change in the median tail of each gene, tested across units.
Also reported: the overall tail of each sample, tails by transcript category (reference type, or SQANTI3 class for
novel models), NMD and retained-intron isoforms against the coding isoform of the same gene, and the share of short
tails per gene set. Gene sets are ribosomal proteins, histones, snoRNA hosts and mitochondrial genes, or your own
(gene_classes:).
Alternative polyadenylation
The 3′ ends of the reads are clustered per gene and each cluster is placed on the gene's Ensembl-canonical transcript. Genes are Ensembl gene IDs, so two genes that share a name are never pooled.
| Class | Site | Measure |
|---|---|---|
| Tandem 3′UTR | the two most used sites that IsoForge labels three_prime_utr |
PDUI = distal / (proximal + distal) reads |
| Composite exon | in an intron, on transcripts that read through the upstream exon | share of the gene's reads, against terminal sites |
| Skipped exon | in an intron, on a terminal exon of its own | share |
| Internal exonic | inside an exon other than the last | share |
| 5′UTR | upstream of the start codon | share |
| Intronic (all) | any intronic site | share |
| Non-coding | tandem sites of lncRNAs and novel genes | PDUI, with q over the non-coding genes |
res.volcano("tandem") # also "composite", "skipped", "internal", "five_utr", "intronic",
res.get("noncoding_tandem") # "noncoding_tandem"
res.plot_sites("GENE", unit="line_A")
Each point is a gene tested across units. Under each panel are the two isoforms compared, coloured as the points: orange points are genes that move towards the orange isoform, teal points towards the teal one.
For each gene the tables give both sites of the tandem pair, their distance from the stop codon, the 3′UTR length gained or lost and the transcripts ending at each site. Where two sites are equally used, the one with the lower coordinate is taken. An intronic site is composite or skipped by the majority of the transcripts ending there, of any gene on that strand.
| Option | Default | Alternative |
|---|---|---|
apa.tandem_sites |
three_prime_utr: sites with IsoForge's 3′UTR label |
terminal: sites in the canonical last exon, or up to 5 kb past it |
apa.same_exon_check |
false: every pair is tested; same_exon and share_distal_models_split flag the pairs an intron splits |
true: a pair is tested only when one annotated exon holds both sites and the transcripts ending at the distal site do not splice over the proximal one |
apa.across_units_same_pair |
false: across units, every unit's own pair |
true: only the units whose pair matches the most common pair (same_pair) |
apa.intronic_models |
any: every transcript ending at the site |
gene: only the transcripts of the site's gene |
apa.novel_gene_models |
false: genes outside the reference are not placed |
true: placed on their best-supported IsoForge model |
(A) Usage of the poly(A) sites of one gene (plot_sites). (B) Its tail distribution, with the sample medians that
are tested as ticks (plot_tails).
Transcription start sites
The 5′ ends IsoForge assigns to a start site are clustered and placed on the canonical transcript in the same way.
| Class | Site | Measure |
|---|---|---|
| Promoter switch | the two most used start sites of the gene | DTUI = downstream / (upstream + downstream) reads |
| Upstream TSS | more than 1 kb upstream of the first exon | share, against the canonical first exon |
| Alternative first exon | in an intron, on transcripts with a first exon of their own | share |
| Internal exonic TSS | inside an exon other than the first | share |
res.volcano("tss_switch") # also "tss_upstream", "tss_first_exon", "tss_internal"
res.plot_sites("GENE", unit="line_A", kind="tss")
Each point is a gene tested across units, with the two isoforms compared under each panel, coloured as the points.
A 5′ end inside an internal exon can be a promoter or a truncated read, so confirm internal exonic starts with an independent method.
Statistics
Reads from one sample are not independent, so a test with reads as its unit calls a difference between any two samples. IsoEnds summarises each sample first and tests over replicates.
| Paired replicates | Unpaired replicates | |
|---|---|---|
| Overall tail of a sample, within a unit | paired t-test of the sample medians | Welch t-test of the sample medians |
| Tail of a gene or transcript, within a unit | moderated one-sample t-test of the paired differences | moderated two-sample t-test of the sample medians |
| Site usage, within a unit | Cochran-Mantel-Haenszel test over the pairs | moderated two-sample t-test of each sample's usage |
| Any change, across units | moderated one-sample t-test of the per-unit changes | the same |
The moderated t-test is limma's (empirical Bayes, robust). q is the Benjamini-Hochberg-adjusted P over the genes of a unit, or over the genes tested across units. Across units a gene must be measured in two thirds of the units.
The read-level test is still reported next to the replicate-level one, as a description only
(q_reads_pooled in tails_gene_within_unit):
The genes of one unit tested over its three replicate pairs (A) and with every read as a unit (B).
Output
isoends_out/
report.md results, checks, parameters, tests, figures and table descriptions
tables/ every table as Parquet and TSV
figures/ PDF, SVG and PNG with captions
provenance.json inputs with SHA-256, versions, parameters
logs/ counts at each step
| Table | Content |
|---|---|
tests_summary |
one row per family of tests: the test, n tested, n at q < 0.05 each way |
tails_gene_within_unit, tails_gene_across_units |
tail change per gene |
tails_transcript_within_unit, tails_transcript_across_units |
tail change per transcript, and per intron chain across units |
apa_tandem_*, apa_class_*, apa_ipa_* |
tandem 3′UTR, the site classes, and all intronic sites |
tss_switch_*, tss_class_* |
promoter switches and the start-site classes |
sites, tss_sites |
every poly(A) and start-site cluster with its class and reads |
qc_samples, validation |
per-sample counts and the input checks |
report.md describes every table.
Python API
ie.Experiment.from_config(path) / .from_isoforge(runs, samples, control, treatment, reference_gtf) |
set up an analysis |
exp.validate() |
the input checks as a table |
exp.run(jobs) / exp.tails() / exp.apa() / exp.tss() / exp.report() |
run everything or one stage |
ie.load(outdir) |
open a finished run |
res.analyses() |
the analyses with the genes tested and significant |
res.get(analysis, level, unit) |
one analysis as a table with effect and q |
res.volcano(analysis) / res.volcano_grid(analysis) |
a volcano, or one per unit |
res.plot_sites(gene, unit, kind) / exp.plot_tails(gene, unit) |
one gene |
res.gene(gene) / res.table(name) |
every row of a gene, or any table |
exp.tails_of(gene, unit) |
the tail of a gene per sample |
exp.isotracks(gene, unit) |
the reads of a gene, drawn with IsoTracks |
Tables are pandas DataFrames and plots are Matplotlib axes. docs/make_readme_figures.py draws the figures of this
page, from a simulated experiment (--simulate FOLDER) or from a finished run of your own.
Command line
isoends init [--out isoends.yaml] write a configuration template
isoends validate --config config.yaml check the inputs and the sample sheet
isoends all --config config.yaml tails, apa, tss and the report
isoends tails | apa | tss | report one stage
isoends isotracks --config config.yaml --gene NAME [--unit UNIT]
isoends demo [--out DIR] [--unpaired] a synthetic experiment, analysed
-j N summarises N runs in parallel; summaries are cached, so later stages and reruns start from them.
--control, --treatment and --outdir override the config. Every parameter and its default is in the template
that isoends init writes.
Development
git clone https://github.com/MustafaElshani/IsoEnds
cd IsoEnds
pip install -e ".[test]"
pytest
The tests check the statistics against R limma, statsmodels and SciPy, the site classes on hand-built gene models, and the whole pipeline on synthetic experiments with known effects.
IsoEnds is released under the MIT licence.
Metadata
Release files for isoends 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| isoends-1.0.0.tar.gz | 217.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| isoends-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 432.1 kB
Release files / isoends-1.0.0.tar.gz
| Download URL | isoends-1.0.0.tar.gz |
|---|---|
| Size | 217.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5f9cc7cfd9c8ea00066909810e2e88a8c969ce09c5e09a09c72837f1f54147a
|
|
BLAKE2b-256 checksum How to use checksums |
1b6ae8f31618e38f9a11fe365cab0de4edf50428d498de457397ab295715e9fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / isoends-1.0.0-py3-none-any.whl
| Download URL | isoends-1.0.0-py3-none-any.whl |
|---|---|
| Size | 214.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a1482f34d7567f60cc863ddd992708f5a2c8cc18cdcc26ef9e72cf9e2079cb87
|
|
BLAKE2b-256 checksum How to use checksums |
8c10edb432289a6537714aec2038099eadb03e7d0d5c59194c04b768bd19980e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log