Skip to content

Motifs

The Motif class, and the functions for creating, converting and modifying motifs. Motifs are immutable, so every function here returns new motifs rather than changing the ones it is given, and most also accept a list of motifs and return a list. The column functions at the end work on a single position of a motif, as a list or array of numbers.

Motif

A sequence motif.

Motifs are immutable. The functions in the package, such as convert_type() or trim_motifs(), return new motifs, and replace() returns a copy with some of the metadata changed. New motifs are made with create_motif() or read from a file.

Examples:

>>> import universalmotif as um
>>> motif = um.example_motifs()[3]
>>> print(motif)
       Motif name:   G-box
           Family:   bZIP
         Organism:   synthetic
         Alphabet:   DNA
             Type:   PCM
          Strands:   +-
         Total IC:   7.9
      Pseudocount:   1
        Consensus:   NCACGTGN
     Target sites:   71

   N  C  A  C  G  T  G  N
A 17  6 62  0  2  0 12 27
C  9 62  3 69  1 15  1 10
G 23  1  2  0 64  3 58 21
T 22  2  4  2  4 53  0 13

The matrix has one row per letter and one column per position:

>>> motif.letters, motif.matrix.shape
(['A', 'C', 'G', 'T'], (4, 8))
>>> motif.matrix[:, 2].tolist()
[62.0, 3.0, 2.0, 4.0]

Metadata is changed by making a copy:

>>> renamed = motif.replace(name="G-box copy", organism=None)
>>> renamed.name, renamed.organism, motif.name
('G-box copy', None, 'G-box')

matrix property

matrix: NDArray[float64]

Read-only array of shape (letters, positions).

consensus property

consensus: str | None

One IUPAC letter per position, or None for custom alphabets. Gaps are not shown, so it has one letter per column even for a gapped motif, whose printout marks the gaps with dots. For a CWM each letter describes the shape of its column, however small its contributions.

strand property

strand: str

"+", "-" or "+-". Only DNA and RNA motifs have a reverse strand, so for other alphabets it has no effect.

replace

replace(
    *,
    name: str = ...,
    altname: str | None = ...,
    family: str | None = ...,
    organism: str | None = ...,
    nsites: float | None = ...,
    pseudocount: float = ...,
    bkg: Sequence[float] | Mapping[str, float] = ...,
    bkgsites: float | None = ...,
    strand: str = ...,
    pval: float | None = ...,
    qval: float | None = ...,
    eval: float | None = ...,
    extrainfo: Mapping[str, str] | None = ...,
) -> Motif

Return a copy with some metadata replaced.

The matrix, type and alphabet cannot be changed this way. Changing the pseudocount, background or nsites recomputes the total IC and the consensus.

create_motif

create_motif(
    input: object = None,
    alphabet: str | None = None,
    type: MotifType | None = None,
    *,
    letters: Sequence[str] | None = None,
    name: str = "motif",
    pseudocount: float = 0.0,
    bkg: Sequence[float]
    | Mapping[str, float]
    | None = None,
    nsites: int | None = None,
    altname: str | None = None,
    family: str | None = None,
    organism: str | None = None,
    bkgsites: float | None = None,
    strand: str = "+-",
    pval: float | None = None,
    qval: float | None = None,
    eval: float | None = None,
    extrainfo: Mapping[str, str] | None = None,
    seed: int | None = None,
) -> Motif

Create a motif.

Parameters:

Name Type Description Default
input object

What to build the motif from:

  • None or an integer: a random motif of that width, 10 by default. Each column is drawn from a Dirichlet distribution parameterized by the background.
  • A string: a consensus sequence. IUPAC ambiguity codes are allowed for DNA and amino acids.
  • A sequence of equal-length strings: aligned sites, which are counted. nsites then defaults to the number of strings.
  • A 2-D array of shape (letters, positions). Give the row letters with letters, which are sorted with the rows reordered to match, or give alphabet and supply the rows in alphabet order. The type is detected from the values.
None
alphabet str | None

"DNA", "RNA", "AA", or a string of letters for a custom alphabet. Guessed from the input when omitted.

None
type MotifType | None

Type of the returned motif, a PPM by default. A matrix with negative values needs its type given, as "PWM" or "CWM", since the two are read differently.

None
letters Sequence[str] | None

Row letters of a matrix input.

None
name str

Motif metadata.

'motif'
altname str

Motif metadata.

'motif'
family str

Motif metadata.

'motif'
organism str

Motif metadata.

'motif'
nsites str

Motif metadata.

'motif'
bkgsites str

Motif metadata.

