ASCAT copy-number segmentation¶
Aligned allele-specific copy-number, LogR, and B-allele-frequency tracks show segment estimates over raw probe values for simulated sample S96.
Data use and provenance
The example shows simulated example data for sample S96 from
Allele-specific copy number analysis of tumors
by Loo et al.
What to notice¶
The vertically concatenated tracks share one genomic x axis. The top track
shows slightly offset minor- and major-allele copy-number estimates. The middle
track overlays raw LogR probes with segment means, and the bottom track does the
same for B-allele frequency, including the mirrored 1 - BAF line.
Python implementation¶
The segment table is inherited by the copy-number and segment-mean marks, while
the raw LogR and BAF point layers use the probe table. The three tracks are
combined with & and share their locus scale and axis. Both tables are loaded
directly; Python performs no data preparation. GenomeSpy evaluates the
zoom-responsive point size and mirrored-BAF expression in the browser.
See the official GenomeSpy example for data-wrangling details and the original specification.
For an overview interval that navigates several shared-locus tracks, see the brush-linked genome tracks example.
Code¶
"""ASCAT copy-number segmentation.
Aligned allele-specific copy-number, LogR, and B-allele-frequency tracks show
segment estimates over raw probe values for simulated sample S96.
"""
import genome_spy as gs
SEGMENTS_URL = "https://data.genomespy.app/sample-data/ASCAT/segments_S96.tsv"
RAW_URL = "https://data.genomespy.app/sample-data/ASCAT/raw_S96.tsv"
# Make individual measurements easier to see when zoomed in.
ZOOM_LEVEL = gs.Expression("zoomLevel")
POINT_SIZE = gs.expr(gs.expr.min(10 * gs.expr.pow(ZOOM_LEVEL, 1.5), 200))
# Draw the minor-allele copy count in green.
minor_copy_number = (
gs.Chart()
.mark_rule(minLength=2, yOffset=-3)
.encode(
y=gs.Y("nMinor:Q")
.scale(domain=[0, 6], padding=0.04, clamp=True)
.axis(tickMinStep=1),
size=gs.value(5),
color=gs.value("#88d27a"),
)
.properties(title="nMinor")
)
# Draw the major-allele copy count in red, offset slightly to keep both visible.
major_copy_number = (
gs.Chart()
.mark_rule(minLength=2, yOffset=3)
.encode(
y=gs.Y("nMajor:Q").scale(domain=[0, 6]),
size=gs.value(5),
color=gs.Color("nMajor:Q").scale(
domain=[0, 6, 16], range=["#f06850", "#f06850", "#5F0F0F"]
),
)
.properties(title="nMajor")
)
# Combine both allele counts in the top track.
copy_number = (minor_copy_number + major_copy_number).properties(
name="copyNumberTrack",
title=gs.title("Allele-specific copy numbers", style="overlay"),
)
# Load individual LogR measurements and draw them as faint points.
raw_logr = (
gs.Chart(gs.Data(url=RAW_URL))
.mark_point(size=POINT_SIZE)
.encode(
x=gs.Locus("chr", "pos"),
y=gs.Y("logR:Q").title(None),
color=gs.value("#7090c0"),
opacity=gs.value(0.25),
strokeWidth=gs.value(0),
)
.properties(title="Single probe")
)
# Add a black line for each segment's mean LogR.
mean_logr = (
gs.Chart()
.mark_rule(minLength=3)
.encode(
y=gs.Y("logRMean:Q").title("LogR"),
size=gs.value(3),
color=gs.value("black"),
)
.properties(title="Mean LogR")
)
logr = (raw_logr + mean_logr).properties(name="logRTrack")
# Show individual B-allele frequencies, skipping missing measurements.
raw_baf = (
gs.Chart(gs.Data(url=RAW_URL))
.transform_filter(gs.datum.baf != None) # noqa: E711
.mark_point(size=POINT_SIZE)
.encode(
x=gs.Locus("chr", "pos"),
y=gs.Y("baf:Q").title(None),
color=gs.value("#7090c0"),
opacity=gs.value(0.3),
strokeWidth=gs.value(0),
)
.properties(title="Single probe")
)
# Add the segment's mean B-allele frequency.
mean_baf = (
gs.Chart()
.mark_rule(minLength=3)
.encode(
y=gs.Y("bafMean:Q").scale(domain=[0, 1]).title("B-allele frequency"),
size=gs.value(3),
color=gs.value("black"),
)
.properties(title="Mean BAF")
)
# Mirror the mean around 0.5 to show the complementary allele frequency.
mirrored_baf = (
gs.Chart()
.mark_rule(minLength=3)
.encode(
y=gs.Y(gs.expr(1 - gs.datum.bafMean), type="quantitative").title(None),
size=gs.value(3),
color=gs.value("black"),
)
.properties(title="Mean BAF")
)
baf = (raw_baf + mean_baf + mirrored_baf).properties(name="bafTrack")
# Load the segment estimates and stack the three tracks so they zoom together.
chart = (
(copy_number & logr & baf)
.properties(
assembly="hg18",
data=gs.Data(url=SEGMENTS_URL),
background="#fafafa",
description=(
"ASCAT sample S96 with aligned allele-specific copy-number, LogR, "
"and B-allele-frequency tracks."
),
)
.encode(
x=gs.Locus("chr", "startpos").scale(type="locus"),
x2=gs.Locus("chr", "endpos", offset=1),
)
.resolve_axis(x="shared")
.configure_axis_x(grid=False, chromGrid=True, orient="bottom")
.configure_axis_y(grid=True, gridColor="#f8f8f8")
.configure_legend(disable=True)
.configure_view(
fill="white",
stroke="#c8c8c8",
shadowBlur=8,
shadowColor="black",
shadowOpacity=0.1,
)
)