pycmplot 0.3.0__tar.gz → 0.4.0__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.
- {pycmplot-0.3.0 → pycmplot-0.4.0}/PKG-INFO +125 -24
- {pycmplot-0.3.0 → pycmplot-0.4.0}/README.md +124 -23
- pycmplot-0.4.0/benchmark/build_composite_figure.py +199 -0
- pycmplot-0.4.0/docs-bak/conf.py +90 -0
- pycmplot-0.4.0/pycmplot/__init__.py +50 -0
- pycmplot-0.4.0/pycmplot/__main__.py +6 -0
- pycmplot-0.4.0/pycmplot/_core.py +419 -0
- pycmplot-0.4.0/pycmplot/annotation.py +904 -0
- pycmplot-0.4.0/pycmplot/cache.py +832 -0
- pycmplot-0.4.0/pycmplot/cli.py +614 -0
- pycmplot-0.4.0/pycmplot/constants.py +91 -0
- pycmplot-0.4.0/pycmplot/io.py +1714 -0
- pycmplot-0.4.0/pycmplot/liftover.py +303 -0
- pycmplot-0.4.0/pycmplot/plotting/__init__.py +31 -0
- pycmplot-0.4.0/pycmplot/plotting/circular.py +832 -0
- pycmplot-0.4.0/pycmplot/plotting/linear.py +1728 -0
- pycmplot-0.4.0/pycmplot/plotting/qq.py +938 -0
- pycmplot-0.4.0/pycmplot/resources.py +199 -0
- pycmplot-0.4.0/pycmplot/stats.py +180 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/__init__.py +1 -1
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/_core.py +36 -7
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/annotation.py +298 -0
- pycmplot-0.4.0/pycmplot-bak/cache.py +832 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/cli.py +83 -7
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/io.py +479 -107
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/plotting/circular.py +113 -25
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/plotting/linear.py +67 -7
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/plotting/qq.py +248 -26
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot.egg-info/PKG-INFO +125 -24
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot.egg-info/SOURCES.txt +18 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot.egg-info/top_level.txt +2 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pyproject.toml +1 -1
- {pycmplot-0.3.0 → pycmplot-0.4.0}/setup.cfg +1 -1
- {pycmplot-0.3.0 → pycmplot-0.4.0}/LICENSE +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/benchmark/bench_python.py +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/benchmark/collect_results.py +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/benchmark/generate_multi_sumstats.py +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/benchmark/generate_sumstats.py +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot/data/Homo_sapiens.GRCh37.geneinfo.tsv.gz +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot/data/Homo_sapiens.GRCh38.geneinfo.tsv.gz +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot/data/hg18ToHg38.over.chain.gz +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot/data/hg19ToHg38.over.chain.gz +0 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/__main__.py +0 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/constants.py +0 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/liftover.py +0 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/plotting/__init__.py +0 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/resources.py +0 -0
- {pycmplot-0.3.0/pycmplot → pycmplot-0.4.0/pycmplot-bak}/stats.py +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot.egg-info/dependency_links.txt +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot.egg-info/entry_points.txt +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/pycmplot.egg-info/requires.txt +0 -0
- {pycmplot-0.3.0 → pycmplot-0.4.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycmplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Multi-track circular and linear Manhattan plot generation for GWAS summary statistics
|
|
5
5
|
Author: Kevin Esoh
|
|
6
6
|
Author-email: Kevin Esoh <kesohku1@jh.edu>
|
|
@@ -39,16 +39,60 @@ Dynamic: license-file
|
|
|
39
39
|
|
|
40
40
|
# pycmplot
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
<div>
|
|
43
|
+
<h1 align="center">Multi-track circular and linear Manhattan plotting in Python.</h1>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<img width="600" height="400" src="https://github.com/esohkevin/pycmplot/blob/main/docs/pycmplot-logo-circular.png">
|
|
48
|
+
</p>
|
|
49
|
+
|
|
43
50
|
|
|
44
51
|
```
|
|
45
|
-
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
52
|
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
|
53
|
+
| MULTI-TRACK CIRCULAR AND LINEAR MANHATTAN PLOTTING |
|
|
54
|
+
| in Python |
|
|
55
|
+
| Kevin Esoh, 2026 |
|
|
56
|
+
| kesohku1@jh.edu |
|
|
57
|
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
|
50
58
|
```
|
|
51
59
|
|
|
60
|
+
----------------------------------------------
|
|
61
|
+
|
|
62
|
+
## Content
|
|
63
|
+
1. [Overview](#overview)
|
|
64
|
+
2. [Key features](#key-features)
|
|
65
|
+
- [Column auto-detection](#column-auto-detection)
|
|
66
|
+
- [Density-aware sub-sampling](#density-aware-sub-sampling)
|
|
67
|
+
- [Trim insignificant variants for faster plotting](#trim-insignificant-variants-for-faster-plotting)
|
|
68
|
+
- [Genome build conversion (liftover)](#genome-build-conversion-liftover)
|
|
69
|
+
- [Nearest-gene annotation for GWAS lead SNPs](#nearest-gene-annotation-for-gwas-lead-snps)
|
|
70
|
+
- [Caching & warm resume](#caching--warm-resume)
|
|
71
|
+
- [User-editable hits overlay](#user-editable-hits-overlay)
|
|
72
|
+
- [Per-locus highlight colours & custom legend](#per-locus-highlight-colours--custom-legend)
|
|
73
|
+
- [Multi-panel canvas](#multi-panel-canvas)
|
|
74
|
+
3. [Application](#application)
|
|
75
|
+
4. [Tip](#tip)
|
|
76
|
+
5. [Installation](#installation)
|
|
77
|
+
- [From PyPI](#from-pypi)
|
|
78
|
+
- [From GitHub](#from-github)
|
|
79
|
+
- [Python virtual environment](#python-virtual-environment)
|
|
80
|
+
6. [Test the installation](#test-the-installation)
|
|
81
|
+
7. [Dependencies](#dependencies)
|
|
82
|
+
8. [Command-line usage](#command-line-usage)
|
|
83
|
+
- [Linear Manhattan (default)](#linear-manhattan-default)
|
|
84
|
+
- [Circular Manhattan](#circular-manhattan)
|
|
85
|
+
- [Key options](#key-options)
|
|
86
|
+
9. [Python API](#python-api)
|
|
87
|
+
10. [Contributing](#contributing)
|
|
88
|
+
11. [Contributors](#contributors)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
---------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Overview
|
|
95
|
+
|
|
52
96
|
This package will take any number of per SNP/variant summary statistics, be it GWAS,
|
|
53
97
|
selection scans (e.g. iHS, EHH, FST), etc and generate Manhattan plots. If given a single
|
|
54
98
|
file, a single one-track Manhattan plot will be generated. Multiple files will result in
|
|
@@ -68,8 +112,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
68
112
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
69
113
|
corrdinate system.
|
|
70
114
|
|
|
71
|
-
|
|
72
|
-
|
|
115
|
+
## Key features
|
|
116
|
+
### Column auto-detection
|
|
73
117
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
74
118
|
command-line or python API:
|
|
75
119
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -92,7 +136,7 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
92
136
|
|
|
93
137
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
94
138
|
|
|
95
|
-
|
|
139
|
+
### Density-aware sub-sampling
|
|
96
140
|
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
97
141
|
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
98
142
|
|
|
@@ -103,14 +147,14 @@ scan with the defaults below, this typically cuts the plotted point count from 1
|
|
|
103
147
|
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
104
148
|
band, but two orders of magnitude faster to render.
|
|
105
149
|
|
|
106
|
-
|
|
150
|
+
### Trim insignificant variants for faster plotting
|
|
107
151
|
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
108
152
|
Set with a value to exclude variants with p-value above a certain threshold,
|
|
109
153
|
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
110
154
|
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
111
155
|
See benchmark figure (manuscript in preparation).
|
|
112
156
|
|
|
113
|
-
|
|
157
|
+
### Genome build conversion (liftover)
|
|
114
158
|
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
115
159
|
`pyliftover.LiftOver`.
|
|
116
160
|
|
|
@@ -120,7 +164,7 @@ column to specify the genome build of each position ('hg18', 'hg19', or 'hg38')
|
|
|
120
164
|
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
121
165
|
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
122
166
|
|
|
123
|
-
|
|
167
|
+
### Nearest-gene annotation for GWAS lead SNPs
|
|
124
168
|
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
125
169
|
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
126
170
|
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
@@ -139,12 +183,52 @@ for gene annotation. Also included are UCSC chain files for coordinate conversio
|
|
|
139
183
|
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
140
184
|
|
|
141
185
|
|
|
142
|
-
|
|
186
|
+
### Caching & warm resume
|
|
187
|
+
Loading is the expensive step (I/O + trim + liftover + lead extraction), so
|
|
188
|
+
pycmplot ships a per-track cache keyed on `SHA-256(raw_file_sha256 + version + Stage-1 params)`.
|
|
189
|
+
Warm re-runs of the same `(files, parameters)` combination complete in
|
|
190
|
+
milliseconds; changing any parameter transparently invalidates only the
|
|
191
|
+
affected tracks and regenerates them. Enable with `--cache` (CLI) or
|
|
192
|
+
`cache=True` (Python API); use `--clear_cache` to wipe the tree.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pycmplot --sum_stats hb.tsv,mcv.tsv --labels Hb,MCV \
|
|
196
|
+
--logp --highlight --cache --cache_dir ./.pycmplot
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### User-editable hits overlay
|
|
200
|
+
When caching is on, the auto-generated hits table is written to a
|
|
201
|
+
group-scoped TSV at `<cache_dir>/annotations/hits.<group_key>.tsv`
|
|
202
|
+
that you're *expected to hand-edit*. Rows added or changed there feed
|
|
203
|
+
straight back into the next plot — no re-plumbing required. Each row
|
|
204
|
+
carries `source` (`auto` vs `user`), `highlight_color`, and `category`
|
|
205
|
+
columns; user edits are inherited across cache regenerations by
|
|
206
|
+
`(CHR, POS)` lookup, so re-running with a new parameter never loses
|
|
207
|
+
your annotations.
|
|
208
|
+
|
|
209
|
+
### Per-locus highlight colours & custom legend
|
|
210
|
+
Set the `highlight_color` column on any row of the hits overlay to a
|
|
211
|
+
matplotlib-parseable colour (name, `#rrggbb`, or an RGB tuple) to give
|
|
212
|
+
that locus its own highlight colour; leave `auto` to fall back to the
|
|
213
|
+
plot-wide `--highlight_color`. Set the `category` column
|
|
214
|
+
(e.g. `novel`, `replicated`, `MHC`) to group loci in a
|
|
215
|
+
**"Highlighted Categories"** legend that both the linear and circular
|
|
216
|
+
plotters render automatically. If nothing is edited, no legend is
|
|
217
|
+
added — the pre-feature layout is preserved.
|
|
218
|
+
|
|
219
|
+
### Multi-panel canvas
|
|
220
|
+
Place multiple *groups* of sumstats on the same figure by passing an
|
|
221
|
+
explicit matplotlib `Axes` or `SubFigure` via `ax=` to the plotter.
|
|
222
|
+
Cache files and hits overlays are group-scoped, so two panels with
|
|
223
|
+
different sumstats never clobber each other's artefacts.
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
## Application
|
|
143
227
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
144
228
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
145
229
|
|
|
146
230
|
|
|
147
|
-
|
|
231
|
+
## Tip
|
|
148
232
|
|
|
149
233
|
For signed non-pvalue statistics such as iHS, use `--signif_threshold` and
|
|
150
234
|
`--suggest_threshold` to specify lines to draw for iHS cutoff above and below zero,
|
|
@@ -153,7 +237,7 @@ that is in the positive and negative axes.
|
|
|
153
237
|
|
|
154
238
|
Read more in the package documentation page: https://pycmplot.readthedocs.io/en/latest/
|
|
155
239
|
|
|
156
|
-
|
|
240
|
+
----------------------------
|
|
157
241
|
|
|
158
242
|
## Installation
|
|
159
243
|
|
|
@@ -180,24 +264,25 @@ pip install -e . --break-system-packages
|
|
|
180
264
|
```
|
|
181
265
|
|
|
182
266
|
|
|
183
|
-
###
|
|
184
|
-
```bash
|
|
185
|
-
python -m venv ~/bin/pycmplot
|
|
267
|
+
### Python virtual environment
|
|
186
268
|
|
|
187
|
-
|
|
269
|
+
Use python virtual environment if local installation is not possible
|
|
188
270
|
|
|
189
|
-
|
|
271
|
+
```bash
|
|
272
|
+
python -m venv ~/envs/pycmplot
|
|
273
|
+
source ~/envs/pycmplot/bin/activate
|
|
190
274
|
|
|
191
|
-
|
|
275
|
+
pip install --upgrade pip setuptools wheel
|
|
276
|
+
pip install pycmplot
|
|
192
277
|
```
|
|
193
278
|
|
|
194
279
|
|
|
195
|
-
|
|
280
|
+
## Test the installation
|
|
196
281
|
```bash
|
|
197
282
|
pycmplot -h
|
|
198
283
|
```
|
|
199
284
|
|
|
200
|
-
|
|
285
|
+
## Dependencies
|
|
201
286
|
|
|
202
287
|
| Package | Purpose |
|
|
203
288
|
|---------|---------|
|
|
@@ -264,6 +349,16 @@ pycmplot \
|
|
|
264
349
|
| `-st, --sort_track` | Sort tracks by `label` or `chrom_len` | input order |
|
|
265
350
|
| `-od, --output_dir` | Output directory | `.` |
|
|
266
351
|
| `-of, --output_format` | Output format (`png`, `pdf`, `svg`, `jpg`) | `png` |
|
|
352
|
+
| `--cache` | Enable per-track cache + user-editable hits overlay | off |
|
|
353
|
+
| `--cache_dir` | Where to store cache artefacts | `./.pycmplot` |
|
|
354
|
+
| `--no_resume` | Regenerate but still write fresh cache entries | resume on |
|
|
355
|
+
| `--clear_cache` | Delete the cache tree and exit | off |
|
|
356
|
+
| `-V, --version` | Print version and exit | — |
|
|
357
|
+
|
|
358
|
+
> **QQ note (0.4.0+):** the loader no longer materialises the full sorted
|
|
359
|
+
> p-value array by default. `-qq/--qq_plot` sets `compute_pvals=True`
|
|
360
|
+
> automatically on the CLI; Python-API callers must pass it explicitly
|
|
361
|
+
> to feed `bundle['pvals']` into a QQ plotter.
|
|
267
362
|
|
|
268
363
|
Run `pycmplot -h` for the full option list.
|
|
269
364
|
|
|
@@ -273,6 +368,12 @@ Run `pycmplot -h` for the full option list.
|
|
|
273
368
|
|
|
274
369
|
A demonstration of how to use the python API is provided in this notebook: https://github.com/esohkevin/pycmplot/blob/main/pycmplot_python_api.ipynb
|
|
275
370
|
|
|
371
|
+
For an end-to-end walkthrough of every feature (caching, hits overlay,
|
|
372
|
+
per-locus colours & categories, multi-panel canvas, mixed-build
|
|
373
|
+
liftover, QQ opt-in), see the
|
|
374
|
+
[Tutorial](https://pycmplot.readthedocs.io/en/latest/tutorial.html)
|
|
375
|
+
page in the docs.
|
|
376
|
+
|
|
276
377
|
|
|
277
378
|
---
|
|
278
379
|
|
|
@@ -1,15 +1,59 @@
|
|
|
1
1
|
# pycmplot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div>
|
|
4
|
+
<h1 align="center">Multi-track circular and linear Manhattan plotting in Python.</h1>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img width="600" height="400" src="https://github.com/esohkevin/pycmplot/blob/main/docs/pycmplot-logo-circular.png">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
4
11
|
|
|
5
12
|
```
|
|
6
|
-
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
13
|
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
|
14
|
+
| MULTI-TRACK CIRCULAR AND LINEAR MANHATTAN PLOTTING |
|
|
15
|
+
| in Python |
|
|
16
|
+
| Kevin Esoh, 2026 |
|
|
17
|
+
| kesohku1@jh.edu |
|
|
18
|
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
|
11
19
|
```
|
|
12
20
|
|
|
21
|
+
----------------------------------------------
|
|
22
|
+
|
|
23
|
+
## Content
|
|
24
|
+
1. [Overview](#overview)
|
|
25
|
+
2. [Key features](#key-features)
|
|
26
|
+
- [Column auto-detection](#column-auto-detection)
|
|
27
|
+
- [Density-aware sub-sampling](#density-aware-sub-sampling)
|
|
28
|
+
- [Trim insignificant variants for faster plotting](#trim-insignificant-variants-for-faster-plotting)
|
|
29
|
+
- [Genome build conversion (liftover)](#genome-build-conversion-liftover)
|
|
30
|
+
- [Nearest-gene annotation for GWAS lead SNPs](#nearest-gene-annotation-for-gwas-lead-snps)
|
|
31
|
+
- [Caching & warm resume](#caching--warm-resume)
|
|
32
|
+
- [User-editable hits overlay](#user-editable-hits-overlay)
|
|
33
|
+
- [Per-locus highlight colours & custom legend](#per-locus-highlight-colours--custom-legend)
|
|
34
|
+
- [Multi-panel canvas](#multi-panel-canvas)
|
|
35
|
+
3. [Application](#application)
|
|
36
|
+
4. [Tip](#tip)
|
|
37
|
+
5. [Installation](#installation)
|
|
38
|
+
- [From PyPI](#from-pypi)
|
|
39
|
+
- [From GitHub](#from-github)
|
|
40
|
+
- [Python virtual environment](#python-virtual-environment)
|
|
41
|
+
6. [Test the installation](#test-the-installation)
|
|
42
|
+
7. [Dependencies](#dependencies)
|
|
43
|
+
8. [Command-line usage](#command-line-usage)
|
|
44
|
+
- [Linear Manhattan (default)](#linear-manhattan-default)
|
|
45
|
+
- [Circular Manhattan](#circular-manhattan)
|
|
46
|
+
- [Key options](#key-options)
|
|
47
|
+
9. [Python API](#python-api)
|
|
48
|
+
10. [Contributing](#contributing)
|
|
49
|
+
11. [Contributors](#contributors)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
---------------------------------------------
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Overview
|
|
56
|
+
|
|
13
57
|
This package will take any number of per SNP/variant summary statistics, be it GWAS,
|
|
14
58
|
selection scans (e.g. iHS, EHH, FST), etc and generate Manhattan plots. If given a single
|
|
15
59
|
file, a single one-track Manhattan plot will be generated. Multiple files will result in
|
|
@@ -29,8 +73,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
29
73
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
30
74
|
corrdinate system.
|
|
31
75
|
|
|
32
|
-
|
|
33
|
-
|
|
76
|
+
## Key features
|
|
77
|
+
### Column auto-detection
|
|
34
78
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
35
79
|
command-line or python API:
|
|
36
80
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -53,7 +97,7 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
53
97
|
|
|
54
98
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
55
99
|
|
|
56
|
-
|
|
100
|
+
### Density-aware sub-sampling
|
|
57
101
|
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
58
102
|
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
59
103
|
|
|
@@ -64,14 +108,14 @@ scan with the defaults below, this typically cuts the plotted point count from 1
|
|
|
64
108
|
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
65
109
|
band, but two orders of magnitude faster to render.
|
|
66
110
|
|
|
67
|
-
|
|
111
|
+
### Trim insignificant variants for faster plotting
|
|
68
112
|
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
69
113
|
Set with a value to exclude variants with p-value above a certain threshold,
|
|
70
114
|
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
71
115
|
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
72
116
|
See benchmark figure (manuscript in preparation).
|
|
73
117
|
|
|
74
|
-
|
|
118
|
+
### Genome build conversion (liftover)
|
|
75
119
|
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
76
120
|
`pyliftover.LiftOver`.
|
|
77
121
|
|
|
@@ -81,7 +125,7 @@ column to specify the genome build of each position ('hg18', 'hg19', or 'hg38')
|
|
|
81
125
|
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
82
126
|
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
83
127
|
|
|
84
|
-
|
|
128
|
+
### Nearest-gene annotation for GWAS lead SNPs
|
|
85
129
|
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
86
130
|
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
87
131
|
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
@@ -100,12 +144,52 @@ for gene annotation. Also included are UCSC chain files for coordinate conversio
|
|
|
100
144
|
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
101
145
|
|
|
102
146
|
|
|
103
|
-
|
|
147
|
+
### Caching & warm resume
|
|
148
|
+
Loading is the expensive step (I/O + trim + liftover + lead extraction), so
|
|
149
|
+
pycmplot ships a per-track cache keyed on `SHA-256(raw_file_sha256 + version + Stage-1 params)`.
|
|
150
|
+
Warm re-runs of the same `(files, parameters)` combination complete in
|
|
151
|
+
milliseconds; changing any parameter transparently invalidates only the
|
|
152
|
+
affected tracks and regenerates them. Enable with `--cache` (CLI) or
|
|
153
|
+
`cache=True` (Python API); use `--clear_cache` to wipe the tree.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pycmplot --sum_stats hb.tsv,mcv.tsv --labels Hb,MCV \
|
|
157
|
+
--logp --highlight --cache --cache_dir ./.pycmplot
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### User-editable hits overlay
|
|
161
|
+
When caching is on, the auto-generated hits table is written to a
|
|
162
|
+
group-scoped TSV at `<cache_dir>/annotations/hits.<group_key>.tsv`
|
|
163
|
+
that you're *expected to hand-edit*. Rows added or changed there feed
|
|
164
|
+
straight back into the next plot — no re-plumbing required. Each row
|
|
165
|
+
carries `source` (`auto` vs `user`), `highlight_color`, and `category`
|
|
166
|
+
columns; user edits are inherited across cache regenerations by
|
|
167
|
+
`(CHR, POS)` lookup, so re-running with a new parameter never loses
|
|
168
|
+
your annotations.
|
|
169
|
+
|
|
170
|
+
### Per-locus highlight colours & custom legend
|
|
171
|
+
Set the `highlight_color` column on any row of the hits overlay to a
|
|
172
|
+
matplotlib-parseable colour (name, `#rrggbb`, or an RGB tuple) to give
|
|
173
|
+
that locus its own highlight colour; leave `auto` to fall back to the
|
|
174
|
+
plot-wide `--highlight_color`. Set the `category` column
|
|
175
|
+
(e.g. `novel`, `replicated`, `MHC`) to group loci in a
|
|
176
|
+
**"Highlighted Categories"** legend that both the linear and circular
|
|
177
|
+
plotters render automatically. If nothing is edited, no legend is
|
|
178
|
+
added — the pre-feature layout is preserved.
|
|
179
|
+
|
|
180
|
+
### Multi-panel canvas
|
|
181
|
+
Place multiple *groups* of sumstats on the same figure by passing an
|
|
182
|
+
explicit matplotlib `Axes` or `SubFigure` via `ax=` to the plotter.
|
|
183
|
+
Cache files and hits overlays are group-scoped, so two panels with
|
|
184
|
+
different sumstats never clobber each other's artefacts.
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
## Application
|
|
104
188
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
105
189
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
106
190
|
|
|
107
191
|
|
|
108
|
-
|
|
192
|
+
## Tip
|
|
109
193
|
|
|
110
194
|
For signed non-pvalue statistics such as iHS, use `--signif_threshold` and
|
|
111
195
|
`--suggest_threshold` to specify lines to draw for iHS cutoff above and below zero,
|
|
@@ -114,7 +198,7 @@ that is in the positive and negative axes.
|
|
|
114
198
|
|
|
115
199
|
Read more in the package documentation page: https://pycmplot.readthedocs.io/en/latest/
|
|
116
200
|
|
|
117
|
-
|
|
201
|
+
----------------------------
|
|
118
202
|
|
|
119
203
|
## Installation
|
|
120
204
|
|
|
@@ -141,24 +225,25 @@ pip install -e . --break-system-packages
|
|
|
141
225
|
```
|
|
142
226
|
|
|
143
227
|
|
|
144
|
-
###
|
|
145
|
-
```bash
|
|
146
|
-
python -m venv ~/bin/pycmplot
|
|
228
|
+
### Python virtual environment
|
|
147
229
|
|
|
148
|
-
|
|
230
|
+
Use python virtual environment if local installation is not possible
|
|
149
231
|
|
|
150
|
-
|
|
232
|
+
```bash
|
|
233
|
+
python -m venv ~/envs/pycmplot
|
|
234
|
+
source ~/envs/pycmplot/bin/activate
|
|
151
235
|
|
|
152
|
-
|
|
236
|
+
pip install --upgrade pip setuptools wheel
|
|
237
|
+
pip install pycmplot
|
|
153
238
|
```
|
|
154
239
|
|
|
155
240
|
|
|
156
|
-
|
|
241
|
+
## Test the installation
|
|
157
242
|
```bash
|
|
158
243
|
pycmplot -h
|
|
159
244
|
```
|
|
160
245
|
|
|
161
|
-
|
|
246
|
+
## Dependencies
|
|
162
247
|
|
|
163
248
|
| Package | Purpose |
|
|
164
249
|
|---------|---------|
|
|
@@ -225,6 +310,16 @@ pycmplot \
|
|
|
225
310
|
| `-st, --sort_track` | Sort tracks by `label` or `chrom_len` | input order |
|
|
226
311
|
| `-od, --output_dir` | Output directory | `.` |
|
|
227
312
|
| `-of, --output_format` | Output format (`png`, `pdf`, `svg`, `jpg`) | `png` |
|
|
313
|
+
| `--cache` | Enable per-track cache + user-editable hits overlay | off |
|
|
314
|
+
| `--cache_dir` | Where to store cache artefacts | `./.pycmplot` |
|
|
315
|
+
| `--no_resume` | Regenerate but still write fresh cache entries | resume on |
|
|
316
|
+
| `--clear_cache` | Delete the cache tree and exit | off |
|
|
317
|
+
| `-V, --version` | Print version and exit | — |
|
|
318
|
+
|
|
319
|
+
> **QQ note (0.4.0+):** the loader no longer materialises the full sorted
|
|
320
|
+
> p-value array by default. `-qq/--qq_plot` sets `compute_pvals=True`
|
|
321
|
+
> automatically on the CLI; Python-API callers must pass it explicitly
|
|
322
|
+
> to feed `bundle['pvals']` into a QQ plotter.
|
|
228
323
|
|
|
229
324
|
Run `pycmplot -h` for the full option list.
|
|
230
325
|
|
|
@@ -234,6 +329,12 @@ Run `pycmplot -h` for the full option list.
|
|
|
234
329
|
|
|
235
330
|
A demonstration of how to use the python API is provided in this notebook: https://github.com/esohkevin/pycmplot/blob/main/pycmplot_python_api.ipynb
|
|
236
331
|
|
|
332
|
+
For an end-to-end walkthrough of every feature (caching, hits overlay,
|
|
333
|
+
per-locus colours & categories, multi-panel canvas, mixed-build
|
|
334
|
+
liftover, QQ opt-in), see the
|
|
335
|
+
[Tutorial](https://pycmplot.readthedocs.io/en/latest/tutorial.html)
|
|
336
|
+
page in the docs.
|
|
337
|
+
|
|
237
338
|
|
|
238
339
|
---
|
|
239
340
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build the composite scaling figure used in the manuscript.
|
|
3
|
+
|
|
4
|
+
Output: ``benchmark_composite_scaling.png`` (and an accompanying PDF), written
|
|
5
|
+
to the directory passed via ``--outdir`` (default: same directory as this
|
|
6
|
+
script).
|
|
7
|
+
|
|
8
|
+
Layout (2 rows x 3 columns):
|
|
9
|
+
|
|
10
|
+
Top row -- single-track wall-time scaling at 500K-10M variants:
|
|
11
|
+
(A) Manhattan
|
|
12
|
+
(B) Circular Manhattan
|
|
13
|
+
(C) QQ
|
|
14
|
+
|
|
15
|
+
Bottom row -- multi-track wall-time scaling at 1M and 2M variants
|
|
16
|
+
(multi-track benchmarks were only collected at 1M / 2M):
|
|
17
|
+
(D) Multi-track Manhattan (pycmplot vs CMplot)
|
|
18
|
+
(E) Multi-track circular Manhattan (pycmplot vs CMplot)
|
|
19
|
+
(F) Shared legend
|
|
20
|
+
|
|
21
|
+
The figure reuses :data:`collect_results.SERIES_STYLE` so styling stays
|
|
22
|
+
consistent with the per-plot-type PDFs produced by
|
|
23
|
+
``collect_results.py --plot``.
|
|
24
|
+
|
|
25
|
+
Usage
|
|
26
|
+
-----
|
|
27
|
+
::
|
|
28
|
+
|
|
29
|
+
cd benchmark
|
|
30
|
+
python collect_results.py --resultsdir results --out results/summary.csv
|
|
31
|
+
python build_composite_figure.py # writes alongside this script
|
|
32
|
+
python build_composite_figure.py --outdir ../figures # custom output dir
|
|
33
|
+
python build_composite_figure.py --summary path/to/summary.csv
|
|
34
|
+
"""
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import os
|
|
39
|
+
import sys
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
import matplotlib
|
|
43
|
+
matplotlib.use("Agg")
|
|
44
|
+
import matplotlib.pyplot as plt
|
|
45
|
+
import pandas as pd
|
|
46
|
+
|
|
47
|
+
# Resolve ``collect_results`` relative to this script so the figure builder
|
|
48
|
+
# works regardless of the current working directory.
|
|
49
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
50
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
51
|
+
|
|
52
|
+
from collect_results import SERIES_STYLE, SIZE_N # noqa: E402
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
PANELS = [
|
|
56
|
+
# (row, col, letter, plot_type, title)
|
|
57
|
+
(0, 0, "A", "manhattan", "Manhattan plot"),
|
|
58
|
+
(0, 1, "B", "circular", "Circular Manhattan plot"),
|
|
59
|
+
(0, 2, "C", "qq", "QQ plot"),
|
|
60
|
+
(1, 0, "D", "multitrack_manhattan", "Multi-track Manhattan plot"),
|
|
61
|
+
(1, 1, "E", "multitrack_circular", "Multi-track circular Manhattan plot"),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build(summary_path: Path, outdir: Path, basename: str = "benchmark_composite_scaling") -> Path:
|
|
66
|
+
"""Render the composite figure and return the path to the PNG output."""
|
|
67
|
+
summary = pd.read_csv(summary_path)
|
|
68
|
+
|
|
69
|
+
#fig, axes = plt.subplots(2, 3, figsize=(13, 7.5))
|
|
70
|
+
fig, axes = plt.subplots(2, 3, figsize=(6, 3.5))
|
|
71
|
+
plt.subplots_adjust(
|
|
72
|
+
top=0.94, bottom=0.07, left=0.06, right=0.98, wspace=0.30, hspace=0.45,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
all_handles: list = []
|
|
76
|
+
all_labels: list = []
|
|
77
|
+
seen: set = set()
|
|
78
|
+
|
|
79
|
+
for r, c, letter, pt, title in PANELS:
|
|
80
|
+
ax = axes[r, c]
|
|
81
|
+
sub = summary[summary["plot_type"] == pt].copy()
|
|
82
|
+
sub["n_approx"] = sub["size_label"].map(SIZE_N)
|
|
83
|
+
sub = sub.dropna(subset=["n_approx"])
|
|
84
|
+
|
|
85
|
+
tools_in_data = list(sub["tool"].unique())
|
|
86
|
+
ordered_tools = (
|
|
87
|
+
[t for t in SERIES_STYLE if t in tools_in_data]
|
|
88
|
+
+ [t for t in tools_in_data if t not in SERIES_STYLE]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
for tool in ordered_tools:
|
|
92
|
+
tdf = sub[sub["tool"] == tool].sort_values("n_approx")
|
|
93
|
+
if tdf.empty:
|
|
94
|
+
continue
|
|
95
|
+
s = SERIES_STYLE.get(
|
|
96
|
+
tool, dict(label=tool, color="#888780", ls="-", marker="o"),
|
|
97
|
+
)
|
|
98
|
+
line, = ax.plot(
|
|
99
|
+
tdf["n_approx"], tdf["wall_time_mean"],
|
|
100
|
+
marker=s["marker"], linestyle=s["ls"], color=s["color"],
|
|
101
|
+
label=s["label"] or tool,
|
|
102
|
+
linewidth=1, markersize=2,
|
|
103
|
+
)
|
|
104
|
+
ax.fill_between(
|
|
105
|
+
tdf["n_approx"],
|
|
106
|
+
tdf["wall_time_mean"] - tdf["wall_time_sd"].fillna(0),
|
|
107
|
+
tdf["wall_time_mean"] + tdf["wall_time_sd"].fillna(0),
|
|
108
|
+
alpha=0.12, color=s["color"], linewidth=0,
|
|
109
|
+
)
|
|
110
|
+
label = s["label"] or tool
|
|
111
|
+
if label not in seen:
|
|
112
|
+
all_handles.append(line)
|
|
113
|
+
all_labels.append(label)
|
|
114
|
+
seen.add(label)
|
|
115
|
+
|
|
116
|
+
ax.set_xscale("log")
|
|
117
|
+
ax.set_yscale("log")
|
|
118
|
+
ax.set_xlabel("Number of variants", fontsize=7) #fontsize=10
|
|
119
|
+
if c == 0:
|
|
120
|
+
ax.set_ylabel("Wall-clock time (s)", fontsize=7) #fontsize=10
|
|
121
|
+
ax.set_title(f"({letter}) {title}", fontsize=7, loc="left") #fontsize=10
|
|
122
|
+
ax.grid(True, which="both", linestyle="--", linewidth=0.3, alpha=0.5)
|
|
123
|
+
ax.tick_params(labelsize=5)
|
|
124
|
+
|
|
125
|
+
# Bottom-right cell (1, 2) holds the shared legend so each axes stays
|
|
126
|
+
# uncluttered.
|
|
127
|
+
legend_ax = axes[1, 2]
|
|
128
|
+
legend_ax.axis("off")
|
|
129
|
+
legend_ax.legend(
|
|
130
|
+
all_handles, all_labels,
|
|
131
|
+
loc="center", ncol=1, fontsize=5, frameon=False,
|
|
132
|
+
title="Tool", title_fontsize=5,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
png_path = outdir / f"{basename}.png"
|
|
137
|
+
pdf_path = outdir / f"{basename}.pdf"
|
|
138
|
+
tif_path = outdir / f"{basename}.TIF"
|
|
139
|
+
#eps_path = outdir / f"{basename}.eps"
|
|
140
|
+
fig.savefig(png_path, dpi=300, bbox_inches="tight")
|
|
141
|
+
fig.savefig(tif_path, dpi=300, bbox_inches="tight")
|
|
142
|
+
#fig.savefig(eps_path, dpi=300, bbox_inches="tight")
|
|
143
|
+
fig.savefig(pdf_path, bbox_inches="tight")
|
|
144
|
+
plt.close(fig)
|
|
145
|
+
|
|
146
|
+
print(f"Wrote {png_path}")
|
|
147
|
+
print(f"Wrote {tif_path}")
|
|
148
|
+
#print(f"Wrote {eps_path}")
|
|
149
|
+
print(f"Wrote {pdf_path}")
|
|
150
|
+
return png_path
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main() -> None:
|
|
154
|
+
parser = argparse.ArgumentParser(
|
|
155
|
+
description="Build the composite scaling figure for the manuscript.",
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"--summary",
|
|
159
|
+
type=Path,
|
|
160
|
+
default=None,
|
|
161
|
+
help=(
|
|
162
|
+
"Path to summary.csv produced by collect_results.py. "
|
|
163
|
+
"Default: <script_dir>/summary.csv, then <script_dir>/results/summary.csv."
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--outdir",
|
|
168
|
+
type=Path,
|
|
169
|
+
default=SCRIPT_DIR,
|
|
170
|
+
help="Output directory for the figure files (default: this script's directory).",
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--basename",
|
|
174
|
+
type=str,
|
|
175
|
+
default="benchmark_composite_scaling",
|
|
176
|
+
help="Output file basename (without extension). Default: benchmark_composite_scaling.",
|
|
177
|
+
)
|
|
178
|
+
args = parser.parse_args()
|
|
179
|
+
|
|
180
|
+
if args.summary is None:
|
|
181
|
+
candidates = [SCRIPT_DIR / "summary.csv", SCRIPT_DIR / "results" / "summary.csv"]
|
|
182
|
+
for c in candidates:
|
|
183
|
+
if c.exists():
|
|
184
|
+
args.summary = c
|
|
185
|
+
break
|
|
186
|
+
else:
|
|
187
|
+
sys.exit(
|
|
188
|
+
"Could not locate summary.csv. Pass --summary or run "
|
|
189
|
+
"`python collect_results.py --resultsdir results --out results/summary.csv` first."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if not args.summary.exists():
|
|
193
|
+
sys.exit(f"summary file not found: {args.summary}")
|
|
194
|
+
|
|
195
|
+
build(args.summary, args.outdir, basename=args.basename)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
main()
|