Genomic intervals¶
Composition-matched backgrounds, motif proximity to genomic anchors, hits in genomic coordinates, and ranges passed to and from PyRanges and polars-bio. Positions count from 0 and leave the end out, as in BED files, PyRanges and Python slices, and ranges can be given as DataFrames or tuples. PyRanges objects, pandas DataFrames, including those with PyRanges' Chromosome, Start and End columns, and polars-bio frames can be given wherever ranges are taken; polars-bio frames marked as counting from 1, as polars-bio makes them by default, are converted.
match_bkg
¶
match_bkg(
sequences: Sequence[str] | Mapping[str, str],
universe: Sequence[str]
| Mapping[str, str]
| None = None,
genome: Mapping[str, str] | None = None,
exclude=None,
n_candidates: int | None = None,
chromosomes: Sequence[str] | None = None,
n_per_target: int = 1,
n_bins_gc: int = 20,
n_bins_length: int = 10,
covariates=None,
universe_covariates=None,
bigwig=None,
target_ranges=None,
n_bins_covariates: int | Mapping[str, int] = 10,
bin_type_covariates: str
| Mapping[str, str] = "quantile",
unique: bool = True,
seed: int | None = None,
return_indices: bool = False,
)
Draw background sequences matched to targets on GC content and length.
HOMER-style matching: the universe is binned by GC fraction into
n_bins_gc equal-width bins, by length into n_bins_length
quantile bins and by any covariates, and each target draws
n_per_target sequences from its own bin. When the bin runs short,
neighbouring bins are searched ring by ring, up to two rings or one per
axis; after that
the remaining draws repeat sequences from the bins searched, with a
warning. With unique, each universe sequence is otherwise drawn at
most once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
universe
|
Sequence[str] | Mapping[str, str] | None
|
Candidate background sequences, as a list or a mapping from names. |
None
|
genome
|
Mapping[str, str] | None
|
Instead of |
None
|
covariates
|
Extra values to match on, one row per target and per universe
sequence: DataFrames or mappings with the same columns, or arrays.
|
None
|
|
universe_covariates
|
Extra values to match on, one row per target and per universe
sequence: DataFrames or mappings with the same columns, or arrays.
|
None
|
|
bigwig
|
With |
None
|
|
target_ranges
|
With |
None
|
|
return_indices
|
bool
|
Return a DataFrame of the matches instead, with 0-based |
False
|
Returns:
| Type | Description |
|---|---|
list[str] | dict[str, str]
|
The matched sequences, |
Examples:
>>> import universalmotif as um
>>> targets = um.example_sequences("targets")
>>> controls = um.example_sequences("controls")
>>> bkg = um.match_bkg(targets, controls, seed=1)
>>> len(bkg), list(bkg)[:2]
(100, ['control_047', 'control_090'])
With return_indices, the matches can be checked:
>>> idx = um.match_bkg(targets, controls, seed=1, return_indices=True)
>>> idx.select("target_i", "target_gc", "universe_i", "universe_gc").row(0)
(0, 0.339, 46, 0.344)
motif_proximity
¶
motif_proximity(
hits: DataFrame | None = None,
anchors=None,
motifs: Motif | Iterable[Motif] | None = None,
sequences: Mapping[str, str] | None = None,
universe=None,
method: str = "binomial",
count: str = "hits",
orientation: str = "undirected",
scan_pvalue: float = 0.0001,
rc: bool = True,
dist_thresholds: Sequence[int] | None = None,
n_perm: int = 1000,
perm_per_chrom: bool = True,
ignore_strand: bool = True,
qvalue: float = 0.1,
seed: int | None = None,
nthreads: int = 1,
) -> pl.DataFrame
Test whether motif hits sit closer to genomic anchors than chance.
The genome-wide counterpart of motif_peaks(). Each hit counts at
its middle position. For each distance d in dist_thresholds, the
near zone is every scanned position within d of an anchor; the
default distances are 10, 25, 50, 100 and so on, up to half the smallest
universe region. Three nulls are available:
"binomial"
Hits uniform over the universe, so the number in the near zone is
binomial; the best distance is Bonferroni-corrected for the
distances tried. Fast, but blind to composition bias.
"permutation"
Anchors are placed at random in the universe n_perm times,
keeping each on its own chromosome with perm_per_chrom. This
absorbs composition and mappability bias, and is reproducible with
seed.
"ks"
A one-sided Kolmogorov-Smirnov test of each hit's distance to the
nearest anchor, for undirected zones and count="hits" only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hits
|
DataFrame | None
|
Hits from |
None
|
anchors
|
The anchor coordinates, as a DataFrame with |
None
|
|
universe
|
The regions scanned, as a range table or as a mapping from
chromosome names to lengths for whole chromosomes. Required with
|
None
|
|
count
|
str
|
|
'hits'
|
orientation
|
str
|
|
'undirected'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
A DataFrame of motifs with q-values up to |
Examples:
Treating each example target as a chromosome with an anchor at its middle, the W-box hits sit closer to the anchors than chance:
>>> import universalmotif as um
>>> motifs = [um.example_motifs()[i] for i in (3, 5)]
>>> targets = um.example_sequences("targets")
>>> anchors = [(name, 499, 500) for name in targets]
>>> res = um.motif_proximity(anchors=anchors, motifs=motifs, sequences=targets, nthreads=1)
>>> res.select("motif", "nhits", "best_dist", "hits_near").row(0)
('W-box', 30, 250, 27)
>>> f"{res['pvalue'][0]:.1e}"
'2.2e-05'
genomic_hits
¶
genomic_hits(hits: DataFrame, ranges=None) -> pl.DataFrame
Hits from scanning genomic ranges, moved to genomic coordinates.
scan_sequences() counts positions
from the start of each sequence scanned. When those sequences are
ranges taken from a genome, such as peaks read with
extract_ranges(), this puts the hits
back on the genome: each gets a chrom column, and start, end
and strand become positions on the chromosome's forward strand,
counted from 0 with the end left out. Hits in ranges that were reverse
complemented have their strand flipped. The other columns are kept, so
sequence still names the range each hit came from.
By default the ranges are read from the sequence names, which must be
chrom:start-end with (-) after ranges on the minus strand, as
extract_ranges() and
match_bkg() name them. Otherwise
ranges gives them in the order of the sequences scanned, as a range
table or (chrom, start, end[, strand]) tuples, and hits are matched to
them by sequence_i.
Hits in overlapping ranges are reported once for each, so the same site
can appear more than once, and
.unique(["motif", "chrom", "start", "end", "strand"], maintain_order=True)
keeps one of each.
Examples:
>>> import universalmotif as um
>>> genome = {"chr1": "CCCCCTGACTCACCCCCCCCCC"}
>>> peaks = um.extract_ranges(genome, [("chr1", 2, 15), ("chr1", 2, 15, "-")])
>>> hits = um.scan_sequences(um.example_motifs()[1], peaks, pvalue=1e-3, rc=False)
>>> hits.select("sequence", "start", "end", "strand", "match").rows()
[('chr1:2-15', 2, 11, '+', 'CTGACTCAC'), ('chr1:2-15(-)', 2, 11, '+', 'GTGAGTCAG')]
>>> um.genomic_hits(hits).select("chrom", "start", "end", "strand").rows()
[('chr1', 4, 13, '+'), ('chr1', 4, 13, '-')]
to_pyranges
¶
to_pyranges(df: DataFrame)
A table of ranges, such as hits or peaks, as a PyRanges object.
The chromosome column, which can be any of chrom, seqnames,
sequence, chr or chromosome, becomes Chromosome, and
start, end and strand become Start, End and
Strand. Positions are 0-based and half-open in both, so they are
kept as they are. Other columns are kept too. PyRanges objects can be
given back to any function that takes ranges.
Needs the pyranges extra.
Examples:
>>> import universalmotif as um
>>> gr = um.to_pyranges(um.genomic_hits(hits))
to_polars_bio
¶
to_polars_bio(df: DataFrame) -> pl.DataFrame
A table of ranges, such as hits or peaks, ready for polars-bio.
polars-bio works on polars DataFrames with chrom, start and
end columns, and keeps their coordinate system as metadata. This
renames the chromosome column to chrom if it has another name, such
as the sequence of hits, moves it first, and marks the positions as
0-based, as they are throughout this package, so that polars-bio's
range operations treat them correctly. polars-bio frames can be given
back to any function that takes ranges.
Needs the polars-bio extra.
Examples:
>>> import universalmotif as um
>>> import polars_bio as pb
>>> hits = um.to_polars_bio(um.genomic_hits(hits))
>>> pb.overlap(hits, um.to_polars_bio(peaks))