afquery 0.1.2__tar.gz → 0.1.2.2__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.1.2.2/.github/workflows/docs.yml +38 -0
- afquery-0.1.2.2/PKG-INFO +395 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/README.md +31 -12
- afquery-0.1.2.2/docs/advanced/benchmarking.md +108 -0
- afquery-0.1.2.2/docs/advanced/filter-pass-tracking.md +111 -0
- afquery-0.1.2.2/docs/advanced/performance.md +143 -0
- afquery-0.1.2.2/docs/advanced/ploidy-and-sex-chroms.md +100 -0
- afquery-0.1.2.2/docs/faq.md +129 -0
- afquery-0.1.2.2/docs/getting-started/concepts.md +110 -0
- afquery-0.1.2.2/docs/getting-started/installation.md +78 -0
- afquery-0.1.2.2/docs/getting-started/quickstart.md +145 -0
- afquery-0.1.2.2/docs/guides/annotate-vcf.md +132 -0
- afquery-0.1.2.2/docs/guides/create-database.md +139 -0
- afquery-0.1.2.2/docs/guides/dump-export.md +152 -0
- afquery-0.1.2.2/docs/guides/manifest-format.md +99 -0
- afquery-0.1.2.2/docs/guides/query.md +161 -0
- afquery-0.1.2.2/docs/guides/sample-filtering.md +158 -0
- afquery-0.1.2.2/docs/guides/update-database.md +162 -0
- afquery-0.1.2.2/docs/index.md +94 -0
- afquery-0.1.2.2/docs/reference/cli.md +215 -0
- afquery-0.1.2.2/docs/reference/data-model.md +174 -0
- afquery-0.1.2.2/docs/reference/python-api.md +379 -0
- afquery-0.1.2.2/docs/troubleshooting.md +131 -0
- afquery-0.1.2.2/mkdocs.yml +74 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/pyproject.toml +6 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/recipes/afquery/meta.yaml +10 -3
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/_version.py +2 -2
- afquery-0.1.2/PKG-INFO +0 -15
- {afquery-0.1.2 → afquery-0.1.2.2}/.github/workflows/ci.yml +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/.github/workflows/release.yml +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/.gitignore +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/docs/webhook_schema.md +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/resources/normalize_vcf.sh +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/__init__.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/annotate.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/benchmark.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/bitmaps.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/capture.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/cli.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/cli.pyr +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/constants.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/database.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/dump.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/models.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/ploidy.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/__init__.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/build.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/compact.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/ingest.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/manifest.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/regions.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/synth.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/update.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/query.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/conftest.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/annotate_input.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/annotate_multi_bucket.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/annotate_multi_chrom.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/beds/wes_kit_a.bed +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/beds/wes_kit_b.bed +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/expected_results.json +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/manifest.tsv +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S00.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S01.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S02.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S03.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S04.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S05.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S06.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S07.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S08.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S09.vcf +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_annotate.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_batch.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_benchmark.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_bitmaps.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_capture.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_cli.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_compact.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_constants.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_dump.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_haploid_stats.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_info.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_pass_filter.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_ploidy.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_preprocess.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_query.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_sample_filter.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_synth.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_synthetic_stats.py +0 -0
- {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_update.py +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Deploy Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
paths:
|
|
7
|
+
- 'docs/**'
|
|
8
|
+
- 'mkdocs.yml'
|
|
9
|
+
- 'src/**'
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
pages: write
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: pages
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
deploy:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
environment:
|
|
25
|
+
name: github-pages
|
|
26
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: '3.11'
|
|
33
|
+
|
|
34
|
+
- name: Install docs dependencies
|
|
35
|
+
run: pip install -e ".[docs]"
|
|
36
|
+
|
|
37
|
+
- name: Build and deploy docs
|
|
38
|
+
run: mkdocs gh-deploy --force
|
afquery-0.1.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: afquery
|
|
3
|
+
Version: 0.1.2.2
|
|
4
|
+
Summary: Genomic allele frequency query engine with bitmap-encoded genotypes
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: click>=8.1
|
|
7
|
+
Requires-Dist: cyvcf2>=0.30
|
|
8
|
+
Requires-Dist: duckdb>=0.10
|
|
9
|
+
Requires-Dist: pyarrow>=14.0
|
|
10
|
+
Requires-Dist: pyranges<0.2,>=0.1.2
|
|
11
|
+
Requires-Dist: pyroaring>=0.4.8
|
|
12
|
+
Requires-Dist: tqdm>=4.60
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
|
+
Provides-Extra: docs
|
|
17
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
18
|
+
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
|
|
19
|
+
Requires-Dist: pymdown-extensions>=10.7; extra == 'docs'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# AFQuery
|
|
23
|
+
|
|
24
|
+
Fast, file-based genomic allele frequency queries for large cohorts (10K–50K samples).
|
|
25
|
+
No server, no cloud — just files. Sub-100ms point queries with flexible filtering
|
|
26
|
+
by sex, phenotype (ICD codes), and sequencing technology.
|
|
27
|
+
|
|
28
|
+
## Quick Example
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install afquery
|
|
32
|
+
|
|
33
|
+
# Build database from your VCFs
|
|
34
|
+
afquery create-db --manifest samples.tsv --output-dir ./db/ --genome-build GRCh38
|
|
35
|
+
|
|
36
|
+
# Query allele frequency
|
|
37
|
+
afquery query --db ./db/ --chrom chr1 --pos 123456 --phenotype E11.9 --sex female
|
|
38
|
+
|
|
39
|
+
# Annotate a VCF
|
|
40
|
+
afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**[Full documentation →](https://davidlaewen.github.io/afquery/)**
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- Sub-100ms point queries on 50K-sample cohorts
|
|
48
|
+
- Filter by sex, phenotype (ICD codes), and sequencing technology
|
|
49
|
+
- Bitmap-compressed storage (Roaring Bitmaps + Parquet)
|
|
50
|
+
- Incremental updates (add/remove samples without full rebuild)
|
|
51
|
+
- VCF annotation with custom sample subsets
|
|
52
|
+
- Ploidy-aware AN for sex chromosomes (X/Y/MT)
|
|
53
|
+
- Zero infrastructure — purely file-based
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install afquery
|
|
59
|
+
# or
|
|
60
|
+
conda install -c bioconda -c conda-forge afquery
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Requires Python 3.10+.
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
### 1. Create a Database
|
|
68
|
+
|
|
69
|
+
First, prepare a manifest TSV with sample metadata:
|
|
70
|
+
|
|
71
|
+
```tsv
|
|
72
|
+
sample_name sex tech_name vcf_path phenotype_codes
|
|
73
|
+
sample_1 male wgs vcfs/sample_1.vcf E11.9,I10
|
|
74
|
+
sample_2 female wes_kit_a vcfs/sample_2.vcf E11.9
|
|
75
|
+
sample_3 male wgs vcfs/sample_3.vcf I10
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Key points about the manifest:**
|
|
79
|
+
- `tech_name`: Either `wgs` (case-insensitive) for whole genome, or a custom technology name
|
|
80
|
+
- `vcf_path`: Path to the single-sample VCF file (relative to manifest directory, or absolute)
|
|
81
|
+
- For WES/exome technologies, capture regions are loaded from `--bed-dir/{tech_name}.bed`
|
|
82
|
+
- Example: `tech_name=wes_kit_a` → loads `beds/wes_kit_a.bed`
|
|
83
|
+
|
|
84
|
+
Organize your files:
|
|
85
|
+
```
|
|
86
|
+
project/
|
|
87
|
+
├── manifest.tsv
|
|
88
|
+
├── vcfs/
|
|
89
|
+
│ ├── sample_1.vcf
|
|
90
|
+
│ ├── sample_2.vcf
|
|
91
|
+
│ └── sample_3.vcf
|
|
92
|
+
└── beds/ # Required for non-WGS technologies
|
|
93
|
+
└── wes_kit_a.bed # BED file for WES kit A
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then preprocess your VCF files:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
afquery preprocess \
|
|
100
|
+
--manifest manifest.tsv \
|
|
101
|
+
--bed-dir ./beds/ \
|
|
102
|
+
--output-dir ./my_db/ \
|
|
103
|
+
--genome-build GRCh38
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This creates:
|
|
107
|
+
- `my_db/manifest.json` — database metadata
|
|
108
|
+
- `my_db/metadata.sqlite` — samples, technologies, phenotype codes, precomputed bitmaps
|
|
109
|
+
- `my_db/variants/{chrom}.parquet` — variant data with encoded genotypes
|
|
110
|
+
- `my_db/capture/` — capture regions for each technology
|
|
111
|
+
|
|
112
|
+
### 2. Query Allele Frequencies
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Point query
|
|
116
|
+
afquery query --db my_db --chrom chr1 --pos 1000 --alt G
|
|
117
|
+
|
|
118
|
+
# Batch query (100 positions)
|
|
119
|
+
afquery query-batch --db my_db --positions positions.tsv --phenotype E11.9
|
|
120
|
+
|
|
121
|
+
# Region query
|
|
122
|
+
afquery query --db my_db --chrom chr1 --start 1000 --end 10000 --phenotype E11.9 --sex M
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 3. Annotate VCF Files
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
afquery annotate \
|
|
129
|
+
--db my_db \
|
|
130
|
+
--vcf input.vcf \
|
|
131
|
+
--output annotated.vcf \
|
|
132
|
+
--phenotype E11.9 \
|
|
133
|
+
--tech WGS
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Adds `AFQUERY_AC`, `AFQUERY_AN`, `AFQUERY_AF` and genotype fields to your VCF.
|
|
137
|
+
|
|
138
|
+
## Python API
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from afquery import Database
|
|
142
|
+
|
|
143
|
+
db = Database("/path/to/db")
|
|
144
|
+
|
|
145
|
+
# Single position query
|
|
146
|
+
# Automatically filters samples by: sex + phenotype codes + capture coverage
|
|
147
|
+
result = db.query(
|
|
148
|
+
chrom="chr1",
|
|
149
|
+
pos=1000,
|
|
150
|
+
alt="G",
|
|
151
|
+
phenotype_codes=["E11.9"],
|
|
152
|
+
sex="both"
|
|
153
|
+
)
|
|
154
|
+
print(f"AC={result.ac}, AN={result.an}, AF={result.af}")
|
|
155
|
+
|
|
156
|
+
# Batch query (multi-variant)
|
|
157
|
+
results = db.query_batch(
|
|
158
|
+
"chr1",
|
|
159
|
+
variants=[(1500, "A", "T"), (3500, "G", "C")],
|
|
160
|
+
phenotype=["E11.9"],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Region query (genomic range)
|
|
164
|
+
results = db.query_region(
|
|
165
|
+
chrom="chr1",
|
|
166
|
+
start=1000,
|
|
167
|
+
end=10000,
|
|
168
|
+
phenotype_codes=["E11.9", "I10"]
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Annotate VCF with allele frequencies
|
|
172
|
+
# Note: tech_name filters annotation to samples of that technology
|
|
173
|
+
db.annotate_vcf(
|
|
174
|
+
vcf_path="input.vcf",
|
|
175
|
+
output_path="annotated.vcf",
|
|
176
|
+
phenotype_codes=["E11.9"],
|
|
177
|
+
tech_name="wgs" # Only annotate using WGS samples
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**How samples are filtered in queries**:
|
|
182
|
+
- Sex filter: `male`, `female`, or `both`
|
|
183
|
+
- phenotype filter: All codes must match
|
|
184
|
+
- Capture filter: Automatic—only samples whose tech's BED covers the position
|
|
185
|
+
|
|
186
|
+
## Database Structure
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
my_db/
|
|
190
|
+
├── manifest.json # Metadata: genome_build, sample_count, schema_version
|
|
191
|
+
├── metadata.sqlite # SQLite: samples, technologies, phenotype codes, bitmaps
|
|
192
|
+
├── variants/
|
|
193
|
+
│ ├── chr1.parquet
|
|
194
|
+
│ ├── chr2.parquet
|
|
195
|
+
│ └── ...
|
|
196
|
+
└── capture/
|
|
197
|
+
├── tech_0.pickle # WGS capture region (always covered)
|
|
198
|
+
└── tech_1.pickle # WES kit capture region
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Each variant row contains:
|
|
202
|
+
- `pos` — 1-based genomic position
|
|
203
|
+
- `ref` — reference allele
|
|
204
|
+
- `alt` — alternate allele
|
|
205
|
+
- `het_bitmap` — Roaring Bitmap of heterozygous samples
|
|
206
|
+
- `hom_bitmap` — Roaring Bitmap of homozygous samples
|
|
207
|
+
|
|
208
|
+
## How Capture BED Files Are Associated with Samples
|
|
209
|
+
|
|
210
|
+
Samples are linked to capture regions through their **technology**:
|
|
211
|
+
|
|
212
|
+
1. **Manifest specifies technology**: Each sample lists `tech_name` (e.g., `wgs`, `wes_kit_a`)
|
|
213
|
+
2. **Technology maps to BED file**:
|
|
214
|
+
- **WGS**: No BED file needed (always fully covered)
|
|
215
|
+
- **WES/Custom**: BED file loaded from `{bed_dir}/{tech_name}.bed`
|
|
216
|
+
3. **Storage in database**:
|
|
217
|
+
- `metadata.sqlite::technologies` stores tech_id, tech_name, and bed_path
|
|
218
|
+
- `metadata.sqlite::samples` stores sample_id, sample_name, and tech_id (foreign key)
|
|
219
|
+
4. **Query-time filtering**: When querying, samples are filtered by:
|
|
220
|
+
- Sex (male/female/both)
|
|
221
|
+
- phenotype diagnosis codes
|
|
222
|
+
- Capture region coverage (via tech's BED file)
|
|
223
|
+
|
|
224
|
+
**Example**: If you have samples on two exome kits:
|
|
225
|
+
```tsv
|
|
226
|
+
sample_name sex tech_name vcf_path phenotype_codes
|
|
227
|
+
S001 male exome_v1 vcfs/S001.vcf E11.9
|
|
228
|
+
S002 female exome_v1 vcfs/S002.vcf E11.9
|
|
229
|
+
S003 male exome_v2 vcfs/S003.vcf I10
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Then provide:
|
|
233
|
+
```
|
|
234
|
+
beds/
|
|
235
|
+
├── exome_v1.bed # Coverage for samples S001, S002
|
|
236
|
+
└── exome_v2.bed # Coverage for sample S003
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
At query time, each sample's eligible regions are determined by its tech's BED file.
|
|
240
|
+
|
|
241
|
+
## Advanced Features
|
|
242
|
+
|
|
243
|
+
### Incremental Updates (add_samples)
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
afquery add-samples \
|
|
247
|
+
--db my_db \
|
|
248
|
+
--manifest new_samples.tsv \
|
|
249
|
+
--vcf-dir ./new_vcfs/
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Adds new samples without rebuilding the entire database.
|
|
253
|
+
|
|
254
|
+
### Compact Database
|
|
255
|
+
|
|
256
|
+
Remove samples and reclaim disk space:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
afquery compact --db my_db --samples-to-remove sample_1,sample_2
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Run Benchmarks
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
afquery benchmark --db my_db --n-queries 1000 --query-type point
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Generate Synthetic Data
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
afquery synth --output synthetic_db/ --n-samples 5000 --n-variants 100000
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Ploidy Rules
|
|
275
|
+
|
|
276
|
+
AF computation respects chromosome-specific ploidy:
|
|
277
|
+
|
|
278
|
+
| Chromosome | Formula |
|
|
279
|
+
|---|---|
|
|
280
|
+
| Autosomes | `AN = 2 × eligible_samples` |
|
|
281
|
+
| chrM | `AN = 1 × eligible_samples` |
|
|
282
|
+
| chrY | `AN = 1 × eligible_males` |
|
|
283
|
+
| chrX (PAR) | `AN = 2 × eligible_samples` |
|
|
284
|
+
| chrX (non-PAR) | `AN = 2 × eligible_females + 1 × eligible_males` |
|
|
285
|
+
|
|
286
|
+
Where `eligible` = samples matching sex, phenotype, and technology capture filters.
|
|
287
|
+
|
|
288
|
+
## Performance Targets
|
|
289
|
+
|
|
290
|
+
- **Point query (cold)**: <100 ms
|
|
291
|
+
- **Point query (warm)**: ~10 ms
|
|
292
|
+
- **Batch 100 positions**: ~200 ms
|
|
293
|
+
- **VCF annotation (5K variants)**: ~30 s
|
|
294
|
+
- **VCF annotation (5M variants)**: ~30 min
|
|
295
|
+
|
|
296
|
+
## Command Reference
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
afquery query Query single position
|
|
300
|
+
afquery query-batch Batch query multiple positions
|
|
301
|
+
afquery annotate Annotate VCF file
|
|
302
|
+
afquery info Show database info
|
|
303
|
+
afquery preprocess Build database from VCFs
|
|
304
|
+
afquery add-samples Add new samples to database
|
|
305
|
+
afquery compact Remove samples and reclaim space
|
|
306
|
+
afquery synth Generate synthetic test database
|
|
307
|
+
afquery benchmark Run performance benchmarks
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Run `afquery --help` for full options.
|
|
311
|
+
|
|
312
|
+
## Development
|
|
313
|
+
|
|
314
|
+
### Running Tests
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# All 190 tests
|
|
318
|
+
python3 -m pytest --tb=short -q
|
|
319
|
+
|
|
320
|
+
# Specific test module
|
|
321
|
+
python3 -m pytest tests/test_query.py -v
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Key Modules
|
|
325
|
+
|
|
326
|
+
- `afquery.query` — QueryEngine, point/batch/region queries
|
|
327
|
+
- `afquery.annotate` — VCF annotation pipeline
|
|
328
|
+
- `afquery.database` — Database wrapper (public API)
|
|
329
|
+
- `afquery.preprocess` — Manifest parsing, VCF ingestion, Parquet building
|
|
330
|
+
- `afquery.bitmaps` — Roaring Bitmap encoding/decoding
|
|
331
|
+
- `afquery.ploidy` — Chromosome-specific ploidy rules
|
|
332
|
+
- `afquery.models` — Data classes (QueryResult, ParsedSample, etc.)
|
|
333
|
+
|
|
334
|
+
### Architecture
|
|
335
|
+
|
|
336
|
+
See `brain/architecture.md` for detailed system design, data flow, and query algorithm.
|
|
337
|
+
|
|
338
|
+
## Genome Builds
|
|
339
|
+
|
|
340
|
+
- **GRCh37** (hg19) — PAR regions: chrX:1-2649520
|
|
341
|
+
- **GRCh38** (hg38) — PAR regions: chrX:1-3099677
|
|
342
|
+
|
|
343
|
+
## Technologies Supported
|
|
344
|
+
|
|
345
|
+
- **WGS** — Whole genome sequencing (always fully covered, no BED file needed)
|
|
346
|
+
- Manifest: `tech_name = wgs` (case-insensitive)
|
|
347
|
+
- Query-time: All positions in genome considered covered
|
|
348
|
+
|
|
349
|
+
- **WES** — Whole exome sequencing (coverage defined by BED file)
|
|
350
|
+
- Manifest: `tech_name = wes_kit_a` (or any custom name)
|
|
351
|
+
- Preprocessing: Loads `{bed_dir}/wes_kit_a.bed` (0-based half-open BED format)
|
|
352
|
+
- Query-time: Only positions within BED intervals considered covered
|
|
353
|
+
|
|
354
|
+
- **Custom** — Any technology with a BED file (e.g., gene panels, targeted sequencing)
|
|
355
|
+
- Manifest: Use any `tech_name`
|
|
356
|
+
- Preprocessing: Loads `{bed_dir}/{tech_name}.bed`
|
|
357
|
+
- Query-time: Respects BED file coverage
|
|
358
|
+
|
|
359
|
+
## Troubleshooting
|
|
360
|
+
|
|
361
|
+
### ImportError: `cyvcf2`
|
|
362
|
+
|
|
363
|
+
`cyvcf2` import happens inside worker processes during preprocessing. Do not import at module level.
|
|
364
|
+
|
|
365
|
+
### DuckDB Temp Files
|
|
366
|
+
|
|
367
|
+
Uses Parquet format (not Arrow IPC) for compatibility. Set `DUCKDB_TEMP_DIRECTORY` if needed.
|
|
368
|
+
|
|
369
|
+
### Sample IDs
|
|
370
|
+
|
|
371
|
+
Sample IDs are 0-indexed and monotonically increasing. Never reuse removed IDs—use `compact` to reclaim space.
|
|
372
|
+
|
|
373
|
+
## Contributing
|
|
374
|
+
|
|
375
|
+
1. Read `brain/project_state.json` for current phase and test count
|
|
376
|
+
2. Read `brain/architecture.md` for system design
|
|
377
|
+
3. Follow code conventions in `CLAUDE.md`
|
|
378
|
+
4. Update `brain/` docs after architectural changes
|
|
379
|
+
5. Run tests before submitting
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
(Add your license here)
|
|
384
|
+
|
|
385
|
+
## Citation
|
|
386
|
+
|
|
387
|
+
If you use afquery in research, please cite:
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
(Citation format to be determined)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
**Status**: Phase 5 complete (190 tests passing). Active development.
|
|
@@ -1,23 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AFQuery
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fast, file-based genomic allele frequency queries for large cohorts (10K–50K samples).
|
|
4
|
+
No server, no cloud — just files. Sub-100ms point queries with flexible filtering
|
|
5
|
+
by sex, phenotype (ICD codes), and sequencing technology.
|
|
6
|
+
|
|
7
|
+
## Quick Example
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install afquery
|
|
11
|
+
|
|
12
|
+
# Build database from your VCFs
|
|
13
|
+
afquery create-db --manifest samples.tsv --output-dir ./db/ --genome-build GRCh38
|
|
14
|
+
|
|
15
|
+
# Query allele frequency
|
|
16
|
+
afquery query --db ./db/ --chrom chr1 --pos 123456 --phenotype E11.9 --sex female
|
|
17
|
+
|
|
18
|
+
# Annotate a VCF
|
|
19
|
+
afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**[Full documentation →](https://davidlaewen.github.io/afquery/)**
|
|
4
23
|
|
|
5
24
|
## Features
|
|
6
25
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- **Incremental updates**: Add new samples to existing databases
|
|
15
|
-
- **Multiple genome builds**: Support for GRCh37 and GRCh38
|
|
26
|
+
- Sub-100ms point queries on 50K-sample cohorts
|
|
27
|
+
- Filter by sex, phenotype (ICD codes), and sequencing technology
|
|
28
|
+
- Bitmap-compressed storage (Roaring Bitmaps + Parquet)
|
|
29
|
+
- Incremental updates (add/remove samples without full rebuild)
|
|
30
|
+
- VCF annotation with custom sample subsets
|
|
31
|
+
- Ploidy-aware AN for sex chromosomes (X/Y/MT)
|
|
32
|
+
- Zero infrastructure — purely file-based
|
|
16
33
|
|
|
17
34
|
## Installation
|
|
18
35
|
|
|
19
36
|
```bash
|
|
20
|
-
pip install
|
|
37
|
+
pip install afquery
|
|
38
|
+
# or
|
|
39
|
+
conda install -c bioconda -c conda-forge afquery
|
|
21
40
|
```
|
|
22
41
|
|
|
23
42
|
Requires Python 3.10+.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Benchmarking
|
|
2
|
+
|
|
3
|
+
`afquery benchmark` measures query performance on synthetic or real data and produces a JSON report.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Benchmark
|
|
8
|
+
|
|
9
|
+
Run against synthetic data (no real database required):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
afquery benchmark
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This generates 1,000 synthetic samples and 10,000 variants per chromosome, builds an in-memory database, runs a suite of query types, and writes results to `benchmark_report.json`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Options
|
|
20
|
+
|
|
21
|
+
| Option | Default | Description |
|
|
22
|
+
|--------|---------|-------------|
|
|
23
|
+
| `--n-samples` | `1000` | Number of synthetic samples |
|
|
24
|
+
| `--n-variants` | `10000` | Number of variants per chromosome |
|
|
25
|
+
| `--output` | `benchmark_report.json` | Output path for JSON report |
|
|
26
|
+
| `--db-dir` | None | Run against an existing database instead of synthetic data |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Benchmark Against a Real Database
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
afquery benchmark --db-dir ./my_db/ --output my_db_benchmark.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This uses your actual Parquet files and sample metadata, giving realistic performance numbers for your specific cohort size and variant density.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Report Format
|
|
41
|
+
|
|
42
|
+
The JSON report contains timing results for each query type:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"n_samples": 1000,
|
|
47
|
+
"n_variants": 10000,
|
|
48
|
+
"genome_build": "GRCh38",
|
|
49
|
+
"queries": {
|
|
50
|
+
"point_query_cold_ms": 87.3,
|
|
51
|
+
"point_query_warm_ms": 9.1,
|
|
52
|
+
"region_query_1mbp_ms": 312.4,
|
|
53
|
+
"batch_query_100_ms": 198.7,
|
|
54
|
+
"annotation_100_variants_ms": 523.1
|
|
55
|
+
},
|
|
56
|
+
"timestamp": "2026-03-16T10:00:00Z"
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Interpreting Results
|
|
63
|
+
|
|
64
|
+
| Metric | Target | Notes |
|
|
65
|
+
|--------|--------|-------|
|
|
66
|
+
| `point_query_cold_ms` | < 100 ms | First query after cold start; includes Parquet I/O |
|
|
67
|
+
| `point_query_warm_ms` | < 20 ms | Subsequent queries; OS page cache active |
|
|
68
|
+
| `region_query_1mbp_ms` | < 500 ms | Depends on variant density in region |
|
|
69
|
+
| `batch_query_100_ms` | < 300 ms | 100 pre-specified variants on same chromosome |
|
|
70
|
+
| `annotation_100_variants_ms` | < 2000 ms | VCF annotation, single-threaded |
|
|
71
|
+
|
|
72
|
+
If `point_query_cold_ms` exceeds 500 ms, check disk I/O performance. If `point_query_warm_ms` is slow, check available RAM for OS page cache.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Synthetic Data Generation
|
|
77
|
+
|
|
78
|
+
The benchmark generates:
|
|
79
|
+
- `N` samples with random sex (50/50) and random technology assignment
|
|
80
|
+
- `M` variants per chromosome with uniformly random positions
|
|
81
|
+
- Random genotypes with configurable carrier rates
|
|
82
|
+
|
|
83
|
+
Synthetic data is written to a temporary directory and cleaned up after the benchmark completes.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Tracking Performance Over Time
|
|
88
|
+
|
|
89
|
+
Run the benchmark after major updates to detect regressions:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Before update
|
|
93
|
+
afquery benchmark --db-dir ./db/ --output before.json
|
|
94
|
+
|
|
95
|
+
# After adding 500 samples
|
|
96
|
+
afquery update-db --db ./db/ --add-samples new_batch.tsv
|
|
97
|
+
afquery benchmark --db-dir ./db/ --output after.json
|
|
98
|
+
|
|
99
|
+
# Compare
|
|
100
|
+
python3 -c "
|
|
101
|
+
import json
|
|
102
|
+
before = json.load(open('before.json'))['queries']
|
|
103
|
+
after = json.load(open('after.json'))['queries']
|
|
104
|
+
for k in before:
|
|
105
|
+
delta = after[k] - before[k]
|
|
106
|
+
print(f'{k}: {before[k]:.1f} → {after[k]:.1f} ms ({delta:+.1f})')
|
|
107
|
+
"
|
|
108
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# FILTER=PASS Tracking
|
|
2
|
+
|
|
3
|
+
Schema version 2 introduces tracking of variants that are called but fail quality filters (`FILTER≠PASS`). This allows distinguishing between "variant not present" and "variant present but low quality" in your cohort.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Background: VCF FILTER Field
|
|
8
|
+
|
|
9
|
+
In VCF format, the FILTER column indicates whether a variant call passed quality filters:
|
|
10
|
+
- `PASS` or `.` (missing) — the variant passed all filters
|
|
11
|
+
- Any other value (e.g., `LowQual`, `VQSRTrancheSNP99.90to100.00`) — the variant failed one or more filters
|
|
12
|
+
|
|
13
|
+
AFQuery default behavior:
|
|
14
|
+
- **PASS-only** (default): only `FILTER=PASS` variants are counted in AC/AN
|
|
15
|
+
- **All filters** (`--include-all-filters`): all variant calls are counted regardless of FILTER status
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Schema v2: fail_bitmap
|
|
20
|
+
|
|
21
|
+
In schema v2, AFQuery stores a third bitmap per variant alongside `het_bitmap` and `hom_bitmap`:
|
|
22
|
+
|
|
23
|
+
- **`fail_bitmap`** — bit set for each sample that has a non-ref genotype (AC>0) AND `FILTER≠PASS`
|
|
24
|
+
|
|
25
|
+
This means:
|
|
26
|
+
- A sample in `fail_bitmap` was genotyped with the alt allele but the call failed QC
|
|
27
|
+
- Such samples are **not** counted in AC/AN (they don't affect AF)
|
|
28
|
+
- Their count is exposed as `FAIL_SAMPLES` / `N_FAIL`
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Enabling Schema v2
|
|
33
|
+
|
|
34
|
+
Schema v2 is the default for new databases. The `fail_bitmap` is always written; its content depends on the `--include-all-filters` flag:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Default: PASS-only ingestion (fail_bitmap tracks failed calls)
|
|
38
|
+
afquery create-db --manifest manifest.tsv --output-dir ./db/ --genome-build GRCh38
|
|
39
|
+
|
|
40
|
+
# Include all filters: AC/AN counts all calls; fail_bitmap is all-zeros
|
|
41
|
+
afquery create-db --manifest manifest.tsv --output-dir ./db/ --genome-build GRCh38 \
|
|
42
|
+
--include-all-filters
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Querying FAIL_SAMPLES
|
|
48
|
+
|
|
49
|
+
### CLI
|
|
50
|
+
|
|
51
|
+
The `FAIL_SAMPLES` count is shown automatically in query output for v2 databases:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
afquery query --db ./db/ --chrom chr1 --pos 925952
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
chr1:925952
|
|
59
|
+
REF=G ALT=A AC=142 AN=2742 AF=0.0518 N_HET=138 N_HOM_ALT=2 FAIL=7
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`FAIL=7` means 7 eligible samples had the alt allele called but with FILTER≠PASS.
|
|
63
|
+
|
|
64
|
+
### Python API
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
results = db.query("chr1", pos=925952)
|
|
68
|
+
for r in results:
|
|
69
|
+
print(f"AC={r.AC} AN={r.AN} FAIL={r.N_FAIL}")
|
|
70
|
+
if r.N_FAIL is not None and r.N_FAIL > 0:
|
|
71
|
+
print(f" Warning: {r.N_FAIL} samples have low-quality calls at this site")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`N_FAIL` is `0` for v1 databases (no fail tracking), not `None`. Use `QueryResult.N_FAIL` directly.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## VCF Annotation
|
|
79
|
+
|
|
80
|
+
Schema v2 databases add an additional INFO field to annotated VCFs:
|
|
81
|
+
|
|
82
|
+
| Field | Type | Description |
|
|
83
|
+
|-------|------|-------------|
|
|
84
|
+
| `AFQUERY_N_FAIL` | Integer | Eligible samples with FILTER≠PASS at this variant |
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
v1 databases do not write `AFQUERY_N_FAIL`.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Migration from v1
|
|
95
|
+
|
|
96
|
+
v1 databases (no `schema_version` in `manifest.json`, or `schema_version < 2.0`) are fully compatible with AFQuery v2 code. They return `N_FAIL=0` in query results and do not write `AFQUERY_N_FAIL` in annotation.
|
|
97
|
+
|
|
98
|
+
There is no automatic migration from v1 to v2. To gain fail tracking, rebuild the database with `create-db`.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## When to Use --include-all-filters
|
|
103
|
+
|
|
104
|
+
By default (PASS-only), AF reflects the quality-filtered allele frequency — the frequency of the alt allele among high-quality calls. This is the recommended setting for most clinical and research use cases.
|
|
105
|
+
|
|
106
|
+
Use `--include-all-filters` when:
|
|
107
|
+
- You want to count all called genotypes regardless of quality
|
|
108
|
+
- You are studying filter performance or variant calling artifacts
|
|
109
|
+
- Your upstream pipeline handles filtering separately
|
|
110
|
+
|
|
111
|
+
With `--include-all-filters`, `fail_bitmap` stores zeros for all variants (since all calls are counted in AC/AN).
|