Skip to content

Sequences

Random sequences, shuffling, k-mer counts and backgrounds, sequence complexity, sliding windows, masking, and sampling sites from motifs.

create_sequences

create_sequences(
    alphabet: str = "DNA",
    seqnum: int = 100,
    seqlen: int = 100,
    freqs: Sequence[float]
    | Mapping[str, float]
    | None = None,
    seed: int | None = None,
    nthreads: int = 1,
) -> list[str]

Random sequences.

alphabet is "DNA", "RNA", "AA" or a string of letters. freqs gives the frequency of every k-mer of the alphabet, either as a mapping from k-mers or in the sorted order that get_kmers() gives; their number sets k, and sequences are drawn from the Markov model of order k-1 they define. Letters are uniform by default.

Examples:

>>> import universalmotif as um
>>> um.create_sequences(seqnum=3, seqlen=12, seed=1)
['TTTGGACAGCGC', 'TGGTAGCAAGTA', 'ATAGAAGGACGT']

AT-rich sequences, from letter frequencies:

>>> freqs = {"A": 0.4, "C": 0.1, "G": 0.1, "T": 0.4}
>>> um.create_sequences(seqnum=2, seqlen=12, freqs=freqs, seed=1)
['TTTTGAAATAGA', 'TGGTATCAATTA']

shuffle_sequences

shuffle_sequences(
    sequences: str | Sequence[str] | Mapping[str, str],
    k: int = 1,
    method: str = "euler",
    seed: int | None = None,
    window: bool = False,
    window_size: float = 0.1,
    window_overlap: float = 0.01,
    nthreads: int = 1,
)

Shuffle sequences while preserving their k-mer composition.

The default method, "euler", keeps k-mer counts exactly, along with the first and last k-1 letters. "linear" shuffles non-overlapping blocks of k letters, keeping only those blocks' counts, and "markov" draws a new sequence from the k-mer frequencies, keeping them only approximately. With window, each sliding window is shuffled in turn; window_size below 1 and window_overlap between 0 and 1 are fractions of each sequence's length.

Results depend on seed but not on nthreads.

Returns new sequences in the same form as the input, whether a string, a list or a dict with the same keys.

Examples:

>>> import universalmotif as um
>>> seq = "AACCGGTTACGTACGT"
>>> shuffled = um.shuffle_sequences(seq, k=2, seed=1)
>>> shuffled
'ACCGTACGGTAACGTT'
>>> um.count_kmers(shuffled, k=2).equals(um.count_kmers(seq, k=2))
True

shuffle_string

shuffle_string(
    string: str,
    k: int = 1,
    method: str = "euler",
    seed: int | None = None,
) -> str

Shuffle one string while keeping its k-mer counts, as shuffle_sequences() does.

Examples:

>>> import universalmotif as um
>>> um.shuffle_string("ACGTACGTTTGGCCAA", k=2, seed=1)
'ACCGTTGGCAACGTTA'

get_bkg

get_bkg(
    sequences: str | Sequence[str] | Mapping[str, str],
    k: int | Sequence[int] = (1, 2, 3),
    pseudocount: int = 0,
    alphabet: str | Sequence[str] | None = None,
    rc: bool = False,
    merge: bool = True,
    window: bool = False,
    window_size: float = 0.1,
    window_overlap: float = 0,
    to_meme: str | PathLike[str] | IO[str] | None = None,
    nthreads: int = 1,
) -> pl.DataFrame | None

k-mer counts and frequencies of sequences.

Parameters:

Name Type Description Default
k int | Sequence[int]

k-mer sizes to count.

(1, 2, 3)
pseudocount int

Added to every count.

0
alphabet str | Sequence[str] | None

Letters to count. Sequences made only of A, C, G, T and N count as DNA and those made of A, C, G, U and N as RNA, with their four letters as the default; otherwise the default is the letters in the sequences.

None
rc bool

Also count the reverse complements.

False
merge bool

Combine all sequences into one set of counts; otherwise each sequence gets its own rows.

True
window bool

Count in sliding windows. Values of window_size below 1, and of window_overlap between 0 and 1, are fractions of each sequence's length. Merged windowed counts need sequences of equal length.

False
window_size bool

Count in sliding windows. Values of window_size below 1, and of window_overlap between 0 and 1, are fractions of each sequence's length. Merged windowed counts need sequences of equal length.

False
window_overlap bool

Count in sliding windows. Values of window_size below 1, and of window_overlap between 0 and 1, are fractions of each sequence's length. Merged windowed counts need sequences of equal length.

False
to_meme str | PathLike[str] | IO[str] | None

A path or text file to write a MEME background file to instead of returning the counts. This needs k to run from 1 upwards, merged counts without windows, and no frequencies of exactly 0 or 1, which a pseudocount avoids.

None

Returns:

Type Description
DataFrame

