Skip to content

dbNSFP — aggregated in-silico predictor scores

dbNSFP (database for Non-Synonymous SNPs' Functional Predictions) doesn't compute its own scores — it aggregates the outputs of dozens of published predictor tools (SIFT, PolyPhen2, CADD, REVEL, AlphaMissense, MetaRNN, ESM1b, …) into one row per variant, one column pair (*_score/*_pred) per tool. iris reads dbNSFP5.0a (released 2025-01-01, GRCh38, built on GENCODE 46/Ensembl 112) as 25 bgzipped, tabix-indexed, per-chromosome TSVs at /mnt/cephfs/hot_nvme/dbNSFP5/ (34 GB total for the files iris actually uses) — same per-chromosome layout as MCPS, unlike dbSNP/CADD's single whole-genome files.

This page documents the file and shows how iris reads it via DbNsfpAnnotator (src/iris/adapters/driven/annotators/dbnsfp.py). Every number and example below ran against the real files.

Other files in the same directory — don't read these directly

File(s) What it is
dbNSFP5.0a_variant.chr{1..22,X,Y,M}.bgz (+ .tbi) What DbNsfpAnnotator reads — 25 files, 34 GB total
dbNSFP5.0a_variant.chr*.gz Plain gzip duplicates of the same 25 files, not bgzip/tabix-indexed
dbNSFP5.0a_grch38.gz (+ .tbi) The same data pre-split, recombined into one 37 GB whole-genome tabix file — not what file_pattern points at, and not needed since the per-chromosome split already gives workers something to parallelize over
dbNSFP5.0_gene.gz Gene-level aggregates (constraint, disease association, expression) — a different table entirely; not read by DbNsfpAnnotator, which is variant-level only
search_dbNSFP50a.jar, h, hg, tryhg*.in, try.vcf dbNSFP's own Java command-line search tool and its example inputs — unrelated to iris

Scale

25 files (22 autosomes + X + Y + M, note: M not MT — see below). Real per-file row counts:

Chromosome Rows Fetch time
21 (smallest autosome) 808,183 4.3s
Y 166,756 0.6s
M (mitochondrial) 25,574 <0.1s

Unlike CADD (exhaustive, ~3 rows per position genome-wide), dbNSFP only carries rows for variants its constituent predictor tools actually scored — overwhelmingly coding and near-coding SNVs, since most of the aggregated tools (SIFT, PolyPhen2, …) are protein-effect predictors by design. 25,574 rows for a 16.6 kb mitochondrial genome is far denser than any nuclear chromosome — consistent with mitochondrial hypervariability (the same pattern showed up in dbSNP's MT contig).

Schema: 59 of 380 real columns

The real files are 380 columns wide; dbnsfp.toml [columns] restricts what DbNsfpAnnotator actually loads to 59 (4 key columns — chr/pos(1-based)/ref/alt — plus 55 predictor/metadata columns), keeping peak memory bounded on files with hundreds of columns per row.

Multi-value columns: "most damaging across transcripts" convention

dbNSFP is built per-variant, but many predictors compute per-transcript — a variant overlapping several transcripts gets multiple ;-separated values in one cell:

AlphaMissense_pred = ".;.;.;YES"      # one entry per overlapping transcript

DbNsfpAnnotator collapses each multi-value cell to a single number by taking the most damaging value — max() for scores where higher means more deleterious (REVEL, PolyPhen2, gMVP, VEST4, MetaSVM, …), min() for the few where lower means more deleterious (ESM1b's log-likelihood ratio, SIFT4G). SIFT_pred is parsed for its prediction category only — dbNSFP doesn't ship a SIFT_score column in this build's [columns] selection, so sift in iris is a prediction-only VariantScore with no numeric value.

What iris extracts, by category

Category Score names (VariantScore.name)
Clinical pathogenicity/protein alphamissense (+pred), revel, cadd_phred, polyphen2_hdiv (+pred), sift (pred only), bayesdel_addaf (+pred), bayesdel_noaf, metarnn (+pred)
Conservation gerp_rs, phylop100way, phastcons100way, gerp91, phylop470, phastcons470, phylop17primate, bstatistic
Extended pathogenicity/protein esm1b (+pred), mutscore, phactboost, gmvp, mpc, primateai (+pred), sift4g (+pred), vest4, clinpred (+pred), dann, metasvm (+pred), mutassessor (+pred), aloft_prob_rec, aloft_prob_dom, aloft_pred
Metadata (VariantAnnotation.metadata) clinvar_id, clinvar_clnsig, clinvar_review — dbNSFP's own embedded ClinVar mirror, same caveat as dbSNP's legacy CLN* block: prefer ClinvarAnnotator for authoritative clinical significance. Also interpro_domain.
AlleleFrequency gnomad4_af, gnomad4_popmax_af, gnomad4_{afr,amr,sas,eas,nfe}_af (gnomAD 4.1 joint), topmed_af (TOPMed freeze 8), alfa_af (ALFA)

Live run against a real coding variant with several predictors populated (21:10538724 A>C):

result = annotator.annotate([va("21", 10_538_724, "A", "C")])
cadd_phred       0.16
phylop100way    -2.446
phastcons100way  0.0
phylop470       -15.654
phastcons470     0.0
phylop17primate  0.52
esm1b           -7.163   prediction=HIGH
gmvp             0.7128484687973009
vest4            0.16
metadata: {'interpro_domain': '.;.;.;.'}

(No AlleleFrequency for this particular variant — not every dbNSFP row has gnomAD/TOPMed/ALFA data populated.)

The M/MT filename mismatch: mitochondrial variants used to never be annotated

DbNsfpAnnotator used to build its per-chromosome file path from normalize_chromosome()'s canonical form, which maps "M" and "MT" both to "MT":

>>> normalize_chromosome("M")
'MT'
>>> normalize_chromosome("MT")
'MT'

But the real file on disk is named dbNSFP5.0a_variant.chrM.bgz — singular M, not MT. This broke matching in two independent places, the same shape as MCPS' chrX bug:

  1. File lookup: _load() checked Path(...).exists() before querying, so the constructed path dbNSFP5.0a_variant.chrMT.bgz never matched anything (the real file only exists as ...chrM.bgz) — the file was never even opened.
  2. Tabix contig: even with the right file open, its internal tabix contig is "M", not "MT"resolve_chromosome()'s bare/chr-toggle logic doesn't bridge that either.

Both are now fixed, verified live against a real chrM row:

annotator.annotate([va("MT", 3_307, "A", "C")])[0].scores
# 10 VariantScore — was ()

The fix: _resolve_chrom_file() tries "MT" first, then falls back to the alias "M" for the file-lookup step; resolve_file_contig() (shared with MCPS' fix, src/iris/adapters/driven/_helpers.py) handles the tabix-contig step the same way MCPS' does — falling back to a file's sole contig when standard resolution fails, which is safe here because DbNsfpAnnotator only opens a per-chromosome file after already selecting it for that exact chromosome. In practice this rarely mattered for germline variant triage (MT variant calling is usually a separate specialized pipeline), but any code that assumed DbNsfpAnnotator covered the whole genome should have known it silently excluded MT before this fix.

Access patterns from iris

Registering the source

Already registered in this environment's ~/.iris/config.toml:

[sources.dbnsfp]
host = "local"
path = "/mnt/cephfs/hot_nvme/dbNSFP5"
from iris.adapters.driven.annotators.dbnsfp import DbNsfpAnnotator

annotator = DbNsfpAnnotator.from_registry()          # resolves [sources.dbnsfp]
annotator = DbNsfpAnnotator.from_registry(workers=8)  # sets the default workers

workers — chromosome-partitioned parallel fetch

Same shape as MCPS: workers controls how many of the (up to 25) per-chromosome files are opened and queried concurrently, each via its own TabixFile handle — parallelism across files, not within one. Useful when a single annotate() call spans many chromosomes (e.g. annotating a whole-genome VCF's rare variants after an AF pre-filter).

Design notes & caveats

  • Mitochondrial variants used to be silently unannotated — fixed; see above.
  • Only 59 of 380 real columns are loaded — extending coverage means adding both a dbnsfp.toml [columns] entry and a _update_entry() / _build_scores() case; the mapping isn't automatic.
  • Multi-transcript values collapse to "most damaging" — see above. This is a deliberate simplification: a variant's dbNSFP score reflects its worst-case transcript, not a specific one. Cross-reference VepFlightAnnotator's TranscriptVariant list when a specific transcript's consequence matters.
  • clinvar_id/clinvar_clnsig/clinvar_review are dbNSFP's own embedded ClinVar snapshot, not necessarily current — prefer ClinvarAnnotator for authoritative clinical significance, same reasoning as dbSNP's legacy CLN* block.
  • SIFT_score isn't loaded — only SIFT_pred (category), so iris' sift VariantScore is prediction-only, no numeric value.
  • Don't read the .gz duplicates or dbNSFP5.0a_grch38.gz directly — see the file table above.
  • Per-chromosome files, unlike dbSNP/CADDworkers parallelizes across files here, the same shape as MCPS.

Reference

iris.adapters.driven.annotators.dbnsfp

dbNSFP in-silico scores and population frequency annotator via tabix.

DbNsfpAnnotator

Bases: TabixVariantAnnotator

Annotates variants with in-silico scores and population data from dbNSFP via tabix.

AlphaMissense, REVEL, CADD, PolyPhen2-HDIV, SIFT, BayesDel (addAF/noAF),

GERP++RS, MetaRNN.

Extended scores: ESM1b, MutScore, PHACTboost, gMVP, MPC, PrimateAI, SIFT4G, VEST4, ClinPred, GERP91, phyloP/phastCons (100way/470way/17way-primate), bStatistic. Metadata: ClinVar (id/clnsig/review), gnomAD4.1 (joint + 5 populations), TOPMed, ALFA, Interpro domain.

Expects bgzipped, tabix-indexed per-chromosome files under data_dir matching file_pattern ({chrom} substituted with the bare chromosome, e.g. "1"). For multi-transcript rows, the most damaging value is kept per score.

workers class-attribute instance-attribute

workers = 1

Default parallel per-chromosome-file workers, used when annotate() omits an override.

from_registry classmethod

from_registry(source='dbnsfp', registry=None, *, workers=1)

Build a DbNsfpAnnotator resolving source from the iris DataRegistry.

Source code in src/iris/adapters/driven/annotators/dbnsfp.py
@classmethod
def from_registry(
    cls, source: str = "dbnsfp", registry=None, *, workers: int = 1
) -> "DbNsfpAnnotator":
    """Build a DbNsfpAnnotator resolving *source* from the iris DataRegistry."""
    from iris.config import get_registry

    r = registry or get_registry()
    return cls(data_dir=r.resolve_path(source), workers=workers)

annotate

annotate(items, *, workers=None)

Annotate variants with dbNSFP in-silico scores and allele frequencies.

PARAMETER DESCRIPTION
items

Variants to annotate.

TYPE: Sequence[VariantAnnotation]

workers

Parallel per-chromosome-file workers for this call; defaults to self.workers (set at construction) when omitted.

TYPE: int | None DEFAULT: None

Source code in src/iris/adapters/driven/annotators/dbnsfp.py
def annotate(
    self, items: Sequence[VariantAnnotation], *, workers: int | None = None
) -> list[VariantAnnotation]:
    """Annotate variants with dbNSFP in-silico scores and allele frequencies.

    Args:
        items: Variants to annotate.
        workers: Parallel per-chromosome-file workers for this call;
            defaults to ``self.workers`` (set at construction) when omitted.
    """
    targets: set[_Key] = set()
    for va in items:
        maybe_key = va.variant.as_key()
        if maybe_key is None:
            continue
        chrom, pos, ref, alt = maybe_key
        targets.add((normalize_chromosome(chrom), pos, ref, alt))
    if not targets:
        return list(items)
    chroms = {key[0] for key in targets}
    hit_map = self._load(chroms, targets, workers=self.workers if workers is None else workers)
    return [self._enrich(va, hit_map) for va in items]