Select genes for follow-up

Download the notebook

Scroll to zoom and drag to brush a region, then click Download selected genes to save a CSV. Points grow as you zoom in.

Loading chart…

The table updates as you drag. It shows up to 20 rows; export includes all selected genes, with Ensembl IDs, fold changes, and p-values. Reloading clears the selection. Use the notebook above to access it in Python as the pandas table selected_genes.

Brushing selects candidates; it does not run a new statistical test.

Code

The chart and Python hooks below come directly from the downloadable notebook. The web demo uses JavaScript for the same interactions; Python hooks run in a notebook.

Chart specification (Python)
import genome_spy as gs
from genome_spy.datasets._airway import airway_differential_expression

# The dataset helper prepares the statistics in Python.
genes, domains = airway_differential_expression()
brush = gs.selection_interval("brush", encodings=["x", "y"], on="mousedown")
chart = (
    gs.Chart(genes)
    .mark_point(size=gs.expr("min(18 * pow(zoomLevel(), 0.75), 180)"), opacity=0.65)
    .encode(
        x=gs.X("log2fc:Q")
        .scale(domain=domains["volcano_x"], zoom=True)
        .title("log2 fold change"),
        y=gs.Y("neglog10_pvalue_plot:Q")
        .scale(domain=domains["volcano_y"], zoom=True)
        .title("−log10 p-value"),
        color=gs.when(brush).then(gs.value("#bf593b")).otherwise(gs.value("#adb7c2")),
        tooltip=["ensgene:N", "log2fc:Q", "padj:Q"],
    )
    .add_params(brush)
    .properties(width=760, height=360)
)
Python hooks and controls
import asyncio
import html
from datetime import datetime
from pathlib import Path

import ipywidgets as widgets
from IPython.display import FileLink, display

# Rerunning starts a fresh session.
if old_task := globals().get("connection_task"):
    old_task.cancel()
if old_widget := globals().get("widget"):
    old_widget.close()

widget = chart.widget(inline=True, controls=False)
selected_genes = genes.iloc[:0].copy()
display(widget)
status = widgets.HTML("Connecting…")
table = widgets.HTML(layout=widgets.Layout(max_height="240px", overflow="auto"))
export_button = widgets.Button(
    description="Export selected genes",
    disabled=True,
    layout=widgets.Layout(width="200px"),
)
export_output = widgets.Output()


def select_rows(snapshot):
    intervals = snapshot["intervals"]
    x, y = intervals.get("x"), intervals.get("y")
    if not snapshot["active"] or x is None or y is None:
        return genes.iloc[:0].copy()
    # Match the plotted coordinates, including the prepared y clipping.
    return genes.loc[
        genes.log2fc.between(*sorted(x))
        & genes.neglog10_pvalue_plot.between(*sorted(y))
    ].copy()


def show_selection(snapshot):
    global selected_genes
    selected_genes = select_rows(snapshot)
    status.value = f"{len(selected_genes)} genes selected. Showing up to 20 rows."
    columns = ["ensgene", "log2fc", "pvalue", "padj"]
    table.value = selected_genes[columns].head(20).to_html(index=False, escape=True)
    export_button.disabled = selected_genes.empty


def export_genes(button):
    if selected_genes.empty:
        return
    path = Path(f"selected-genes.{datetime.now():%Y%m%d-%H%M%S-%f}.csv")
    selected_genes[["ensgene", "log2fc", "pvalue", "padj"]].to_csv(
        path, index=False, mode="x"
    )
    with export_output:
        export_output.clear_output()
        display(FileLink(str(path)))


async def connect():
    global api, selection, stop
    try:
        async with asyncio.timeout(30):
            api = await widget.get_embed_api()
            selection = await api.params.get_selection("brush")
            stop = await selection.subscribe(show_selection)
            show_selection(await selection.get_value())
    except Exception as error:
        status.value = html.escape(
            f"Connection failed: {type(error).__name__}: {error}"
        )


export_button.on_click(export_genes)
display(widgets.VBox([status, export_button, table, export_output]))
connection_task = asyncio.create_task(connect())

Data use and provenance

Himes et al. airway RNA-seq (GSE52778), using the packaged Bioconnector workshop counts (CC BY-NC-SA 4.0). The package computes paired log-count tests and Benjamini–Hochberg adjusted p-values in Python; GenomeSpy plots the results. This is an illustrative analysis, not DESeq2. See data notices.