A DataFrame with columns kmer, count and probability, plus sequence for per-sequence counts and start and end for windows, counted from 0 with the end left out. Without windows, rows are sorted by k-mer size and then alphabetically, and per-sequence counts list every sequence in turn under each k-mer. With windows, k-mers are sorted alphabetically whatever their size and each k-mer's windows follow one another, with per-sequence counts grouped by sequence first.

Examples:

>>> import universalmotif as um
>>> seqs = um.example_sequences()
>>> um.get_bkg(seqs, k=1).rows()
[('A', 32793, 0.32793), ('C', 17095, 0.17095), ('G', 17101, 0.17101), ('T', 33011, 0.33011)]
>>> um.get_bkg(seqs, k=[1, 2]).shape
(20, 3)

count_kmers

count_kmers(
    string: str,
    k: int = 1,
    alph: str | Sequence[str] | None = None,
) -> pl.DataFrame

Count the k-mers in a string.

Every k-mer of alph, which defaults to the letters in the string, gets a row in sorted order; k-mers with letters outside alph are not counted. Returns a DataFrame with columns kmer and count, as in get_bkg().

Examples:

>>> import universalmotif as um
>>> um.count_kmers("AACGTTAC").rows()
[('A', 3), ('C', 2), ('G', 1), ('T', 2)]
>>> counts = um.count_kmers("AACGTTAC", k=2)
>>> counts.shape
(16, 2)
>>> counts.filter(counts["count"] > 0).rows()
[('AA', 1), ('AC', 2), ('CG', 1), ('GT', 1), ('TA', 1), ('TT', 1)]

get_kmers

get_kmers(
    letters: str | Sequence[str], k: int = 1
) -> list[str]

All k-mers of some letters, in order, with the first position varying slowest. The letters are used in the order given.

Examples:

>>> import universalmotif as um
>>> um.get_kmers("AC", k=2)
['AA', 'AC', 'CA', 'CC']
>>> len(um.get_kmers("ACGT", k=3))
64

calc_windows

calc_windows(
    n: int,
    window: int = 1,
    overlap: int = 0,
    return_incomp: bool = True,
) -> pl.DataFrame

Start and end positions of sliding windows over a sequence of length n, counted from 0 with the end left out, so that each window is sequence[start:end].

The last window may be shorter than the others unless return_incomp is false. A window wider than n shrinks to n.

Examples:

>>> import universalmotif as um
>>> um.calc_windows(11, window=4, overlap=1).rows()
[(0, 4), (3, 7), (6, 10), (9, 11)]
>>> um.calc_windows(11, window=4, overlap=1, return_incomp=False).rows()
[(0, 4), (3, 7), (6, 10)]

window_string

window_string(
    string: str,
    window: int = 1,
    overlap: int = 0,
    return_incomp: bool = True,
) -> list[str]

Split a string into sliding windows, as laid out by calc_windows().

Examples:

>>> import universalmotif as um
>>> um.window_string("ACGTACGTACG", window=4)
['ACGT', 'ACGT', 'ACG']
>>> um.window_string("ACGTACGTACG", window=4, overlap=1)
['ACGT', 'TACG', 'GTAC', 'CG']

slide_fun

slide_fun(
    string: str,
    fun: Callable[[str], Any],
    window: int = 1,
    overlap: int = 0,
    return_incomp: bool = True,
) -> list[Any]

Apply fun to each sliding window of string, as made by window_string(). A window longer than the string is shortened to it when return_incomp is set and is an error otherwise.

Examples:

>>> import universalmotif as um
>>> def gc(window):
...     return (window.count("G") + window.count("C")) / len(window)
>>> um.slide_fun("ACGTTTAGCAGG", gc, window=4)
[0.5, 0.25, 0.75]

calc_complexity

calc_complexity(
    string: str | Sequence[str],
    complexity_method: str = "WoottonFederhen",
    alph: str | Sequence[str] | None = None,
    trifonov_max_word_size: int = 7,
)

Sequence complexity.

complexity_method is one of "WoottonFederhen", its approximation "WoottonFederhenFast", "Trifonov", "TrifonovFast" or "DUST". The methods come from Wootton and Federhen 1993, Trifonov 1990 and Morgulis et al. 2006, and lower DUST values mean higher complexity. alph gives the possible letters. By default it is ACGT when the strings hold only those letters and N, ACGU likewise for RNA, and otherwise every letter found in the strings, so that all strings are scored against the same alphabet. Returns a float for one string and a numpy array for several.

Examples:

>>> import universalmotif as um
>>> round(um.calc_complexity("ACGTTGCAAC"), 3)
0.731
>>> um.calc_complexity(["ACGTTGCAAC", "ATATATATAT"], alph="ACGT").round(3).tolist()
[0.731, 0.399]

sequence_complexity

sequence_complexity(
    sequences: str | Sequence[str] | Mapping[str, str],
    window_size: float = 20,
    window_overlap: float | None = None,
    method: str = "WoottonFederhen",
    trifonov_max_word_size: int = 7,
    nthreads: int = 1,
) -> pl.DataFrame

