Deriving PSI values from splice junctions#
Note
This tutorial uses AlphaGenome 1 (alphagenome.models.v1), but the same logic applies to AlphaGenome 2 (alphagenome.models.v2): pass condition= instead of ontology_terms= to predict_variant, and the returned JunctionData object has the same structure.
This tutorial derives percent spliced in (PSI) values from predicted splice junction counts. For a junction from donor D to acceptor A, PSI5 is the fraction of reads leaving donor D that splice to acceptor A (choice among acceptors for donor D), and PSI3 is the fraction of reads entering acceptor A that come from donor D (choice among donors for acceptor A).
Imports#
# @title Install AlphaGenome
# @markdown Run this cell to install AlphaGenome.
from IPython.display import clear_output
! pip install alphagenome
clear_output()
from alphagenome import colab_utils
from alphagenome.data import gene_annotation
from alphagenome.data import genome
from alphagenome.data import transcript as transcript_utils
from alphagenome.models.v1 import dna_client
from alphagenome.visualization import plot_components
import matplotlib.pyplot as plt
import pandas as pd
Load the model#
dna_model = dna_client.create(colab_utils.get_api_key())
Predict splice junctions for a variant#
Predict splice junctions for a variant near a known splice site:
# Define a variant near a splice site in the APOL4 gene.
variant = genome.Variant(
chromosome='chr22',
position=36201698,
reference_bases='A',
alternate_bases='C',
)
# Create a 1 Mb interval centered on the variant.
interval = variant.reference_interval.resize(dna_client.SEQUENCE_LENGTH_1MB)
# Predict junction counts for both REF and ALT alleles
output = dna_model.predict_variant(
interval=interval,
variant=variant,
requested_outputs=[dna_client.OutputType.SPLICE_JUNCTIONS],
ontology_terms=['CL:0000084'],
)
The junction data holds one row per predicted junction and one column per RNA-seq track for T cells (CL:0000084):
print(f"Junctions shape: {output.reference.splice_junctions.junctions.shape}")
print(f"Value shape: {output.reference.splice_junctions.values.shape}")
Junctions shape: (107,)
Value shape: (107, 2)
Junction counts come from RNA-seq, and T cells have two RNA-seq assays: poly(A)+ RNA-seq, which enriches for mature spliced mRNA, and total RNA-seq, which also captures pre-mRNA and can show incomplete splicing.
output.reference.splice_junctions.metadata
| name | ontology_curie | biosample_name | biosample_type | biosample_life_stage | gtex_tissue | data_source | Assay title | |
|---|---|---|---|---|---|---|---|---|
| 0 | junction_CL:0000084 polyA plus RNA-seq | CL:0000084 | T-cell | primary_cell | adult | encode | polyA plus RNA-seq | |
| 1 | junction_CL:0000084 total RNA-seq | CL:0000084 | T-cell | primary_cell | adult | encode | total RNA-seq |
Derive the PSI values#
Create a function to calculate PSI values from splice junction counts for either RNA track:
def compute_psi_from_raw(junction_data, track_idx):
# Create the dataframe from known attributes
data = []
for i, j in enumerate(junction_data.junctions):
# Determine Donor and Acceptor based on strand
if j.strand == '+':
donor, acceptor = j.start, j.end
else:
donor, acceptor = j.end, j.start
data.append({
'donor': donor,
'acceptor': acceptor,
'count': junction_data.values[i, track_idx],
})
df = pd.DataFrame(data)
# Pivot into the matrix: Rows = Donors, Cols = Acceptors
matrix = df.pivot_table(
index='donor', columns='acceptor', values='count', fill_value=0
)
# PSI5: Normalize row-wise (Percent of donor usage for each acceptor)
# PSI5 = Count / (Total counts for that Donor)
psi5 = matrix.div(matrix.sum(axis=1), axis=0).fillna(0)
# PSI3: Normalize column-wise (Percent of acceptor usage for each donor)
# PSI3 = Count / (Total counts for that Acceptor)
psi3 = matrix.div(matrix.sum(axis=0), axis=1).fillna(0)
return psi5, psi3
Select RNA track and calculate:
# Select RNA track index and compute PSI5 and PSI3 for the polyA plus RNA-seq track
titles = (
output.reference.splice_junctions.metadata['Assay title']
.astype(str)
.tolist()
)
polya_idx = next(
(i for i, t in enumerate(titles) if 'polya' in t.lower()), None
)
total_idx = next(
(i for i, t in enumerate(titles) if 'total' in t.lower()), None
)
print(f'Indices found -> PolyA: {polya_idx}, Total: {total_idx}')
psi5_polya, psi3_polya = compute_psi_from_raw(
output.reference.splice_junctions, polya_idx
)
Indices found -> PolyA: 0, Total: 1
Inspect results#
print(
'The proportion of total splicing activity at a specific acceptor site that'
' is contributed by a given donor'
)
display(psi3_polya.head())
print(
'The proportion of total splicing activity at a specific donor site that is'
' directed to a given acceptor'
)
display(psi5_polya.head())
The proportion of total splicing activity at a specific acceptor site that is contributed by a given donor
The proportion of total splicing activity at a specific donor site that is directed to a given acceptor
| acceptor | 36191912 | 36195432 | 36195437 | 36197831 | 36198896 | 36199369 | 36199376 | 36199608 | 36200291 | 36201796 | 36201851 | 36201888 | 36202055 | 36202094 | 36202933 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| donor | |||||||||||||||
| 36192925 | 0.018771 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36195094 | 0.016950 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36195310 | 0.909040 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36195912 | 0.004808 | 0.093865 | 0.016693 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36197944 | 0.005055 | 0.087138 | 0.017416 | 0.100716 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| acceptor | 36191912 | 36195432 | 36195437 | 36197831 | 36198896 | 36199369 | 36199376 | 36199608 | 36200291 | 36201796 | 36201851 | 36201888 | 36202055 | 36202094 | 36202933 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| donor | |||||||||||||||
| 36192925 | 1.000000 | 0.000000 | 0.000000 | 0.00000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36195094 | 1.000000 | 0.000000 | 0.000000 | 0.00000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36195310 | 1.000000 | 0.000000 | 0.000000 | 0.00000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36195912 | 0.153819 | 0.215299 | 0.630882 | 0.00000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 36197944 | 0.130356 | 0.161100 | 0.530524 | 0.17802 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
Visualize junctions with a Sashimi plot#
For splicing, it helps to show alternative isoforms, so we annotate all protein-coding transcripts rather than MANE Select only:
# The GTF file contains information on the location of all transcripts.
# Note that we use genome assembly hg38 for human.
gtf = pd.read_feather(
'https://storage.googleapis.com/alphagenome/reference/gencode/'
'hg38/gencode.v46.annotation.gtf.gz.feather'
)
transcript_extractor = transcript_utils.TranscriptExtractor(
gene_annotation.filter_protein_coding(gtf)
)
Build the Sashimi plot. Numbers above the arcs are predicted normalized junction read counts, not PSI values:
# We will plot for both polyA and total RNA-seq
ref_output = output.reference
alt_output = output.alternate
# Plot
_ = plot_components.plot(
[
# Transcript annotations
plot_components.TranscriptAnnotation(
transcript_extractor.extract(interval)
),
# Sashimi ref and alt plots
plot_components.Sashimi(
ref_output.splice_junctions,
ylabel_template='Reference {biosample_name} ({strand})\n{name}',
color='skyblue',
),
plot_components.Sashimi(
alt_output.splice_junctions,
ylabel_template='Alternate {biosample_name} ({strand})\n{name}',
color='red',
),
],
# Annotate the variant position as a vertical line across all panels
annotations=[plot_components.VariantAnnotation([variant])],
interval=interval.resize(40000),
)
plt.show()