PDB, PubMed & Enrichment

11 builtins for querying protein structures from the RCSB PDB, searching biomedical literature via PubMed, and running gene set enrichment analysis. All functions return structured records or tables ready for piping.

PDB Structures

pdb_entry(pdb_id) String → Record

Fetches an entry from the RCSB Protein Data Bank. Returns a record with structural metadata including the title, experimental method, resolution, source organism, and release date.

# requires: internet connection (PDB API)
let hb = pdb_entry("4HHB")
println(hb.title)         # "THE CRYSTAL STRUCTURE OF HUMAN DEOXYHAEMOGLOBIN..."
println(hb.method)        # "X-RAY DIFFRACTION"
println(hb.resolution)    # 1.74
println(hb.organism)      # "Homo sapiens"
println(hb.release_date)  # "1984-07-17"
pdb_chains(pdb_id) String → List

Lists the polymer chains in a structure. Each entry is a record with chain ID, entity ID, and description.

# requires: internet connection (PDB API)
let chains = pdb_chains("4HHB")
chains |> each(|c| println(str(c.entity_id) + ": " + c.description))
# 1: Hemoglobin subunit alpha
# 2: Hemoglobin subunit beta
pdb_entity(pdb_id, entity_id) String, Int → Record

Fetches detailed information about a specific entity within a structure. Returns a record with the entity description, type, molecular weight, chain assignments, and organism source.

# requires: internet connection (PDB API)
let alpha = pdb_entity("4HHB", 1)
println(alpha.description)   # "Hemoglobin subunit alpha"
println(alpha.entity_type)   # "polypeptide(L)"
println(len(alpha.sequence)) # 141
pdb_sequence(pdb_id, entity_id) String, Int → String

Retrieves the amino acid sequence for a given entity. Returns a plain protein sequence string suitable for alignment or further analysis.

# requires: internet connection (PDB API)
let seq = pdb_sequence("4HHB", 1)
println(len(seq))       # 141 residues
println(seq[0..20])     # "VLSPADKTNVKAAWGKVGAH"

Example: Compare Hemoglobin Chains

Fetch both hemoglobin subunit sequences from PDB entry 4HHB and compare their lengths and composition.

# requires: internet connection (PDB API)
# Fetch chain information
let chains = pdb_chains("4HHB")
let entities = chains
  |> map(|c| c.entity_id)
  |> unique()

# Get sequences for each unique entity
let seqs = entities |> map(|eid| {
  entity_id: eid,
  info: pdb_entity("4HHB", eid),
  sequence: pdb_sequence("4HHB", eid),
})

# Compare subunits
seqs |> each(|s| {
  let seq = s.sequence
  println(s.info.description)
  println("  Length:   " + str(len(seq)) + " residues")
  println("  His pct: " + str(round(count_if(chars(to_string(seq)), |aa| aa == "H") / len(seq) * 100, 1)) + "%")
})

# Output:
#   HEMOGLOBIN ALPHA CHAIN
#     Length:   141 residues
#     Chains:  A, C
#     His pct: 7.1%
#   HEMOGLOBIN BETA CHAIN
#     Length:   146 residues
#     Chains:  B, D
#     His pct: 6.2%

PubMed

pubmed_abstract(pmid) String → Record

Fetches the abstract and metadata for a single PubMed article. Returns a record with title, abstract, authors, journal, year, and doi.

# requires: internet connection (NCBI PubMed API)
# pubmed_abstract returns the MEDLINE record as text, not a parsed record.
let paper = pubmed_abstract("32553272")
println(paper |> lines() |> first())
# 1. Cell. 2020 Jul 9;182(1):145-161.e23. doi: 10.1016/j.cell.2020.05.021. Epub

# Pull out a field with a plain text search.
let title_line = paper |> lines() |> find(|l| starts_with(l, "Search-and-replace"))
println(title_line)

Example: Literature Review Pipeline

Search for recent papers on a topic, fetch their abstracts, and build a summary table for review.

# requires: internet connection (NCBI PubMed API)
# Search for recent gene therapy papers
let hits = pubmed_search("AAV gene therapy retinal dystrophy", 10)
println("Found " + str(hits.count) + " articles")

