Skip to content

Reading and writing

Readers and writers for motif files, and for FASTA files of sequences. Readers always return a list of motifs, even when the file holds just one. Gzipped FASTA files, bgzip included, are read as they are.

read_meme

read_meme(
    file: PathLike,
    skip: int = 0,
    readsites: bool = False,
    readsites_meta: bool = False,
    cwm: bool = False,
) -> list[Motif] | dict[str, object]

Read MEME motifs, from MEME, DREME or STREME output or minimal MEME files.

Parameters:

Name Type Description Default
readsites bool

Also read the sites listed in the BLOCKS sections of MEME output.

False
readsites_meta bool

Also read the per-site P-value tables and the combined block diagrams of MEME output.

False
cwm bool

Read the matrices as contribution weight matrices, as TF-MoDISco writes them, instead of probabilities.

False

Returns:

Type Description
list[Motif] | dict

A list of motifs, or with readsites or readsites_meta a dict with the key "motifs" plus:

  • "sites": a dict from motif name to a DataFrame of sequence and site.
  • "sites_meta": one DataFrame of motif, sequence, strand, position, pvalue and site.
  • "sites_meta_combined": a DataFrame of sequence, pvalue and diagram.

Examples:

>>> import universalmotif as um
>>> motifs = um.read_meme(um.example_file("meme"))
>>> [m.name for m in motifs]
['TATA-box', 'AP-1', 'CRE', 'G-box']
>>> motifs[1].type, motifs[1].nsites, motifs[1].consensus
('PPM', 49.0, 'NTGASTCAN')

read_jaspar

read_jaspar(file: PathLike, skip: int = 0) -> list[Motif]

Read JASPAR count matrices.

Rows may be labelled, as in A [ 1 2 3 ], or bare numbers, which are read as DNA when there are four rows and as amino acids when there are twenty. Motifs are returned as PCMs with nsites set to the largest column sum. skip drops lines from the start of the file.

Examples:

>>> import universalmotif as um
>>> motifs = um.read_jaspar(um.example_file("jaspar"))
>>> [m.name for m in motifs]
['TATA-box', 'AP-1', 'CRE', 'G-box']
>>> motifs[0].type, motifs[0].nsites, motifs[0].consensus
('PCM', 23.0, 'KTATAWAWRN')

read_homer

read_homer(file: PathLike, skip: int = 0) -> list[Motif]

Read HOMER motifs.

The log-odds detection threshold is kept in extrainfo as "logodds.threshold", and site counts and P-values are read from HOMER's statistics column when present.

Examples:

>>> import universalmotif as um
>>> motifs = um.read_homer(um.example_file("homer"))
>>> [(m.name, m.type) for m in motifs[:2]]
[('TATA-box', 'PPM'), ('AP-1', 'PPM')]
>>> round(float(motifs[0].extrainfo["logodds.threshold"]), 2)
7.46

read_transfac

read_transfac(file: PathLike, skip: int = 0) -> list[Motif]

Read TRANSFAC matrices, returned as PCMs.

The accession in AC is used as the name if there is one, with the identifier in ID or the factor name in NA as the alternate name; HC and OS give the family and organism.

Examples:

>>> import universalmotif as um
>>> motifs = um.read_transfac(um.example_file("transfac"))
>>> [(m.name, m.family) for m in motifs]
[('TATA-box', 'TBP'), ('AP-1', 'bZIP'), ('CRE', 'bZIP'), ('G-box', 'bZIP')]
>>> motifs[0].type, motifs[0].organism
('PCM', 'synthetic')

read_cisbp

read_cisbp(file: PathLike, skip: int = 0) -> list[Motif]

Read CIS-BP motifs, using the TF, TF Name, Family and Species fields as the name, alternate name, family and organism.

Examples:

>>> import universalmotif as um
>>> motifs = um.read_cisbp(um.example_file("cisbp"))
>>> [m.name for m in motifs]
['TATA-box', 'AP-1', 'CRE', 'G-box']
>>> motifs[0].type, motifs[0].family, motifs[0].organism
('PPM', 'TBP', 'synthetic')

read_uniprobe

read_uniprobe(file: PathLike, skip: int = 0) -> list[Motif]

