Manhattan plot¶
Genome-wide association hits on a locus-aware chromosome axis. Alternating chromosome colors separate the blocks, dashed rules mark significance thresholds, and the strongest peaks are outlined for emphasis.
Move the −log10 p cutoff slider to change the red significance line. Points above the line turn red immediately; a higher cutoff is stricter. GenomeSpy updates the rule and highlighted set in the browser.
Data use and provenance
The packaged table is a subset of HapMap variants distributed through the
manhattanly and
Plotly datasets
projects. Genomic coordinates and annotations are real, but the association
statistics are simulated and are not biological findings. During data loading,
the package removes invalid p-values and calculates -log10(p). GenomeSpy then
renders the prepared table and applies the interactive threshold in the browser.
Code¶
"""Manhattan plot.
Genome-wide association hits on a locus-aware chromosome axis. Alternating
chromosome colors separate the blocks, dashed rules mark significance
thresholds, and the strongest peaks are outlined for emphasis.
"""
import genome_spy as gs
from genome_spy.datasets._hapmap import hapmap_manhattan_data
from genome_spy.schema import GenomeAxis, Scale
GENOME_WIDE_P = 5e-8
SUGGESTIVE_P = 1e-5
# Load association results and the starting significance thresholds.
data, _top_hits, domains = hapmap_manhattan_data(
genome_wide_p=GENOME_WIDE_P,
suggestive_p=SUGGESTIVE_P,
)
# Moving this slider redraws the red guide and outlines the hits above it.
significance_cutoff = gs.param(
"manhattanSignificanceCutoff",
value=round(domains["genome_wide_y"], 1),
bind=gs.binding_range(
min=3,
max=domains["y_domain"][1],
step=0.1,
name="−log10 p cutoff: ",
),
)
# Alternate colors to make neighboring chromosomes easier to distinguish.
chrom_colors = Scale().range(["#5b8fd6", "#8f98a3"])
# Label chromosomes and add faint backgrounds to separate them.
axis = (
GenomeAxis()
.title("Genomic position")
.chromGrid(True)
.chromGridOpacity(0.14)
.chromGridFillEven("#f4f6fb")
.chromGridFillOdd("#ffffff")
.chromLabels(True)
.chromLabelFontSize(11)
.chromTicks(True)
.chromTickSize(10)
.labelFontSize(10)
.grid(False)
)
# Draw one point per variant at its genomic position.
points = (
gs.Chart()
.mark_point(size=20, filled=True, opacity=0.82)
.encode(
x=gs.Locus("chrom", "BP").scale(assembly="hg18").axis(axis),
y=gs.Y("neglog:Q")
.scale(reverse=False, domain=domains["y_domain"])
.title("−log10 p"),
color=gs.Color("chrom_group:N")
.scale(chrom_colors)
.legend(title="Chromosome group"),
)
)
# Move the red significance line with the slider.
genome_wide_rule = (
gs.Chart([{}])
.mark_rule(strokeDash=[6, 4], size=1.4, color="#c53b2c")
.encode(
y=gs.Y(gs.datum(significance_cutoff), type="quantitative")
.scale(reverse=False, domain=domains["y_domain"])
.title("−log10 p")
)
)
# Keep a second, less strict threshold visible for comparison.
suggestive_rule = (
gs.Chart([{"threshold": domains["suggestive_y"]}])
.mark_rule(strokeDash=[2, 4], size=1.2, color="#d48b31")
.encode(
y=gs.Y("threshold:Q")
.scale(reverse=False, domain=domains["y_domain"])
.title("−log10 p")
)
)
# Highlight variants above the slider's current threshold.
highlight_points = (
gs.Chart()
.transform_collect()
.transform_filter(gs.datum.neglog >= significance_cutoff)
.mark_point(
size=48,
filled=True,
color="#c53b2c",
stroke="black",
strokeWidth=0.5,
)
.encode(
x=gs.Locus("chrom", "BP").scale(assembly="hg18"),
y=gs.Y("neglog:Q")
.scale(reverse=False, domain=domains["y_domain"])
.title("−log10 p"),
)
)
# Combine the points and guide lines, then attach the slider.
association_track = genome_wide_rule + suggestive_rule + points + highlight_points
chart = association_track.properties(
assembly="hg18",
title="HapMap genome-wide association scan",
data=data,
description=(
"A Manhattan plot with a locus-aware chromosome axis and an "
"interactive significance threshold."
),
).add_params(significance_cutoff)