# Fetch abstracts for each result
let papers = hits.ids |> map(|pmid| pubmed_abstract(pmid))

# Each abstract is MEDLINE text, so a summary is built by reading lines rather
# than by field access. The citation line carries the journal, year and DOI.
let review = papers |> map(|p| {
  pmid: p |> lines() |> first(),
  citation: p |> lines() |> take(2) |> join(" "),
})

# Sort by year descending and display
review
  |> to_table()
  |> println()

# Export for reference management
review |> to_table() |> write_tsv("literature_review.tsv")

Enrichment Analysis

BioLang provides builtins for two standard gene set enrichment approaches: over-representation analysis (ORA) and gene set enrichment analysis (GSEA). Both work with gene sets in GMT format and return tables of significant terms.

read_gmt(path) String → Record

Reads a Gene Matrix Transposed (GMT) file. Each line defines one gene set as: set_name\tdescription\tgene1\tgene2\t.... Returns a record mapping set names to their gene lists.

# GMT format (tab-separated):
#   GO:0006915  Apoptotic process  BAX  BCL2  CASP3  CASP9  TP53
#   GO:0007049  Cell cycle         CDK1 CDK2  CCNB1  CCND1  RB1

let gene_sets = read_gmt("c5.go.bp.v2024.1.symbols.gmt")
println(len(keys(gene_sets)))  # number of gene sets loaded
println(gene_sets["GO:0006915"])  # ["BAX", "BCL2", "CASP3", "CASP9", "TP53"]
enrich(genes, gene_sets, background) List, Record, List → Table

Performs over-representation analysis (ORA) using the hypergeometric test. Tests whether each gene set is enriched in your gene list relative to the background. Returns a table with columns: term, overlap, set_size, p_value, fdr (Benjamini-Hochberg adjusted), and genes (the overlapping gene symbols). Rows are sorted by p-value ascending.

let de_genes = ["TP53", "BRCA1", "CDK2", "CASP3", "BAX", "BCL2", "MYC"]
let gene_sets = read_gmt("c5.go.bp.v2024.1.symbols.gmt")
let background = read_lines("all_expressed_genes.txt")

let ora = enrich(de_genes, gene_sets, background)
ora
  |> filter(|r| r.fdr < 0.05)
  |> println()

#  term           | overlap | set_size | p_value  | fdr     | genes
# ----------------+---------+----------+----------+---------+-----------------------
#  GO:0006915     | 4       | 312      | 1.2e-06  | 3.4e-04 | TP53,BAX,BCL2,CASP3
#  GO:0008219     | 3       | 198      | 8.7e-05  | 0.012   | TP53,BAX,CASP3
#  ...
gsea(ranked_genes, gene_sets) Table, Record → Table

Performs Gene Set Enrichment Analysis on a pre-ranked gene list. The ranked_genes table must have gene and score columns (e.g., log2 fold change or signed -log10(p)). Uses a permutation test (1000 permutations) to compute enrichment scores and significance. Returns a table with: term, es (enrichment score), nes (normalized ES), p_value, fdr, and leading_edge (the core enrichment genes).

# Prepare ranked gene list (e.g., from differential expression)
let de_results = tsv("de_results.tsv")
let ranked = de_results
  |> mutate("score", |r| r.log2fc * -log10(r.pvalue))
  |> select("gene", "score")
  |> sort_by(|r| -r.score)

let gene_sets = read_gmt("h.all.v2024.1.symbols.gmt")

let gsea_results = gsea(ranked, gene_sets)
gsea_results
  |> filter(|r| r.fdr < 0.25)
  |> println()

#  term                      | es    | nes   | p_value | fdr   | leading_edge
# ---------------------------+-------+-------+---------+-------+-------------------
#  HALLMARK_APOPTOSIS        | 0.62  | 2.14  | 0.0     | 0.002 | BAX,CASP3,TP53...
#  HALLMARK_P53_PATHWAY      | 0.58  | 2.01  | 0.001   | 0.008 | CDKN1A,MDM2,BAX...
#  HALLMARK_MYC_TARGETS_V1   | -0.45 | -1.87 | 0.002   | 0.015 | NOP56,RRP1,NPM1...

GMT Format