Read UniPROBE motifs from minimal files or from full output, whose enrichment scores are kept in extrainfo.

Examples:

>>> import universalmotif as um
>>> motifs = um.read_uniprobe(um.example_file("uniprobe"))
>>> [(m.name, m.consensus) for m in motifs[:2]]
[('TATA-box', 'KTATAWAWRN'), ('AP-1', 'NTGASTCAN')]

read_matrix

read_matrix(
    file: PathLike,
    skip: int = 0,
    type: MotifType | None = None,
    positions: Positions = "columns",
    alphabet: str = "DNA",
    sep: str | None = "",
    headers: bool | str = True,
    rownames: bool = False,
    comment: str | None = None,
) -> list[Motif]

Read motifs stored as plain matrices of numbers.

Parameters:

Name Type Description Default
type MotifType | None

The matrix type; detected from the values by default.

None
positions Positions

Whether positions are "columns" or "rows".

'columns'
alphabet str

Alphabet of the motifs; the letters' rows or columns are read in its order.

'DNA'
sep str | None

Line separating motifs; "" means blank lines and None means motifs are only separated by headers.

''
headers bool | str

True if the first line of each motif is its name, False for no names, or a string marking header lines. Each header line starts a new motif, and the name is the line with the string removed.

True
rownames bool

Whether the values come with letter labels, which are skipped.

False
comment str | None

Lines starting with this string are ignored.

None

Examples:

The example file has each motif's name on its own line, followed by one row of counts per letter:

>>> import universalmotif as um
>>> motifs = um.read_matrix(um.example_file("matrix"))
>>> [m.name for m in motifs]
['TATA-box', 'AP-1', 'CRE', 'G-box']
>>> motifs[0].type, motifs[0].consensus
('PCM', 'KTATAWAWRN')

write_meme

write_meme(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    version: int = 5,
    bkg: Sequence[float] | None = None,
    strand: str | None = None,
    overwrite: bool = False,
    append: bool = False,
    cwm: bool = False,
) -> None

Write motifs in MEME format.

Motifs are converted to PPMs, unless cwm=True, in which case every motif must be a CWM and is written as is. bkg gives the background in the order letters are written, which for DNA is A, C, G, T; by default the motifs' shared background is used, or a uniform one if they differ. strand overrides the strands: line. When appending to an existing file only the motifs are written. Equal signs in names become periods and spaces become dashes.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.meme"
...     um.write_meme(motifs, path)
...     lines = path.read_text().splitlines()
...     back = um.read_meme(path)
>>> lines[:3]
['MEME version 5', '', 'ALPHABET= ACGT']
>>> [(m.name, m.type) for m in back]
[('TATA-box', 'PPM'), ('AP-1', 'PPM')]

write_jaspar

write_jaspar(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    overwrite: bool = False,
    append: bool = False,
) -> None

Write DNA, RNA or amino acid motifs as JASPAR count matrices.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.jaspar"
...     um.write_jaspar(motifs, path)
...     for line in path.read_text().splitlines()[:5]:
...         print(line)
>TATA-box
A [  2  2 23  1 18 11 20  8 10  7 ]
C [  1  0  0  1  3  1  3  1  0  3 ]
G [ 11  0  0  1  1  1  0  1 13  5 ]
T [  9 21  0 20  1 10  0 13  0  8 ]

write_homer

write_homer(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    threshold: float | Sequence[float] = 0.8,
    threshold_type: Literal[
        "logodds", "logodds_abs", "pvalue"
    ] = "logodds",
    overwrite: bool = False,
    append: bool = False,
) -> None

Write DNA or RNA motifs in HOMER format.

The detection threshold in each header is on HOMER's own score scale: HOMER scores a site as the sum of ln(p / 0.25) over the probabilities written in the file, floored at 0.001. With threshold_type="logodds" it is threshold times the motif's maximum score, with "logodds_abs" it is threshold itself, and with "pvalue" it is a score that random sites from a uniform background reach with at most that probability. Give one value or one per motif.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.motif"
...     um.write_homer(motifs, path, threshold=0.9)
...     back = um.read_homer(path)
>>> [(m.name, round(float(m.extrainfo["logodds.threshold"]), 2)) for m in back]
[('TATA-box', 8.39), ('AP-1', 8.34)]

