afquery 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {afquery-0.3.2 → afquery-0.3.3}/PKG-INFO +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/debugging-results.md +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/filter-pass-tracking.md +16 -13
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/multi-cohort-strategies.md +7 -3
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/performance.md +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/ploidy-and-sex-chroms.md +3 -3
- {afquery-0.3.2 → afquery-0.3.3}/docs/faq.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/concepts.md +7 -7
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/quickstart.md +1 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/tutorial.md +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/understanding-output.md +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/annotate-vcf.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/create-database.md +6 -8
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/sample-filtering.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/update-database.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/index.md +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/docs/reference/data-model.md +22 -17
- {afquery-0.3.2 → afquery-0.3.3}/docs/reference/glossary.md +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/docs/reference/python-api.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/troubleshooting.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/acmg-use-cases.md +1 -1
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/sex-specific-af.md +4 -4
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/_version.py +2 -2
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/cli.py +10 -12
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_cli.py +28 -0
- {afquery-0.3.2 → afquery-0.3.3}/.dockerignore +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/.github/workflows/ci.yml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/.github/workflows/docs.yml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/.github/workflows/release.yml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/.gitignore +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/CONTRIBUTING.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/Dockerfile +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/LICENSE +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/README.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/.gitignore +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/INSTALL.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/README.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/Snakefile +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/.gitignore +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/01a_assign_samples.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/01b_write_manifests.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/02_build_databases.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/03_compute_metrics.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/04_classify_acmg.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/05_plot_figures.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/README.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/Snakefile +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/__init__.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/beds/SureSelect_v5.bed +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/beds/SureSelect_v6.bed +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/beds/SureSelect_v7.bed +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/capture_kit/config.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/config.yaml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/envs/benchmark.yaml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/.gitignore +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/01_prepare_data.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/02_query_scaling.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/03_build.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/04_annotate.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/05_vs_bcftools.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/06_plot.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/README.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/Snakefile +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/collect_annotate.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/collect_bcftools.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/collect_build_perf.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/collect_prepare.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/collect_query_scaling.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/config.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/performance/config_smoke.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/shared/__init__.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/shared/config.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/shared/rules/download_1kg.smk +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/benchmarks/shared/utils.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/benchmarking.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/coverage-evidence.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/advanced/pipeline-integration.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/assets/img/gap2_mixed_technologies.png +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/installation.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/motivation.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/getting-started/preprocessing.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/dump-export.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/manifest-format.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/query.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/guides/variant-info.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/reference/cli.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/scripts/gen_gap2_figure.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/stylesheets/extra.css +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/clinical-prioritization.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/cohort-stratification.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/population-specific-af.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/pseudo-controls.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/docs/use-cases/technology-integration.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/examples/demo/README.md +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/examples/demo/create_demo_data.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/mkdocs.yml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/pyproject.toml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/recipes/afquery/meta.yaml +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/resources/normalize_vcf.sh +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/__init__.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/annotate.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/benchmark.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/bitmaps.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/capture.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/cli.pyr +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/constants.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/database.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/dump.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/models.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/ploidy.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/__init__.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/build.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/compact.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/ingest.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/manifest.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/regions.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/synth.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/preprocess/update.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/query.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/src/afquery/variant_info.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/conftest.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/annotate_input.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/annotate_multi_bucket.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/annotate_multi_chrom.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/beds/wes_kit_a.bed +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/beds/wes_kit_b.bed +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/expected_results.json +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/manifest.tsv +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S00.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S01.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S02.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S03.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S04.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S05.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S06.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S07.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S08.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/data/vcfs/S09.vcf +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_annotate.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_batch.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_benchmark.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_bitmaps.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_capture.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_cli_docs_consistency.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_compact.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_constants.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_dump.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_haploid_stats.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_info.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_no_coverage.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_pass_filter.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_ploidy.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_preprocess.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_query.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_sample_filter.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_synth.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_synthetic_stats.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_update.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_update_metadata.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_variant_info.py +0 -0
- {afquery-0.3.2 → afquery-0.3.3}/tests/test_warnings.py +0 -0
|
@@ -34,13 +34,13 @@ A variant you expect to find is not in the database:
|
|
|
34
34
|
| Check | Details |
|
|
35
35
|
|-------|---------|
|
|
36
36
|
| Was it in the source VCFs? | AFQuery only stores variants present in ingested VCFs |
|
|
37
|
-
| Was
|
|
37
|
+
| Was the alt allele ever called? | A variant is stored only if at least one sample carries the alt allele or has a failed call there. A site seen only as `0/0` in every sample is genuinely absent. Note that non-PASS calls *are* kept (in `fail_bitmap`) — such a variant is not missing, it just shows `AC=0`. |
|
|
38
38
|
| Multiallelic sites | AFQuery stores each ALT separately. Query the specific ALT allele, not just position |
|
|
39
39
|
| Chromosome naming | Ensure consistent `chr` prefix usage |
|
|
40
40
|
|
|
41
41
|
### 4. Unexpected N_FAIL > 0
|
|
42
42
|
|
|
43
|
-
`N_FAIL > 0` means some eligible samples had
|
|
43
|
+
`N_FAIL > 0` means some eligible samples had a call with `FILTER≠PASS` at this position. These samples are excluded from AC, but they remain eligible and still count in AN. This is usually benign (1–2 samples), but a high N_FAIL warrants investigation:
|
|
44
44
|
|
|
45
45
|
| N_FAIL relative to n_eligible | Likely cause |
|
|
46
46
|
|---|---|
|
|
@@ -11,9 +11,10 @@ In VCF format, the FILTER column indicates whether a variant call passed quality
|
|
|
11
11
|
- `PASS` or `.` (missing) — the variant passed all filters
|
|
12
12
|
- Any other value (e.g., `LowQual`, `VQSRTrancheSNP99.90to100.00`) — the variant failed one or more filters
|
|
13
13
|
|
|
14
|
-
AFQuery default behavior:
|
|
14
|
+
AFQuery's default behavior:
|
|
15
15
|
|
|
16
|
-
-
|
|
16
|
+
- Only `FILTER=PASS` calls contribute to **AC** (and therefore to **AF**). This is always enforced.
|
|
17
|
+
- **AN** is not affected by the filter — it counts every eligible sample at the position, failed calls included.
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
@@ -21,19 +22,19 @@ AFQuery default behavior:
|
|
|
21
22
|
|
|
22
23
|
AFQuery stores a third bitmap per variant alongside `het_bitmap` and `hom_bitmap`:
|
|
23
24
|
|
|
24
|
-
- **`fail_bitmap`** — bit set for each sample
|
|
25
|
+
- **`fail_bitmap`** — bit set for each sample whose call at this site has `FILTER≠PASS`. In practice this is two cases: a sample that carries the alt allele but whose call failed a filter, and a sample with a missing genotype (`./.`) at a site that itself failed a filter.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
What this means for a sample in `fail_bitmap`:
|
|
27
28
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
29
|
+
- Its alt alleles are **not** counted in AC, so it does not raise AF.
|
|
30
|
+
- It is still an eligible sample, so it **is** counted in AN. The failed call lowers AF by sitting in the denominator without contributing to the numerator.
|
|
31
|
+
- Its count is exposed separately as `N_FAIL`.
|
|
31
32
|
|
|
32
33
|
---
|
|
33
34
|
|
|
34
35
|
## Database Creation
|
|
35
36
|
|
|
36
|
-
The `fail_bitmap` is always written
|
|
37
|
+
The `fail_bitmap` is always written, regardless of build options:
|
|
37
38
|
|
|
38
39
|
```bash
|
|
39
40
|
afquery create-db --manifest manifest.tsv --output-dir ./db/ --genome-build GRCh38
|
|
@@ -52,10 +53,10 @@ afquery query --db ./db/ --locus chr1:925952
|
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
```
|
|
55
|
-
chr1:925952 G>A AC=142 AN=2742 AF=0.0518 n_eligible=1371 N_HET=138 N_HOM_ALT=2 N_HOM_REF=
|
|
56
|
+
chr1:925952 G>A AC=142 AN=2742 AF=0.0518 n_eligible=1371 N_HET=138 N_HOM_ALT=2 N_HOM_REF=1224 N_FAIL=7
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
`N_FAIL=7` means 7 eligible samples had
|
|
59
|
+
`N_FAIL=7` means 7 eligible samples had a call with FILTER≠PASS at this site. They are part of `n_eligible` (and of AN), so the genotype counts still add up: 138 + 2 + 1224 + 7 = 1371.
|
|
59
60
|
|
|
60
61
|
### Python API
|
|
61
62
|
|
|
@@ -83,21 +84,23 @@ Each carrier row shows its `filter` column as `PASS` or `FAIL`, along with sampl
|
|
|
83
84
|
|
|
84
85
|
## VCF Annotation
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
Among the INFO fields `afquery annotate` writes, one reports the failed-call count:
|
|
87
88
|
|
|
88
89
|
| Field | Type | Description |
|
|
89
90
|
|-------|------|-------------|
|
|
90
|
-
| `AFQUERY_N_FAIL` | Integer | Eligible samples with FILTER≠PASS at this variant |
|
|
91
|
+
| `AFQUERY_N_FAIL` | Integer | Eligible samples with `FILTER≠PASS` at this variant |
|
|
91
92
|
|
|
92
93
|
```bash
|
|
93
94
|
afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
|
|
94
95
|
```
|
|
95
96
|
|
|
97
|
+
See [Understanding Output](../getting-started/understanding-output.md#vcf-annotation-fields) for the full set of `AFQUERY_*` fields.
|
|
98
|
+
|
|
96
99
|
---
|
|
97
100
|
|
|
98
101
|
## PASS-Only Enforcement
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
PASS-only counting is always enforced and cannot be turned off. It applies to the numerator only: a failed call never adds an alt allele to AC, but the sample stays in the eligible set and so remains in AN. The result is a deliberately conservative AF — a failed carrier weighs on the denominator without lifting the numerator, which is the safe direction for clinical and research use.
|
|
101
104
|
|
|
102
105
|
---
|
|
103
106
|
|
|
@@ -61,7 +61,7 @@ All samples in one database, with phenotype codes distinguishing cohorts.
|
|
|
61
61
|
### Manifest Design
|
|
62
62
|
|
|
63
63
|
```tsv
|
|
64
|
-
|
|
64
|
+
sample_name vcf_path sex tech_name phenotype_codes
|
|
65
65
|
CARD_001 /data/card/001.vcf.gz female wgs cardiology,EUR,control
|
|
66
66
|
CARD_002 /data/card/002.vcf.gz male wgs cardiology,EUR,case_HCM
|
|
67
67
|
NEURO_001 /data/neuro/001.vcf.gz female wes neurology,AFR,case_epilepsy
|
|
@@ -99,7 +99,7 @@ afquery query --db ./merged/ --locus chr1:12345678 --ref C --alt T \
|
|
|
99
99
|
|-----------|-------------|
|
|
100
100
|
| Single database to maintain | Rebuilding requires all VCFs accessible |
|
|
101
101
|
| Cross-cohort queries via phenotype filters | Phenotype code design must be planned upfront |
|
|
102
|
-
| One annotation pass covers all cohorts | Adding a new cohort requires `afquery update` |
|
|
102
|
+
| One annotation pass covers all cohorts | Adding a new cohort requires `afquery update-db` |
|
|
103
103
|
| Flexible ad-hoc stratification | Larger database, longer rebuild time |
|
|
104
104
|
|
|
105
105
|
---
|
|
@@ -134,7 +134,11 @@ Maintain both per-cohort and merged databases.
|
|
|
134
134
|
afquery annotate --db /databases/institutional/cardiology/ \
|
|
135
135
|
--input patient.vcf.gz --output step1.vcf.gz
|
|
136
136
|
|
|
137
|
-
# Annotate against shared controls
|
|
137
|
+
# Annotate against shared controls in a second pass
|
|
138
|
+
# Note: the AFQUERY_* INFO fields are overwritten on each pass — if you need
|
|
139
|
+
# both sets of frequencies, extract the values from step1.vcf.gz first.
|
|
140
|
+
afquery annotate --db /databases/shared/combined_controls/ \
|
|
141
|
+
--input step1.vcf.gz --output step2.vcf.gz
|
|
138
142
|
```
|
|
139
143
|
|
|
140
144
|
---
|
|
@@ -50,7 +50,7 @@ Expected scaling (50K samples, 2,500 buckets, GRCh38):
|
|
|
50
50
|
| 32 | ~18 min | ~24× |
|
|
51
51
|
| 52 | ~13 min | ~38× |
|
|
52
52
|
|
|
53
|
-
Scaling is near-linear up to ~32 cores; beyond that, I/O
|
|
53
|
+
Scaling is near-linear up to ~32 cores; beyond that, disk I/O contention limits further gains.
|
|
54
54
|
|
|
55
55
|
Total RAM required: `build_threads × build_memory`
|
|
56
56
|
|
|
@@ -80,7 +80,7 @@ afquery create-db ... --threads 32 --build-threads 16 --build-memory 4GB
|
|
|
80
80
|
graph TD
|
|
81
81
|
A["Query Request<br/>chr1:925952"]
|
|
82
82
|
B["Open DuckDB<br/>connection"]
|
|
83
|
-
C["Locate Parquet<br/>bucket_0
|
|
83
|
+
C["Locate Parquet<br/>bucket_0.parquet"]
|
|
84
84
|
D["Read rows<br/>matching pos"]
|
|
85
85
|
E["Deserialize<br/>bitmaps"]
|
|
86
86
|
F["Bitmap AND<br/>with eligible<br/>sample set"]
|
|
@@ -103,12 +103,12 @@ afquery query --db ./db/ --locus chrM:3243
|
|
|
103
103
|
|
|
104
104
|
For every query result, the following identity holds:
|
|
105
105
|
|
|
106
|
-
**N_HET + N_HOM_ALT + N_HOM_REF + N_FAIL = n_eligible**
|
|
106
|
+
**N_HET + N_HOM_ALT + N_HOM_REF + N_FAIL + N_NO_COVERAGE = n_eligible**
|
|
107
107
|
|
|
108
|
-
This can be used to validate results. N_HOM_REF is the number of eligible samples that are homozygous reference (i.e., do not carry the alt allele and passed quality filters).
|
|
108
|
+
This can be used to validate results. N_HOM_REF is the number of eligible samples that are homozygous reference (i.e., do not carry the alt allele and passed quality filters). N_NO_COVERAGE is 0 unless a coverage-evidence filter is active — see [Coverage Evidence](coverage-evidence.md).
|
|
109
109
|
|
|
110
110
|
!!! note "Mutual exclusivity"
|
|
111
|
-
N_HET, N_HOM_ALT, N_HOM_REF, and
|
|
111
|
+
N_HET, N_HOM_ALT, N_HOM_REF, N_FAIL, and N_NO_COVERAGE are mutually exclusive. A sample with a non-ref allele but FILTER≠PASS is counted in N_FAIL only — it does not appear in N_HET or N_HOM_ALT. Likewise, N_HOM_REF counts only PASS-filtered samples.
|
|
112
112
|
|
|
113
113
|
### chrX non-PAR
|
|
114
114
|
|
|
@@ -149,7 +149,7 @@ On chrX non-PAR positions, males contribute AN=1 and females contribute AN=2. Wh
|
|
|
149
149
|
|
|
150
150
|
## What does N_FAIL mean in query output?
|
|
151
151
|
|
|
152
|
-
`N_FAIL` is the count of eligible samples
|
|
152
|
+
`N_FAIL` is the count of eligible samples whose call had `FILTER≠PASS` at the position. Shown as `N_FAIL=N` in text output. These samples are excluded from AC, but they stay eligible and still count toward AN. See [FILTER=PASS Tracking](advanced/filter-pass-tracking.md).
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
@@ -126,13 +126,13 @@ graph TB
|
|
|
126
126
|
|
|
127
127
|
### Per-Variant Bitmaps
|
|
128
128
|
|
|
129
|
-
For each variant row, AFQuery stores
|
|
129
|
+
For each variant row, AFQuery stores per-sample genotype information as [Roaring Bitmaps](https://roaringbitmap.org/). Three of them drive every query:
|
|
130
130
|
|
|
131
|
-
- **`het_bitmap`** —
|
|
132
|
-
- **`hom_bitmap`** —
|
|
133
|
-
- **`fail_bitmap`**
|
|
131
|
+
- **`het_bitmap`** — sample is heterozygous (`GT=0/1`) with `FILTER=PASS`
|
|
132
|
+
- **`hom_bitmap`** — sample is homozygous alt (`GT=1/1`, or `GT=1` on haploid regions) with `FILTER=PASS`
|
|
133
|
+
- **`fail_bitmap`** — the sample's call at this site has `FILTER≠PASS`
|
|
134
134
|
|
|
135
|
-
Each sample has a stable integer ID (0-indexed). The bit position in the bitmap equals the sample ID.
|
|
135
|
+
Each sample has a stable integer ID (0-indexed). The bit position in the bitmap equals the sample ID. Databases built with coverage-quality filters carry two additional bitmaps — see [Data Model](../reference/data-model.md#parquet-schema).
|
|
136
136
|
|
|
137
137
|
### Parquet Storage
|
|
138
138
|
|
|
@@ -141,8 +141,8 @@ Bitmaps are serialized and stored in Parquet files, partitioned by chromosome an
|
|
|
141
141
|
```
|
|
142
142
|
variants/
|
|
143
143
|
chr1/
|
|
144
|
-
bucket_0
|
|
145
|
-
bucket_1
|
|
144
|
+
bucket_0.parquet ← positions 0–999,999
|
|
145
|
+
bucket_1.parquet ← positions 1,000,000–1,999,999
|
|
146
146
|
...
|
|
147
147
|
chr2/
|
|
148
148
|
...
|
|
@@ -135,6 +135,7 @@ The output VCF gains INFO fields (see [Annotate a VCF](../guides/annotate-vcf.md
|
|
|
135
135
|
| `AFQUERY_N_HOM_ALT` | A (per ALT) | Homozygous alt sample count |
|
|
136
136
|
| `AFQUERY_N_HOM_REF` | A (per ALT) | Homozygous ref sample count |
|
|
137
137
|
| `AFQUERY_N_FAIL` | 1 (per site) | Samples with FILTER≠PASS |
|
|
138
|
+
| `AFQUERY_N_NO_COVERAGE` | A (per ALT) | Eligible samples lacking coverage evidence (0 unless a coverage-evidence filter is active) |
|
|
138
139
|
|
|
139
140
|
---
|
|
140
141
|
|
|
@@ -86,7 +86,7 @@ Every query result includes the following fields:
|
|
|
86
86
|
| **AC** | Allele count — alt allele copies in eligible samples |
|
|
87
87
|
| **AN** | Allele number — total alleles examined (adjusted for coverage and ploidy) |
|
|
88
88
|
| **AF** | Allele frequency — `AC / AN` |
|
|
89
|
-
| **n_eligible** |
|
|
89
|
+
| **n_eligible** | Eligible samples at this position — those passing the metadata filters *and* covered here. Ploidy is applied afterwards, to AN. Here, 10 = no filter applied and every sample is covered. |
|
|
90
90
|
| **N_HET** | Eligible samples heterozygous for the alt allele |
|
|
91
91
|
| **N_HOM_ALT** | Eligible samples homozygous for the alt allele |
|
|
92
92
|
| **N_HOM_REF** | Eligible samples homozygous reference |
|
|
@@ -94,7 +94,7 @@ Every query result includes the following fields:
|
|
|
94
94
|
|
|
95
95
|
For `chr1:925952`: AN=20 = 10 diploid samples × 2 alleles; AC=6 = 4 het samples (1 copy each) + 1 hom-alt (2 copies).
|
|
96
96
|
|
|
97
|
-
**Accounting identity:** `n_eligible = N_HET + N_HOM_ALT + N_HOM_REF + N_FAIL` always holds (10 = 4+1+5+0
|
|
97
|
+
**Accounting identity:** `n_eligible = N_HET + N_HOM_ALT + N_HOM_REF + N_FAIL + N_NO_COVERAGE` always holds. `N_NO_COVERAGE` is 0 here (it only becomes non-zero with a coverage-evidence filter), so 10 = 4+1+5+0+0 ✓.
|
|
98
98
|
|
|
99
99
|
### N_FAIL in practice
|
|
100
100
|
|
|
@@ -14,8 +14,8 @@ This page explains what each field in AFQuery output means and how to interpret
|
|
|
14
14
|
| **N_HET** | int | Number of eligible samples heterozygous for the alt allele (GT=0/1) |
|
|
15
15
|
| **N_HOM_ALT** | int | Number of eligible samples homozygous for the alt allele (GT=1/1 or GT=1). Includes haploid carriers on sex chromosomes and chrM. See [Ploidy](../advanced/ploidy-and-sex-chroms.md#genotype-counting). |
|
|
16
16
|
| **N_HOM_REF** | int | Number of eligible samples homozygous reference (GT=0/0 or GT=0) |
|
|
17
|
-
| **n_eligible** | int | Number of samples
|
|
18
|
-
| **N_FAIL** | int | Number of eligible samples
|
|
17
|
+
| **n_eligible** | int | Number of eligible samples — those passing the sex/phenotype/tech filters *and* covered at this position |
|
|
18
|
+
| **N_FAIL** | int | Number of eligible samples whose call at this position had FILTER≠PASS. These samples are counted *only* in N_FAIL — not in N_HET, N_HOM_ALT, or N_HOM_REF — but they stay eligible and still count toward AN. |
|
|
19
19
|
| **N_NO_COVERAGE** | int | Number of eligible samples whose tech lacks coverage evidence at this position. Excluded from `N_HOM_REF` to keep AC/AN conservative. Always `0` unless a coverage-evidence filter is active. See [Coverage Evidence](../advanced/coverage-evidence.md). |
|
|
20
20
|
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@ afquery annotate \
|
|
|
26
26
|
| `AFQUERY_N_HET` | Integer | A (per ALT) | Heterozygous sample count |
|
|
27
27
|
| `AFQUERY_N_HOM_ALT` | Integer | A (per ALT) | Homozygous alt sample count |
|
|
28
28
|
| `AFQUERY_N_HOM_REF` | Integer | A (per ALT) | Homozygous ref sample count |
|
|
29
|
-
| `AFQUERY_N_FAIL` | Integer | 1 (per site) |
|
|
29
|
+
| `AFQUERY_N_FAIL` | Integer | 1 (per site) | Eligible samples whose call had FILTER≠PASS. Excluded from AC, but still counted in AN. Mutually exclusive with N_HET/N_HOM_ALT/N_HOM_REF. |
|
|
30
30
|
| `AFQUERY_N_NO_COVERAGE` | Integer | A (per ALT) | Eligible samples whose tech lacks coverage evidence at this position. Excluded from `N_HOM_REF` to keep AC/AN conservative. Always `0` unless a coverage-evidence filter is active. See [Coverage Evidence](../advanced/coverage-evidence.md). |
|
|
31
31
|
|
|
32
32
|
!!! note "Multi-allelic sites"
|
|
@@ -27,8 +27,8 @@ afquery create-db \
|
|
|
27
27
|
|
|
28
28
|
## What Happens
|
|
29
29
|
|
|
30
|
-
1. **Ingest phase** — Each VCF is parsed with cyvcf2. Genotypes and
|
|
31
|
-
2. **Build phase** — DuckDB reads the
|
|
30
|
+
1. **Ingest phase** — Each VCF is parsed with cyvcf2. Genotypes and quality fields are written to a temporary per-sample Parquet file, one row per variant per sample.
|
|
31
|
+
2. **Build phase** — DuckDB reads the temporary Parquet files, aggregates per 1-Mbp bucket, and writes Roaring Bitmap Parquet files partitioned by chromosome and bucket.
|
|
32
32
|
3. **Finalize** — `manifest.json` and `metadata.sqlite` are written to the output directory.
|
|
33
33
|
|
|
34
34
|
---
|
|
@@ -39,12 +39,10 @@ afquery create-db \
|
|
|
39
39
|
./db/
|
|
40
40
|
├── manifest.json # Build configuration (genome build, schema version, etc.)
|
|
41
41
|
├── metadata.sqlite # Sample/phenotype/technology/changelog metadata
|
|
42
|
-
├── variants/ #
|
|
42
|
+
├── variants/ # Parquet files partitioned by chromosome and bucket
|
|
43
43
|
│ ├── chr1/
|
|
44
|
-
│ │ ├── bucket_0
|
|
45
|
-
│ │
|
|
46
|
-
│ │ ├── bucket_1/ # Positions 1,000,000–1,999,999
|
|
47
|
-
│ │ │ └── data.parquet
|
|
44
|
+
│ │ ├── bucket_0.parquet # Positions 0–999,999
|
|
45
|
+
│ │ ├── bucket_1.parquet # Positions 1,000,000–1,999,999
|
|
48
46
|
│ │ └── ...
|
|
49
47
|
│ ├── chr2/
|
|
50
48
|
│ └── ...
|
|
@@ -95,7 +93,7 @@ afquery create-db --manifest manifest.tsv --output-dir ./db/ --genome-build GRCh
|
|
|
95
93
|
|
|
96
94
|
## FILTER=PASS Behavior
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
Only `FILTER=PASS` calls (or calls with no FILTER field) contribute to AC, and therefore to AF. AN is not affected — it counts every eligible sample, failed calls included. Calls that fail a filter are tracked in `fail_bitmap` and surfaced as `N_FAIL`. PASS-only counting is always enforced — there is currently no CLI option to change this behaviour.
|
|
99
97
|
|
|
100
98
|
See [FILTER=PASS Tracking](../advanced/filter-pass-tracking.md) for details.
|
|
101
99
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sample Filtering
|
|
2
2
|
|
|
3
|
-
AFQuery supports flexible metadata-based selection of samples for AF computation. Filters are available on all query commands (`query`, `annotate`, `dump`).
|
|
3
|
+
AFQuery supports flexible metadata-based selection of samples for AF computation. Filters are available on all query commands (`query`, `annotate`, `dump`, `variant-info`).
|
|
4
4
|
|
|
5
5
|
**Phenotype codes are arbitrary string labels** — you define them in your manifest. They can be ICD-10 codes, HPO terms, project tags (`control`, `pilot`), or any strings meaningful to your cohort. The filtering system does not interpret the codes — it matches them exactly as stored.
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Update a Database
|
|
2
2
|
|
|
3
|
-
`afquery update-db`
|
|
3
|
+
`afquery update-db` adds new samples, removes existing samples, updates sample metadata, and compacts the database to reclaim space.
|
|
4
4
|
|
|
5
5
|
### Update Timeline
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ AFQuery pre-indexes genotypes as [Roaring Bitmaps](https://roaringbitmap.org/) i
|
|
|
31
31
|
- **Ploidy-aware** — correct AN on chrX PAR/non-PAR, chrY, and chrM.
|
|
32
32
|
- **Technology-aware AN** — per-position capture BED intersection across WGS, WES kits, and panels.
|
|
33
33
|
- **Carrier lookup** — list samples carrying any variant with full metadata (sex, tech, phenotypes, genotype, FILTER status).
|
|
34
|
-
- **VCF annotation** — add `AFQUERY_AC/AN/AF/N_HET/N_HOM_ALT/N_HOM_REF/N_FAIL` INFO fields from any sample subset.
|
|
34
|
+
- **VCF annotation** — add `AFQUERY_AC/AN/AF/N_HET/N_HOM_ALT/N_HOM_REF/N_FAIL/N_NO_COVERAGE` INFO fields from any sample subset.
|
|
35
35
|
- **Audit changelog** — every database operation is recorded for reproducibility.
|
|
36
36
|
|
|
37
37
|
---
|
|
@@ -43,7 +43,7 @@ graph TD
|
|
|
43
43
|
A["🔍 Input VCFs<br/>single-sample"]
|
|
44
44
|
B["📥 Ingest<br/>cyvcf2 reads VCFs →<br/>per-sample Parquet files emitted"]
|
|
45
45
|
C["🏗️ Build<br/>DuckDB aggregates per bucket →<br/>Roaring Bitmaps → Parquet"]
|
|
46
|
-
D["💾 Database on Disk<br/>variants/chr*/bucket_
|
|
46
|
+
D["💾 Database on Disk<br/>variants/chr*/bucket_*.parquet<br/>capture/*.pkl<br/>metadata.sqlite<br/>manifest.json"]
|
|
47
47
|
E["⚡ Query Engine<br/>Load bitmap → filter samples →<br/>compute AC/AN/AF<br/>~10-100ms"]
|
|
48
48
|
F["📊 Annotate"]
|
|
49
49
|
G["🔄 Update"]
|
|
@@ -10,12 +10,10 @@ This page documents the on-disk layout of an AFQuery database, including file fo
|
|
|
10
10
|
<db_dir>/
|
|
11
11
|
├── manifest.json # Build configuration
|
|
12
12
|
├── metadata.sqlite # Sample/phenotype/technology/changelog metadata
|
|
13
|
-
├── variants/ #
|
|
13
|
+
├── variants/ # Parquet variant data, partitioned by chromosome and bucket
|
|
14
14
|
│ ├── chr1/
|
|
15
|
-
│ │ ├── bucket_0
|
|
16
|
-
│ │
|
|
17
|
-
│ │ ├── bucket_1/ # Positions 1,000,000–1,999,999
|
|
18
|
-
│ │ │ └── data.parquet
|
|
15
|
+
│ │ ├── bucket_0.parquet # Positions 0–999,999
|
|
16
|
+
│ │ ├── bucket_1.parquet # Positions 1,000,000–1,999,999
|
|
19
17
|
│ │ └── ...
|
|
20
18
|
│ ├── chr2/
|
|
21
19
|
│ └── ...
|
|
@@ -33,11 +31,13 @@ Stores the build configuration used during `create-db`.
|
|
|
33
31
|
| Field | Type | Description |
|
|
34
32
|
|-------|------|-------------|
|
|
35
33
|
| `genome_build` | string | `"GRCh37"` or `"GRCh38"` |
|
|
36
|
-
| `
|
|
37
|
-
| `pass_only_filter` | bool | Whether FILTER=PASS was enforced during ingest |
|
|
34
|
+
| `version` | string | manifest.json format version |
|
|
38
35
|
| `db_version` | string | User-specified version label |
|
|
36
|
+
| `sample_count` | int | Number of samples at build time |
|
|
37
|
+
| `schema_version` | string | `"2.0"`, or `"3.0"` when the database was built with coverage-quality filters |
|
|
38
|
+
| `pass_only_filter` | bool | Always `true` — PASS-only counting is always enforced |
|
|
39
|
+
| `coverage_filter` | object | The `min_dp` / `min_gq` / `min_qual` / `min_covered` thresholds used at build time (all `0` when unused) |
|
|
39
40
|
| `created_at` | string | ISO 8601 timestamp |
|
|
40
|
-
| `manifest_path` | string | Path to original manifest TSV |
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
@@ -102,12 +102,16 @@ Each bucket Parquet file has this schema:
|
|
|
102
102
|
| `pos` | `uint32` | 1-based genomic position |
|
|
103
103
|
| `ref` | `large_utf8` | Reference allele |
|
|
104
104
|
| `alt` | `large_utf8` | Alternate allele |
|
|
105
|
-
| `het_bitmap` | `large_binary` |
|
|
106
|
-
| `hom_bitmap` | `large_binary` |
|
|
107
|
-
| `fail_bitmap` | `large_binary` |
|
|
105
|
+
| `het_bitmap` | `large_binary` | Roaring Bitmap of heterozygous, `FILTER=PASS` sample IDs |
|
|
106
|
+
| `hom_bitmap` | `large_binary` | Roaring Bitmap of homozygous-alt, `FILTER=PASS` sample IDs |
|
|
107
|
+
| `fail_bitmap` | `large_binary` | Roaring Bitmap of sample IDs whose call has `FILTER≠PASS` at this site |
|
|
108
|
+
| `filtered_bitmap` | `large_binary` | Roaring Bitmap of WES non-carriers with uncertain coverage |
|
|
109
|
+
| `quality_pass_bitmap` | `large_binary` | Roaring Bitmap of carriers meeting the DP/GQ/QUAL thresholds |
|
|
108
110
|
|
|
109
111
|
Rows are sorted by `(pos, alt)` within each bucket.
|
|
110
112
|
|
|
113
|
+
`filtered_bitmap` and `quality_pass_bitmap` back the [coverage-evidence](../advanced/coverage-evidence.md) feature. The columns are always present, but they are empty unless the database was built with coverage-quality filters (schema version `3.0`).
|
|
114
|
+
|
|
111
115
|
!!! important "large_utf8 / large_binary"
|
|
112
116
|
AFQuery uses `large_utf8` and `large_binary` (64-bit offsets) rather than `utf8` / `binary` (32-bit). This is required for compatibility with DuckDB's Parquet reader on large chromosomes.
|
|
113
117
|
|
|
@@ -118,9 +122,10 @@ Rows are sorted by `(pos, alt)` within each bucket.
|
|
|
118
122
|
Bitmaps use the [Roaring Bitmap](https://roaringbitmap.org/) format, serialized by pyroaring's portable serialization.
|
|
119
123
|
|
|
120
124
|
- Bit position = sample ID (0-indexed integer)
|
|
121
|
-
- `het_bitmap`: bit set iff sample is heterozygous
|
|
122
|
-
- `hom_bitmap`: bit set iff sample is homozygous alt
|
|
123
|
-
- `fail_bitmap`: bit set iff sample has genotype
|
|
125
|
+
- `het_bitmap`: bit set iff the sample is heterozygous with `FILTER=PASS`
|
|
126
|
+
- `hom_bitmap`: bit set iff the sample is homozygous alt with `FILTER=PASS`
|
|
127
|
+
- `fail_bitmap`: bit set iff the sample's call has `FILTER≠PASS` — either a carrier whose call failed a filter, or a missing genotype (`./.`) at a failed site
|
|
128
|
+
- `filtered_bitmap`, `quality_pass_bitmap`: see the Parquet schema above; both are empty unless the database was built with coverage-quality filters
|
|
124
129
|
|
|
125
130
|
To deserialize in Python:
|
|
126
131
|
```python
|
|
@@ -152,11 +157,11 @@ bucket_id = pos // 1_000_000
|
|
|
152
157
|
```
|
|
153
158
|
|
|
154
159
|
!!! warning "DuckDB integer arithmetic"
|
|
155
|
-
When computing bucket IDs in DuckDB SQL, always use:
|
|
160
|
+
When computing bucket IDs in DuckDB SQL, always use the integer-division operator:
|
|
156
161
|
```sql
|
|
157
|
-
CAST(pos AS BIGINT)
|
|
162
|
+
CAST(pos AS BIGINT) // 1000000
|
|
158
163
|
```
|
|
159
|
-
Not `CAST(pos / 1000000 AS BIGINT)` —
|
|
164
|
+
Not `CAST(pos / 1000000 AS BIGINT)` — the `/` operator does float division, and casting the rounded result afterwards produces wrong bucket IDs.
|
|
160
165
|
|
|
161
166
|
---
|
|
162
167
|
|
|
@@ -34,7 +34,7 @@ A sample that passes all query filters (sex, phenotype, technology) and has cove
|
|
|
34
34
|
|
|
35
35
|
## N_FAIL
|
|
36
36
|
|
|
37
|
-
The count of eligible samples at a position
|
|
37
|
+
The count of eligible samples whose call at a position had FILTER≠PASS in the source VCF. These samples are excluded from AC, but they remain eligible and so still count toward AN. See [FILTER=PASS Tracking](../advanced/filter-pass-tracking.md).
|
|
38
38
|
|
|
39
39
|
## N_NO_COVERAGE
|
|
40
40
|
|
|
@@ -66,7 +66,7 @@ A compressed bitset data structure that efficiently stores sets of integers. AFQ
|
|
|
66
66
|
|
|
67
67
|
## Schema Version
|
|
68
68
|
|
|
69
|
-
The AFQuery database format version stored in `manifest.json
|
|
69
|
+
The AFQuery database format version stored in `manifest.json` — `2.0`, or `3.0` when the database was built with coverage-quality filters. See [Data Model](data-model.md).
|
|
70
70
|
|
|
71
71
|
## Technology
|
|
72
72
|
|
|
@@ -453,7 +453,7 @@ class QueryResult:
|
|
|
453
453
|
N_HET: int # Heterozygous count
|
|
454
454
|
N_HOM_ALT: int # Homozygous alt count
|
|
455
455
|
N_HOM_REF: int # Homozygous ref count
|
|
456
|
-
N_FAIL: int #
|
|
456
|
+
N_FAIL: int # Eligible samples whose call had FILTER≠PASS (excluded from AC, kept in AN)
|
|
457
457
|
N_NO_COVERAGE: int # Eligible samples whose tech lacks evidence (excluded from N_HOM_REF)
|
|
458
458
|
```
|
|
459
459
|
|
|
@@ -77,7 +77,7 @@ AFQuery requires DuckDB to use Parquet for all temporary files. Arrow IPC is not
|
|
|
77
77
|
|
|
78
78
|
**Cause:** A DuckDB float-division rounding bug was present in older AFQuery versions. When computing bucket IDs, `CAST(pos / 1000000 AS BIGINT)` rounds floats incorrectly (e.g., position 1,500,000 → bucket 2 instead of 1).
|
|
79
79
|
|
|
80
|
-
**Fix:** Upgrade to the latest AFQuery version. The fix uses `CAST(pos AS BIGINT)
|
|
80
|
+
**Fix:** Upgrade to the latest AFQuery version. The fix uses `CAST(pos AS BIGINT) // 1000000` — the integer-division operator — for correct bucket IDs. Rebuild the database after upgrading.
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
@@ -207,7 +207,7 @@ On chrX non-PAR regions, males contribute AN=1 and females AN=2. A cohort with 8
|
|
|
207
207
|
|
|
208
208
|
### High N_FAIL at the Variant Site
|
|
209
209
|
|
|
210
|
-
`N_FAIL` counts eligible samples
|
|
210
|
+
`N_FAIL` counts eligible samples whose call at the site failed quality filters (`FILTER≠PASS`). These samples are excluded from AC but stay in AN, so they cannot inflate AF — if anything they make it slightly more conservative. A high N_FAIL still indicates the site has systematic QC problems, and warrants caution before applying ACMG criteria.
|
|
211
211
|
|
|
212
212
|
| N_FAIL / n_eligible | Interpretation |
|
|
213
213
|
|---|---|
|
|
@@ -34,10 +34,10 @@ afquery query --db ./db/ --locus chrX:153296777
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
```
|
|
37
|
-
chrX:153296777 C>T AC=15 AN=2500 AF=0.0060 n_eligible=1500 N_HET=5 N_HOM_ALT=
|
|
37
|
+
chrX:153296777 C>T AC=15 AN=2500 AF=0.0060 n_eligible=1500 N_HET=5 N_HOM_ALT=10 N_HOM_REF=1485 N_FAIL=0
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
AN=2500: 500 males × 1 + 1000 females × 2 = 2500 (mixed ploidy)
|
|
40
|
+
AN=2500: 500 males × 1 + 1000 females × 2 = 2500 (mixed ploidy). The 10 hemizygous male carriers land in N_HOM_ALT, not N_HET — on haploid positions there is no heterozygous state.
|
|
41
41
|
|
|
42
42
|
### 2. Query males only (hemizygous frequency)
|
|
43
43
|
|
|
@@ -46,10 +46,10 @@ afquery query --db ./db/ --locus chrX:153296777 --sex male
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
```
|
|
49
|
-
chrX:153296777 C>T AC=10 AN=500 AF=0.0200 n_eligible=500 N_HET=
|
|
49
|
+
chrX:153296777 C>T AC=10 AN=500 AF=0.0200 n_eligible=500 N_HET=0 N_HOM_ALT=10 N_HOM_REF=490 N_FAIL=0
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Hemizygous rate: 2
|
|
52
|
+
Hemizygous rate: 2%. The carriers appear in N_HOM_ALT, not N_HET — males are haploid at chrX non-PAR, so `GT=1` carriers are counted as homozygous-alt.
|
|
53
53
|
|
|
54
54
|
### 3. Query females only (carrier frequency)
|
|
55
55
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -74,26 +74,23 @@ def _print_results(results, fmt: str) -> None:
|
|
|
74
74
|
"ref": r.variant.ref, "alt": r.variant.alt,
|
|
75
75
|
"AC": r.AC, "AN": r.AN, "AF": r.AF, "n_eligible": r.n_samples_eligible,
|
|
76
76
|
"N_HET": r.N_HET, "N_HOM_ALT": r.N_HOM_ALT, "N_HOM_REF": r.N_HOM_REF,
|
|
77
|
+
"N_FAIL": r.N_FAIL, "N_NO_COVERAGE": r.N_NO_COVERAGE,
|
|
77
78
|
}
|
|
78
|
-
if r.N_FAIL is not None:
|
|
79
|
-
entry["N_FAIL"] = r.N_FAIL
|
|
80
79
|
out.append(entry)
|
|
81
80
|
click.echo(json.dumps(out, indent=2))
|
|
82
81
|
elif fmt == "tsv":
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
header = (
|
|
83
|
+
"chrom\tpos\tref\talt\tAC\tAN\tAF\tn_eligible\t"
|
|
84
|
+
"N_HET\tN_HOM_ALT\tN_HOM_REF\tN_FAIL\tN_NO_COVERAGE"
|
|
85
|
+
)
|
|
87
86
|
click.echo(header)
|
|
88
87
|
for r in results:
|
|
89
88
|
af = f"{r.AF:.6f}" if r.AF is not None else "NA"
|
|
90
89
|
line = (
|
|
91
90
|
f"{r.variant.chrom}\t{r.variant.pos}\t{r.variant.ref}\t"
|
|
92
91
|
f"{r.variant.alt}\t{r.AC}\t{r.AN}\t{af}\t{r.n_samples_eligible}\t"
|
|
93
|
-
f"{r.N_HET}\t{r.N_HOM_ALT}\t{r.N_HOM_REF}"
|
|
92
|
+
f"{r.N_HET}\t{r.N_HOM_ALT}\t{r.N_HOM_REF}\t{r.N_FAIL}\t{r.N_NO_COVERAGE}"
|
|
94
93
|
)
|
|
95
|
-
if has_fail:
|
|
96
|
-
line += f"\t{r.N_FAIL if r.N_FAIL is not None else '?'}"
|
|
97
94
|
click.echo(line)
|
|
98
95
|
else: # text
|
|
99
96
|
if not results:
|
|
@@ -101,11 +98,11 @@ def _print_results(results, fmt: str) -> None:
|
|
|
101
98
|
return
|
|
102
99
|
for r in results:
|
|
103
100
|
af = f"{r.AF:.4f}" if r.AF is not None else "NA"
|
|
104
|
-
fail_str = f" N_FAIL={r.N_FAIL}" if r.N_FAIL is not None else ""
|
|
105
101
|
click.echo(
|
|
106
102
|
f"{r.variant.chrom}:{r.variant.pos} {r.variant.ref}>{r.variant.alt} "
|
|
107
103
|
f"AC={r.AC} AN={r.AN} AF={af} n_eligible={r.n_samples_eligible} "
|
|
108
|
-
f"N_HET={r.N_HET} N_HOM_ALT={r.N_HOM_ALT} N_HOM_REF={r.N_HOM_REF}
|
|
104
|
+
f"N_HET={r.N_HET} N_HOM_ALT={r.N_HOM_ALT} N_HOM_REF={r.N_HOM_REF} "
|
|
105
|
+
f"N_FAIL={r.N_FAIL} N_NO_COVERAGE={r.N_NO_COVERAGE}"
|
|
109
106
|
)
|
|
110
107
|
|
|
111
108
|
|
|
@@ -324,7 +321,8 @@ def annotate(db, input_vcf, output_vcf, phenotype, sex, tech, threads, verbose,
|
|
|
324
321
|
AFQUERY_N_HET heterozygous carrier count (per ALT)
|
|
325
322
|
AFQUERY_N_HOM_ALT homozygous alt count (per ALT)
|
|
326
323
|
AFQUERY_N_HOM_REF homozygous ref count (per ALT)
|
|
327
|
-
AFQUERY_N_FAIL samples with FILTER!=PASS
|
|
324
|
+
AFQUERY_N_FAIL samples with FILTER!=PASS (per site)
|
|
325
|
+
AFQUERY_N_NO_COVERAGE eligible samples lacking coverage evidence (per ALT)
|
|
328
326
|
"""
|
|
329
327
|
if no_warn:
|
|
330
328
|
import warnings
|
|
@@ -435,6 +435,34 @@ def test_query_tsv_with_fail_col(runner, test_db):
|
|
|
435
435
|
assert "N_FAIL" in lines[0]
|
|
436
436
|
|
|
437
437
|
|
|
438
|
+
def test_query_output_includes_n_no_coverage_all_formats(runner, test_db):
|
|
439
|
+
"""N_FAIL and N_NO_COVERAGE appear in text/tsv/json output, and TSV columns line up."""
|
|
440
|
+
base = ["query", "--db", test_db, "--locus", "chr1:1500"]
|
|
441
|
+
|
|
442
|
+
text = runner.invoke(cli, base)
|
|
443
|
+
assert text.exit_code == 0
|
|
444
|
+
assert "N_FAIL=" in text.output
|
|
445
|
+
assert "N_NO_COVERAGE=" in text.output
|
|
446
|
+
|
|
447
|
+
tsv = runner.invoke(cli, base + ["--format", "tsv"])
|
|
448
|
+
assert tsv.exit_code == 0
|
|
449
|
+
tsv_lines = tsv.output.strip().splitlines()
|
|
450
|
+
assert len(tsv_lines) >= 2
|
|
451
|
+
header = tsv_lines[0].split("\t")
|
|
452
|
+
assert "N_FAIL" in header
|
|
453
|
+
assert header[-1] == "N_NO_COVERAGE"
|
|
454
|
+
for row in tsv_lines[1:]:
|
|
455
|
+
assert len(row.split("\t")) == len(header)
|
|
456
|
+
|
|
457
|
+
js = runner.invoke(cli, base + ["--format", "json"])
|
|
458
|
+
assert js.exit_code == 0
|
|
459
|
+
data = json.loads(js.output)
|
|
460
|
+
assert len(data) >= 1
|
|
461
|
+
for entry in data:
|
|
462
|
+
assert isinstance(entry["N_FAIL"], int)
|
|
463
|
+
assert isinstance(entry["N_NO_COVERAGE"], int)
|
|
464
|
+
|
|
465
|
+
|
|
438
466
|
# --- afquery annotate: verbose flag ---
|
|
439
467
|
|
|
440
468
|
def test_annotate_verbose(runner, test_db, tmp_path):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|