Skip to main content

AADR cross-version GeneticID / MasterID join utility for ancient-DNA / population-genetics workflows.

Project description

aadr-resolve

AADR cross-version GeneticID / MasterID join utility for ancient-DNA and population-genetics workflows.

aadr-resolve reads AADR (Allen Ancient DNA Resource) .anno files across one or more releases and resolves the cross-version sample-ID join through the Master ID column — the part every ancient-DNA pipeline currently re-implements with custom awk. It handles AADR's progressive de-anonymization (I0001 in v44.3 → Loschbour.AG in v66) and the periodic Master-ID renames (9-18 per consecutive version pair; ~62 cumulative v44.3 → v66.0) automatically.

Designed and bench-verified against AADR releases v44.3, v50.0, v52.2, v54.1, v62.0, and v66.0; six schema classes (A–F) are recognized out-of-the-box via in-package YAML header signatures.

Install

pip install aadr-resolve

Requires Python 3.11+ and pip 23+ with setuptools 61+. If you are on a system Python (Ubuntu 22.04 ships pip 22 / setuptools 59), upgrade first:

python3.11 -m venv .venv && source .venv/bin/activate
pip install --upgrade pip   # brings pip to 23+, which upgrades setuptools automatically
pip install aadr-resolve

Installing without upgrading pip on older toolchains causes the package to appear as UNKNOWN 0.0.0, which breaks co-installation with other packages (see #3).

Dependencies: pandas 2.x, click 8.x, PyYAML 6.x.

Quickstart

Resolve a single sample across two AADR releases.

aadr-resolve lookup I0001 \
    --anno-files v44.3_1240K_public.anno \
    --anno-files v66.0_1240K_public.anno

Output (stdout):

query: I0001
canonical individual_id: Loschbour    (matched via individual_id)
v44.3 rows: 1
  I0001  Luxembourg_Loschbour  537,182 SNPs
v66.0 rows: 2
  Loschbour.AG  Luxembourg_Mesolithic.AG  155,036 SNPs  pgid=33
  Loschbour.DG  Luxembourg_Mesolithic.DG  620,881 SNPs  pgid=39136
master_id_bridge: v44.3 I0001 → v66.0 Loschbour (via shared GID Loschbour.DG)
status: present_in_2_of_2_versions; multi_row; individual_id_renamed

Recreate a cohort against a newer release.

aadr-resolve cohort patterson_2022_whga.txt \
    --anno-files v44.3_1240K_public.anno \
    --anno-files v66.0_1240K_public.anno \
    --cohort-version v44.3 \
    -o whga_v66_manifest.tsv

The manifest is a TSV with one row per (individual × library), with per-version genetic_id / group_id / snps_hit_1240k columns plus per-adjacent-pair group_id_change_class_v{old}_to_v{new} columns, ready to feed into downstream relabeling tools like pgen-samplebind.

Output (stdout summary block):

Loaded 2 .anno file(s):
  [v44.3] v44.3_1240K_public.anno: 9,275 rows × 43 cols, class A
  [v66.0] v66.0_1240K_public.anno: 23,250 rows × 49 cols, class E

Cross-version bridge:
  GID-stable MID-rename detection:  9 events
  Manual --mid-bridge entries:      0
  Cross-lab MID collision check:    no collisions detected

Cohort input: patterson_2022_whga.txt (40 individuals)
  Resolved in latest version:  37
  Added after earliest:        1
  Removed before latest:       2

Group ID changes (v44.3 → v66.0):
  convention_restructure_suffix      18
  partial                             1
  substantive_regroup                 2

Wrote whga_v66_manifest.tsv (40 rows × 13 cols)
Sample turnover within cohort: 5.0% — PASS

Done in 1.4s.

Add --quiet to suppress the block. Add --report-json summary.json to also emit a run-level JSON sidecar that loads cheaply via json.load — see docs/REPORT_JSON_SCHEMA.md.

Structured diff between two releases.

aadr-resolve diff v62.0_1240K_public.anno v66.0_1240K_public.anno \
    --tsv > v62_to_v66_changes.tsv

Emits one row per change event: added, removed, genetic_id_renamed, master_id_renamed, group_changed (with a per-class label — convention_restructure_suffix etc.).

For large diffs at AADR scale, stream the per-event TSV alongside a small summary JSON instead of buffering the full event list:

aadr-resolve diff v62.0_1240K_public.anno v66.0_1240K_public.anno \
    -o changes_summary.json \
    --report changes_events.tsv \
    --report-json summary.json

--report PATH streams one row per event (constant memory) and --report-json PATH writes the run-level summary (~few KB, loads cheaply via json.load). The diff stdout summary block routes to stderr when stdout is carrying the JSON payload, so pipes stay clean.

Subcommands

Command Purpose
lookup Resolve a single sample across N versions
cohort Emit a cross-version manifest for a user-supplied cohort
diff Structured diff between two versions
join Wide-format pairwise table over the full intersection
schema Diagnostic: report the detected schema class

aadr-resolve lookup

aadr-resolve lookup INDIVIDUAL_OR_GENETIC_ID \
    --anno-files PATH [--anno-files PATH ...]
    [--json]

Treated as individual_id by default; falls back to genetic_id if no IID matches. The MID-rename bridge is built automatically from the supplied versions and reported under master_id_bridge in the output.

aadr-resolve cohort

aadr-resolve cohort COHORT_FILE \
    --anno-files PATH [--anno-files PATH ...]
    [--cohort-version LABEL]
    -o OUT.tsv [--json]
    [--no-propagate]
    [--collapse-to-individual]
    [--gid-preference AG,DG,SG,HO,TW,BY,AA,EC,WGC,bare]
    [--turnover-warn 0.05] [--turnover-fail 0.30]
    [--cohort-coverage-warn 0.50] [--cohort-coverage-fail 0.25]
    [--report-json PATH]

COHORT_FILE is a TSV: one column for individual_id, optional second column for cohort_label. --cohort-version is auto-detected from the supplied annos when omitted. Default output is row-per-(individual × library); --collapse-to-individual reduces to one row per individual via the --gid-preference suffix priority. --report-json PATH writes a run-level summary sidecar (~few KB) for CI dashboards.

aadr-resolve diff

aadr-resolve diff V_OLD.anno V_NEW.anno
    [--json | --tsv]
    [-o OUT]
    [--include-class CLASS [--include-class CLASS ...]]
    [--all-events]
    [--turnover-warn 0.05] [--turnover-fail 0.30]
    [--substantive-regroup-fail INT]
    [--report PATH] [--report-json PATH]

JSON output is summary-first: per-class counts always included; per-event arrays only for substantive_regroup (always) and any class named via --include-class, or all classes when --all-events is set. --tsv switches to streamed one-row-per-event format.

For large diffs, prefer the streamed sidecars: --report PATH writes per-event TSV with constant memory; --report-json PATH writes the run-level summary. The summary block routes to stderr when stdout is the JSON payload, so aadr-resolve diff a.anno b.anno | jq ... works without breaking the pipe.

aadr-resolve join

aadr-resolve join V_OLD.anno V_NEW.anno
    -o OUT.tsv [--json]
    [--collapse-to-individual]
    [--gid-preference AG,DG,SG,HO,TW,BY,AA,EC,WGC,bare]

Wide-format pairwise table over the full v_old ∪ v_new canonical individual_id set. Same output schema as cohort; useful when you don't have a pre-existing cohort list.

aadr-resolve schema

aadr-resolve schema PATH [--json]

Diagnostic: detects which schema class (A–F) the .anno belongs to, reports the column layout. Useful for debugging "why does this .anno not load."

Shared options

These apply to all subcommands:

Option Default Notes
--schema-override CLASS auto Force schema class A/B/C/D/E (e.g., renamed .anno)
--version-label LABEL auto Force version label (when filename pattern doesn't match)
--mid-bridge FILE none Manual master_id-rename TSV layered on auto-detected bridge
--on-mid-collision {error,warn} error Cross-lab MID collision policy
--quiet false Suppress the stdout summary block (cohort + diff + join write phase)

Library API

The same functionality is available in-process:

from aadr_resolve import (
    AnnoFrame,
    resolve_master_ids,
    resolve_genetic_ids,
)

# Resolve v44.3 Master IDs to v66.0 GeneticIDs
result = resolve_master_ids(
    ["I0001", "Bichon", "Mota"],
    src_version="v44.3",
    dst_version="v66.0",
    anno_paths={
        "v44.3": "v44.3_1240K_public.anno",
        "v66.0": "v66.0_1240K_public.anno",
    },
    mid_bridge="manual_renames.tsv",  # optional override TSV
)
# result = {"I0001": "Loschbour.AG", "Bichon": "Bichon.SG", "Mota": None}

resolve_genetic_ids does the GID → GID inverse:

result = resolve_genetic_ids(
    ["I0001"],
    src_version="v44.3",
    dst_version="v66.0",
    anno_paths={...},
)
# result = {"I0001": ["Loschbour.AG", "Loschbour.DG"]}  # multi-row IID

Both functions also accept anno_frames={label: AnnoFrame, ...} as an alternative to anno_paths={label: path, ...} — useful when you've already loaded the frames once and want to reuse them across calls.

Direct AnnoFrame access for lower-level work:

from aadr_resolve import AnnoFrame

af = AnnoFrame.from_path("v66.0_1240K_public.anno", version_label="v66.0")
af.schema_class       # SchemaClass.E
af.individual_id      # pd.Series of canonical IIDs
af.genetic_id         # pd.Series
af.persistent_genetic_id  # pd.Series of Int64 nullable for class E; None for A-D
af.date_calbp         # pd.Series of Int64 nullable
af.coverage           # pd.Series of Float64 nullable
af.path               # original Path, useful for re-creating anno_paths dicts

Exception hierarchy

All errors derive from aadr_resolve.AadrResolveError. Sibling tools catching aadr-resolve errors can except aadr_resolve.<Class>:

Class Maps to exit Trigger
ValidationError 1 Turnover gate, coverage gate, substantive-regroup gate
IOFailure 2 File not found, lock held, malformed TSV
InvariantViolation 3 Schema YAML malformed (rare)
SchemaDetectionError 3 Header signature unknown
MissingNativeFieldError 3 Canonical field requested for a class that lacks it
CollisionDetected 3 Cross-lab MID collision under error policy
UsageError 4 Bad CLI args; cohort file has no matching version

Exit codes

Stable across versions. CI workflows can grep:

  • 0 — success
  • 1 — soft-validation failure (any of the gates)
  • 2 — I/O failure
  • 3 — invariant violation (schema, MID collision)
  • 4 — usage error (bad CLI args)

Troubleshooting

"unknown .anno schema signature" — your .anno header doesn't match any of the 5 known classes. Either the file is from a newer AADR release (file an issue with the bench-verify diff), or the file has been edited. Workarounds:

  • --schema-override A|B|C|D|E|F forces a class without signature check.
  • --version-label vN.N forces a version label when the filename doesn't match a known pattern. It sets the version-keying label only — the column layout (for releases that share a class but shift columns, e.g. v50.0) is detected from the header content, so --version-label does not force a layout when the headers indicate a different one.

"cross-lab MID collision" — the GID-stability check found a Master ID that maps to two different individuals in different versions. This indicates either a real data error in AADR or a cross-lab naming collision (rare). Workarounds:

  • --on-mid-collision warn continues with a stderr warning and marks affected rows with library_chain_ambiguous status.
  • --mid-bridge FILE lets you specify the correct mapping manually.

"sample turnover gate (fail)" — removal rate exceeded the --turnover-fail threshold (default 30%). Indicates either a major AADR cleanup (the v62→v66 bump removed ~17%) or that the wrong files are being compared. Override with --turnover-fail 1.0 to disable.

"cohort coverage gate (fail)" — fewer than 25% of cohort entries resolved in the supplied versions. Usually means the cohort file uses IDs from a version not in the supplied set. Check --cohort-version.

"substantive_regroup gate (fail)" — only fires on diff when --substantive-regroup-fail INT is set and the count of substantive_regroup events exceeds the threshold. Inspect the events via --report PATH or --include-class substantive_regroup before adjusting the threshold.

Pandas ParserError on a v52 / v54 .anno — these versions contain embedded quote characters in some full_date cells. aadr-resolve reads with csv.QUOTE_NONE to side-step pandas's default quote-handling; upgrade if you're on an older version.

Composition with the broader ecosystem

The manifest TSV is the handoff format to downstream relabeling tools. Typical pipeline pairing aadr-resolve's join output with a PLINK2 sample-rename step:

aadr-resolve cohort patterson_2022.txt \
    --anno-files v44.3_1240K_public.anno \
    --anno-files v66.0_1240K_public.anno \
    -o cohort_manifest.tsv
pgen-samplebind merge \
    --relabel-from cohort_manifest.tsv \
    --output merged_v66.pgen \
    v44.3.pgen v66.0.pgen

Cohort manifest column layout

Stable column order across versions:

  1. cohort_label, cohort_label_source, individual_id_canonical, library_token
  2. Per-version triples in user-supplied order: v{X}_genetic_id, v{X}_group_id, v{X}_snps_hit_1240k
  3. Per-adjacent-pair classifier columns: group_id_change_class_v_{old}_to_v_{new} (one per consecutive pair; values: convention_restructure_{suffix,country,order,punct}, partial, substantive_regroup, none, or -- if absent)
  4. persistent_genetic_id (only when the latest version is class E and at least one row has a value)
  5. status

Missing-cell sentinel is -- for every column (both string fields and nullable-Int64 numerics).

Contributing

  • CONTRIBUTING.md covers local development setup, the test layout (default / slow / external / perf markers), lint and type-check invocations, and the release process.
  • DEVELOPMENT.md is the architecture tour — module map, data flow, key abstractions, invariants. Start there before filing a non-trivial PR.
  • ROADMAP.md tracks deferred work and known scope-cuts.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aadr_resolve-0.4.1.tar.gz (76.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aadr_resolve-0.4.1-py3-none-any.whl (90.7 kB view details)

Uploaded Python 3

File details

Details for the file aadr_resolve-0.4.1.tar.gz.

File metadata

  • Download URL: aadr_resolve-0.4.1.tar.gz
  • Upload date:
  • Size: 76.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aadr_resolve-0.4.1.tar.gz
Algorithm Hash digest
SHA256 5c3572926b028b9cc39fc4838162dabbeaa43827b2e1a738415e779525263e18
MD5 8854b708648e5a9449df9369f93fa0aa
BLAKE2b-256 cf7a5587cdcd23383b45f187dbeb1c29fa18eaeaa5a2352c5e517e898e3d377e

See more details on using hashes here.

Provenance

The following attestation bundles were made for aadr_resolve-0.4.1.tar.gz:

Publisher: publish.yml on carstenerickson/aadr-resolve

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aadr_resolve-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: aadr_resolve-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 90.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aadr_resolve-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4a49414320cd129654c6717ac18b02145a13bfc2d1a8008367554d4e3c5fbe3a
MD5 13790f63289444b2978a98656428f651
BLAKE2b-256 5a98262ff5441d3d4db816a9e7d6368914608f67bc694160f768b8b6120f3704

See more details on using hashes here.

Provenance

The following attestation bundles were made for aadr_resolve-0.4.1-py3-none-any.whl:

Publisher: publish.yml on carstenerickson/aadr-resolve

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page