Encodings and channels

An encoding maps data to a visible property. The property being controlled is called a channel: x controls horizontal position, color controls color, and size controls mark size. The field supplies the values, while its data type tells GenomeSpy how those values should be interpreted.

Field and type shorthand

The compact string "score:Q" contains a field name (score) and a type code (Q) separated by a colon. The shorthand and explicit forms below produce the same encodings:

import genome_spy as gs

measurements = [
    {"sample": "A", "stage": "low", "score": 2.1, "amount": 18, "confidence": 0.55},
    {"sample": "A", "stage": "medium", "score": 3.4, "amount": 32, "confidence": 0.82},
    {"sample": "A", "stage": "high", "score": 4.2, "amount": 24, "confidence": 0.74},
    {"sample": "B", "stage": "low", "score": 1.7, "amount": 22, "confidence": 0.61},
    {"sample": "B", "stage": "medium", "score": 2.8, "amount": 15, "confidence": 0.48},
    {"sample": "B", "stage": "high", "score": 3.6, "amount": 36, "confidence": 0.91},
]

shorthand_chart = (
    gs.Chart(measurements)
    .mark_point()
    .encode(
        x="score:Q",
        y="stage:O",
        color="sample:N",
    )
)

explicit_chart = (
    gs.Chart(measurements)
    .mark_point()
    .encode(
        x=gs.X("score", type="quantitative"),
        y=gs.Y("stage", type="ordinal"),
        color=gs.Color("sample", type="nominal"),
    )
)

Use shorthand when a field and type are enough. Use a channel class such as X, Color, or Tooltip when adding a title, scale, axis, legend, or another channel option. Setter methods can be chained:

gs.X("score:Q").scale(zero=False).axis(grid=True).title("Score")

Data types describe meaning

Choose a type from what a field means, not merely from how Python stores it:

Code

Type

Concrete example

Q

quantitative

A measured score such as 3.4

N

nominal

An unordered sample label such as A or B

O

ordinal

An ordered stage such as low, medium, or high

I

index

A numbered sequence position with a regular slot

L

locus

A chromosome-aware genomic position

A numeric identifier is usually nominal, not quantitative: sample 12 is not twice sample 6. Likewise, an ordinal field describes order but does not claim that the distance from low to medium equals the distance from medium to high. The selected type affects the default scale and the guide GenomeSpy creates, as described in visual encoding.

Visual channels

Several encodings can describe different parts of the same row:

channel_chart = (
    gs.Chart(measurements)
    .mark_point(filled=True, stroke="white", strokeWidth=1)
    .encode(
        x=gs.X("score:Q").scale(zero=False).title("Score"),
        y=gs.Y("stage:O").title("Stage"),
        color=gs.Color("sample:N").legend(title="Sample"),
        shape="sample:N",
        size=gs.Size("amount:Q").legend(title="Amount"),
        opacity=gs.Opacity("confidence:Q").scale(range=[0.35, 1]),
        tooltip=[
            gs.Tooltip("sample:N").title("Sample"),
            gs.Tooltip("score:Q").title("Score"),
            gs.Tooltip("confidence:Q").format(".0%").title("Confidence"),
        ],
    )
    .properties(title="Several channels can describe each row")
)

The most common channels have distinct jobs:

Channels

Purpose

x, y

Primary horizontal and vertical positions

x2, y2

Secondary endpoints for ranges, rectangles, links, and arrows

color, opacity, size, shape

Visible mark properties

text

Content drawn by a text mark

tooltip

Details shown when pointing at a mark

Position is generally the easiest visual channel to compare accurately. Color and shape are useful for categories; size and opacity can show quantities but are harder to compare precisely. A tooltip adds details without replacing a clear visible encoding. The GenomeSpy documentation lists every channel, including the offset channels, in channels.

Index positions

The I type combines numbered positions with regular-width slots. It is useful for bases, amino acids, matrix columns, and other ordered integer locations:

sequence = [
    {"position": 0, "base": "A"},
    {"position": 1, "base": "C"},
    {"position": 2, "base": "G"},
    {"position": 3, "base": "T"},
    {"position": 4, "base": "G"},
    {"position": 5, "base": "A"},
]

index_chart = (
    gs.Chart(sequence)
    .mark_text(size=22)
    .encode(
        x=gs.X("position:I").title("Zero-based index"),
        y=gs.value(0.5),
        text="base:N",
        color=gs.Color("base:N").legend(None),
    )
    .properties(title="Index values occupy regular slots")
)

Unlike an ordinal category, an index remains linear and can be zoomed: index and locus scales both zoom and pan by default, with no option to set. Each integer also has a band, allowing a rectangle to fill one indexed position. The index scale describes that behavior in detail.

Genomic loci

The L type places positions on a chromosome-aware axis. Locus() is the clearest form when chromosome and position are stored in separate fields:

features = [
    {"chrom": "chr17", "start": 43_044_000, "end": 43_050_000, "kind": "A"},
    {"chrom": "chr17", "start": 43_057_000, "end": 43_061_000, "kind": "B"},
    {"chrom": "chr17", "start": 43_068_000, "end": 43_075_000, "kind": "A"},
]

locus_chart = (
    gs.Chart(features)
    .mark_rect()
    .encode(
        x=gs.Locus("chrom", "start").scale(
            domain=[
                {"chrom": "chr17", "pos": 43_040_000},
                {"chrom": "chr17", "pos": 43_080_000},
            ]
        ),
        x2=gs.Locus("chrom", "end"),
        y=gs.Y("kind:N").title("Feature kind"),
        color=gs.Color("kind:N").legend(None),
    )
    .properties(assembly="hg38", title="Locus values follow the genome")
)

The chart’s assembly="hg38" supplies chromosome names, lengths, and order. The domain sets the initial view to a 40 kb window around these 31 kb of features, making them immediately visible in detail. The view remains fully zoomable, so users can zoom out to explore the broader genomic context. The genomic coordinates guide covers locus domains and coordinate conventions in detail, as does the locus scale in the GenomeSpy documentation.

Field, datum, value, and expression

Most encodings read a field, but an encoding definition can obtain its value in four ways:

definition_chart = (
    gs.Chart(measurements)
    .mark_point(filled=True)
    .encode(
        x=gs.X("score:Q").scale(zero=False),
        y=gs.datum(0, type="quantitative").scale(domain=[-1, 1]),
        color=gs.value("#4c78a8"),
        size=gs.Size(
            gs.expr(gs.datum.amount * gs.datum.confidence),
            type="quantitative",
        )
        .scale(domain=[0, 35])
        .legend(None),
    )
    .properties(title="Field, datum, value, and expression definitions")
)

Definition

Meaning

Example above

Field

Read a value from every row

gs.X

Datum

Use a constant in the scale’s data domain

gs.datum(...)

Value

Use a constant visual value without a scale

gs.value(...)

Expression

Calculate a value while the chart runs

gs.expr

A datum goes through the channel’s scale, so these points sit mid-axis; a positional value of 0 would mean the start of the visual range instead.

Constants and expressions give a scale nothing to derive a domain from, so both set an explicit domain. Expressions also need an explicit type. The expression language describes what an expression may contain.

The multiple sequence alignment example encodes index positions, text, and color from the same rows.