Select genes for follow-up¶
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.