pycmplot 0.2.6__tar.gz → 0.2.7__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.6 → pycmplot-0.2.7}/PKG-INFO +51 -6
- {pycmplot-0.2.6 → pycmplot-0.2.7}/README.md +50 -5
- pycmplot-0.2.7/benchmark/bench_python.py +266 -0
- pycmplot-0.2.7/benchmark/collect_results.py +318 -0
- pycmplot-0.2.7/benchmark/generate_sumstats.py +133 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/__init__.py +1 -1
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/_core.py +17 -2
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/annotation.py +4 -3
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/cli.py +68 -6
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/data/hg18ToHg38.over.chain.gz +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/io.py +300 -38
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/plotting/circular.py +47 -26
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/plotting/linear.py +680 -323
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/plotting/qq.py +29 -11
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/stats.py +9 -7
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot.egg-info/PKG-INFO +51 -6
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot.egg-info/SOURCES.txt +3 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot.egg-info/top_level.txt +1 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pyproject.toml +1 -1
- {pycmplot-0.2.6 → pycmplot-0.2.7}/setup.cfg +1 -1
- {pycmplot-0.2.6 → pycmplot-0.2.7}/LICENSE +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/__main__.py +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/constants.py +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/data/Homo_sapiens.GRCh37.geneinfo.tsv.gz +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/data/Homo_sapiens.GRCh38.geneinfo.tsv.gz +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/data/hg19ToHg38.over.chain.gz +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/liftover.py +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/plotting/__init__.py +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot/resources.py +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot.egg-info/dependency_links.txt +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot.egg-info/entry_points.txt +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/pycmplot.egg-info/requires.txt +0 -0
- {pycmplot-0.2.6 → pycmplot-0.2.7}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycmplot
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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>
|
|
@@ -68,6 +68,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
68
68
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
69
69
|
corrdinate system.
|
|
70
70
|
|
|
71
|
+
# Key features
|
|
72
|
+
## Column auto-detection
|
|
71
73
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
72
74
|
command-line or python API:
|
|
73
75
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -90,11 +92,54 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
90
92
|
|
|
91
93
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
## Density-aware sub-sampling
|
|
96
|
+
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
97
|
+
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
98
|
+
|
|
99
|
+
Every variant whose "interestingness" signal is at or above ``keep_threshold`` is preserved (so peaks, suggestive hits, genome-wide-significant hits, and extreme
|
|
100
|
+
selection-scan values are kept verbatim). It uniformly sub-samples the dense bulk
|
|
101
|
+
below the threshold down to at most ``max_below`` rows in total. For a 10 M-variant
|
|
102
|
+
scan with the defaults below, this typically cuts the plotted point count from 10 M
|
|
103
|
+
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
104
|
+
band, but two orders of magnitude faster to render.
|
|
105
|
+
|
|
106
|
+
## Trim insignificant variants for faster plotting
|
|
107
|
+
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
108
|
+
Set with a value to exclude variants with p-value above a certain threshold,
|
|
109
|
+
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
110
|
+
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
111
|
+
See benchmark figure (manuscript in preparation).
|
|
112
|
+
|
|
113
|
+
## Genome build conversion (liftover)
|
|
114
|
+
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
115
|
+
`pyliftover.LiftOver`.
|
|
116
|
+
|
|
117
|
+
This means you can concatenate multiple summary stats into one file and include a `BUILD`
|
|
118
|
+
column to specify the genome build of each position ('hg18', 'hg19', or 'hg38') and all
|
|
119
|
+
'hg18' and 'hg19' positions will be converted to 'hg38' so that all positions are plotted
|
|
120
|
+
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
121
|
+
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
122
|
+
|
|
123
|
+
## Nearest-gene annotation for GWAS lead SNPs
|
|
124
|
+
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
125
|
+
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
126
|
+
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
127
|
+
conversion. Resolved from ``PYCMPLOT_CHAIN_HG19_HG38`` or the bundled
|
|
128
|
+
``hg19ToHg38.over.chain.gz``.
|
|
129
|
+
- ``chain_hg18_hg38`` -- UCSC LiftOver chain file for hg18 to hg38
|
|
130
|
+
conversion. Resolved from ``PYCMPLOT_CHAIN_HG18_HG38`` or the bundled
|
|
131
|
+
``hg18ToHg38.over.chain.gz``. Only required when any input summary
|
|
132
|
+
statistics file carries a ``hg18`` build label.
|
|
133
|
+
- ``geneinfo_hg38`` -- Ensembl gene-info TSV for GRCh38, used for
|
|
134
|
+
nearest-gene annotation. Resolved from ``PYCMPLOT_GENEINFO_HG38`` or
|
|
135
|
+
the bundled ``Homo_sapiens.GRCh38.geneinfo.tsv.gz``.
|
|
136
|
+
- ``geneinfo_hg19`` -- Ensembl gene-info TSV for GRCh37, used when
|
|
137
|
+
input data carry a hg19 build label. Resolved from
|
|
138
|
+
``PYCMPLOT_GENEINFO_HG19`` or the bundled
|
|
139
|
+
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Application
|
|
98
143
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
99
144
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
100
145
|
|
|
@@ -29,6 +29,8 @@ option of the package should be used to indicate the column and then the package
|
|
|
29
29
|
postions in hg19 to hg38 ensuring that hits table generation and plotting are done with one unified
|
|
30
30
|
corrdinate system.
|
|
31
31
|
|
|
32
|
+
# Key features
|
|
33
|
+
## Column auto-detection
|
|
32
34
|
A key functionality of the package is its ability to auto-detect certain columns if ommited on the
|
|
33
35
|
command-line or python API:
|
|
34
36
|
- Chromosome column: `-chr, --chrom_column` or ommited
|
|
@@ -51,11 +53,54 @@ bld_candidates = [build, 'BUILD', 'Genome', 'Genome_Build', 'Genome-build']
|
|
|
51
53
|
|
|
52
54
|
> NB: Upper and lower cases of the candidates are also considered, making each candidate expanded 3 times.
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
## Density-aware sub-sampling
|
|
57
|
+
Another key feature is density-aware sub-sampling for Manhattan-style scatter plots.
|
|
58
|
+
This was inspired by ``gwaslab``'s default behaviour (https://cloufield.github.io/gwaslab/).
|
|
59
|
+
|
|
60
|
+
Every variant whose "interestingness" signal is at or above ``keep_threshold`` is preserved (so peaks, suggestive hits, genome-wide-significant hits, and extreme
|
|
61
|
+
selection-scan values are kept verbatim). It uniformly sub-samples the dense bulk
|
|
62
|
+
below the threshold down to at most ``max_below`` rows in total. For a 10 M-variant
|
|
63
|
+
scan with the defaults below, this typically cuts the plotted point count from 10 M
|
|
64
|
+
to ~200 K + a few hundred peaks — visually indistinguishable above the suggestive
|
|
65
|
+
band, but two orders of magnitude faster to render.
|
|
66
|
+
|
|
67
|
+
## Trim insignificant variants for faster plotting
|
|
68
|
+
An optional parameter `-tp, --trim_pval` is provided to increase speed even further.
|
|
69
|
+
Set with a value to exclude variants with p-value above a certain threshold,
|
|
70
|
+
e.g. `0.01 (1e-2)` or `0.001 (1e-3)`. Performed on top of the default auto-thin
|
|
71
|
+
feature above, it siginificant increases speed and reduces peak memory usage.
|
|
72
|
+
See benchmark figure (manuscript in preparation).
|
|
73
|
+
|
|
74
|
+
## Genome build conversion (liftover)
|
|
75
|
+
Conversion of a both hg18 and hg19 positions to their hg38 equivalent is included through
|
|
76
|
+
`pyliftover.LiftOver`.
|
|
77
|
+
|
|
78
|
+
This means you can concatenate multiple summary stats into one file and include a `BUILD`
|
|
79
|
+
column to specify the genome build of each position ('hg18', 'hg19', or 'hg38') and all
|
|
80
|
+
'hg18' and 'hg19' positions will be converted to 'hg38' so that all positions are plotted
|
|
81
|
+
using one coordinate system. If only 'hg18' or 'hg19' positions are present, no liftover
|
|
82
|
+
be necessary. Hence, liftover is only performed in cases of mixed genome builds.
|
|
83
|
+
|
|
84
|
+
## Nearest-gene annotation for GWAS lead SNPs
|
|
85
|
+
The package bundles GFF3 files in hg19 and hg38 coordinates processed to reduce size
|
|
86
|
+
for gene annotation. Also included are UCSC chain files for coordinate conversion (liftover).
|
|
87
|
+
- ``chain_hg19_hg38`` -- UCSC LiftOver chain file for hg19 to hg38
|
|
88
|
+
conversion. Resolved from ``PYCMPLOT_CHAIN_HG19_HG38`` or the bundled
|
|
89
|
+
``hg19ToHg38.over.chain.gz``.
|
|
90
|
+
- ``chain_hg18_hg38`` -- UCSC LiftOver chain file for hg18 to hg38
|
|
91
|
+
conversion. Resolved from ``PYCMPLOT_CHAIN_HG18_HG38`` or the bundled
|
|
92
|
+
``hg18ToHg38.over.chain.gz``. Only required when any input summary
|
|
93
|
+
statistics file carries a ``hg18`` build label.
|
|
94
|
+
- ``geneinfo_hg38`` -- Ensembl gene-info TSV for GRCh38, used for
|
|
95
|
+
nearest-gene annotation. Resolved from ``PYCMPLOT_GENEINFO_HG38`` or
|
|
96
|
+
the bundled ``Homo_sapiens.GRCh38.geneinfo.tsv.gz``.
|
|
97
|
+
- ``geneinfo_hg19`` -- Ensembl gene-info TSV for GRCh37, used when
|
|
98
|
+
input data carry a hg19 build label. Resolved from
|
|
99
|
+
``PYCMPLOT_GENEINFO_HG19`` or the bundled
|
|
100
|
+
``Homo_sapiens.GRCh37.geneinfo.tsv.gz``.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Application
|
|
59
104
|
A potential useful application is **comparative visualization** of results from multiple imputation panels,
|
|
60
105
|
multiple populations, or multiple traits to observe shared genetic architecture.
|
|
61
106
|
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
bench_python.py
|
|
4
|
+
Benchmarks Python GWAS visualization tools: pycmplot, gwaslab, qmplot.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python bench_python.py --tool pycmplot --input data/sumstats_1M.tsv \
|
|
8
|
+
--size 1M --replicates 5 --outdir results/
|
|
9
|
+
|
|
10
|
+
Writes one CSV row per replicate to results/bench_python.csv.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import csv
|
|
15
|
+
import gc
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
import tracemalloc
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
RESULT_COLS = [
|
|
23
|
+
"tool", "plot_type", "size_label", "n_variants",
|
|
24
|
+
"replicate", "wall_time_s", "peak_mem_mb", "out_file_kb"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _record(writer, row: dict):
|
|
29
|
+
writer.writerow({k: row.get(k, "") for k in RESULT_COLS})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Individual tool wrappers
|
|
34
|
+
# Each wrapper must:
|
|
35
|
+
# 1. Load data from disk (include I/O in timing)
|
|
36
|
+
# 2. Produce a PNG to out_path
|
|
37
|
+
# 3. Return nothing
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def run_pycmplot(input_path: str, out_path: str, plot_type: str = "manhattan"):
|
|
41
|
+
"""
|
|
42
|
+
pycmplot benchmark.
|
|
43
|
+
Adjust import path / function names to match your actual API.
|
|
44
|
+
"""
|
|
45
|
+
import pandas as pd
|
|
46
|
+
import pycmplot
|
|
47
|
+
|
|
48
|
+
df = pd.read_csv(input_path, sep="\t")
|
|
49
|
+
|
|
50
|
+
if plot_type == "manhattan":
|
|
51
|
+
pycmplot.plot(
|
|
52
|
+
df,
|
|
53
|
+
chrom="CHR",
|
|
54
|
+
pos="BP",
|
|
55
|
+
pval="P",
|
|
56
|
+
snp="SNP",
|
|
57
|
+
plot_type="manhattan",
|
|
58
|
+
out=out_path,
|
|
59
|
+
dpi=150,
|
|
60
|
+
)
|
|
61
|
+
elif plot_type == "circular":
|
|
62
|
+
pycmplot.plot(
|
|
63
|
+
df,
|
|
64
|
+
chrom="CHR",
|
|
65
|
+
pos="BP",
|
|
66
|
+
pval="P",
|
|
67
|
+
snp="SNP",
|
|
68
|
+
plot_type="circular",
|
|
69
|
+
out=out_path,
|
|
70
|
+
dpi=150,
|
|
71
|
+
)
|
|
72
|
+
elif plot_type == "qq":
|
|
73
|
+
pycmplot.plot(
|
|
74
|
+
df,
|
|
75
|
+
chrom="CHR",
|
|
76
|
+
pos="BP",
|
|
77
|
+
pval="P",
|
|
78
|
+
plot_type="qq",
|
|
79
|
+
out=out_path,
|
|
80
|
+
dpi=150,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run_gwaslab(input_path: str, out_path: str, plot_type: str = "manhattan"):
|
|
85
|
+
"""
|
|
86
|
+
gwaslab benchmark.
|
|
87
|
+
https://cloufield.github.io/gwaslab/
|
|
88
|
+
"""
|
|
89
|
+
import pandas as pd
|
|
90
|
+
import gwaslab as gl
|
|
91
|
+
import matplotlib
|
|
92
|
+
matplotlib.use("Agg")
|
|
93
|
+
|
|
94
|
+
df = pd.read_csv(input_path, sep="\t")
|
|
95
|
+
|
|
96
|
+
mysumstats = gl.Sumstats(
|
|
97
|
+
df,
|
|
98
|
+
snpid="SNP",
|
|
99
|
+
chrom="CHR",
|
|
100
|
+
pos="BP",
|
|
101
|
+
p="P",
|
|
102
|
+
beta="BETA",
|
|
103
|
+
se="SE",
|
|
104
|
+
ea="A1",
|
|
105
|
+
nea="A2",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if plot_type == "manhattan":
|
|
109
|
+
mysumstats.plot_mqq(
|
|
110
|
+
mode="m",
|
|
111
|
+
save=out_path,
|
|
112
|
+
save_args={"dpi": 150},
|
|
113
|
+
verbose=False,
|
|
114
|
+
)
|
|
115
|
+
elif plot_type == "qq":
|
|
116
|
+
mysumstats.plot_mqq(
|
|
117
|
+
mode="q",
|
|
118
|
+
save=out_path,
|
|
119
|
+
save_args={"dpi": 150},
|
|
120
|
+
verbose=False,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def run_qmplot(input_path: str, out_path: str, plot_type: str = "manhattan"):
|
|
125
|
+
"""
|
|
126
|
+
qmplot benchmark.
|
|
127
|
+
https://github.com/ShujiaHuang/qmplot
|
|
128
|
+
"""
|
|
129
|
+
import pandas as pd
|
|
130
|
+
import matplotlib
|
|
131
|
+
matplotlib.use("Agg")
|
|
132
|
+
import matplotlib.pyplot as plt
|
|
133
|
+
from qmplot import manhattanplot, qqplot
|
|
134
|
+
|
|
135
|
+
df = pd.read_csv(input_path, sep="\t")
|
|
136
|
+
|
|
137
|
+
fig, ax = plt.subplots(figsize=(12, 4))
|
|
138
|
+
|
|
139
|
+
if plot_type in ("manhattan", "circular"):
|
|
140
|
+
manhattanplot(
|
|
141
|
+
data=df,
|
|
142
|
+
chrom="CHR",
|
|
143
|
+
pos="BP",
|
|
144
|
+
pv="P",
|
|
145
|
+
snp="SNP",
|
|
146
|
+
ax=ax,
|
|
147
|
+
)
|
|
148
|
+
elif plot_type == "qq":
|
|
149
|
+
qqplot(
|
|
150
|
+
data=df["P"],
|
|
151
|
+
ax=ax,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
fig.savefig(out_path, dpi=150, bbox_inches="tight")
|
|
155
|
+
plt.close(fig)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
# Timing + memory harness
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
TOOL_RUNNERS = {
|
|
163
|
+
"pycmplot": run_pycmplot,
|
|
164
|
+
"gwaslab": run_gwaslab,
|
|
165
|
+
"qmplot": run_qmplot,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def benchmark_one(tool: str, input_path: str, out_path: str, plot_type: str):
|
|
170
|
+
"""
|
|
171
|
+
Run one timed, memory-tracked benchmark call.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
tuple[float, float]
|
|
176
|
+
(wall_time_seconds, peak_memory_mb)
|
|
177
|
+
"""
|
|
178
|
+
runner = TOOL_RUNNERS[tool]
|
|
179
|
+
|
|
180
|
+
# Force a full GC cycle before each run so prior allocations don't inflate
|
|
181
|
+
gc.collect()
|
|
182
|
+
|
|
183
|
+
tracemalloc.start()
|
|
184
|
+
t0 = time.perf_counter()
|
|
185
|
+
|
|
186
|
+
runner(input_path, out_path, plot_type)
|
|
187
|
+
|
|
188
|
+
t1 = time.perf_counter()
|
|
189
|
+
_, peak_bytes = tracemalloc.get_traced_memory()
|
|
190
|
+
tracemalloc.stop()
|
|
191
|
+
|
|
192
|
+
wall_time = t1 - t0
|
|
193
|
+
peak_mem_mb = peak_bytes / 1024 / 1024
|
|
194
|
+
|
|
195
|
+
return wall_time, peak_mem_mb
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def main():
|
|
199
|
+
parser = argparse.ArgumentParser(description="Benchmark Python GWAS visualization tools")
|
|
200
|
+
parser.add_argument("--tool", required=True, choices=list(TOOL_RUNNERS.keys()))
|
|
201
|
+
parser.add_argument("--input", required=True, help="Path to sumstats TSV")
|
|
202
|
+
parser.add_argument("--size", required=True, help="Dataset size label (e.g. 1M)")
|
|
203
|
+
parser.add_argument("--plot-type", default="manhattan",
|
|
204
|
+
choices=["manhattan", "circular", "qq"],
|
|
205
|
+
help="Plot type to benchmark")
|
|
206
|
+
parser.add_argument("--replicates", type=int, default=5)
|
|
207
|
+
parser.add_argument("--outdir", default="results", help="Directory for CSV results")
|
|
208
|
+
parser.add_argument("--figdir", default="figures", help="Directory for generated figures")
|
|
209
|
+
args = parser.parse_args()
|
|
210
|
+
|
|
211
|
+
os.makedirs(args.outdir, exist_ok=True)
|
|
212
|
+
os.makedirs(args.figdir, exist_ok=True)
|
|
213
|
+
|
|
214
|
+
csv_path = os.path.join(args.outdir, "bench_python.csv")
|
|
215
|
+
write_header = not os.path.exists(csv_path)
|
|
216
|
+
|
|
217
|
+
# Count variants in input
|
|
218
|
+
import pandas as pd
|
|
219
|
+
n_variants = sum(1 for _ in open(args.input)) - 1 # subtract header
|
|
220
|
+
|
|
221
|
+
print(f"\n[bench] tool={args.tool} size={args.size} "
|
|
222
|
+
f"n={n_variants:,} plot={args.plot_type} reps={args.replicates}")
|
|
223
|
+
|
|
224
|
+
with open(csv_path, "a", newline="") as fh:
|
|
225
|
+
writer = csv.DictWriter(fh, fieldnames=RESULT_COLS)
|
|
226
|
+
if write_header:
|
|
227
|
+
writer.writeheader()
|
|
228
|
+
|
|
229
|
+
for rep in range(1, args.replicates + 1):
|
|
230
|
+
out_fig = os.path.join(
|
|
231
|
+
args.figdir,
|
|
232
|
+
f"{args.tool}_{args.plot_type}_{args.size}_rep{rep}.png"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
wall, mem = benchmark_one(args.tool, args.input, out_fig, args.plot_type)
|
|
237
|
+
out_kb = os.path.getsize(out_fig) / 1024 if os.path.exists(out_fig) else 0
|
|
238
|
+
|
|
239
|
+
row = dict(
|
|
240
|
+
tool=args.tool,
|
|
241
|
+
plot_type=args.plot_type,
|
|
242
|
+
size_label=args.size,
|
|
243
|
+
n_variants=n_variants,
|
|
244
|
+
replicate=rep,
|
|
245
|
+
wall_time_s=round(wall, 3),
|
|
246
|
+
peak_mem_mb=round(mem, 2),
|
|
247
|
+
out_file_kb=round(out_kb, 1),
|
|
248
|
+
)
|
|
249
|
+
writer.writerow(row)
|
|
250
|
+
fh.flush() # write incrementally in case of OOM on large runs
|
|
251
|
+
print(f" rep {rep}/{args.replicates} "
|
|
252
|
+
f"time={wall:.2f}s mem={mem:.0f}MB fig={out_kb:.0f}KB")
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
print(f" rep {rep} FAILED: {e}", file=sys.stderr)
|
|
256
|
+
writer.writerow(dict(
|
|
257
|
+
tool=args.tool, plot_type=args.plot_type,
|
|
258
|
+
size_label=args.size, n_variants=n_variants,
|
|
259
|
+
replicate=rep, wall_time_s="ERROR",
|
|
260
|
+
peak_mem_mb="ERROR", out_file_kb="ERROR"
|
|
261
|
+
))
|
|
262
|
+
fh.flush()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
main()
|