MCPS — Mexico City Prospective Study allele frequencies¶
MCPS (Mexico City Prospective Study) is a large cohort with mixed
whole-genome, whole-exome, and array-based genotyping, contributing
population-specific allele frequencies with particular strength in
Mexican/Latino ancestry — a population under-represented in gnomAD and most
other public frequency panels. iris reads it as 23 bgzipped,
tabix-indexed, per-chromosome TSVs (chr{N}.freq.cpra.tsv.bgz) at
/mnt/cephfs/hot_nvme/mcps/mcps-variant-browser-afs/ (5.1 GB total) — the
opposite layout from dbSNP and CADD's single
whole-genome files.
This page documents the files and shows how iris reads them via
McpsAFAnnotator (src/iris/adapters/driven/annotators/mcps.py). Every
number and example below ran against the real files.
Scale¶
22 autosomes + X — no Y, no MT. Full genome-wide scan, real counts:
| Contig | Rows |
|---|---|
| chr1 | 10,916,583 |
| chr21 | 1,720,825 |
| chrX | 6,464,980 |
| Total (all 23 files) | 141,802,412 |
(Full scan of all 23 files: 84.9s.)
Schema¶
#CHROM START END REF ALT ID CPRA SOURCE STON GHOM AHOM
AN_RAW AC_RAW AF_RAW AN_AFR AC_AFR AF_AFR AN_EUR AC_EUR AF_EUR AN_MEX AC_MEX AF_MEX
The file ships its own header line, but TabixReader._query() strips it on
random-access fetches — mcps.toml [fieldnames] declares the same 23 names
explicitly so csv.DictReader can reconstruct rows regardless.
Populations¶
Four population buckets, each with its own AN/AC/AF triplet, mapped to
AlleleFrequency.population via mcps.toml [[populations]]:
| Column prefix | iris population label |
Meaning |
|---|---|---|
*_RAW |
mcps |
Whole cohort, all ancestries |
*_AFR |
mcps_afr |
African-ancestry subset |
*_EUR |
mcps_eur |
European-ancestry subset |
*_MEX |
mcps_mex |
Mexican/Amerindian-ancestry subset — MCPS' distinguishing contribution |
McpsAFAnnotator only emits an AlleleFrequency for a population when at
least one of AF/AC/AN is present (not .); populations with no data for a
given variant are simply absent from the result, not zero-filled.
SOURCE — genotyping platform per variant¶
Five distinct values observed across a full scan of all 23 files:
McpsAFAnnotator(freq_dir=..., source=["WGSOnly"]) restricts matching to
rows whose SOURCE is in that list — useful to exclude array-only variants
(typically pre-selected common SNPs, not a representative random sample) when
computing rarity statistics.
STON / GHOM / AHOM — passed through as metadata, not formally documented¶
These three columns aren't parsed into any typed domain field — they're
copied verbatim into VariantAnnotation.metadata.data (keys ston, ghom,
ahom, alongside mcps_id from the ID column) via mcps.toml
[metadata_int_fields]/[metadata_str_fields]. Their exact denotation isn't
documented in the source files themselves; by column-naming convention they
likely mean singleton count/flag, genotype-homozygous count, and
allele-homozygous count respectively, but treat that as an informed guess,
not a verified definition — iris treats them as opaque pass-through data
for exactly this reason, rather than mapping them into typed fields it can't
vouch for.
AN/AC are fractional, not integer sample counts¶
Real row (21:5033276 C>T, live-annotated below): AN_EUR=6046.505,
AN_MEX=13084.461 — not whole numbers. This is a weighted/effective allele
count (likely accounting for relatedness or imputation dosage across the
mixed WGS/WES/array sources), not a literal sample tally.
AlleleFrequency.count/.total are typed int | None, so
McpsAFAnnotator truncates via int(float(x)) — 6046.505 becomes
6046, silently dropping the fractional part. AF itself (a float) is
unaffected and carries full precision.
Bug found and fixed while writing this page: chrX never matched¶
chrX.freq.cpra.tsv.bgz's internal tabix contig name is "23", not
"X" or "chrX":
Verified against every one of the file's 6,464,980 rows (full scan, not
a sample): CHROM and CPRA — the two structured, position-bearing columns
— are "23" on 100% of rows, with zero exceptions. ID, by contrast, is a
free-text identifier column (like a VCF ID field) and is not uniformly
"23" or "X": 96.68% start with X: (a synthesized CPRA-style
placeholder), 3.05% start with 23: (same placeholder, inconsistent prefix
choice), and 0.28% are external identifiers with no chromosome prefix at
all — rsIDs and genotyping-array probe names (rs5939320,
JHU_X.2701482, …), all correlating exactly with SOURCE=ArrayOnly/
ArrayOverlap rows, which naturally carry their platform's own ID instead
of a synthetic one. None of this matters for the bug or its fix: iris
never reads ID, only CHROM/CPRA, and those are unconditionally "23".
This broke matching in two independent places:
TabixReader._query()'s chromosome resolution (resolve_chromosome()) only tries the bare name and achr-prefix toggle — neither bridges"X"→"23"— so the tabix fetch itself never found the file's rows.- Even with the fetch fixed,
_cpra_to_key()parses the row's ownCPRAcolumn, which would still yield chrom"23"— not matching a caller's"X"-keyed targets.
Both are now fixed in McpsAFAnnotator, verified live:
annotator = McpsAFAnnotator(freq_dir=MCPS_DIR)
annotator.annotate([va("X", 10_009, "A", "G")])[0].allele_frequencies
# 4 AlleleFrequency entries (mcps/mcps_afr/mcps_eur/mcps_mex) — was ()
annotator.query_region(GenomicPosition(chromosome="X", start=10_000, end=10_020))
# 6 VariantAnnotation, all with position.chromosome == "X" — was []
The fix has two parts, matching the two failure points above:
- Tabix fetch:
resolve_file_contig()(src/iris/adapters/driven/_helpers.py, shared with dbNSFP's analogous MT bug) falls back to a file's sole contig when the standard bare/chr-toggle resolution fails — safe here (unlike making the genericresolve_chromosome()itself do this) specifically becauseMcpsAFAnnotatoronly ever openspathafter already selecting it for the exact chromosome being queried viafile_pattern, so a lone differently-named contig can't be some other, genuinely unrelated chromosome. - Row parsing:
_load_chrom()/_row_to_annotation()now override whatever chromosome the row's ownCPRAcolumn carries with the caller's already-known canonical chromosome, rather than trusting CPRA's literal"23"directly (harmless for this fix specifically, since CPRA is 100% consistent — see above — but the override also meansirisnever has to special-case"23"anywhere outside this one translation step).
This is the same root-cause shape as dbSNP's RefSeq contig
handling — a contig
name that generic chr-prefix-only resolution can't bridge — solved the
same way dbSNP solves it: locally, in the annotator that already knows
which chromosome a given file represents, rather than in the shared,
necessarily-conservative resolve_chromosome().
Access patterns from iris¶
Registering the source¶
Already registered in this environment's ~/.iris/config.toml:
from iris.adapters.driven.annotators.mcps import McpsAFAnnotator
annotator = McpsAFAnnotator.from_registry() # all sources
annotator = McpsAFAnnotator.from_registry(workers=4)
annotator = McpsAFAnnotator(freq_dir=MCPS_DIR, source=["WGSOnly"]) # direct construction, source-filtered
annotate() — point-lookup enrichment¶
Live run against a real row with non-trivial metadata:
AlleleFrequency(frequency=5.03e-05, count=1, total=19896, population='mcps', allele='T')
AlleleFrequency(frequency=0.0, count=0, total=765, population='mcps_afr', allele='T')
AlleleFrequency(frequency=0.0, count=0, total=6046, population='mcps_eur', allele='T')
AlleleFrequency(frequency=7.64e-05, count=1, total=13084, population='mcps_mex', allele='T')
metadata: Metadata(source='mcps', data={'ston': 1, 'ghom': 0, 'ahom': 1, 'mcps_id': '21:5033276:C:T'})
(total values above are the truncated AN_* — the untruncated source was
19896, 765.032, 6046.505, 13084.461.)
query_region() — bulk region scan¶
Unlike annotate() (point lookup against known keys), query_region()
builds fresh VariantAnnotation objects for every row overlapping a region
— the same shape as RgcmeAFAnnotator.query_region() and
uvar's fetch(). Subject to the same
chrX limitation above (region resolution goes through the same
resolve_chromosome() call).
workers — chromosome-partitioned parallel fetch¶
Parallelizes across files, not within one — workers controls how many
of the (up to 23) per-chromosome files are opened and queried concurrently,
each via its own TabixFile handle. Given each file is small (largest is
~150 MB, vs. dbSNP/CADD's single 75-90 GB files), the win from workers
here comes from overlapping I/O across many files in a multi-chromosome
query, not from splitting one huge file.
Design notes & caveats¶
- chrX lookups used to silently return nothing — fixed; see above.
- No Y, no MT coverage at all — 23 files, only autosomes + X (real
absence, not a bug — there's no
chrY.freq.cpra.tsv.bgz/chrMT.*file to fix a lookup for). AN/ACare fractional and get truncated toint— see above. UseAFdirectly rather than reconstructing it from truncatedcount/totalif precision matters.STON/GHOM/AHOMare opaque pass-through metadata — not mapped to typed fields; see above.- Per-chromosome files, unlike dbSNP/CADD —
workershere parallelizes across files, not within a single large one. SOURCEfiltering matters for rarity analysis —ArrayOnly/ArrayOverlaprows reflect a pre-selected genotyping array content, not a random population sample; considersource=["WGSOnly", "WESOnly", "WESOverlap"]when that bias matters.
Reference¶
iris.adapters.driven.annotators.mcps
¶
MCPS allele frequency annotator via tabix random-access.
McpsAFAnnotator
¶
Bases: TabixVariantAnnotator
Annotates variants with MCPS allele frequencies (global + AFR/EUR/MEX ancestries).
Expects bgzipped, tabix-indexed per-chromosome files named
chr{N}.freq.cpra.tsv.bgz under freq_dir.
Column layout is defined in mcps.toml and matches the file header::
#CHROM START END REF ALT ID CPRA SOURCE STON GHOM AHOM
AN_RAW AC_RAW AF_RAW AN_AFR AC_AFR AF_AFR
AN_EUR AC_EUR AF_EUR AN_MEX AC_MEX AF_MEX
workers
class-attribute
instance-attribute
¶
Default parallel tabix workers, used when annotate()/query_region() omit an override.
from_registry
classmethod
¶
Build a McpsAFAnnotator resolving source_name from the iris DataRegistry.
Source code in src/iris/adapters/driven/annotators/mcps.py
annotate
¶
Annotate variants with MCPS allele frequencies.
| PARAMETER | DESCRIPTION |
|---|---|
items
|
Variants to annotate.
TYPE:
|
workers
|
Parallel tabix workers for this call — controls how many
per-chromosome files are queried in parallel, each opening its
own
TYPE:
|
Source code in src/iris/adapters/driven/annotators/mcps.py
query_region
¶
Return all MCPS variants whose positions overlap the given regions.
Each row in the per-chromosome frequency file becomes a fresh
VariantAnnotation containing the parsed allele frequencies.
workers controls how many chromosome files are scanned in
parallel; defaults to self.workers when omitted. Files that do
not exist on disk are silently skipped.