pycmplot 0.2.9__tar.gz → 0.3.1__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.2.9 → pycmplot-0.3.1}/PKG-INFO +65 -24
- {pycmplot-0.2.9 → pycmplot-0.3.1}/README.md +64 -23
- pycmplot-0.3.1/benchmark/build_composite_figure.py +199 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/__init__.py +1 -1
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/_core.py +6 -3
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/annotation.py +2 -2
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/cli.py +8 -6
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/io.py +46 -41
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/circular.py +12 -9
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/linear.py +15 -8
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/PKG-INFO +65 -24
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/SOURCES.txt +1 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pyproject.toml +1 -1
- {pycmplot-0.2.9 → pycmplot-0.3.1}/setup.cfg +1 -1
- {pycmplot-0.2.9 → pycmplot-0.3.1}/LICENSE +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/bench_python.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/collect_results.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/generate_multi_sumstats.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/generate_sumstats.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/__main__.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/constants.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/Homo_sapiens.GRCh37.geneinfo.tsv.gz +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/Homo_sapiens.GRCh38.geneinfo.tsv.gz +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/hg18ToHg38.over.chain.gz +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/hg19ToHg38.over.chain.gz +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/liftover.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/__init__.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/qq.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/resources.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/stats.py +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/dependency_links.txt +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/entry_points.txt +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/requires.txt +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/top_level.txt +0 -0
- {pycmplot-0.2.9 → pycmplot-0.3.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycmplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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,56 @@ 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="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
|
+
3. [Application](#application)
|
|
71
|
+
4. [Tip](#tip)
|
|
72
|
+
5. [Installation](#installation)
|
|
73
|
+
- [From PyPI](#from-pypi)
|
|
74
|
+
- [From GitHub](#from-github)
|
|
75
|
+
- [Python virtual environment](#python-virtual-environment)
|
|
76
|
+
6. [Test the installation](#test-the-installation)
|
|
77
|
+
7. [Dependencies](#dependencies)
|
|
78
|
+
8. [Command-line usage](#command-line-usage)
|
|
79
|
+
- [Linear Manhattan (default)](#linear-manhattan-default)
|
|
80
|
+
- [Circular Manhattan](#circular-manhattan)
|
|
81
|
+
- [Key options](#key-options)
|
|
82
|
+
9. [Python API](#python-api)
|
|
83
|
+
10. [Contributing](#contributing)
|
|
84
|
+
11. [Contributors](#contributors)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
---------------------------------------------
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## Overview
|
|
91
|
+
|
|
52
92
|
This package will take any number of per SNP/variant summary statistics, be it GWAS,
|
|
53
93
|
selection scans (e.g. iHS, EHH, FST), etc and generate Manhattan plots. If given a single
|
|
54
94
|
file, a single one-track Manhattan plot will be generated. Multiple files will result in
|
|
@@ -68,8 +108,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
68
108
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
69
109
|
corrdinate system.
|
|
70
110
|
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
## Key features
|
|
112
|
+
### Column auto-detection
|
|
73
113
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
74
114
|
command-line or python API:
|
|
75
115
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -92,7 +132,7 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
92
132
|
|
|
93
133
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
94
134
|
|
|
95
|
-
|
|
135
|
+
### Density-aware sub-sampling
|
|
96
136
|
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
97
137
|
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
98
138
|
|
|
@@ -103,14 +143,14 @@ scan with the defaults below, this typically cuts the plotted point count from 1
|
|
|
103
143
|
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
104
144
|
band, but two orders of magnitude faster to render.
|
|
105
145
|
|
|
106
|
-
|
|
146
|
+
### Trim insignificant variants for faster plotting
|
|
107
147
|
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
108
148
|
Set with a value to exclude variants with p-value above a certain threshold,
|
|
109
149
|
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
110
150
|
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
111
151
|
See benchmark figure (manuscript in preparation).
|
|
112
152
|
|
|
113
|
-
|
|
153
|
+
### Genome build conversion (liftover)
|
|
114
154
|
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
115
155
|
`pyliftover.LiftOver`.
|
|
116
156
|
|
|
@@ -120,7 +160,7 @@ column to specify the genome build of each position ('hg18', 'hg19', or 'hg38')
|
|
|
120
160
|
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
121
161
|
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
122
162
|
|
|
123
|
-
|
|
163
|
+
### Nearest-gene annotation for GWAS lead SNPs
|
|
124
164
|
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
125
165
|
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
126
166
|
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
@@ -139,12 +179,12 @@ for gene annotation. Also included are UCSC chain files for coordinate conversio
|
|
|
139
179
|
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
140
180
|
|
|
141
181
|
|
|
142
|
-
|
|
182
|
+
## Application
|
|
143
183
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
144
184
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
145
185
|
|
|
146
186
|
|
|
147
|
-
|
|
187
|
+
## Tip
|
|
148
188
|
|
|
149
189
|
For signed non-pvalue statistics such as iHS, use `--signif_threshold` and
|
|
150
190
|
`--suggest_threshold` to specify lines to draw for iHS cutoff above and below zero,
|
|
@@ -153,7 +193,7 @@ that is in the positive and negative axes.
|
|
|
153
193
|
|
|
154
194
|
Read more in the package documentation page: https://pycmplot.readthedocs.io/en/latest/
|
|
155
195
|
|
|
156
|
-
|
|
196
|
+
----------------------------
|
|
157
197
|
|
|
158
198
|
## Installation
|
|
159
199
|
|
|
@@ -180,24 +220,25 @@ pip install -e . --break-system-packages
|
|
|
180
220
|
```
|
|
181
221
|
|
|
182
222
|
|
|
183
|
-
###
|
|
184
|
-
```bash
|
|
185
|
-
python -m venv ~/bin/pycmplot
|
|
223
|
+
### Python virtual environment
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
Use python virtual environment if local installation is not possible
|
|
188
226
|
|
|
189
|
-
|
|
227
|
+
```bash
|
|
228
|
+
python -m venv ~/envs/pycmplot
|
|
229
|
+
source ~/envs/pycmplot/bin/activate
|
|
190
230
|
|
|
191
|
-
|
|
231
|
+
pip install --upgrade pip setuptools wheel
|
|
232
|
+
pip install pycmplot
|
|
192
233
|
```
|
|
193
234
|
|
|
194
235
|
|
|
195
|
-
|
|
236
|
+
## Test the installation
|
|
196
237
|
```bash
|
|
197
238
|
pycmplot -h
|
|
198
239
|
```
|
|
199
240
|
|
|
200
|
-
|
|
241
|
+
## Dependencies
|
|
201
242
|
|
|
202
243
|
| Package | Purpose |
|
|
203
244
|
|---------|---------|
|
|
@@ -1,15 +1,55 @@
|
|
|
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="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
|
+
3. [Application](#application)
|
|
32
|
+
4. [Tip](#tip)
|
|
33
|
+
5. [Installation](#installation)
|
|
34
|
+
- [From PyPI](#from-pypi)
|
|
35
|
+
- [From GitHub](#from-github)
|
|
36
|
+
- [Python virtual environment](#python-virtual-environment)
|
|
37
|
+
6. [Test the installation](#test-the-installation)
|
|
38
|
+
7. [Dependencies](#dependencies)
|
|
39
|
+
8. [Command-line usage](#command-line-usage)
|
|
40
|
+
- [Linear Manhattan (default)](#linear-manhattan-default)
|
|
41
|
+
- [Circular Manhattan](#circular-manhattan)
|
|
42
|
+
- [Key options](#key-options)
|
|
43
|
+
9. [Python API](#python-api)
|
|
44
|
+
10. [Contributing](#contributing)
|
|
45
|
+
11. [Contributors](#contributors)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
---------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Overview
|
|
52
|
+
|
|
13
53
|
This package will take any number of per SNP/variant summary statistics, be it GWAS,
|
|
14
54
|
selection scans (e.g. iHS, EHH, FST), etc and generate Manhattan plots. If given a single
|
|
15
55
|
file, a single one-track Manhattan plot will be generated. Multiple files will result in
|
|
@@ -29,8 +69,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
29
69
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
30
70
|
corrdinate system.
|
|
31
71
|
|
|
32
|
-
|
|
33
|
-
|
|
72
|
+
## Key features
|
|
73
|
+
### Column auto-detection
|
|
34
74
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
35
75
|
command-line or python API:
|
|
36
76
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -53,7 +93,7 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
53
93
|
|
|
54
94
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
55
95
|
|
|
56
|
-
|
|
96
|
+
### Density-aware sub-sampling
|
|
57
97
|
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
58
98
|
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
59
99
|
|
|
@@ -64,14 +104,14 @@ scan with the defaults below, this typically cuts the plotted point count from 1
|
|
|
64
104
|
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
65
105
|
band, but two orders of magnitude faster to render.
|
|
66
106
|
|
|
67
|
-
|
|
107
|
+
### Trim insignificant variants for faster plotting
|
|
68
108
|
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
69
109
|
Set with a value to exclude variants with p-value above a certain threshold,
|
|
70
110
|
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
71
111
|
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
72
112
|
See benchmark figure (manuscript in preparation).
|
|
73
113
|
|
|
74
|
-
|
|
114
|
+
### Genome build conversion (liftover)
|
|
75
115
|
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
76
116
|
`pyliftover.LiftOver`.
|
|
77
117
|
|
|
@@ -81,7 +121,7 @@ column to specify the genome build of each position ('hg18', 'hg19', or 'hg38')
|
|
|
81
121
|
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
82
122
|
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
83
123
|
|
|
84
|
-
|
|
124
|
+
### Nearest-gene annotation for GWAS lead SNPs
|
|
85
125
|
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
86
126
|
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
87
127
|
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
@@ -100,12 +140,12 @@ for gene annotation. Also included are UCSC chain files for coordinate conversio
|
|
|
100
140
|
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
101
141
|
|
|
102
142
|
|
|
103
|
-
|
|
143
|
+
## Application
|
|
104
144
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
105
145
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
106
146
|
|
|
107
147
|
|
|
108
|
-
|
|
148
|
+
## Tip
|
|
109
149
|
|
|
110
150
|
For signed non-pvalue statistics such as iHS, use `--signif_threshold` and
|
|
111
151
|
`--suggest_threshold` to specify lines to draw for iHS cutoff above and below zero,
|
|
@@ -114,7 +154,7 @@ that is in the positive and negative axes.
|
|
|
114
154
|
|
|
115
155
|
Read more in the package documentation page: https://pycmplot.readthedocs.io/en/latest/
|
|
116
156
|
|
|
117
|
-
|
|
157
|
+
----------------------------
|
|
118
158
|
|
|
119
159
|
## Installation
|
|
120
160
|
|
|
@@ -141,24 +181,25 @@ pip install -e . --break-system-packages
|
|
|
141
181
|
```
|
|
142
182
|
|
|
143
183
|
|
|
144
|
-
###
|
|
145
|
-
```bash
|
|
146
|
-
python -m venv ~/bin/pycmplot
|
|
184
|
+
### Python virtual environment
|
|
147
185
|
|
|
148
|
-
|
|
186
|
+
Use python virtual environment if local installation is not possible
|
|
149
187
|
|
|
150
|
-
|
|
188
|
+
```bash
|
|
189
|
+
python -m venv ~/envs/pycmplot
|
|
190
|
+
source ~/envs/pycmplot/bin/activate
|
|
151
191
|
|
|
152
|
-
|
|
192
|
+
pip install --upgrade pip setuptools wheel
|
|
193
|
+
pip install pycmplot
|
|
153
194
|
```
|
|
154
195
|
|
|
155
196
|
|
|
156
|
-
|
|
197
|
+
## Test the installation
|
|
157
198
|
```bash
|
|
158
199
|
pycmplot -h
|
|
159
200
|
```
|
|
160
201
|
|
|
161
|
-
|
|
202
|
+
## Dependencies
|
|
162
203
|
|
|
163
204
|
| Package | Purpose |
|
|
164
205
|
|---------|---------|
|
|
@@ -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()
|
|
@@ -281,6 +281,7 @@ def main() -> None:
|
|
|
281
281
|
highlight_color = highlight_color,
|
|
282
282
|
highlight_line = highlight_line,
|
|
283
283
|
highlight_line_color = highlight_line_color,
|
|
284
|
+
suggest_line = True if suggest_threshold is not None else False,
|
|
284
285
|
colors = colors,
|
|
285
286
|
point_size=point_size,
|
|
286
287
|
chrom_label_side = chrom_label_side,
|
|
@@ -318,6 +319,8 @@ def main() -> None:
|
|
|
318
319
|
highlight_color=highlight_color,
|
|
319
320
|
highlight_line=highlight_line,
|
|
320
321
|
highlight_line_color=highlight_line_color,
|
|
322
|
+
signif_line = signif_line,
|
|
323
|
+
suggest_line = True if suggest_threshold is not None else False,
|
|
321
324
|
annotate=annotate,
|
|
322
325
|
annotation_size=annotation_size,
|
|
323
326
|
hits_table=hits_table if not hits_table.empty else None,
|
|
@@ -351,7 +354,7 @@ def main() -> None:
|
|
|
351
354
|
max_points=qq_max_points,
|
|
352
355
|
output_path=qq_stem,
|
|
353
356
|
colors=colors,
|
|
354
|
-
signif_threshold=signif_threshold
|
|
357
|
+
signif_threshold=signif_threshold,
|
|
355
358
|
dpi=dpi,
|
|
356
359
|
fontsize=fsize,
|
|
357
360
|
fig_format=output_format,
|
|
@@ -363,7 +366,7 @@ def main() -> None:
|
|
|
363
366
|
thin_below=thin_below,
|
|
364
367
|
max_points=qq_max_points,
|
|
365
368
|
colors=colors,
|
|
366
|
-
signif_threshold=signif_threshold
|
|
369
|
+
signif_threshold=signif_threshold,
|
|
367
370
|
dpi=dpi,
|
|
368
371
|
fontsize=fsize,
|
|
369
372
|
title=plot_title,
|
|
@@ -378,7 +381,7 @@ def main() -> None:
|
|
|
378
381
|
max_points=qq_max_points,
|
|
379
382
|
colors=colors,
|
|
380
383
|
ncols=qq_ncols,
|
|
381
|
-
signif_threshold=signif_threshold
|
|
384
|
+
signif_threshold=signif_threshold,
|
|
382
385
|
dpi=dpi,
|
|
383
386
|
fontsize=fsize,
|
|
384
387
|
title=plot_title,
|
|
@@ -589,11 +589,11 @@ def get_annotation_column(
|
|
|
589
589
|
try:
|
|
590
590
|
if row["genic"]:
|
|
591
591
|
label_clm = "nearest_upstream_gene"
|
|
592
|
-
label_msg = "'POS' is genic"
|
|
592
|
+
label_msg = f"Signal {row['SNP']} at {row['POS']} is genic [{row['nearest_upstream_gene']}]"
|
|
593
593
|
else:
|
|
594
594
|
label_clm = "top_gene"
|
|
595
595
|
label_msg = "'POS' is not genic"
|
|
596
|
-
logger.info("%s", label_msg)
|
|
596
|
+
#logger.info("%s", label_msg)
|
|
597
597
|
except Exception:
|
|
598
598
|
logger.warning(
|
|
599
599
|
"Annotation columns '%s' and '%s' not found in hits table: %s; "
|
|
@@ -365,13 +365,15 @@ def get_arguments(descmsg: str = DESCMSG) -> argparse.Namespace:
|
|
|
365
365
|
)
|
|
366
366
|
opt.add_argument(
|
|
367
367
|
"-sig", "--signif_threshold",
|
|
368
|
-
default=None, const=
|
|
369
|
-
|
|
368
|
+
default=None, const=None,
|
|
369
|
+
nargs="?", type=float, metavar="float",
|
|
370
|
+
help="Genome-wide significance threshold (default: 0.05/number of records)."
|
|
370
371
|
)
|
|
371
372
|
opt.add_argument(
|
|
372
373
|
"-sigl", "--signif_line",
|
|
373
|
-
default=None, const=
|
|
374
|
-
|
|
374
|
+
default=None, const=999999, # arbitriary number to evaluate
|
|
375
|
+
nargs="?", type=float, metavar="float",
|
|
376
|
+
help="Value for genome-wide significance line if different from `-sig` (default: 0.05/number of records)."
|
|
375
377
|
)
|
|
376
378
|
opt.add_argument(
|
|
377
379
|
"-sug", "--suggest_threshold",
|
|
@@ -403,8 +405,8 @@ def get_arguments(descmsg: str = DESCMSG) -> argparse.Namespace:
|
|
|
403
405
|
help="Highlight significant loci."
|
|
404
406
|
)
|
|
405
407
|
opt.add_argument(
|
|
406
|
-
"-ht", "--highlight_thresh", default=
|
|
407
|
-
help="P-value threshold for highlighting (default:
|
|
408
|
+
"-ht", "--highlight_thresh", default=None, type=float, metavar="float",
|
|
409
|
+
help="P-value threshold for highlighting (default: '--signif_threshold')."
|
|
408
410
|
)
|
|
409
411
|
opt.add_argument(
|
|
410
412
|
"-hc", "--highlight_color", default="brown", type=str, metavar="str",
|
|
@@ -888,9 +888,9 @@ def get_sumstats_and_merged_sector_list(
|
|
|
888
888
|
table_out: Optional[str] = None,
|
|
889
889
|
signif_threshold: Optional[float] = None,
|
|
890
890
|
signif_line: Optional[float] = None,
|
|
891
|
-
suggest_threshold: Optional[float] =
|
|
891
|
+
suggest_threshold: Optional[float] = 1e-5,
|
|
892
892
|
highlight: Optional[bool] = False,
|
|
893
|
-
highlight_thresh: Optional[float] =
|
|
893
|
+
highlight_thresh: Optional[float] = None,
|
|
894
894
|
resources: Optional[ResourceConfig] = None,
|
|
895
895
|
compute_pvals: bool = True,
|
|
896
896
|
auto_thin: bool = True,
|
|
@@ -1017,6 +1017,8 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1017
1017
|
|
|
1018
1018
|
sumstats_loaded: dict[str, list] = {}
|
|
1019
1019
|
pval_dict: dict[str, np.ndarray | pd.Series] = {}
|
|
1020
|
+
snp_counts: dict[str, np.ndarray | pd.Series] = {}
|
|
1021
|
+
signif_lines: list[dict[str, float]] = []
|
|
1020
1022
|
all_lead_snps: list[pd.DataFrame] = []
|
|
1021
1023
|
|
|
1022
1024
|
for label in sumstats.keys() & (file_info or {}).keys():
|
|
@@ -1078,6 +1080,40 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1078
1080
|
else:
|
|
1079
1081
|
pval_dict[label] = None
|
|
1080
1082
|
|
|
1083
|
+
# Get SNP counts for significance threshold calculation
|
|
1084
|
+
snp_counts[label] = len(df["P"].dropna().astype(float).values)
|
|
1085
|
+
|
|
1086
|
+
# Derive significance/suggestive thresholds
|
|
1087
|
+
n = snp_counts[label]
|
|
1088
|
+
if signif_threshold is None:
|
|
1089
|
+
#last_label = list(sumstats_loaded)[-1]
|
|
1090
|
+
signif_threshold = max(0.05 / n, 5e-8)
|
|
1091
|
+
|
|
1092
|
+
if suggest_threshold is not None:
|
|
1093
|
+
suggest_line = suggest_threshold
|
|
1094
|
+
else:
|
|
1095
|
+
suggest_line = 1e-5
|
|
1096
|
+
if logp:
|
|
1097
|
+
suggest_line = -np.log10(suggest_line)
|
|
1098
|
+
|
|
1099
|
+
if signif_line is None:
|
|
1100
|
+
signif_line = signif_threshold
|
|
1101
|
+
if logp:
|
|
1102
|
+
signif_line = -np.log10(signif_line)
|
|
1103
|
+
else:
|
|
1104
|
+
# significance line was set without value
|
|
1105
|
+
# fallback to sig_thresh
|
|
1106
|
+
if signif_line == 999999:
|
|
1107
|
+
signif_line = signif_threshold
|
|
1108
|
+
# significance line was set with value, use value
|
|
1109
|
+
else:
|
|
1110
|
+
signif_line = np.float64(signif_line)
|
|
1111
|
+
|
|
1112
|
+
if logp and signif_line < 1:
|
|
1113
|
+
signif_line = -np.log10(signif_line)
|
|
1114
|
+
|
|
1115
|
+
signif_lines.append({"genome": signif_line, "suggestive": suggest_line})
|
|
1116
|
+
|
|
1081
1117
|
# Density-aware auto-thinning for Manhattan / circular rendering.
|
|
1082
1118
|
# Applied after lead-SNP extraction so the leads come from the full
|
|
1083
1119
|
# dataset, and after liftover so coordinates are final. Variants
|
|
@@ -1189,11 +1225,12 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1189
1225
|
# into ``sumstats_loaded[label][0]`` here raised ``KeyError`` (e.g.
|
|
1190
1226
|
# ``KeyError: 'MCV'``) the first time the liftover branch fired on
|
|
1191
1227
|
# a given track.
|
|
1228
|
+
builds = df["BUILD"].unique()
|
|
1192
1229
|
if "BUILD" in df.columns and (
|
|
1193
|
-
"
|
|
1230
|
+
"hg18" in builds or ("hg19" in builds and "hg38" in builds)
|
|
1194
1231
|
):
|
|
1195
1232
|
builds_present = sorted(
|
|
1196
|
-
b for b in
|
|
1233
|
+
b for b in builds if b in {"hg18", "hg19"}
|
|
1197
1234
|
)
|
|
1198
1235
|
logger.info(
|
|
1199
1236
|
"Converting %s coordinates to hg38 ...", "/".join(builds_present)
|
|
@@ -1210,7 +1247,7 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1210
1247
|
df=df,
|
|
1211
1248
|
window=500_000,
|
|
1212
1249
|
highlight=highlight,
|
|
1213
|
-
highlight_thresh=highlight_thresh,
|
|
1250
|
+
highlight_thresh=highlight_thresh if highlight_thresh is not None else signif_threshold,
|
|
1214
1251
|
logp=logp,
|
|
1215
1252
|
)
|
|
1216
1253
|
|
|
@@ -1222,6 +1259,9 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1222
1259
|
# logp=logp,
|
|
1223
1260
|
#)
|
|
1224
1261
|
|
|
1262
|
+
if not leads.empty:
|
|
1263
|
+
leads = leads[leads["P"] <= signif_threshold]
|
|
1264
|
+
|
|
1225
1265
|
all_lead_snps.append(leads)
|
|
1226
1266
|
|
|
1227
1267
|
# Number of distinct chromosomes (for track sorting)
|
|
@@ -1234,11 +1274,7 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1234
1274
|
if all_lead_snps
|
|
1235
1275
|
else pd.DataFrame()
|
|
1236
1276
|
)
|
|
1237
|
-
|
|
1238
|
-
all_lead_snps_df = all_lead_snps_df[
|
|
1239
|
-
all_lead_snps_df["P"] <= signif_threshold
|
|
1240
|
-
]
|
|
1241
|
-
|
|
1277
|
+
|
|
1242
1278
|
hits_table = (
|
|
1243
1279
|
get_hits_summary_table(
|
|
1244
1280
|
leads_df=all_lead_snps_df,
|
|
@@ -1250,37 +1286,6 @@ def get_sumstats_and_merged_sector_list(
|
|
|
1250
1286
|
else pd.DataFrame()
|
|
1251
1287
|
)
|
|
1252
1288
|
|
|
1253
|
-
# Derive significance/suggestive thresholds
|
|
1254
|
-
if not signif_threshold:
|
|
1255
|
-
if trim_pval:
|
|
1256
|
-
signif_threshold = 5e-8
|
|
1257
|
-
elif sumstats_loaded:
|
|
1258
|
-
last_label = list(sumstats_loaded)[-1]
|
|
1259
|
-
n = len(sumstats_loaded[last_label][0]["P"])
|
|
1260
|
-
signif_threshold = max(0.05 / n, 5e-8)
|
|
1261
|
-
else:
|
|
1262
|
-
signif_threshold = 5e-8
|
|
1263
|
-
|
|
1264
|
-
if not suggest_threshold:
|
|
1265
|
-
suggest_threshold = 1e-5
|
|
1266
|
-
|
|
1267
|
-
suggest_line = suggest_threshold
|
|
1268
|
-
if logp:
|
|
1269
|
-
suggest_line = -np.log10(suggest_threshold)
|
|
1270
|
-
|
|
1271
|
-
if signif_line is None:
|
|
1272
|
-
signif_line = signif_threshold
|
|
1273
|
-
if logp:
|
|
1274
|
-
signif_line = -np.log10(signif_threshold)
|
|
1275
|
-
else:
|
|
1276
|
-
if logp and signif_line < 1:
|
|
1277
|
-
signif_line = -np.log10(signif_line)
|
|
1278
|
-
|
|
1279
|
-
signif_lines = [
|
|
1280
|
-
{"genome": signif_line, "suggestive": suggest_line}
|
|
1281
|
-
for _ in sumstats
|
|
1282
|
-
]
|
|
1283
|
-
|
|
1284
1289
|
# sort dicts by user-supplied order
|
|
1285
1290
|
sumstats_loaded = {key: sumstats_loaded[key] for key in labels if key in sumstats_loaded}
|
|
1286
1291
|
pval_dict = {key: pval_dict[key] for key in labels if key in pval_dict}
|
|
@@ -131,10 +131,10 @@ def plot_circosm(
|
|
|
131
131
|
track_index: int = 0,
|
|
132
132
|
assoc_label: Optional[str] = None,
|
|
133
133
|
logp: bool = True,
|
|
134
|
-
signif_line: Optional[float] =
|
|
135
|
-
signif_threshold: Optional[float] =
|
|
136
|
-
suggest_line:
|
|
137
|
-
suggest_threshold: Optional[float] =
|
|
134
|
+
signif_line: Optional[float] = None,
|
|
135
|
+
signif_threshold: Optional[float] = None,
|
|
136
|
+
suggest_line: bool = False,
|
|
137
|
+
suggest_threshold: Optional[float] = None,
|
|
138
138
|
highlight: bool = False,
|
|
139
139
|
highlight_color: str = 'brown',
|
|
140
140
|
colors: Optional[list[str]] = ['steelblue','orange'],
|
|
@@ -336,7 +336,7 @@ def plot_circosm(
|
|
|
336
336
|
# ------------------------------------------------------------------
|
|
337
337
|
# Significance lines
|
|
338
338
|
# ------------------------------------------------------------------
|
|
339
|
-
if signif_line:
|
|
339
|
+
if signif_line is not None:
|
|
340
340
|
track.line(
|
|
341
341
|
x=[sector.start, sector.end],
|
|
342
342
|
y=[genome_wide_sig, genome_wide_sig],
|
|
@@ -365,12 +365,13 @@ def plot_circular(
|
|
|
365
365
|
label_col: str = None,
|
|
366
366
|
chrom_label_side: str = 'inside',
|
|
367
367
|
chrom_label_size: float = 6,
|
|
368
|
-
signif_line: float =
|
|
368
|
+
signif_line: float = None,
|
|
369
369
|
highlight: bool = False,
|
|
370
|
-
highlight_thresh: float =
|
|
370
|
+
highlight_thresh: float = None,
|
|
371
371
|
highlight_color: str = 'brown',
|
|
372
372
|
highlight_line: bool = False,
|
|
373
373
|
highlight_line_color: str = 'grey',
|
|
374
|
+
suggest_line: bool = False,
|
|
374
375
|
colors: list[str] = ['steelblue','silver'],
|
|
375
376
|
point_size: float = 6,
|
|
376
377
|
track_label_size: float = 6,
|
|
@@ -557,6 +558,7 @@ def plot_circular(
|
|
|
557
558
|
|
|
558
559
|
chrom_label_loc = outside_loc if chrom_label_side == "outside" else inside_loc
|
|
559
560
|
|
|
561
|
+
"""
|
|
560
562
|
if not signif_lines:
|
|
561
563
|
signif_line = -np.log10(signif_line) if signif_line < 1 else signif_line
|
|
562
564
|
suggest_line = -np.log10(1e-5)
|
|
@@ -564,6 +566,7 @@ def plot_circular(
|
|
|
564
566
|
{"genome": signif_line, "suggestive": suggest_line}
|
|
565
567
|
for _ in sumstats_loaded
|
|
566
568
|
]
|
|
569
|
+
"""
|
|
567
570
|
|
|
568
571
|
for index, (sector_radius, sumstats_key, sumstats_value, signif_dict) in enumerate(
|
|
569
572
|
zip(
|
|
@@ -607,9 +610,9 @@ def plot_circular(
|
|
|
607
610
|
assoc_by_chr=assoc_by_chr,
|
|
608
611
|
assoc_label=sumstat_name,
|
|
609
612
|
logp=logp,
|
|
610
|
-
signif_line=
|
|
613
|
+
signif_line=signif_line,
|
|
611
614
|
signif_threshold=sig_thresh,
|
|
612
|
-
suggest_line=
|
|
615
|
+
suggest_line=suggest_line,
|
|
613
616
|
suggest_threshold=sug_thresh,
|
|
614
617
|
highlight=highlight,
|
|
615
618
|
highlight_color=highlight_color,
|
|
@@ -960,6 +960,9 @@ def plot_linearm(
|
|
|
960
960
|
highlight_color: str = 'brown',
|
|
961
961
|
highlight_line: bool = False,
|
|
962
962
|
highlight_line_color: str = 'grey',
|
|
963
|
+
signif_line: Optional[float] = None,
|
|
964
|
+
suggest_line: bool = False,
|
|
965
|
+
sig_lines: Optional[list[dict]] = None,
|
|
963
966
|
logp: bool = True,
|
|
964
967
|
label_col: Optional[str] = 'SNP',
|
|
965
968
|
chr_order: Optional[list[str]] = None,
|
|
@@ -969,7 +972,6 @@ def plot_linearm(
|
|
|
969
972
|
annot_rail_frac: float = 0.95,
|
|
970
973
|
point_size: float = 8,
|
|
971
974
|
colors: Optional[list[str]] = ['steelblue','silver'],
|
|
972
|
-
sig_lines: Optional[list[dict]] = None,
|
|
973
975
|
plt_name: Optional[str] = None,
|
|
974
976
|
no_track_labels: bool = False,
|
|
975
977
|
ylabel: Optional[str] = None,
|
|
@@ -1355,10 +1357,12 @@ def plot_linearm(
|
|
|
1355
1357
|
|
|
1356
1358
|
if sig_lines is not None and i < len(sig_lines):
|
|
1357
1359
|
sl = sig_lines[i]
|
|
1358
|
-
if
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1360
|
+
if signif_line is not None:
|
|
1361
|
+
if "genome" in sl:
|
|
1362
|
+
ax.axhline(y=sl["genome"], color="orangered", linestyle="--", linewidth=0.5)
|
|
1363
|
+
if suggest_line:
|
|
1364
|
+
if "suggestive" in sl:
|
|
1365
|
+
ax.axhline(y=sl["suggestive"], color="navy", linestyle="--", linewidth=0.5)
|
|
1362
1366
|
|
|
1363
1367
|
ax.spines[["top", "right"]].set_visible(False)
|
|
1364
1368
|
|
|
@@ -1375,7 +1379,6 @@ def plot_linearm(
|
|
|
1375
1379
|
differences = np.diff(df_chr['POS']).tolist()
|
|
1376
1380
|
less_than_spread_width.append(list(filter(lambda x: x < s_width, differences)))
|
|
1377
1381
|
less_than_spread_width = [l for l in less_than_spread_width if not len(l) == 0]
|
|
1378
|
-
print(len(less_than_spread_width))
|
|
1379
1382
|
if len(less_than_spread_width) < 5:
|
|
1380
1383
|
_draw_annotation_arrows(
|
|
1381
1384
|
ax_annot,
|
|
@@ -1484,7 +1487,10 @@ def plot_linear(
|
|
|
1484
1487
|
highlight: bool = False,
|
|
1485
1488
|
highlight_color: str = 'brown',
|
|
1486
1489
|
highlight_line: bool = False,
|
|
1487
|
-
highlight_line_color: str = 'grey',
|
|
1490
|
+
highlight_line_color: str = 'grey',
|
|
1491
|
+
signif_lines: Optional[dict] = None,
|
|
1492
|
+
signif_line: Optional[float] = None,
|
|
1493
|
+
suggest_line: bool = False,
|
|
1488
1494
|
hits_table: Optional[pd.DataFrame] = None,
|
|
1489
1495
|
annotate: str = None,
|
|
1490
1496
|
annotation_size: float = 8,
|
|
@@ -1493,7 +1499,6 @@ def plot_linear(
|
|
|
1493
1499
|
linear_track_spacing: Optional[float] = None,
|
|
1494
1500
|
annot_rail_frac: Optional[float] = 0.98,
|
|
1495
1501
|
colors: list[str] = ['steelblue','silver'],
|
|
1496
|
-
signif_lines: Optional[dict] = None,
|
|
1497
1502
|
plot_title: Optional[str] = None,
|
|
1498
1503
|
no_track_labels: bool = False,
|
|
1499
1504
|
ylabel: Optional[str] = None,
|
|
@@ -1638,6 +1643,8 @@ def plot_linear(
|
|
|
1638
1643
|
highlight_color = highlight_color,
|
|
1639
1644
|
highlight_line = highlight_line,
|
|
1640
1645
|
highlight_line_color = highlight_line_color,
|
|
1646
|
+
signif_line = signif_line,
|
|
1647
|
+
suggest_line = suggest_line,
|
|
1641
1648
|
annotate=annotate,
|
|
1642
1649
|
annotation_size=annotation_size,
|
|
1643
1650
|
annot_df=hits_table if hits_table is not None and not hits_table.empty else None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycmplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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,56 @@ 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="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
|
+
3. [Application](#application)
|
|
71
|
+
4. [Tip](#tip)
|
|
72
|
+
5. [Installation](#installation)
|
|
73
|
+
- [From PyPI](#from-pypi)
|
|
74
|
+
- [From GitHub](#from-github)
|
|
75
|
+
- [Python virtual environment](#python-virtual-environment)
|
|
76
|
+
6. [Test the installation](#test-the-installation)
|
|
77
|
+
7. [Dependencies](#dependencies)
|
|
78
|
+
8. [Command-line usage](#command-line-usage)
|
|
79
|
+
- [Linear Manhattan (default)](#linear-manhattan-default)
|
|
80
|
+
- [Circular Manhattan](#circular-manhattan)
|
|
81
|
+
- [Key options](#key-options)
|
|
82
|
+
9. [Python API](#python-api)
|
|
83
|
+
10. [Contributing](#contributing)
|
|
84
|
+
11. [Contributors](#contributors)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
---------------------------------------------
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## Overview
|
|
91
|
+
|
|
52
92
|
This package will take any number of per SNP/variant summary statistics, be it GWAS,
|
|
53
93
|
selection scans (e.g. iHS, EHH, FST), etc and generate Manhattan plots. If given a single
|
|
54
94
|
file, a single one-track Manhattan plot will be generated. Multiple files will result in
|
|
@@ -68,8 +108,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
68
108
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
69
109
|
corrdinate system.
|
|
70
110
|
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
## Key features
|
|
112
|
+
### Column auto-detection
|
|
73
113
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
74
114
|
command-line or python API:
|
|
75
115
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -92,7 +132,7 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
92
132
|
|
|
93
133
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
94
134
|
|
|
95
|
-
|
|
135
|
+
### Density-aware sub-sampling
|
|
96
136
|
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
97
137
|
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
98
138
|
|
|
@@ -103,14 +143,14 @@ scan with the defaults below, this typically cuts the plotted point count from 1
|
|
|
103
143
|
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
104
144
|
band, but two orders of magnitude faster to render.
|
|
105
145
|
|
|
106
|
-
|
|
146
|
+
### Trim insignificant variants for faster plotting
|
|
107
147
|
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
108
148
|
Set with a value to exclude variants with p-value above a certain threshold,
|
|
109
149
|
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
110
150
|
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
111
151
|
See benchmark figure (manuscript in preparation).
|
|
112
152
|
|
|
113
|
-
|
|
153
|
+
### Genome build conversion (liftover)
|
|
114
154
|
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
115
155
|
`pyliftover.LiftOver`.
|
|
116
156
|
|
|
@@ -120,7 +160,7 @@ column to specify the genome build of each position ('hg18', 'hg19', or 'hg38')
|
|
|
120
160
|
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
121
161
|
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
122
162
|
|
|
123
|
-
|
|
163
|
+
### Nearest-gene annotation for GWAS lead SNPs
|
|
124
164
|
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
125
165
|
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
126
166
|
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
@@ -139,12 +179,12 @@ for gene annotation. Also included are UCSC chain files for coordinate conversio
|
|
|
139
179
|
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
140
180
|
|
|
141
181
|
|
|
142
|
-
|
|
182
|
+
## Application
|
|
143
183
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
144
184
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
145
185
|
|
|
146
186
|
|
|
147
|
-
|
|
187
|
+
## Tip
|
|
148
188
|
|
|
149
189
|
For signed non-pvalue statistics such as iHS, use `--signif_threshold` and
|
|
150
190
|
`--suggest_threshold` to specify lines to draw for iHS cutoff above and below zero,
|
|
@@ -153,7 +193,7 @@ that is in the positive and negative axes.
|
|
|
153
193
|
|
|
154
194
|
Read more in the package documentation page: https://pycmplot.readthedocs.io/en/latest/
|
|
155
195
|
|
|
156
|
-
|
|
196
|
+
----------------------------
|
|
157
197
|
|
|
158
198
|
## Installation
|
|
159
199
|
|
|
@@ -180,24 +220,25 @@ pip install -e . --break-system-packages
|
|
|
180
220
|
```
|
|
181
221
|
|
|
182
222
|
|
|
183
|
-
###
|
|
184
|
-
```bash
|
|
185
|
-
python -m venv ~/bin/pycmplot
|
|
223
|
+
### Python virtual environment
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
Use python virtual environment if local installation is not possible
|
|
188
226
|
|
|
189
|
-
|
|
227
|
+
```bash
|
|
228
|
+
python -m venv ~/envs/pycmplot
|
|
229
|
+
source ~/envs/pycmplot/bin/activate
|
|
190
230
|
|
|
191
|
-
|
|
231
|
+
pip install --upgrade pip setuptools wheel
|
|
232
|
+
pip install pycmplot
|
|
192
233
|
```
|
|
193
234
|
|
|
194
235
|
|
|
195
|
-
|
|
236
|
+
## Test the installation
|
|
196
237
|
```bash
|
|
197
238
|
pycmplot -h
|
|
198
239
|
```
|
|
199
240
|
|
|
200
|
-
|
|
241
|
+
## Dependencies
|
|
201
242
|
|
|
202
243
|
| Package | Purpose |
|
|
203
244
|
|---------|---------|
|
|
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
|