Layered charts with independent scales

Overlay probe measurements and segment means with separate vertical axes.

Inputs and adaptation

Table

Fields

Meaning

Probes

Position:I, logR:Q

Integer position and a probe measurement.

Segments

startpos:I, endpos:I, segMean:Q

Integer interval bounds and a prepared segment mean.

Replace both URLs with accessible tables or pass prepared tables to gs.Chart. Update field names and units; keep the x coordinates compatible across layers. The means are supplied, not computed by this chart. Retain independent y scales and axes only when comparing the two value mappings is intentional. Point size also changes with zoom here; use a fixed size if that behavior is not needed. GenomeSpy fetches and renders the URL data in the browser.

Points show individual probe measurements, and black lines show segment means. Their positions share an x scale, but each layer has its own y scale and axis. With independent scales, vertical overlap does not imply equal values.

Data use and provenance

This uses the two chromosome 19 copy-number tables hosted with GenomeSpy’s example. They are fetched directly, not bundled with this package. The source specification does not identify the original study; this is a scale-resolution demonstration, not a biological interpretation.

Adapted from the official GenomeSpy example.

Code

"""Layered charts with independent scales.

Overlay probe measurements and segment means with separate vertical axes.
"""

import genome_spy as gs


# Keep the upstream tables remote, rather than embedding them in the spec.
probes = gs.Data(url="https://genomespy.app/docs/data/cnv_chr19_raw.tsv")
segments = gs.Data(url="https://genomespy.app/docs/data/cnv_chr19_segs.tsv")
zoom = gs.Expression("zoomLevel")

points = (
    gs.Chart(probes)
    .mark_point(size=gs.expr(gs.expr.min(2 * gs.expr.pow(zoom, 1.5), 200)))
    .encode(x=gs.X("Position:I"), y=gs.Y("logR:Q"), opacity=gs.value(0.15))
)
means = (
    gs.Chart(segments)
    .mark_rule(size=3, minLength=3, color="black")
    .encode(x=gs.X("startpos:I"), x2=gs.X2("endpos"), y=gs.Y("segMean:Q"))
)

# Align positions horizontally, but give each measurement its own y scale.
chart = (points + means).resolve_scale(y="independent").resolve_axis(y="independent")