| name | bio-core-domains |
| description | Build/scan PWMs (PPM, log-odds, info content, logos) for DNA motifs, convert PROSITE patterns to regex, parse HMMER domtblout/Pfam hits. Use for TF binding scans, promoter motifs, PROSITE matches, or Pfam/InterPro domain mapping. |
| tool_type | python |
| primary_tool | NumPy |
Sequence Motifs and Protein Domains
When to Use
- Building a PWM/PPM from a set of aligned TF binding sites and scanning a promoter, enhancer, or genome window for matches on both strands
- Computing per-position information content and drawing a sequence logo for a DNA or protein motif
- Converting a PROSITE pattern (e.g.
N-{P}-[ST]-{P} for N-glycosylation) into a regex and scanning a protein sequence for hits
- Parsing HMMER
hmmscan --domtblout output or Pfam/InterPro accessions to identify and annotate protein domains
- Visualizing or comparing domain architecture across a protein family (e.g. SH3-SH2-kinase in Src-family kinases)
Version Compatibility
- Python ≥3.9, NumPy ≥1.24, matplotlib ≥3.7 (for logos/heatmaps)
- HMMER ≥3.3 (
hmmscan, hmmsearch) with a hmmpress-indexed Pfam-A.hmm (release ≥35)
- InterProScan ≥5.60 for combined Pfam/SMART/PROSITE/CDD scans
- No external motif library required for the core code below (pure NumPy/
re); logomaker ≥0.8 is a drop-in for publication-quality logos
Prerequisites
pip install numpy matplotlib
- For real domain scans:
conda install -c bioconda hmmer plus a local Pfam-A.hmm database
- Familiarity with FASTA/protein sequences (
bio-sequence-io-read-sequences) and basic regex syntax
Key Concepts
- Motif: short functional pattern (<20 aa/bp), does not fold independently (e.g. NLS, phosphorylation site, TF binding site)
- Domain: structurally independent unit (50–300 aa), folds on its own, recurs across proteins (e.g. SH2, kinase domain)
- PFM → PPM → PWM: PFM is raw counts; PPM = PFM/N with pseudocounts; PWM = log2(PPM/background) — only the PWM is a valid additive scoring matrix
- Profile HMM (Pfam): represents a domain family with match/insert/delete states; scores in bits vs a null model
Goal: build a PWM from aligned binding sites, score candidate windows, and scan both strands of a longer sequence.
Approach: count bases per position with a pseudocount (PFM→PPM), convert to log-odds against a uniform background (PWM), then slide a window of the motif's length across the sequence and its reverse complement, keeping windows scoring above a fraction of the maximum possible score.
import numpy as np
import re
BASES = ['A', 'C', 'G', 'T']
def build_ppm(sequences, pseudocount=0.1):
"""Position Probability Matrix from a list of equal-length aligned sequences."""
n_pos = len(sequences[0])
counts = np.full((4, n_pos), pseudocount)
for seq in sequences:
for pos, base in enumerate(seq.upper()):
if base in BASES:
counts[BASES.index(base), pos] += 1
return counts / counts.sum(axis=0)
def ppm_to_pwm(ppm, background=None):
"""Convert a PPM to a log-odds PWM (bits) against a background composition."""
if background is None:
background = np.array([0.25, 0.25, 0.25, 0.25])
bg = background[:, np.newaxis]
return np.log2((ppm + 1e-10) / bg)
def information_content(ppm):
"""Per-position information content in bits. Max = 2 bits for DNA (log2(4))."""
ic = np.zeros(ppm.shape[1])
for pos in (ppm.shape[]):
p = ppm[:, pos]
entropy = -np.(p * np.log2(p + ))
ic[pos] = - entropy
ic
():
(pwm[BASES.index(b), i]
i, b (sequence.upper()) b BASES)
():
motif_len = pwm.shape[]
max_score = np.(np.(pwm, axis=))
threshold = threshold_pct * max_score
hits = []
i ((sequence) - motif_len + ):
subseq = sequence[i:i + motif_len]
s = score_sequence(pwm, subseq)
s >= threshold:
hits.append((i, subseq, s))
(hits, key= x: -x[])
():
comp = .maketrans(, )
seq.translate(comp)[::-]
():
motif_len = pwm.shape[]
fwd = [(p, s, sc, ) p, s, sc scan_sequence(pwm, sequence, threshold_pct)]
rev = scan_sequence(pwm, reverse_complement(sequence), threshold_pct)
rev_fwd = [((sequence) - p - motif_len, s, sc, ) p, s, sc rev]
(fwd + rev_fwd, key= x: -x[])
crp_sites = [
, ,
, ,
]
ppm = build_ppm(crp_sites, pseudocount=)
pwm = ppm_to_pwm(ppm)
(, .join(BASES[i] i np.argmax(ppm, axis=)))
(, information_content(ppm).().())
Goal: match short functional motifs (PROSITE patterns) inside a protein sequence.
Approach: convert the dash-separated PROSITE syntax (x, [..], {..}, (n), (n,m)) into an equivalent Python regex, then use re.finditer to report 1-based match positions.
def prosite_to_regex(pattern):
"""Convert a PROSITE pattern string (e.g. 'N-{P}-[ST]-{P}') to a Python regex."""
pattern = pattern.strip('.')
regex_parts = []
for elem in pattern.split('-'):
m = re.match(r'^(.+?)\((\d+)(?:,(\d+))?\)$', elem)
core, low, high = (m.group(1), m.group(2), m.group(3)) if m else (elem, None, None)
if core == 'x':
r = '.'
elif core.startswith('['):
r = core
elif core.startswith('{'):
r = f'[^{core[1:-1]}]'
else:
r = core
if low is not None:
r += f'{{{low},{high}}}' if high else f'{{{low}}}'
regex_parts.append(r)
return ''.join(regex_parts)
def scan_prosite(sequence, prosite_pattern, pattern_name="pattern"):
"""Scan a protein sequence for a PROSITE pattern; returns 1-based (start, end, match) tuples."""
regex = prosite_to_regex(prosite_pattern)
[(m.start() + , m.end(), m.group()) m re.finditer(regex, sequence)]
protein =
(scan_prosite(protein, , ))
Goal: turn a real hmmscan --domtblout file into annotated protein domains.
Approach: parse the fixed-width whitespace fields (domain name/acc, sequence/domain E-values and scores, alignment coordinates), filter on domain-level E-value, then map hits onto the protein for an architecture plot.
def parse_domtblout(text):
"""Parse HMMER --domtblout text into a list of domain-hit dicts, sorted by start position."""
hits = []
for line in text.strip().split("\n"):
if line.startswith("#") or not line.strip():
continue
fields = line.split()
if len(fields) >= 23:
hits.append({
"domain_name": fields[0],
"domain_acc": fields[1],
"query_name": fields[3],
"e_value": float(fields[6]),
"score": float(fields[7]),
"dom_e_value": float(fields[12]),
"dom_score": float(fields[13]),
"ali_from": int(fields[17]),
"ali_to": int(fields[18]),
})
return sorted(hits, key=lambda h: h["ali_from"])
def domains_above_threshold():
[h h hits h[] <= dom_evalue_max]
Domain Database Overview
| Database | Content | Best use |
|---|
| Pfam | Profile HMMs, ~20k families | General domain annotation |
| InterPro | Integrates Pfam, SMART, CDD, PROSITE | First-pass comprehensive scan |
| SMART | Signaling domains | Kinases, receptors |
| CDD | NCBI-curated, integrates Pfam/SMART | Free with BLAST |
| PROSITE | Patterns and profiles | Active sites, short motifs |
hmmscan --domtblout results.domtbl Pfam-A.hmm protein.fasta
interproscan.sh -i protein.fasta -f tsv -o results.tsv
Pitfalls
- Profile HMM E-values:
hmmscan reports two E-values — sequence-level and domain-level; use the domain E-value (dom_e_value above) when annotating individual domain hits, not the sequence-level one
- Gathering threshold (GA): Pfam uses family-specific GA bit-score thresholds, not a universal cutoff; respect per-family thresholds rather than a fixed E-value everywhere
- Domain boundaries are approximate: Pfam HMM boundaries reflect consensus, not exact structural extents — check the alignment for precise boundaries
- PROSITE false positives: short degenerate patterns (e.g.
N-x-[ST]) match frequently by chance; cross-validate with structural or conservation evidence
- Scan both strands: TF binding sites occur on either strand of dsDNA — always scan the reverse complement too, as
scan_both_strands does
- PWM threshold choice: 50–70% of the maximum score is a reasonable starting point; tune against known sites in your organism's genome
- Pseudocounts matter: without pseudocounts, one missing base at a position gives −∞ log-odds; use ≥0.1 pseudocount per position (0.5 is a common default for small site sets)
See Also
bio-sequence-manipulation-motif-search — simpler single-motif regex/consensus searches
bio-chip-seq-motif-analysis — de novo motif discovery from ChIP-seq peaks (MEME-style)
jaspar-database — fetching real PFMs/PWMs for known TFs instead of building them from scratch
bio-genome-annotation-functional-annotation — genome-scale Pfam/InterPro annotation pipelines