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.
Files changed (35) hide show
  1. {pycmplot-0.2.9 → pycmplot-0.3.1}/PKG-INFO +65 -24
  2. {pycmplot-0.2.9 → pycmplot-0.3.1}/README.md +64 -23
  3. pycmplot-0.3.1/benchmark/build_composite_figure.py +199 -0
  4. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/__init__.py +1 -1
  5. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/_core.py +6 -3
  6. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/annotation.py +2 -2
  7. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/cli.py +8 -6
  8. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/io.py +46 -41
  9. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/circular.py +12 -9
  10. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/linear.py +15 -8
  11. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/PKG-INFO +65 -24
  12. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/SOURCES.txt +1 -0
  13. {pycmplot-0.2.9 → pycmplot-0.3.1}/pyproject.toml +1 -1
  14. {pycmplot-0.2.9 → pycmplot-0.3.1}/setup.cfg +1 -1
  15. {pycmplot-0.2.9 → pycmplot-0.3.1}/LICENSE +0 -0
  16. {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/bench_python.py +0 -0
  17. {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/collect_results.py +0 -0
  18. {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/generate_multi_sumstats.py +0 -0
  19. {pycmplot-0.2.9 → pycmplot-0.3.1}/benchmark/generate_sumstats.py +0 -0
  20. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/__main__.py +0 -0
  21. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/constants.py +0 -0
  22. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/Homo_sapiens.GRCh37.geneinfo.tsv.gz +0 -0
  23. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/Homo_sapiens.GRCh38.geneinfo.tsv.gz +0 -0
  24. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/hg18ToHg38.over.chain.gz +0 -0
  25. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/data/hg19ToHg38.over.chain.gz +0 -0
  26. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/liftover.py +0 -0
  27. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/__init__.py +0 -0
  28. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/plotting/qq.py +0 -0
  29. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/resources.py +0 -0
  30. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot/stats.py +0 -0
  31. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/dependency_links.txt +0 -0
  32. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/entry_points.txt +0 -0
  33. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/requires.txt +0 -0
  34. {pycmplot-0.2.9 → pycmplot-0.3.1}/pycmplot.egg-info/top_level.txt +0 -0
  35. {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.2.9
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
- Multi-track **circular** and **linear** Manhattan plot generation for GWAS summary statistics.
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
- | PACKAGE FOR CIRCULAR AND LINEAR MANHATTAN PLOTTING |
47
- | Kevin Esoh, 2026 |
48
- | kesohku1@jh.edu |
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
- # Key features
72
- ## Column auto-detection
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
- ## Density-aware sub-sampling
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
- ## Trim insignificant variants for faster plotting
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
- ## Genome build conversion (liftover)
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
- ## Nearest-gene annotation for GWAS lead SNPs
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
- # Application
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
- # Tip
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
- ### Use python virtual environment if local installation is not possible
184
- ```bash
185
- python -m venv ~/bin/pycmplot
223
+ ### Python virtual environment
186
224
 
187
- source ~/bin/pycmplot/bin/activate
225
+ Use python virtual environment if local installation is not possible
188
226
 
189
- pip install --upgrade pip setuptools wheel
227
+ ```bash
228
+ python -m venv ~/envs/pycmplot
229
+ source ~/envs/pycmplot/bin/activate
190
230
 
191
- # then follow any of the installation steps above
231
+ pip install --upgrade pip setuptools wheel
232
+ pip install pycmplot
192
233
  ```
193
234
 
194
235
 
195
- # Test the installation
236
+ ## Test the installation
196
237
  ```bash
197
238
  pycmplot -h
198
239
  ```
199
240
 
200
- ### Dependencies
241
+ ## Dependencies
201
242
 
202
243
  | Package | Purpose |
203
244
  |---------|---------|
@@ -1,15 +1,55 @@
1
1
  # pycmplot
2
2
 
3
- Multi-track **circular** and **linear** Manhattan plot generation for GWAS summary statistics.
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
- | PACKAGE FOR CIRCULAR AND LINEAR MANHATTAN PLOTTING |
8
- | Kevin Esoh, 2026 |
9
- | kesohku1@jh.edu |
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
- # Key features
33
- ## Column auto-detection
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
- ## Density-aware sub-sampling
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
- ## Trim insignificant variants for faster plotting
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
- ## Genome build conversion (liftover)
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
- ## Nearest-gene annotation for GWAS lead SNPs
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
- # Application
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
- # Tip
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
- ### Use python virtual environment if local installation is not possible
145
- ```bash
146
- python -m venv ~/bin/pycmplot
184
+ ### Python virtual environment
147
185
 
148
- source ~/bin/pycmplot/bin/activate
186
+ Use python virtual environment if local installation is not possible
149
187
 
150
- pip install --upgrade pip setuptools wheel
188
+ ```bash
189
+ python -m venv ~/envs/pycmplot
190
+ source ~/envs/pycmplot/bin/activate
151
191
 
152
- # then follow any of the installation steps above
192
+ pip install --upgrade pip setuptools wheel
193
+ pip install pycmplot
153
194
  ```
154
195
 
155
196
 
156
- # Test the installation
197
+ ## Test the installation
157
198
  ```bash
158
199
  pycmplot -h
159
200
  ```
160
201
 
161
- ### Dependencies
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()
@@ -47,4 +47,4 @@ __all__ = [
47
47
  "ResourceConfig",
48
48
  ]
49
49
 
50
- __version__ = "0.2.9"
50
+ __version__ = "0.3.1"
@@ -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 or 5e-8,
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 or 5e-8,
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 or 5e-8,
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=5e-8, nargs="?", type=float, metavar="float",
369
- help="Genome-wide significance threshold (default: 5e-8)."
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=5e-8, nargs="?", type=float, metavar="float",
374
- help="Value for genome-wide significance line if different from `-sig` (default: 5e-8)."
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=5e-8, type=float, metavar="float",
407
- help="P-value threshold for highlighting (default: 5e-8)."
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] = None,
891
+ suggest_threshold: Optional[float] = 1e-5,
892
892
  highlight: Optional[bool] = False,
893
- highlight_thresh: Optional[float] = 5e-08,
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
- "hg19" in df["BUILD"].unique() or "hg18" in df["BUILD"].unique()
1230
+ "hg18" in builds or ("hg19" in builds and "hg38" in builds)
1194
1231
  ):
1195
1232
  builds_present = sorted(
1196
- b for b in df["BUILD"].unique() if b in {"hg18", "hg19"}
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
- if not all_lead_snps_df.empty and signif_threshold:
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] = 5e-8,
135
- signif_threshold: Optional[float] = 5e-8,
136
- suggest_line: Optional[float] = 1e-5,
137
- suggest_threshold: Optional[float] = 1e-5,
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 = 5e-8,
368
+ signif_line: float = None,
369
369
  highlight: bool = False,
370
- highlight_thresh: float = 5e-8,
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=sig_thresh,
613
+ signif_line=signif_line,
611
614
  signif_threshold=sig_thresh,
612
- suggest_line=True if signif_line else False,
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 "genome" in sl:
1359
- ax.axhline(y=sl["genome"], color="orangered", linestyle="--", linewidth=0.5)
1360
- if "suggestive" in sl:
1361
- ax.axhline(y=sl["suggestive"], color="navy", linestyle="--", linewidth=0.5)
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.2.9
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
- Multi-track **circular** and **linear** Manhattan plot generation for GWAS summary statistics.
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
- | PACKAGE FOR CIRCULAR AND LINEAR MANHATTAN PLOTTING |
47
- | Kevin Esoh, 2026 |
48
- | kesohku1@jh.edu |
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
- # Key features
72
- ## Column auto-detection
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
- ## Density-aware sub-sampling
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
- ## Trim insignificant variants for faster plotting
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
- ## Genome build conversion (liftover)
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
- ## Nearest-gene annotation for GWAS lead SNPs
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
- # Application
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
- # Tip
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
- ### Use python virtual environment if local installation is not possible
184
- ```bash
185
- python -m venv ~/bin/pycmplot
223
+ ### Python virtual environment
186
224
 
187
- source ~/bin/pycmplot/bin/activate
225
+ Use python virtual environment if local installation is not possible
188
226
 
189
- pip install --upgrade pip setuptools wheel
227
+ ```bash
228
+ python -m venv ~/envs/pycmplot
229
+ source ~/envs/pycmplot/bin/activate
190
230
 
191
- # then follow any of the installation steps above
231
+ pip install --upgrade pip setuptools wheel
232
+ pip install pycmplot
192
233
  ```
193
234
 
194
235
 
195
- # Test the installation
236
+ ## Test the installation
196
237
  ```bash
197
238
  pycmplot -h
198
239
  ```
199
240
 
200
- ### Dependencies
241
+ ## Dependencies
201
242
 
202
243
  | Package | Purpose |
203
244
  |---------|---------|
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  setup.cfg
5
5
  setup.py
6
6
  benchmark/bench_python.py
7
+ benchmark/build_composite_figure.py
7
8
  benchmark/collect_results.py
8
9
  benchmark/generate_multi_sumstats.py
9
10
  benchmark/generate_sumstats.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pycmplot"
7
- version = "0.2.9"
7
+ version = "0.3.1"
8
8
  description = "Multi-track circular and linear Manhattan plot generation for GWAS summary statistics"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = pycmplot
3
- version = 0.2.9
3
+ version = 0.3.1
4
4
  author = Kevin Esoh
5
5
  author_email = kesohku1@jh.edu
6
6
  description = Multi-track circular and linear Manhattan plot generation for GWAS summary statistics
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes