Multiple sequence alignment

An alignment view combines a sequence-logo summary with a zoomable row for each aligned sequence.

Data use and provenance

The alignment file 16SRNA_Deino_87seq.aln is identified by the NCBI tutorial and is available from the NCBI FTP server. NCBI states that it places no restrictions on the use or distribution of molecular data in its databases, but original submitters may claim intellectual-property rights in submitted data. This dataset is used here for demonstration and visualization purposes.

What to notice

The upper logo summarizes base frequency and information content. The lower track preserves one row per aligned sequence. Letters become readable when zoomed in, while colored rectangles provide a compact overview at wider scales.

Python implementation

FASTA input is flattened to one row per aligned base. Aggregate, formula, and information-content stack transforms build the logo, and & aligns it with the sequence matrix on a shared index scale.

See the official GenomeSpy example for the information-content calculation and original specification.

Code

"""Multiple sequence alignment.

An alignment view combines a sequence-logo summary with a zoomable row for
each aligned sequence.
"""

import genome_spy as gs
from genome_spy.schema import Scale


# Use the same base colors in the summary and sequence rows; gaps are pale grey.
BASE_COLORS = Scale(
    domain=["A", "C", "T", "G", "N", "-"],
    range=["#4FBF45", "#4D96E8", "#E85F78", "#E8B322", "#BDBDBD", "#f5f5f5"],
)

# Summarize each position with stacked letters; taller stacks mean more agreement.
logo = (
    gs.Chart()
    .transform_aggregate(groupby=["pos", "sequence"])
    .transform_formula(
        expr=gs.expr.if_(gs.datum.sequence == "-", None, gs.datum.sequence),
        as_="base",
    )
    .transform_stack(
        field="count",
        groupby=["pos"],
        offset="information",
        as_=["_y0", "_y1"],
        baseField="base",
        sort=gs.compare("count", order="ascending"),
    )
    .mark_text(
        font="Source Sans Pro",
        fontWeight=700,
        size=90,
        squeeze=True,
        fitToBand=True,
        logoLetters=True,
        paddingX=0,
        paddingY=0,
    )
    .encode(
        x=gs.X("pos:I").axis(None),
        y=gs.Y("_y0:Q").scale(domain=[0, 2], zoom=True).title("Information"),
        y2=gs.Y2("_y1"),
        text=gs.Text("base:N"),
        color=gs.Color("base:N").scale(BASE_COLORS).legend(None),
    )
    .properties(height=70, title="Sequence logo")
)

# Show each sequence as colored tiles with letters, in a scrollable list.
alignment = (
    gs.layer(
        gs.Chart().mark_rect(),
        gs.Chart()
        .mark_text(size=11, fitToBand=True, opacity=0.7, tooltip=None)
        .encode(color=gs.value("black"), text=gs.Text("sequence:N")),
    )
    .encode(
        x=gs.X("pos:I"),
        y=gs.Y("identifier:N").title(None),
        color=gs.Color("sequence:N").scale(BASE_COLORS).legend(None),
    )
    .properties(height=gs.step(14), viewportHeight="container", title="Alignment")
)

# Load the aligned sequences and put the summary above them.
chart = (
    gs.vconcat(logo, alignment, spacing=8)
    .properties(
        assembly="hg38",
        data=gs.Data(
            url="https://data.genomespy.app/sample-data/16SRNA_Deino_87seq.aln",
            format=gs.data_format(type="fasta"),
        ),
        scales=gs.scales(x=gs.Scale(domain=[190, 230], zoom={"extent": "data"})),
        description="A multiple sequence alignment with a sequence-logo overview.",
    )
    # Split each sequence into individual letters at numbered positions.
    .transform_flatten_sequence()
    .resolve_scale(x="shared")
    .resolve_axis(x="independent")
)