Complexity in sliding windows along sequences.

window_size below 1 is a fraction of each sequence's length, and so is window_overlap between 0 and 1, which defaults to half the window, rounded. A window longer than a sequence shrinks to it. See calc_complexity() for the methods. Returns a DataFrame with columns sequence, start, end and complexity, where positions count from 0 and leave the end out.

Examples:

>>> import universalmotif as um
>>> seqs = {"seq": "ACGTTGCAACATATATATATATATAT"}
>>> res = um.sequence_complexity(seqs, window_size=10, window_overlap=5)
>>> res.select("start", "end").rows()
[(0, 10), (5, 15), (10, 20), (15, 25), (20, 26)]
>>> res["complexity"].round(3).to_list()
[0.731, 0.644, 0.399, 0.399, 0.36]

mask_seqs

mask_seqs(
    sequences,
    pattern: str,
    rc: bool = False,
    letter: str = "-",
    alphabet: str = "DNA",
)

Replace every occurrence of pattern in the sequences with letter. Overlapping occurrences are all masked and letters match in either case. With rc, occurrences of the reverse complement of DNA and RNA patterns are masked as well. The sequences come back in the form they were given, whether a string, a list or a dict.

Examples:

>>> import universalmotif as um
>>> um.mask_seqs("AAATTTGCAAATTT", "AAA")
'---TTTGC---TTT'
>>> um.mask_seqs({"s1": "GATTACAGG", "s2": "TTGTAATCT"}, "GATTACA", rc=True)
{'s1': '-------GG', 's2': 'T-------T'}

mask_ranges

mask_ranges(
    sequences: Mapping[str, str], ranges, letter: str = "-"
) -> dict[str, str]

Replace the positions in ranges with letter.

sequences maps names to sequences and ranges names positions on them, counted from 0 with the end left out as in a slice, either as (chrom, start, end) tuples or as a DataFrame with a start column, an end, stop or width column, and the sequence names in a column called chrom, seqnames, sequence, chr or chromosome, so that the hits from scan_sequences() can be masked directly. Ranges may overlap; ranges on unknown sequences or past their ends are errors.

Examples:

>>> import universalmotif as um
>>> seqs = {"chr1": "ACGTACGTACGT", "chr2": "TTTTGGGG"}
>>> um.mask_ranges(seqs, [("chr1", 2, 6), ("chr2", 0, 2)])
{'chr1': 'AC----GTACGT', 'chr2': '--TTGGGG'}

sample_sites

sample_sites(
    motif: Motif, n: int = 100, seed: int | None = None
) -> list[str]

Draw n sites from a motif, each letter chosen with the motif's probabilities at its position.

Examples:

>>> import universalmotif as um
>>> wbox = um.example_motifs()[5]
>>> wbox.consensus
'GBTTGACYS'
>>> um.sample_sites(wbox, n=3, seed=1)
['TTTTGACCG', 'CGTTGACCC', 'ACTTGACTG']

implant_motifs

implant_motifs(
    motifs: Motif | Iterable[Motif],
    sequences: str | Sequence[str] | Mapping[str, str],
    n_per_seq: int = 1,
    rate: float | None = None,
    positions: Sequence[Sequence[int]] | None = None,
    strand: str = "both",
    centre_bias: int = 1,
    min_spacing: int = 0,
    max_retries: int = 100,
    seed: int | None = None,
    return_indices: bool = False,
)

Plant sites sampled from DNA or RNA motifs into sequences.

Each sequence gets n_per_seq sites, a Poisson number with mean rate times its length if rate is given, or one site at each of its positions, given as starts counted from 0 with one list per sequence. Every site comes from a motif chosen at random, on a random strand unless strand="+", and overwrites the sequence where it lands.

Random positions are the mean of centre_bias uniform draws, so values above 1 favour the middle of sequences, and keep at least min_spacing letters from other sites; a site that cannot be placed in max_retries attempts is skipped with a warning.

Returns the new sequences in the same form as the input, and with return_indices also a DataFrame of the sites with the columns sequence_i, motif_i, start, width, strand and planted. The indices and start count from 0, and planted is the site as it reads on the forward strand.

Examples:

>>> import universalmotif as um
>>> wbox = um.example_motifs()[5]
>>> seqs = um.create_sequences(seqnum=3, seqlen=20, seed=1)
>>> new, sites = um.implant_motifs(wbox, seqs, seed=1, return_indices=True)
>>> new
['TTTGGATCTTGACTATCGAA', 'TGGTAGGTCAAAGTGAGCTG', 'AGCTCGACCGGTCTGTCTAG']
>>> sites.select("sequence_i", "start", "strand", "planted").rows()
[(0, 6, '+', 'TCTTGACTA'), (1, 4, '-', 'AGGTCAAAG'), (2, 1, '+', 'GCTCGACCG')]