write_transfac

write_transfac(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    overwrite: bool = False,
    append: bool = False,
    name_tag: str = "ID",
    altname_tag: str = "NA",
) -> None

Write DNA or RNA motifs as TRANSFAC count matrices. name_tag and altname_tag choose the fields used for the name and alternate name.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.transfac"
...     um.write_transfac(motifs, path)
...     for line in path.read_text().splitlines()[:3]:
...         print(line)
...     back = um.read_transfac(path)
ID TATA-box
HC TBP
OS synthetic
>>> [(m.name, m.type, m.nsites) for m in back]
[('TATA-box', 'PCM', 23.0), ('AP-1', 'PCM', 49.0)]

write_cisbp

write_cisbp(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    overwrite: bool = False,
    append: bool = False,
) -> None

Write DNA or RNA motifs in CIS-BP format, as probability matrices.

The name, alternate name, family and organism go in the TF, TF Name, Family and Species fields. Files written here can be read back with read_cisbp().

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.txt"
...     um.write_cisbp(motifs, path)
...     back = um.read_cisbp(path)
>>> [(m.name, m.type, m.family) for m in back]
[('TATA-box', 'PPM', 'TBP'), ('AP-1', 'PPM', 'bZIP')]

write_uniprobe

write_uniprobe(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    overwrite: bool = False,
    append: bool = False,
) -> None

Write DNA motifs in the minimal UniPROBE format: a name line followed by A:, C:, G: and T: rows of probabilities.

Files written here can be read back with read_uniprobe().

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.txt"
...     um.write_uniprobe(motifs, path)
...     back = um.read_uniprobe(path)
>>> [(m.name, m.type, m.consensus) for m in back]
[('TATA-box', 'PPM', 'KTATAWAWRN'), ('AP-1', 'PPM', 'NTGASTCAN')]

write_matrix

write_matrix(
    motifs: Motif | Iterable[Motif],
    file: PathLike,
    positions: Positions = "columns",
    rownames: bool = False,
    type: MotifType | None = None,
    sep: str | None = "",
    headers: bool | str = True,
    overwrite: bool = False,
    append: bool = False,
    digits: int = 6,
) -> None

Write motifs as plain matrices of numbers.

headers writes the name on its own line when True, prefixed with a string when given one, or not at all when False. sep is written after each motif unless it is None, type converts the motifs first, and digits sets the number of decimal places.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> motifs = um.example_motifs()[:2]
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "motifs.txt"
...     um.write_matrix(motifs, path, headers=">", digits=1)
...     lines = path.read_text().splitlines()
...     back = um.read_matrix(path, headers=">")
>>> lines[0], lines[1].split()
('>TATA-box', ['2.0', '2.0', '23.0', '1.0', '18.0', '11.0', '20.0', '8.0', '10.0', '7.0'])
>>> [(m.name, m.type) for m in back]
[('TATA-box', 'PCM'), ('AP-1', 'PCM')]

meme_alph

meme_alph(
    core: str,
    file: str | PathLike[str] | None = None,
    complements: str | None = None,
    ambiguity: Mapping[str, str] | None = None,
    like: str | None = None,
    alph_name: str | None = None,
    letter_names: Mapping[str, str] | None = None,
    colours: Mapping[str, str] | None = None,
) -> str | None

Write a MEME custom alphabet definition.

core holds the alphabet's letters and complements their complements, if it has any. ambiguity maps ambiguous letters to the core letters they stand for, like is "DNA", "RNA" or "PROTEIN" for alphabets that extend one, and letter_names and colours describe each letter, with colours as hexadecimal RGB. The definition is returned as text, or written to file if one is given.

Examples:

>>> import universalmotif as um
>>> text = um.meme_alph("ACGT", complements="TGCA", like="DNA", alph_name="MyDNA")
>>> print(text, end="")
ALPHABET "MyDNA" DNA-LIKE

# Core symbols
A ~ T
C ~ G

read_fasta

read_fasta(
    file: PathLike,
    skip: int = 0,
    packed: bool = False,
    alphabet: str = "DNA",
    trim_names: bool = False,
) -> dict[str, str] | PackedSequences

