Skip to content

CADD — genome-wide SNV deleteriousness scores

CADD (Combined Annotation Dependent Depletion) is a genome-wide score of variant deleteriousness, trained to rank the relative pathogenicity of every possible substitution against a background of simulated and derived alleles. iris reads the GRCh38 v1.7 whole-genome SNV table as a single bgzipped, tabix-indexed TSV, whole_genome_SNVs.sorted.tsv.bgz, at /mnt/cephfs/hot_nvme/cadd/ (81.58 GB / 75.98 GiB, .tbi index ~2.65 MiB). Like dbSNP, this is one file for the whole genome — not split per chromosome like MCPS or dbNSFP.

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

##CADD GRCh38-v1.7 (c) University of Washington, Hudson-Alpha Institute for
Biotechnology and Berlin Institute of Health at Charite - Universitatsmedizin
Berlin 2013-2023. All rights reserved.
#Chrom  Pos      Ref  Alt  RawScore  PHRED
1       10001    T    A    0.767991  7.993
1       10001    T    C    0.799933  8.285
1       10001    T    G    0.777613  8.080

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

File Size What it is
whole_genome_SNVs.sorted.tsv.bgz (+ .tbi) 81.58 GB The one CaddAnnotator reads — sorted, bgzipped, tabix-indexed
whole_genome_SNVs.tsv.gz 87.47 GB Plain gzip, not bgzip — not tabix-indexed, no random access. An earlier/unsorted copy of the same data.
localinstall/GRCh38_v1.7.tar.gz 89 GB CADD's own offline scoring engine + its bundled annotation sources (for computing new CADD scores from a VCF) — unrelated to reading precomputed scores, which is all CaddAnnotator does

Scale

24 contigs (22 autosomes + X + Y — no MT, unlike dbSNP), bare names ("1", not "chr1" or NC_...). Real per-contig counts, fetched end-to-end:

Contig Rows Fetch time vs. 3×length (exhaustive SNV upper bound)
1 (248.9 Mb) 691,443,036 106.6s 92.6%
21 (46.7 Mb) 120,265,857 18.6s 85.8%
Y (57.2 Mb) 79,245,129 13.1s 15.4%

Every genomic position gets up to 3 rows (one per possible alternate base), same coverage model as uvar for its SNV core — but CADD's file is SNV-only, no indels (there is no separate CADD indel table wired into iris). The gap from the theoretical 3×length maximum is assembly gaps (N-runs, centromeres, telomeres) and, sharply on chrY, large heterochromatic/repeat-masked regions where CADD doesn't score at all — chrY covers only ~15% of its theoretical maximum vs. ~86-93% for chr1/chr21.

Schema

Six tab-separated columns, positional (no named-column lookup — CaddAnnotator reads them by position, parts[:6]):

Column Meaning
Chrom Bare chromosome name ("1""22", "X", "Y")
Pos 1-based position
Ref / Alt Single-base reference/alternate (SNV only)
RawScore Raw CADD C-score — the model's linear SVM output; not itself bounded or directly interpretable across releases
PHRED PHRED-scaled rank of RawScore against CADD's background distribution — the score most people mean by "CADD score". Widely-cited (not iris-specific) conventions: ≥10 top 10% most deleterious substitutions genome-wide, ≥20 top 1%, ≥30 top 0.1%.

CaddAnnotator extracts both as separate VariantScores (cadd_raw, cadd_phred), both typed VariantScoreType.PATHOGENICITY.

Access patterns from iris

Registering the source

# ~/.iris/config.toml
[sources.cadd]
host = "local"
path = "/mnt/cephfs/hot_nvme/cadd/whole_genome_SNVs.sorted.tsv.bgz"
from iris.adapters.driven.annotators.cadd import CaddAnnotator

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

annotate() — point-lookup enrichment, SNV-only

Live run: two real SNV hits at 1:10001, plus a real dbSNP indel (1:10013 TA>T, rs1639538231) to demonstrate the SNV-only limitation concretely rather than just asserting it:

items = [va("1", 10_001, "T", "A"), va("1", 10_001, "T", "C"), va("1", 10_013, "TA", "T")]
result = annotator.annotate(items)
0.030s
1:10001:T:A   [('cadd_raw', 0.767991), ('cadd_phred', 7.993)]
1:10001:T:C   [('cadd_raw', 0.799933), ('cadd_phred', 8.285)]
1:10013:TA:T  []   # indel — not in the SNV-only table, silently unmatched

Unmatched variants (the indel above, or any position/allele CADD didn't score) are returned unchanged, not dropped — same contract as every other VariantAnnotator. There's no way to distinguish "not scored because indel" from "not scored because outside coverage" from the result alone; check variant_type on the input if that distinction matters.

workers — chromosome-partitioned parallel fetch

Inherited from TabixVariantAnnotator/TabixReader._queryworkers partitions targets by chromosome across a ThreadPoolExecutor, each worker opening its own TabixFile handle into the same 81.58 GB file. Given the file is single and global (like dbSNP, unlike the per-chromosome sources), this is the only form of parallelism available — there's no "one file per chromosome" split to parallelize over at the filesystem level, just concurrent random-access reads into the same tabix index.

Design notes & caveats

  • SNV-only. No indel scores anywhere in this pipeline — CaddAnnotator will silently pass through any indel unmatched. If indel-level pathogenicity is needed, look at dbNSFP or VEP-based scores instead.
  • One file for the whole genome, not per-chromosome — same shape as dbSNP, unlike MCPS/dbNSFP/RGC-ME's per-chromosome layout.
  • RawScore isn't comparable across CADD releases — only PHRED (a rank against that release's own background distribution) is meant for cross-release or cross-tool comparison. iris extracts both, but treat cadd_raw as release-internal.
  • Don't point CaddAnnotator at whole_genome_SNVs.tsv.gz — it's plain gzip, not bgzip, and has no .tbi; only whole_genome_SNVs.sorted.tsv.bgz works with tabix random access. See the file table above.
  • Not registered in this environment's ~/.iris/config.toml today — the [sources.cadd] snippet above needs to be added before CaddAnnotator.from_registry() will resolve; passing tsv_path= directly works without it (as used for every live example on this page).

Reference

iris.adapters.driven.annotators.cadd

CADD RawScore and PHRED annotator via tabix random-access.

CaddAnnotator

Bases: TabixVariantAnnotator

Annotates variants with CADD RawScore and PHRED via tabix random-access.

Requires the CADD TSV to be bgzipped and tabix-indexed (.tbi alongside it).

workers class-attribute instance-attribute

workers = 1

Default parallel tabix workers, used when annotate() is called without an override.

from_registry classmethod

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

Build a CaddAnnotator resolving source from the iris DataRegistry.

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

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

annotate

annotate(items, *, workers=None)

Annotate variants with CADD RawScore and PHRED scores.

PARAMETER DESCRIPTION
items

Variants to annotate.

TYPE: Sequence[VariantAnnotation]

workers

Parallel tabix 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/cadd.py
def annotate(
    self, items: Sequence[VariantAnnotation], *, workers: int | None = None
) -> list[VariantAnnotation]:
    """Annotate variants with CADD RawScore and PHRED scores.

    Args:
        items: Variants to annotate.
        workers: Parallel tabix 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
        targets.add(maybe_key)
    if not targets:
        return list(items)
    scores = self._load(targets, workers=self.workers if workers is None else workers)
    return [self._enrich(va, scores) for va in items]