The Gene Matrix Transposed (GMT) format is a tab-delimited text file widely used by MSigDB, Enrichr, and other enrichment tools. Each line defines one gene set:

SET_NAME    description_or_url    GENE1    GENE2    GENE3    ...
HALLMARK_APOPTOSIS    https://www.gsea-msigdb.org/...    AIM2    ALKBH5    ANP32B    ...
HALLMARK_P53_PATHWAY    https://www.gsea-msigdb.org/...    AEN    ANKLE1    APC    ...

The first column is the set name, the second is a description (often a URL), and all remaining columns are gene symbols. BioLang’s read_gmt() discards the description column and maps set names to gene lists. Common GMT sources include the Molecular Signatures Database (MSigDB), Gene Ontology, KEGG, and Reactome exports.

Example: RNA-seq Enrichment Pipeline

A complete workflow: load differential expression results, run both ORA and GSEA, and export the significant pathways.

# Load differential expression results
let de = tsv("deseq2_results.tsv")
  |> filter(|r| !is_nan(r.padj))

# --- Over-Representation Analysis ---

# Select significant DE genes (padj < 0.05, |log2FC| > 1)
let sig_genes = de
  |> filter(|r| r.padj < 0.05 and abs(r.log2fc) > 1.0)
  |> map(|r| r.gene)

# All tested genes as background
let background = de |> map(|r| r.gene)

# Load GO Biological Process gene sets
let go_bp = read_gmt("c5.go.bp.v2024.1.symbols.gmt")

# Run ORA
let ora_results = enrich(sig_genes, go_bp, background)
let sig_ora = ora_results |> filter(|r| r.fdr < 0.05)
println("ORA: " + str(nrow(sig_ora)) + " significant GO terms")
sig_ora |> head(10) |> println()

# --- Gene Set Enrichment Analysis ---

# Rank all genes by signed significance
let ranked = de
  |> mutate("score", |r| sign(r.log2fc) * -log10(max(r.pvalue, 1e-300)))
  |> select("gene", "score")
  |> sort_by(|r| -r.score)

# Load Hallmark gene sets
let hallmarks = read_gmt("h.all.v2024.1.symbols.gmt")

# Run GSEA
let gsea_results = gsea(ranked, hallmarks)
let sig_gsea = gsea_results |> filter(|r| r.fdr < 0.25)
println("GSEA: " + str(nrow(sig_gsea)) + " significant hallmark pathways")

# Separate up- and down-regulated pathways
let up_paths = sig_gsea |> filter(|r| r.nes > 0) |> sort_by(|r| -r.nes)
let dn_paths = sig_gsea |> filter(|r| r.nes < 0) |> sort_by(|r| r.nes)

println("Upregulated pathways:")
up_paths |> each(|r| println("  " + r.term + " (NES=" + str(round(r.nes, 2)) + ")"))

println("Downregulated pathways:")
dn_paths |> each(|r| println("  " + r.term + " (NES=" + str(round(r.nes, 2)) + ")"))

# Export results
sig_ora |> write_tsv("ora_significant.tsv")
sig_gsea |> write_tsv("gsea_significant.tsv")

PDB Reference

BuiltinReturnsDescription
pdb_entry(pdb_id)RecordFetch structure metadata (title, method, resolution, organism, release_date)
pdb_search(query)ListSearch PDB by keyword; returns list of PDB IDs
pdb_chains(pdb_id)ListList polymer chains (entity_id, entity_type, description, sequence)
pdb_entity(pdb_id, entity_id)RecordEntity detail (description, type, weight, chains)
pdb_sequence(pdb_id, entity_id)StringAmino acid sequence for an entity

PubMed Reference

BuiltinReturnsDescription
pubmed_search(query, max?)RecordSearch PubMed; returns {count, ids}
pubmed_abstract(pmid)RecordFetch article metadata and abstract text

Enrichment Reference

BuiltinReturnsDescription
read_gmt(path)RecordLoad GMT file as {set_name: [genes]} record
enrich(genes, sets, bg)TableOver-representation analysis (hypergeometric + BH FDR)
gsea(ranked, sets)TableGene Set Enrichment Analysis (permutation test, 1000 perms)