Read a FASTA file into a dict from names to sequences, where each name is the whole header line after >, or with trim_names only its first word, as most tools use. The file is read line by line, so a genome needs little more memory than its sequences, and the first skip lines are ignored. Gzipped files, bgzip included, are recognized from their first bytes and decompressed as they are read. When names repeat, the last sequence with each name is kept, with a warning.

With packed, the sequences are packed for scanning as they are read and returned as PackedSequences for alphabet, which is "DNA", "RNA", "AA" or the letters of a custom alphabet. Packed sequences hold a genome in a quarter of the memory, and names must then be unique. To scan a file just once, pass its path to scan_sequences() instead.

Examples:

>>> import universalmotif as um
>>> seqs = um.read_fasta(um.example_file("fasta"))
>>> len(seqs), len(seqs["target_001"])
(100, 1000)
>>> packed = um.read_fasta(um.example_file("fasta"), packed=True)
>>> len(packed), packed.alphabet
(100, 'DNA')

write_fasta

write_fasta(
    sequences: Mapping[str, str] | Sequence[str],
    file: PathLike,
    width: int = 80,
    overwrite: bool = False,
    append: bool = False,
) -> None

Write sequences to a FASTA file, wrapping lines at width characters, or not at all when it is 0. Unnamed sequences are called seq1, seq2 and so on. A path ending in .gz is written gzipped, as it is for every writer in the package.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> import universalmotif as um
>>> with tempfile.TemporaryDirectory() as tmp:
...     path = Path(tmp) / "seqs.fa"
...     um.write_fasta({"first": "ACGTACGTAC", "second": "TTGA"}, path, width=4)
...     print(path.read_text(), end="")
>first
ACGT
ACGT
AC
>second
TTGA

PackedSequences

PackedSequences(inner: _PackedSequences)

Bases: Mapping[str, str]

Named sequences packed for scanning, from read_fasta() with packed=True or from pack_sequences().

DNA and RNA take two bits per letter, so a human genome needs about 0.8 GB instead of 3.1 GB as text; other alphabets take a byte per letter. scan_sequences() scans them as they are, which makes them the way to scan one large set of sequences many times.

They are also a read-only mapping from names to sequences, so they can go wherever a dict of sequences can. Each sequence is then decoded when it is looked up. DNA, RNA and protein letters come back in upper case, so soft-masking is lost, while the letters of custom alphabets and any other letters, such as N, come back as they were.

Examples:

>>> import universalmotif as um
>>> packed = um.pack_sequences({"chr1": "ACGTNNacgt", "chr2": "TTGACC"})
>>> list(packed), packed.lengths
(['chr1', 'chr2'], {'chr1': 10, 'chr2': 6})
>>> packed["chr1"]
'ACGTNNACGT'

alphabet property

alphabet: str

The alphabet the sequences were packed for.

lengths property

lengths: dict[str, int]

The length of each sequence.

nbytes property

nbytes: int

Memory held by the packed letters, in bytes.

subseq

subseq(name: str, start: int, end: int) -> str

Positions start up to but not including end of the sequence called name, counted from 0 as in a slice, decoding only those.

Examples:

>>> import universalmotif as um
>>> packed = um.pack_sequences({"chr1": "ACGTNNACGT"})
>>> packed.subseq("chr1", 2, 8)
'GTNNAC'

pack_sequences

pack_sequences(
    sequences: str | Sequence[str] | Mapping[str, str],
    alphabet: str = "DNA",
    nthreads: int = 1,
) -> PackedSequences

Pack sequences for scanning as PackedSequences.

alphabet is "DNA", "RNA", "AA" or the letters of a custom alphabet, and must match the motifs the sequences will be scanned with. Unnamed sequences are called seq1, seq2 and so on; names must be unique.

Examples:

>>> import universalmotif as um
>>> seqs = um.example_sequences()
>>> packed = um.pack_sequences(seqs)
>>> len(packed), packed.alphabet
(100, 'DNA')
>>> wbox = um.example_motifs()[5]
>>> um.scan_sequences(wbox, packed).equals(um.scan_sequences(wbox, seqs))
True