gtars.lola
Python bindings for gtars-lola — LOLA (Locus Overlap Analysis) enrichment testing. Given a user set of regions, a universe (background), and a region database, computes Fisher's exact test enrichment of the user set against every database region set.
See the Rust gtars-lola page for the full statistical reference (Fisher's exact test, CMLE odds ratio, contingency table semantics, R LOLA compatibility notes). This page is a Python-focused reference.
End-to-end workflow
Section titled “End-to-end workflow”from gtars.models import RegionSetfrom gtars.lola import RegionDB, run_lola
# 1. Load the region database from a LOLA-format folderdb = RegionDB.from_folder( "LOLACore/hg38", collections=None, # None = all; or pass a list of collection names limit=None, # None = all files; or pass a per-collection cap)
# 2. Prepare user sets and universe as lists of (chr, start, end) tuplespeaks = RegionSet("peaks.bed")universe = RegionSet("universe.bed")
def to_tuples(rs): return [(r.chr, r.start, r.end) for r in rs]
user_sets = [to_tuples(peaks)] # list of user sets; each is a list of tuplesuniverse_tuples = to_tuples(universe)
# 3. Run the enrichmentresults = run_lola( user_sets, universe_tuples, db, min_overlap=1, direction="enrichment", # or "depletion")
# results is a column-oriented dict — DataFrame-friendlyimport pandas as pddf = pd.DataFrame(results)df = df.sort_values("pValueLog", ascending=False)print(df.head(20))The 4-step workflow mirrors R LOLA: load database → convert inputs → run enrichment → pandas-ify.
RegionDB
Section titled “RegionDB”Wraps a gtars-lola RegionDB — an IGD overlap index plus original region sets plus per-file annotations, loaded from the standard LOLA directory layout:
db_path/├── collection1/│ ├── collection.txt # collector, date, source, description (TSV/CSV)│ ├── index.txt # per-file annotations (TSV/CSV)│ └── regions/│ ├── file1.bed│ └── file2.bed├── collection2/│ └── …RegionDB.from_folder
Section titled “RegionDB.from_folder”RegionDB.from_folder(db_path, collections=None, limit=None) -> RegionDBdb_path— path to the LOLA database root.collections— optional list of collection names to include (others are skipped).limit— optional per-collection file cap (useful for smoke tests).
collection.txt and index.txt are auto-detected TSV or CSV. Recognized index.txt columns: filename, cellType, description, tissue, dataSource, antibody, treatment. Unknown columns are ignored; missing optional columns become None.
RegionDB.from_bed_files
Section titled “RegionDB.from_bed_files”Build a database from a list of BED file paths without the full LOLA layout:
db = RegionDB.from_bed_files( ["file1.bed", "file2.bed", "file3.bed"], filenames=None, # optional; defaults to basenames)Useful for ad-hoc analyses where you have a list of peak files but don't want to set up a collection directory.
Accessors
Section titled “Accessors”db.num_region_sets # int — total files in the database
db.list_region_sets() # list[str] — all filenamesdb.list_region_sets(collections=["enc3"]) # filtered
# Extract region sets as a gtars.models.RegionSetList (names populated from filenames)rsl = db.get_region_sets() # all setsrsl = db.get_region_sets(indices=[0, 5, 12])
# Annotations as lists of dictsdb.region_anno # per-file dicts with filename/cellType/description/tissue/...db.collection_anno # per-collection dicts with collectionname/collector/date/source/descriptionget_region_sets() returns a gtars.models.RegionSetList, which has names populated from the database filenames. This is the one path in Python where RegionSetList.names is non-None — normal RegionSetList(...) construction can't set names.
Running enrichment
Section titled “Running enrichment”run_lola
Section titled “run_lola”run_lola( user_sets: list[list[tuple[str, int, int]]], universe: list[tuple[str, int, int]], region_db: RegionDB, min_overlap: int = 1, direction: str = "enrichment",) -> dictParameters:
user_sets— a list of user sets, where each user set is a list of(chr, start, end)tuples. Pass one-element list[peaks_tuples]for a single-user-set analysis.universe— a single list of(chr, start, end)tuples representing the background.region_db— aRegionDB.min_overlap— minimum bp overlap to count as overlapping (default 1).direction—"enrichment"(default, P(X ≥ a), alternative "greater") or"depletion"(P(X ≤ a), alternative "less"). The strings"greater"/"less"are accepted as aliases.
Returns a column-oriented dict with these parallel lists (one entry per (user_set, db_set) pair):
| key | type | meaning |
|---|---|---|
userSet | list[int] | 0-based user-set index |
dbSet | list[int] | 0-based db-set index |
collection | list[str | None] | from index.txt |
pValueLog | list[float] | -log10(p) from Fisher's exact test, capped at ~322 |
oddsRatio | list[float] | CMLE odds ratio (matches R fisher.test()$estimate) |
support | list[int] | overlap count between user set and db set — the contingency a |
rnkPV, rnkOR, rnkSup | list[int] | 1-based per-metric ranks within the user set |
maxRnk | list[int] | max of the three ranks |
meanRnk | list[float] | mean of the three ranks |
b, c, d | list[int] | signed contingency values (can be negative if user set extends outside universe) |
qValue | list[float | None] | BH-adjusted p-value (applied automatically inside run_lola) |
description, cellType, tissue, antibody, treatment, dataSource | list[str | None] | from index.txt |
filename | list[str] | db set file name |
size | list[int] | number of regions in the db set |
The Python run_lola already calls annotate_results + apply_fdr_correction internally, so qValue and the annotation columns come back populated — you don't need to post-process.
Rows are sorted by descending pValueLog, then ascending meanRnk (matching R LOLA output order).
Universe preparation
Section titled “Universe preparation”LOLA results are only meaningful when your universe contains your user sets. Three helpers for checking and shaping the universe:
check_universe
Section titled “check_universe”from gtars.lola import check_universe
report = check_universe( user_sets, # list[list[tuple[str, int, int]]] universe_tuples, # list[tuple[str, int, int]])
# report is a dict with parallel lists:report["userSet"] # list[int]report["totalRegions"] # list[int]report["regionsInUniverse"] # list[int]report["coverage"] # list[float] — 0.0 to 1.0report["manyToMany"] # list[int] — number of user regions overlapping >1 universe regionreport["warnings"] # list[str] — free-form warning messagesWarnings fire when coverage is under 50% (severe) or under 90% (moderate), and when any user region overlaps more than one universe region.
redefine_user_sets
Section titled “redefine_user_sets”Rewrite each user set in terms of universe regions — for every user region, find the universe regions it overlaps and emit those as the new user set. Eliminates many-to-many mapping artifacts. This is the Python equivalent of R LOLA's redefineUserSets().
from gtars.lola import redefine_user_sets
redefined = redefine_user_sets(user_sets, universe_tuples)# redefined is list[list[tuple[str, int, int]]] — same shape as input, but rewritten
# Now run enrichment on the redefined versionresults = run_lola(redefined, universe_tuples, db)build_restricted_universe
Section titled “build_restricted_universe”For differential enrichment analysis — build a universe that is exactly the union of all user sets, disjoined into non-overlapping pieces. This is R LOLA's disjoin(unlist(userSets)).
from gtars.lola import build_restricted_universe
restricted = build_restricted_universe(user_sets)# restricted: list[tuple[str, int, int]]
# Use it as the universe in a differential runresults = run_lola(user_sets, restricted, db)Complete example with pandas
Section titled “Complete example with pandas”import pandas as pdfrom gtars.models import RegionSetfrom gtars.lola import RegionDB, run_lola, check_universe, redefine_user_sets
# Convert a RegionSet to the tuple form lola expectsdef to_tuples(rs): return [(r.chr, r.start, r.end) for r in rs]
# Load inputsdb = RegionDB.from_folder("LOLACore/hg38")peaks_a = to_tuples(RegionSet("condition_a.bed"))peaks_b = to_tuples(RegionSet("condition_b.bed"))universe = to_tuples(RegionSet("universe.bed"))
user_sets = [peaks_a, peaks_b]
# Universe sanity checkreport = check_universe(user_sets, universe)for us, cov, mm in zip( report["userSet"], report["coverage"], report["manyToMany"]): print(f"user set {us}: {cov:.1%} coverage, {mm} many-to-many")for w in report["warnings"]: print(f" ⚠ {w}")
# Optional: eliminate many-to-many artifactsuser_sets = redefine_user_sets(user_sets, universe)
# Run enrichmentresults = run_lola(user_sets, universe, db, direction="enrichment")
df = pd.DataFrame(results)print(df.shape) # (n_user_sets * n_db_sets, 23)
# Top 10 per user setfor us in df["userSet"].unique(): top = ( df[df["userSet"] == us] .sort_values("pValueLog", ascending=False) .head(10) ) print(f"\n=== User set {us} — top 10 ===") print(top[["filename", "cellType", "pValueLog", "oddsRatio", "support", "qValue"]])See also
Section titled “See also”gtars.models—RegionSet,RegionSetList, and the types returned byRegionDB.get_region_sets().- gtars-lola — full Rust API reference, including contingency table math, p-value computation via hypergeometric survival function, CMLE odds ratio, and the R LOLA TSV output format.