'motif'
strand str

Motif metadata.

'motif'
pval str

Motif metadata.

'motif'
qval str

Motif metadata.

'motif'
eval str

Motif metadata.

'motif'
extrainfo str

Motif metadata.

'motif'
pseudocount float

Pseudocount used when converting between types.

0.0
bkg Sequence[float] | Mapping[str, float] | None

Background frequencies, either one value per letter in alphabet order or a mapping from letters to values. Uniform by default.

None
seed int | None

Random seed, for random motifs only.

None

Examples:

A motif from a consensus sequence:

>>> import universalmotif as um
>>> motif = um.create_motif("TATAWAW", name="TATA", nsites=10)
>>> print(motif)
       Motif name:   TATA
         Alphabet:   DNA
             Type:   PPM
          Strands:   +-
         Total IC:   12
      Pseudocount:   0
        Consensus:   TATAWAW
     Target sites:   10

  T A T A   W A   W
A 0 1 0 1 0.5 1 0.5
C 0 0 0 0 0.0 0 0.0
G 0 0 0 0 0.0 0 0.0
T 1 0 1 0 0.5 0 0.5

A count matrix from aligned sites:

>>> import universalmotif as um
>>> motif = um.create_motif(["TATAAA", "TATAAT", "TATATA"], type="PCM")
>>> motif.nsites, motif.consensus
(3.0, 'TATAWW')
>>> motif.matrix.tolist()
[[0.0, 3.0, 0.0, 3.0, 2.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [3.0, 0.0, 3.0, 0.0, 1.0, 1.0]]

convert_type

convert_type(
    motifs: Motif,
    type: MotifType,
    pseudocount: float | None = ...,
    relative_entropy: bool = ...,
) -> Motif
convert_type(
    motifs: Iterable[Motif],
    type: MotifType,
    pseudocount: float | None = ...,
    relative_entropy: bool = ...,
) -> list[Motif]
convert_type(
    motifs, type, pseudocount=None, relative_entropy=False
)

Convert motifs to another type.

Parameters:

Name Type Description Default
motifs

A motif or an iterable of motifs.

required
type

"PCM", "PPM", "PWM" or "ICM". Probability-based motifs cannot be turned into "CWM" contribution weight matrices, so asking for that is an error unless the motif already is one.

required
pseudocount

Replaces each motif's own pseudocount.

None
relative_entropy

For ICMs, compute heights as relative entropy against the background instead of Shannon information content.

False
Notes

A motif without nsites is treated as having 100 sites when counts are needed. The consensus and total IC are kept from the input motif.

Examples:

>>> import universalmotif as um
>>> ap1 = um.example_motifs()[1]
>>> ap1.type, ap1.consensus
('PCM', 'NTGASTCAN')
>>> ppm = um.convert_type(ap1, "PPM")
>>> ppm.matrix[:, 1].round(3).tolist()
[0.065, 0.025, 0.005, 0.905]
>>> [m.type for m in um.convert_type(um.example_motifs()[:3], "PWM")]
['PWM', 'PWM', 'PWM']

summarize_motifs

summarize_motifs(
    motifs, na_rm: bool = True
) -> pl.DataFrame

Summarize motifs as a polars DataFrame, one row per motif.

With na_rm=True, columns that are empty for every motif are dropped.

Examples:

>>> import universalmotif as um
>>> summary = um.summarize_motifs(um.example_motifs())
>>> summary.shape
(12, 8)
>>> summary.columns
['name', 'family', 'organism', 'consensus', 'alphabet', 'strand', 'icscore', 'nsites']
>>> summary["consensus"].to_list()[:4]
['KTATAWAWRN', 'NTGASTCAN', 'TGACGTCA', 'NCACGTGN']
>>> [round(x, 2) for x in summary["icscore"].to_list()[:4]]
[8.83, 9.65, 10.73, 7.9]

filter_motifs

filter_motifs(
    motifs: Iterable[Motif],
    name: str | Iterable[str] | None = None,
    altname: str | Iterable[str] | None = None,
    family: str | Iterable[str] | None = None,
    organism: str | Iterable[str] | None = None,
    width: int | None = None,
    alphabet: str | Iterable[str] | None = None,
    type: str | Iterable[str] | None = None,
    icscore: float | None = None,
    nsites: float | None = None,
    strand: str | Iterable[str] | None = None,
    pval: float | None = None,
    qval: float | None = None,
    eval: float | None = None,
    extrainfo: Mapping[str, str] | None = None,
) -> list[Motif]

Keep the motifs that pass every filter given.

Text filters keep motifs whose value is one of those given. width, icscore and nsites are minimums, and pval, qval and eval maximums; motifs without the value are dropped. extrainfo keeps motifs with every one of its entries.

Examples:

>>> import universalmotif as um
>>> motifs = um.example_motifs()
>>> [m.name for m in um.filter_motifs(motifs, family="bZIP")]
['AP-1', 'CRE', 'G-box']
>>> [m.name for m in um.filter_motifs(motifs, family=["bZIP", "ETS"], icscore=10)]
['CRE', 'ETS']

normalize

normalize(motifs)

Re-apply each motif's pseudocount by converting to counts and back.

A pseudocount of zero is replaced by 1. PCMs are returned unchanged.

Examples:

>>> import universalmotif as um
>>> motif = um.create_motif(["TGACGTCA"] * 10)
>>> motif.pseudocount, motif.matrix[:, 0].tolist()
(0.0, [0.0, 0.0, 0.0, 1.0])
>>> normalized = um.normalize(motif)
>>> normalized.pseudocount, normalized.matrix[:, 0].round(3).tolist()
(1.0, [0.023, 0.023, 0.023, 0.932])

round_motif

round_motif(motifs, pct_tolerance: float | str = 0.05)

Set small probabilities to zero.

Probabilities below pct_tolerance, given as a fraction or as a string such as "5%", are set to zero and each column is renormalized. The motif is returned in its original type.

Examples:

>>> import universalmotif as um
>>> motif = um.create_motif(
...     [[0.9, 0.02], [0.04, 0.48], [0.03, 0.48], [0.03, 0.02]], alphabet="DNA"
... )
>>> um.round_motif(motif).matrix.tolist()
[[1.0, 0.0], [0.0, 0.5], [0.0, 0.5], [0.0, 0.0]]
>>> um.round_motif(motif, "3%").matrix.tolist()
[[0.9, 0.0], [0.04, 0.5], [0.03, 0.5], [0.03, 0.0]]

motif_rc

motif_rc(motifs, ignore_alphabet: bool = False)

Reverse complement DNA or RNA motifs.

With ignore_alphabet=True other alphabets are accepted, and their rows and columns are simply reversed.

Examples:

>>> import universalmotif as um
>>> wbox = um.example_motifs()[5]
>>> wbox.consensus, um.motif_rc(wbox).consensus
('GBTTGACYS', 'SRGTCAAVC')

switch_alph

switch_alph(motifs)

Switch motifs between the DNA and RNA alphabets.

Examples:

>>> import universalmotif as um
>>> rna = um.switch_alph(um.create_motif("TGACGTCA"))
>>> rna.alphabet, rna.consensus
('RNA', 'UGACGUCA')

trim_motifs

trim_motifs(
    motifs,
    min_ic: float = 0.25,
    trim_from: TrimFrom = "both",
)

Trim low information content columns from the edges of motifs.

Columns with an information content below min_ic are removed from the chosen ends until a column at or above it is reached. Motifs that are trimmed away completely are dropped from a list with a warning; a single motif that is trimmed away raises ValueError.

Examples:

>>> import universalmotif as um
>>> ap1 = um.example_motifs()[1]
>>> ap1.consensus, um.trim_motifs(ap1).consensus
('NTGASTCAN', 'TGASTCA')
>>> um.trim_motifs(ap1, trim_from="left").consensus
'TGASTCAN'

trim_cwm

trim_cwm(
    motifs,
    trim_threshold: float = 0.3,
    abs_threshold: float | None = None,
    trim_from: TrimFrom = "both",
)

Trim the edges of contribution weight matrices.

A column is kept if the sum of its absolute values reaches the cutoff, which is abs_threshold if given and otherwise trim_threshold times the largest column sum. Everything between the first and last kept columns is retained.

Examples:

>>> import universalmotif as um
>>> cwm = um.create_motif(
...     [[0.01, 0.0, 0.6, 0.0, 0.02],
...      [0.0, 0.5, 0.0, 0.0, 0.0],
...      [0.02, 0.0, 0.0, 0.7, 0.0],
...      [0.0, 0.0, -0.1, 0.0, 0.01]],
...     alphabet="DNA",
...     type="CWM",
... )
>>> trimmed = um.trim_cwm(cwm)
>>> trimmed.width
3
>>> trimmed.matrix.tolist()
[[0.0, 0.6, 0.0], [0.5, 0.0, 0.0], [0.0, 0.0, 0.7], [0.0, -0.1, 0.0]]

add_gap

add_gap(
    motifs,
    gaploc: int | Sequence[int] | None = None,
    mingap: int | Sequence[int] = 1,
    maxgap: int | Sequence[int] = 5,
)

Add gaps to motifs. Gap i comes after the first gaploc[i] positions, in the middle of the motif by default, and spans mingap[i] to maxgap[i] positions; shorter lists are recycled.

Examples:

>>> import universalmotif as um
>>> motif = um.create_motif("TGACGTCA")
>>> gapped = um.add_gap(motif, gaploc=4, mingap=1, maxgap=5)
>>> gapped.isgapped, gapped.gaploc, gapped.mingap, gapped.maxgap
(True, [4], [1], [5])

ungap

ungap(motifs, delete: bool = False)

Turn motifs' gaps off. Unless delete, the gap positions and sizes are kept, and passing them back to add_gap(), as in add_gap(m, gaploc=m.gaploc, mingap=m.mingap, maxgap=m.maxgap), turns the gaps on again.

Examples:

>>> import universalmotif as um
>>> gapped = um.add_gap(um.create_motif("TGACGTCA"), gaploc=4)
>>> off = um.ungap(gapped)
>>> off.isgapped, off.gaploc
(False, [4])
>>> um.ungap(gapped, delete=True).gaploc
[]

shuffle_motifs

shuffle_motifs(
    motifs: Motif | Iterable[Motif],
    k: int = 2,
    seed: int | None = None,
)

Shuffle motif columns among the motifs, in blocks of k columns.

The new motifs keep the original widths and types, with " [shuffled]" added to their names. With k above 1, columns move in blocks of k and leftover columns stay at the ends.

Examples:

>>> import universalmotif as um
>>> motifs = um.example_motifs()[:3]
>>> shuffled = um.shuffle_motifs(motifs, seed=1)
>>> [(m.name, m.width) for m in shuffled]
[('TATA-box [shuffled]', 10), ('AP-1 [shuffled]', 9), ('CRE [shuffled]', 8)]
>>> [m.consensus for m in shuffled]
['KASTAACNNW', 'AWRTATCTG', 'ANCAGTTG']

get_consensus

get_consensus(
    position: Sequence[float],
    alphabet: Literal["DNA", "RNA"] = "DNA",
    type: MotifType = "PPM",
    pseudocount: float = 1.0,
) -> str

IUPAC consensus letter of a DNA or RNA column, with its values in A, C, G and T or U order.

Examples:

>>> import universalmotif as um
>>> columns = [[0.9, 0.05, 0.05, 0.0], [0.5, 0.0, 0.5, 0.0], [0.25, 0.25, 0.25, 0.25]]
>>> [um.get_consensus(col) for col in columns]
['A', 'R', 'N']
>>> um.get_consensus([1, 0, 1, 18], alphabet="RNA", type="PCM")
'U'

get_consensus_aa

get_consensus_aa(
    position: Sequence[float],
    type: MotifType = "PPM",
    pseudocount: float = 0.0,
) -> str

Consensus letter of an amino acid column, including B, Z, J and X.

Examples:

The column is in alphabetical order of the one-letter codes, so here D and N share the probability:

>>> import universalmotif as um
>>> col = [0.0] * 20
>>> col[2] = col[11] = 0.5
>>> um.get_consensus_aa(col)
'B'

consensus_to_ppm

consensus_to_ppm(letter: str) -> np.ndarray

The DNA probability column for an IUPAC letter. A single base gets 0.997, leaving 0.001 for each of the others, unlike a motif made by create_motif() from a consensus string, whose columns hold exact zeros.

Examples:

>>> import universalmotif as um
>>> um.consensus_to_ppm("W").tolist()
[0.499, 0.001, 0.001, 0.499]
>>> um.consensus_to_ppm("N").tolist()
[0.25, 0.25, 0.25, 0.25]

consensus_to_ppm_aa

consensus_to_ppm_aa(letter: str) -> np.ndarray

The amino acid probability column for a consensus letter.

Examples:

>>> import universalmotif as um
>>> col = um.consensus_to_ppm_aa("B")
>>> letters = "ACDEFGHIKLMNPQRSTVWY"
>>> {a: round(float(p), 3) for a, p in zip(letters, col) if p > 0.1}
{'D': 0.491, 'N': 0.491}

position_icscore

position_icscore(
    position: Sequence[float],
    bkg: Sequence[float] | None = None,
    type: MotifType = "PPM",
    pseudocount: float = 1.0,
    nsites: float = 100.0,
    relative_entropy: bool = False,
) -> float

Information content of a single motif column.

PPM columns are rounded to nsites counts and given the pseudocount before their information content is taken, as they are for a motif's total IC, so with the defaults a column of pure A gives about 1.93 bits rather than 2; set pseudocount=0 for the information content of the column as it is. PCM columns get the pseudocount directly.

Examples:

>>> import universalmotif as um
>>> um.position_icscore([0.25, 0.25, 0.25, 0.25])
0.0
>>> um.position_icscore([1.0, 0.0, 0.0, 0.0], pseudocount=0)
2.0
>>> round(um.position_icscore([0.5, 0.0, 0.0, 0.5], pseudocount=0), 3)
1.0

average_ic

average_ic(
    motifs: Motif | Iterable[Motif], average: str = "a.mean"
) -> float | npt.NDArray[np.float64]

The average information content of the motifs' positions, taken as the arithmetic mean with "a.mean", the geometric mean with "g.mean", the median with "median" or the Fisher z-transform with "fzt".

Examples:

>>> import universalmotif as um
>>> motifs = um.example_motifs()
>>> round(um.average_ic(motifs[0]), 3)
0.883
>>> um.average_ic(motifs[:3], average="median").round(3).tolist()
[0.865, 1.215, 1.395]

pcm_to_ppm

pcm_to_ppm(
    position: Sequence[float], pseudocount: float = 0.0
) -> npt.NDArray[np.float64]

Convert a column of counts to probabilities, spreading a positive pseudocount evenly across the letters.

Examples:

>>> import universalmotif as um
>>> um.pcm_to_ppm([3, 1, 0, 4]).tolist()
[0.375, 0.125, 0.0, 0.5]
>>> um.pcm_to_ppm([3, 1, 0, 4], pseudocount=1).round(3).tolist()
[0.361, 0.139, 0.028, 0.472]

ppm_to_pcm

ppm_to_pcm(
    position: Sequence[float], nsites: float = 100.0
) -> npt.NDArray[np.float64]

Convert a column of probabilities to whole counts summing to nsites.

Examples:

>>> import universalmotif as um
>>> um.ppm_to_pcm([0.25, 0.1, 0.05, 0.6], nsites=20).tolist()
[5.0, 2.0, 1.0, 12.0]

ppm_to_pwm

ppm_to_pwm(
    position: Sequence[float],
    bkg: Sequence[float] | None = None,
    pseudocount: float = 1.0,
    nsites: float = 100.0,
    smooth: bool = True,
) -> npt.NDArray[np.float64]

Convert a column of probabilities to log2 odds against bkg, which is uniform by default. With smooth, the pseudocount is first applied by converting to nsites counts and back.

Examples:

>>> import universalmotif as um
>>> um.ppm_to_pwm([0.7, 0.1, 0.1, 0.1], smooth=False).round(3).tolist()
[1.485, -1.322, -1.322, -1.322]
>>> um.ppm_to_pwm([0.7, 0.1, 0.1, 0.1]).round(3).tolist()
[1.476, -1.301, -1.301, -1.301]

pwm_to_ppm

pwm_to_ppm(
    position: Sequence[float],
    bkg: Sequence[float] | None = None,
) -> npt.NDArray[np.float64]

Convert a column of log2 odds back to probabilities.

Examples:

>>> import universalmotif as um
>>> um.pwm_to_ppm([1.485, -1.322, -1.322, -1.322]).round(3).tolist()
[0.7, 0.1, 0.1, 0.1]

ppm_to_icm

ppm_to_icm(
    position: Sequence[float],
    bkg: Sequence[float] | None = None,
    relative_entropy: bool = False,
) -> npt.NDArray[np.float64]

Convert a column of probabilities to information content, as relative entropy against bkg if asked. With relative entropy, letters less common than in the background get 0 rather than a negative height.

Examples:

>>> import universalmotif as um
>>> um.ppm_to_icm([0.7, 0.1, 0.1, 0.1]).round(3).tolist()
[0.45, 0.064, 0.064, 0.064]
>>> um.ppm_to_icm([0.7, 0.1, 0.1, 0.1], relative_entropy=True).round(3).tolist()
[1.04, 0.0, 0.0, 0.0]

icm_to_ppm

icm_to_ppm(
    position: Sequence[float],
) -> npt.NDArray[np.float64]

Convert a column of information content back to probabilities.

Examples:

>>> import universalmotif as um
>>> icm = um.ppm_to_icm([0.7, 0.1, 0.1, 0.1])
>>> um.icm_to_ppm(icm).round(3).tolist()
[0.7, 0.1, 0.1, 0.1]