Pseudo-bulk maps¶
One cell’s contact map is far too sparse to call compartments, domains or loops; summed over the cells of a
group — a cell type, a cluster, an age, any partition of the cells — the maps become bulk-like pseudo-bulk
maps that the map callers read. uc.tl.pseudobulk (uchrom.fea.pseudobulk) sums
the per-cell maps linked to the cells per group, writes one ICE-balanced .cool per group and links it to
the ChromData as pseudobulk.<group>:
import uchrom as uc
import uchrom.datasets as ds
cd = ds.load("dschic_aging_cortex") # atlas store over HTTP: 32,777 cells, 1 Mb maps embedded
summary = uc.tl.pseudobulk(cd, "cell_type") # one map per cell type: group, n_cells, n_contacts, key, path
uc.tl.call_compartments(cd, method="eig", contacts="pseudobulk.Microglial cells", phasing=...)
Task |
API |
|---|---|
one map per group: a |
|
only some cells or groups; coarser bins |
|
random sets of cells with the same number of contacts per group (matched depth, replicates, a depth series) |
|
cis contacts only (compartments, domains, loops are cis) |
|
where the maps go |
|
the maps afterwards |
|
structures on them |
|
The per-cell maps are read where they are: a linked .scool cell by cell, or — in an atlas store — the
embedded maps one chromosome partition at a time for all selected cells, added up by group as they stream
(no per-cell file is exported). On dscHi-C (32,777 cells, 2.6 G contacts, 1 Mb, over HTTP) the seven
cell-type maps take about 75 s with the contacts between chromosomes and about 30 s with trans=False, and
they sum pixel for pixel to the store’s own all-cells map.
Comparing groups: matched depth¶
Groups of cells rarely have the same number of contacts (the cell types of dscHi-C differ almost 20-fold),
and what is measured on a map depends on its depth: loop calls vanish in shallow maps, and the saddle
strength of a shallow map is inflated. Compare groups on random subsets of cells with the same number of
contacts, several disjoint subsets per group to see the sampling spread: sample_cells_by_depth draws them,
and one pseudobulk call with its set column makes them all in one pass over the maps:
from uchrom.fea import sample_cells_by_depth
cis = cd.cells["n_contacts"] * (1 - cd.cells["frac_trans"]) # the maps below hold cis contacts only
sets = sample_cells_by_depth(cd, ["cell_type", "age"], n_contacts=5e6, replicates=3, contacts=cis)
uc.tl.pseudobulk(cd, sets["set"], trans=False, key_prefix="subset") # one map per set: subset.<set>
On dscHi-C at 1 Mb this shows the cell-type differences in compartment strength at matched depth (neurons lowest, microglia highest), while the strength of a map’s own eigenvector rises below ~20 M cis contacts (+15 % at 2 M).
At bulk depth, structures are called; below it, known loops are scored by pileup (uc.tl.pileup): 24 deep
GM12878 single cells (about 1 % of a bulk map’s depth) give an APA of 4.96 for the published GM12878 loops
where HiCCUPS calls 12 loops (benchmarks/hic_structures/README.md).
Tutorial¶
API: uchrom.fea (pseudobulk, pileup), compartments.