pylocuszoom 0.6.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/workflows/ci.yml +1 -8
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/workflows/publish.yml +2 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.gitignore +2 -0
- pylocuszoom-0.8.0/.pre-commit-config.yaml +17 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/CHANGELOG.md +44 -1
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/PKG-INFO +46 -25
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/README.md +43 -22
- pylocuszoom-0.8.0/bioconda/meta.yaml +63 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/docs/USER_GUIDE.md +38 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/eqtl_bokeh.html +5 -5
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/eqtl_overlay.png +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/eqtl_plotly.html +1 -1
- pylocuszoom-0.8.0/examples/finemapping_bokeh.html +61 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/finemapping_plot.png +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/finemapping_plotly.html +1 -1
- pylocuszoom-0.8.0/examples/forest_plot.png +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/generate_readme_plots.py +31 -7
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/getting_started.ipynb +167 -56
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/regional_plot.png +0 -0
- pylocuszoom-0.8.0/examples/stacked_plot.png +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/pyproject.toml +22 -4
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/__init__.py +15 -0
- pylocuszoom-0.8.0/src/pylocuszoom/backends/__init__.py +147 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/base.py +363 -60
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/bokeh_backend.py +77 -15
- pylocuszoom-0.8.0/src/pylocuszoom/backends/hover.py +198 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/matplotlib_backend.py +263 -3
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/plotly_backend.py +73 -16
- pylocuszoom-0.8.0/src/pylocuszoom/ensembl.py +476 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/eqtl.py +15 -19
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/finemapping.py +17 -26
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/forest.py +9 -11
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/gene_track.py +161 -135
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/loaders.py +3 -1
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/phewas.py +10 -11
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/plotter.py +120 -194
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/recombination.py +19 -3
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/utils.py +52 -0
- pylocuszoom-0.8.0/src/pylocuszoom/validation.py +172 -0
- pylocuszoom-0.8.0/tests/test_backends.py +294 -0
- pylocuszoom-0.8.0/tests/test_ensembl.py +466 -0
- pylocuszoom-0.8.0/tests/test_ensembl_integration.py +77 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_finemapping.py +3 -7
- pylocuszoom-0.8.0/tests/test_hover.py +351 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_plotter.py +101 -0
- pylocuszoom-0.8.0/tests/test_utils.py +162 -0
- pylocuszoom-0.8.0/tests/test_validation.py +343 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/uv.lock +1 -1
- pylocuszoom-0.6.0/.pre-commit-config.yaml +0 -7
- pylocuszoom-0.6.0/bioconda/meta.yaml +0 -54
- pylocuszoom-0.6.0/examples/finemapping_bokeh.html +0 -61
- pylocuszoom-0.6.0/examples/forest_plot.png +0 -0
- pylocuszoom-0.6.0/examples/stacked_plot.png +0 -0
- pylocuszoom-0.6.0/src/pylocuszoom/backends/__init__.py +0 -48
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.gitattributes +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/CONTRIBUTING.md +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/LICENSE.md +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/docs/ARCHITECTURE.md +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/phewas_plot.png +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/logo.svg +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/colors.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/labels.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/ld.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/logging.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/py.typed +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/reference_data/__init__.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/schemas.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/conftest.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_colors.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_forest.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_gene_track.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_labels.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_ld.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_loaders.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_logging.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_notebook_backends.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_phewas.py +0 -0
- {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_recombination.py +0 -0
|
@@ -44,14 +44,7 @@ jobs:
|
|
|
44
44
|
run: uv sync --extra dev --extra all
|
|
45
45
|
|
|
46
46
|
- name: Run tests
|
|
47
|
-
run: uv run pytest --cov=pylocuszoom --cov-report=
|
|
48
|
-
|
|
49
|
-
- name: Upload coverage
|
|
50
|
-
uses: codecov/codecov-action@v4
|
|
51
|
-
if: matrix.python-version == '3.11'
|
|
52
|
-
with:
|
|
53
|
-
files: ./coverage.xml
|
|
54
|
-
fail_ci_if_error: false
|
|
47
|
+
run: uv run pytest --cov=pylocuszoom --cov-report=term-missing
|
|
55
48
|
|
|
56
49
|
build:
|
|
57
50
|
runs-on: ubuntu-latest
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.9.1
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: local
|
|
10
|
+
hooks:
|
|
11
|
+
- id: pytest-cov
|
|
12
|
+
name: pytest with coverage
|
|
13
|
+
entry: uv run python -m pytest -q
|
|
14
|
+
language: system
|
|
15
|
+
types: [python]
|
|
16
|
+
pass_filenames: false
|
|
17
|
+
always_run: true
|
|
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.8.0] - 2026-01-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `set_yticks()` backend method for consistent y-axis labels across all backends
|
|
14
|
+
- Shared `convert_latex_to_unicode()` utility for interactive backends
|
|
15
|
+
- Automatic gene annotation fetching from Ensembl REST API (`auto_genes=True`)
|
|
16
|
+
- `get_genes_for_region()` function to fetch genes from Ensembl with disk caching
|
|
17
|
+
- `fetch_genes_from_ensembl()` and `fetch_exons_from_ensembl()` low-level API functions
|
|
18
|
+
- `clear_ensembl_cache()` utility to clear cached Ensembl data
|
|
19
|
+
- Support for human, mouse, rat, and any Ensembl species
|
|
20
|
+
- Retry logic with exponential backoff for Ensembl API resilience
|
|
21
|
+
- 5Mb region size validation (Ensembl API limit)
|
|
22
|
+
- `DataFrameValidator` builder class for consistent validation across modules
|
|
23
|
+
- `filter_by_region()` shared utility for chromosome/position filtering
|
|
24
|
+
- `HoverDataBuilder` for constructing hover tooltips across backends
|
|
25
|
+
- Backend capability system with `supports_*` properties for feature detection
|
|
26
|
+
- Backend registration system with `get_backend()` and automatic fallback
|
|
27
|
+
- Pre-commit hook for pytest with coverage enforcement (70% minimum)
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Forest plot example now uses odds ratios with `null_value=1.0` (more representative)
|
|
31
|
+
- PheWAS and forest plot y-axis labels now work correctly in Plotly and Bokeh backends
|
|
32
|
+
- Gene track styling: arrows now 75% height and 10% wider for better proportions
|
|
33
|
+
- Gene track labels increased from 5.5pt to 7pt for improved readability
|
|
34
|
+
- Migrated eQTL, finemapping, phewas, and forest validation to `DataFrameValidator`
|
|
35
|
+
- Plotter now uses capability-based dispatch instead of backend name checks
|
|
36
|
+
- Removed empty `__init__` methods from backend classes
|
|
37
|
+
- Removed unused matplotlib imports from plotter (now backend-agnostic)
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- `load_gwas()` now forwards `**kwargs` to format-specific loaders
|
|
41
|
+
- Forest plot validator now checks that effect and CI columns are numeric
|
|
42
|
+
- PheWAS validator now checks that p-values are numeric and within (0, 1] range
|
|
43
|
+
|
|
44
|
+
### Security
|
|
45
|
+
- Tar extraction now includes path traversal protection for recombination map downloads
|
|
46
|
+
|
|
47
|
+
## [0.7.0] - 2026-01-27
|
|
48
|
+
|
|
8
49
|
## [0.6.0] - 2026-01-27
|
|
9
50
|
|
|
10
51
|
### Added
|
|
@@ -140,7 +181,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
140
181
|
- bokeh >= 3.8.2
|
|
141
182
|
- kaleido >= 0.2.0
|
|
142
183
|
|
|
143
|
-
[Unreleased]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.
|
|
184
|
+
[Unreleased]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.8.0...HEAD
|
|
185
|
+
[0.8.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.7.0...v0.8.0
|
|
186
|
+
[0.7.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.6.0...v0.7.0
|
|
144
187
|
[0.6.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.5.0...v0.6.0
|
|
145
188
|
[0.5.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.4.0...v0.5.0
|
|
146
189
|
[0.4.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.3.0...v0.4.0
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pylocuszoom
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Publication-ready regional association plots with LD coloring, gene tracks, and recombination overlays
|
|
5
5
|
Project-URL: Homepage, https://github.com/michael-denyer/pylocuszoom
|
|
6
6
|
Project-URL: Documentation, https://github.com/michael-denyer/pylocuszoom#readme
|
|
7
7
|
Project-URL: Repository, https://github.com/michael-denyer/pylocuszoom
|
|
8
|
-
Author: Michael Denyer
|
|
8
|
+
Author-email: Michael Denyer <code.denyer@gmail.com>
|
|
9
9
|
License-Expression: GPL-3.0-or-later
|
|
10
10
|
License-File: LICENSE.md
|
|
11
11
|
Keywords: genetics,gwas,locus-zoom,locuszoom,regional-plot,visualization
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -44,20 +44,18 @@ Requires-Dist: pyspark>=3.0.0; extra == 'spark'
|
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
|
|
46
46
|
[](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml)
|
|
47
|
-
[](https://codecov.io/gh/michael-denyer/pyLocusZoom)
|
|
48
47
|
[](https://pypi.org/project/pylocuszoom/)
|
|
49
|
-
[](https://anaconda.org/bioconda/pylocuszoom)
|
|
50
48
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
51
49
|
[](https://www.python.org/downloads/)
|
|
52
50
|
[](https://github.com/astral-sh/ruff)
|
|
53
51
|
[](https://matplotlib.org/)
|
|
54
|
-
[](https://plotly.com/python/)
|
|
55
53
|
[](https://bokeh.org/)
|
|
56
54
|
[](https://pandas.pydata.org/)
|
|
57
55
|
<img src="logo.svg" alt="pyLocusZoom logo" width="120" align="right">
|
|
58
56
|
# pyLocusZoom
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
Designed for publication-ready GWAS visualization with regional association plots, gene tracks, eQTL, PheWAS, fine-mapping, and forest plots.
|
|
61
59
|
|
|
62
60
|
Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.com/myles-lewis/locuszoomr).
|
|
63
61
|
|
|
@@ -68,20 +66,22 @@ Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.c
|
|
|
68
66
|
- **Multi-species support**: Built-in reference data for *Canis lupus familiaris* (CanFam3.1/CanFam4) and *Felis catus* (FelCat9), or optionally provide your own for any species
|
|
69
67
|
- **LD coloring**: SNPs colored by linkage disequilibrium (R²) with lead variant
|
|
70
68
|
- **Gene tracks**: Annotated gene/exon positions below the association plot
|
|
71
|
-
- **Recombination rate**:
|
|
72
|
-
- **SNP labels (matplotlib)**: Automatic labeling of
|
|
73
|
-
- **
|
|
69
|
+
- **Recombination rate**: Optional overlay across region (*Canis lupus familiaris* built-in, not shown in example image)
|
|
70
|
+
- **SNP labels (matplotlib)**: Automatic labeling of top SNPs by p-value (RS IDs)
|
|
71
|
+
- **Hover tooltips (Plotly and Bokeh)**: Detailed SNP data on hover
|
|
74
72
|
|
|
75
|
-

|
|
73
|
+

|
|
74
|
+
*Regional association plot with LD coloring, gene/exon track, and top SNP labels (recombination overlay disabled in example).*
|
|
76
75
|
|
|
77
76
|
2. **Stacked plots**: Compare multiple GWAS/phenotypes vertically
|
|
78
77
|
3. **eQTL plot**: Expression QTL data aligned with association plots and gene tracks
|
|
79
78
|
4. **Fine-mapping plots**: Visualize SuSiE credible sets with posterior inclusion probabilities
|
|
80
79
|
5. **PheWAS plots**: Phenome-wide association study visualization across multiple phenotypes
|
|
81
80
|
6. **Forest plots**: Meta-analysis effect size visualization with confidence intervals
|
|
82
|
-
7. **Multiple
|
|
81
|
+
7. **Multiple backends**: matplotlib (publication-ready), plotly (interactive), bokeh (dashboard integration)
|
|
83
82
|
8. **Pandas and PySpark support**: Works with both Pandas and PySpark DataFrames for large-scale genomics data
|
|
84
83
|
9. **Convenience data file loaders**: Load and validate common GWAS, eQTL and fine-mapping file formats
|
|
84
|
+
10. **Automatic gene annotations**: Fetch gene/exon data from Ensembl REST API with caching (human, mouse, rat, canine, feline, and any Ensembl species)
|
|
85
85
|
|
|
86
86
|
## Installation
|
|
87
87
|
|
|
@@ -181,28 +181,46 @@ fig = plotter.plot(
|
|
|
181
181
|
)
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
## Automatic Gene Annotations
|
|
185
|
+
|
|
186
|
+
pyLocusZoom can automatically fetch gene annotations from Ensembl for any species:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# Enable automatic gene fetching
|
|
190
|
+
plotter = LocusZoomPlotter(species="human", auto_genes=True)
|
|
191
|
+
|
|
192
|
+
# No need to provide genes_df - fetched automatically
|
|
193
|
+
fig = plotter.plot(gwas_df, chrom=13, start=32000000, end=33000000)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Supported species aliases: `human`, `mouse`, `rat`, `canine`/`dog`, `feline`/`cat`, or any Ensembl species name.
|
|
197
|
+
Data is cached locally for fast subsequent plots. Maximum region size is 5Mb (Ensembl API limit).
|
|
198
|
+
|
|
184
199
|
## Backends
|
|
185
200
|
|
|
186
|
-
pyLocusZoom supports multiple rendering backends:
|
|
201
|
+
pyLocusZoom supports multiple rendering backends (set at initialization):
|
|
187
202
|
|
|
188
203
|
```python
|
|
189
204
|
# Static publication-quality plot (default)
|
|
190
|
-
|
|
205
|
+
plotter = LocusZoomPlotter(species="canine", backend="matplotlib")
|
|
206
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
191
207
|
fig.savefig("plot.png", dpi=150)
|
|
192
208
|
|
|
193
209
|
# Interactive Plotly (hover tooltips, pan/zoom)
|
|
194
|
-
|
|
210
|
+
plotter = LocusZoomPlotter(species="canine", backend="plotly")
|
|
211
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
195
212
|
fig.write_html("plot.html")
|
|
196
213
|
|
|
197
214
|
# Interactive Bokeh (dashboard-ready)
|
|
198
|
-
|
|
215
|
+
plotter = LocusZoomPlotter(species="canine", backend="bokeh")
|
|
216
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
199
217
|
```
|
|
200
218
|
|
|
201
219
|
| Backend | Output | Best For | Features |
|
|
202
220
|
|---------|--------|----------|----------|
|
|
203
|
-
| `matplotlib` | Static PNG/PDF/SVG |
|
|
204
|
-
| `plotly` | Interactive HTML | Web reports,
|
|
205
|
-
| `bokeh` | Interactive HTML |
|
|
221
|
+
| `matplotlib` | Static PNG/PDF/SVG | Publication-ready figures | Full feature set with SNP labels |
|
|
222
|
+
| `plotly` | Interactive HTML | Web reports, exploration | Hover tooltips, pan/zoom |
|
|
223
|
+
| `bokeh` | Interactive HTML | Dashboard integration | Hover tooltips, pan/zoom |
|
|
206
224
|
|
|
207
225
|
> **Note:** All backends support scatter plots, gene tracks, recombination overlay, and LD legend. SNP labels (auto-positioned with adjustText) are matplotlib-only; interactive backends use hover tooltips instead.
|
|
208
226
|
|
|
@@ -221,7 +239,8 @@ fig = plotter.plot_stacked(
|
|
|
221
239
|
)
|
|
222
240
|
```
|
|
223
241
|
|
|
224
|
-

|
|
242
|
+

|
|
243
|
+
*Stacked plot comparing two phenotypes with LD coloring and shared gene track.*
|
|
225
244
|
|
|
226
245
|
## eQTL Overlay
|
|
227
246
|
|
|
@@ -244,6 +263,7 @@ fig = plotter.plot_stacked(
|
|
|
244
263
|
```
|
|
245
264
|
|
|
246
265
|

|
|
266
|
+
*eQTL overlay with effect direction (up/down triangles) and magnitude binning.*
|
|
247
267
|
|
|
248
268
|
## Fine-mapping Visualization
|
|
249
269
|
|
|
@@ -266,6 +286,7 @@ fig = plotter.plot_stacked(
|
|
|
266
286
|
```
|
|
267
287
|
|
|
268
288
|

|
|
289
|
+
*Fine-mapping visualization with PIP line and credible set coloring (CS1/CS2).*
|
|
269
290
|
|
|
270
291
|
## PheWAS Plots
|
|
271
292
|
|
|
@@ -286,6 +307,7 @@ fig = plotter.plot_phewas(
|
|
|
286
307
|
```
|
|
287
308
|
|
|
288
309
|

|
|
310
|
+
*PheWAS plot showing associations across phenotype categories with significance threshold.*
|
|
289
311
|
|
|
290
312
|
## Forest Plots
|
|
291
313
|
|
|
@@ -308,19 +330,18 @@ fig = plotter.plot_forest(
|
|
|
308
330
|
```
|
|
309
331
|
|
|
310
332
|

|
|
333
|
+
*Forest plot with effect sizes, confidence intervals, and weight-proportional markers.*
|
|
311
334
|
|
|
312
335
|
## PySpark Support
|
|
313
336
|
|
|
314
|
-
For large-scale genomics data,
|
|
337
|
+
For large-scale genomics data, convert PySpark DataFrames with `to_pandas()` before plotting:
|
|
315
338
|
|
|
316
339
|
```python
|
|
317
340
|
from pylocuszoom import LocusZoomPlotter, to_pandas
|
|
318
341
|
|
|
319
|
-
# PySpark DataFrame (
|
|
320
|
-
fig = plotter.plot(spark_gwas_df, chrom=1, start=1000000, end=2000000)
|
|
321
|
-
|
|
322
|
-
# Or convert manually with sampling for very large data
|
|
342
|
+
# Convert PySpark DataFrame (optionally sampled for very large data)
|
|
323
343
|
pandas_df = to_pandas(spark_gwas_df, sample_size=100000)
|
|
344
|
+
fig = plotter.plot(pandas_df, chrom=1, start=1000000, end=2000000)
|
|
324
345
|
```
|
|
325
346
|
|
|
326
347
|
Install PySpark support: `uv add pylocuszoom[spark]`
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
[](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml)
|
|
2
|
-
[](https://codecov.io/gh/michael-denyer/pyLocusZoom)
|
|
3
2
|
[](https://pypi.org/project/pylocuszoom/)
|
|
4
|
-
[](https://anaconda.org/bioconda/pylocuszoom)
|
|
5
3
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
6
4
|
[](https://www.python.org/downloads/)
|
|
7
5
|
[](https://github.com/astral-sh/ruff)
|
|
8
6
|
[](https://matplotlib.org/)
|
|
9
|
-
[](https://plotly.com/python/)
|
|
10
8
|
[](https://bokeh.org/)
|
|
11
9
|
[](https://pandas.pydata.org/)
|
|
12
10
|
<img src="logo.svg" alt="pyLocusZoom logo" width="120" align="right">
|
|
13
11
|
# pyLocusZoom
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Designed for publication-ready GWAS visualization with regional association plots, gene tracks, eQTL, PheWAS, fine-mapping, and forest plots.
|
|
16
14
|
|
|
17
15
|
Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.com/myles-lewis/locuszoomr).
|
|
18
16
|
|
|
@@ -23,20 +21,22 @@ Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.c
|
|
|
23
21
|
- **Multi-species support**: Built-in reference data for *Canis lupus familiaris* (CanFam3.1/CanFam4) and *Felis catus* (FelCat9), or optionally provide your own for any species
|
|
24
22
|
- **LD coloring**: SNPs colored by linkage disequilibrium (R²) with lead variant
|
|
25
23
|
- **Gene tracks**: Annotated gene/exon positions below the association plot
|
|
26
|
-
- **Recombination rate**:
|
|
27
|
-
- **SNP labels (matplotlib)**: Automatic labeling of
|
|
28
|
-
- **
|
|
24
|
+
- **Recombination rate**: Optional overlay across region (*Canis lupus familiaris* built-in, not shown in example image)
|
|
25
|
+
- **SNP labels (matplotlib)**: Automatic labeling of top SNPs by p-value (RS IDs)
|
|
26
|
+
- **Hover tooltips (Plotly and Bokeh)**: Detailed SNP data on hover
|
|
29
27
|
|
|
30
|
-

|
|
28
|
+

|
|
29
|
+
*Regional association plot with LD coloring, gene/exon track, and top SNP labels (recombination overlay disabled in example).*
|
|
31
30
|
|
|
32
31
|
2. **Stacked plots**: Compare multiple GWAS/phenotypes vertically
|
|
33
32
|
3. **eQTL plot**: Expression QTL data aligned with association plots and gene tracks
|
|
34
33
|
4. **Fine-mapping plots**: Visualize SuSiE credible sets with posterior inclusion probabilities
|
|
35
34
|
5. **PheWAS plots**: Phenome-wide association study visualization across multiple phenotypes
|
|
36
35
|
6. **Forest plots**: Meta-analysis effect size visualization with confidence intervals
|
|
37
|
-
7. **Multiple
|
|
36
|
+
7. **Multiple backends**: matplotlib (publication-ready), plotly (interactive), bokeh (dashboard integration)
|
|
38
37
|
8. **Pandas and PySpark support**: Works with both Pandas and PySpark DataFrames for large-scale genomics data
|
|
39
38
|
9. **Convenience data file loaders**: Load and validate common GWAS, eQTL and fine-mapping file formats
|
|
39
|
+
10. **Automatic gene annotations**: Fetch gene/exon data from Ensembl REST API with caching (human, mouse, rat, canine, feline, and any Ensembl species)
|
|
40
40
|
|
|
41
41
|
## Installation
|
|
42
42
|
|
|
@@ -136,28 +136,46 @@ fig = plotter.plot(
|
|
|
136
136
|
)
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
## Automatic Gene Annotations
|
|
140
|
+
|
|
141
|
+
pyLocusZoom can automatically fetch gene annotations from Ensembl for any species:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# Enable automatic gene fetching
|
|
145
|
+
plotter = LocusZoomPlotter(species="human", auto_genes=True)
|
|
146
|
+
|
|
147
|
+
# No need to provide genes_df - fetched automatically
|
|
148
|
+
fig = plotter.plot(gwas_df, chrom=13, start=32000000, end=33000000)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Supported species aliases: `human`, `mouse`, `rat`, `canine`/`dog`, `feline`/`cat`, or any Ensembl species name.
|
|
152
|
+
Data is cached locally for fast subsequent plots. Maximum region size is 5Mb (Ensembl API limit).
|
|
153
|
+
|
|
139
154
|
## Backends
|
|
140
155
|
|
|
141
|
-
pyLocusZoom supports multiple rendering backends:
|
|
156
|
+
pyLocusZoom supports multiple rendering backends (set at initialization):
|
|
142
157
|
|
|
143
158
|
```python
|
|
144
159
|
# Static publication-quality plot (default)
|
|
145
|
-
|
|
160
|
+
plotter = LocusZoomPlotter(species="canine", backend="matplotlib")
|
|
161
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
146
162
|
fig.savefig("plot.png", dpi=150)
|
|
147
163
|
|
|
148
164
|
# Interactive Plotly (hover tooltips, pan/zoom)
|
|
149
|
-
|
|
165
|
+
plotter = LocusZoomPlotter(species="canine", backend="plotly")
|
|
166
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
150
167
|
fig.write_html("plot.html")
|
|
151
168
|
|
|
152
169
|
# Interactive Bokeh (dashboard-ready)
|
|
153
|
-
|
|
170
|
+
plotter = LocusZoomPlotter(species="canine", backend="bokeh")
|
|
171
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
154
172
|
```
|
|
155
173
|
|
|
156
174
|
| Backend | Output | Best For | Features |
|
|
157
175
|
|---------|--------|----------|----------|
|
|
158
|
-
| `matplotlib` | Static PNG/PDF/SVG |
|
|
159
|
-
| `plotly` | Interactive HTML | Web reports,
|
|
160
|
-
| `bokeh` | Interactive HTML |
|
|
176
|
+
| `matplotlib` | Static PNG/PDF/SVG | Publication-ready figures | Full feature set with SNP labels |
|
|
177
|
+
| `plotly` | Interactive HTML | Web reports, exploration | Hover tooltips, pan/zoom |
|
|
178
|
+
| `bokeh` | Interactive HTML | Dashboard integration | Hover tooltips, pan/zoom |
|
|
161
179
|
|
|
162
180
|
> **Note:** All backends support scatter plots, gene tracks, recombination overlay, and LD legend. SNP labels (auto-positioned with adjustText) are matplotlib-only; interactive backends use hover tooltips instead.
|
|
163
181
|
|
|
@@ -176,7 +194,8 @@ fig = plotter.plot_stacked(
|
|
|
176
194
|
)
|
|
177
195
|
```
|
|
178
196
|
|
|
179
|
-

|
|
197
|
+

|
|
198
|
+
*Stacked plot comparing two phenotypes with LD coloring and shared gene track.*
|
|
180
199
|
|
|
181
200
|
## eQTL Overlay
|
|
182
201
|
|
|
@@ -199,6 +218,7 @@ fig = plotter.plot_stacked(
|
|
|
199
218
|
```
|
|
200
219
|
|
|
201
220
|

|
|
221
|
+
*eQTL overlay with effect direction (up/down triangles) and magnitude binning.*
|
|
202
222
|
|
|
203
223
|
## Fine-mapping Visualization
|
|
204
224
|
|
|
@@ -221,6 +241,7 @@ fig = plotter.plot_stacked(
|
|
|
221
241
|
```
|
|
222
242
|
|
|
223
243
|

|
|
244
|
+
*Fine-mapping visualization with PIP line and credible set coloring (CS1/CS2).*
|
|
224
245
|
|
|
225
246
|
## PheWAS Plots
|
|
226
247
|
|
|
@@ -241,6 +262,7 @@ fig = plotter.plot_phewas(
|
|
|
241
262
|
```
|
|
242
263
|
|
|
243
264
|

|
|
265
|
+
*PheWAS plot showing associations across phenotype categories with significance threshold.*
|
|
244
266
|
|
|
245
267
|
## Forest Plots
|
|
246
268
|
|
|
@@ -263,19 +285,18 @@ fig = plotter.plot_forest(
|
|
|
263
285
|
```
|
|
264
286
|
|
|
265
287
|

|
|
288
|
+
*Forest plot with effect sizes, confidence intervals, and weight-proportional markers.*
|
|
266
289
|
|
|
267
290
|
## PySpark Support
|
|
268
291
|
|
|
269
|
-
For large-scale genomics data,
|
|
292
|
+
For large-scale genomics data, convert PySpark DataFrames with `to_pandas()` before plotting:
|
|
270
293
|
|
|
271
294
|
```python
|
|
272
295
|
from pylocuszoom import LocusZoomPlotter, to_pandas
|
|
273
296
|
|
|
274
|
-
# PySpark DataFrame (
|
|
275
|
-
fig = plotter.plot(spark_gwas_df, chrom=1, start=1000000, end=2000000)
|
|
276
|
-
|
|
277
|
-
# Or convert manually with sampling for very large data
|
|
297
|
+
# Convert PySpark DataFrame (optionally sampled for very large data)
|
|
278
298
|
pandas_df = to_pandas(spark_gwas_df, sample_size=100000)
|
|
299
|
+
fig = plotter.plot(pandas_df, chrom=1, start=1000000, end=2000000)
|
|
279
300
|
```
|
|
280
301
|
|
|
281
302
|
Install PySpark support: `uv add pylocuszoom[spark]`
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{% set name = "pylocuszoom" %}
|
|
2
|
+
{% set version = "0.6.0" %}
|
|
3
|
+
|
|
4
|
+
package:
|
|
5
|
+
name: {{ name|lower }}
|
|
6
|
+
version: {{ version }}
|
|
7
|
+
|
|
8
|
+
source:
|
|
9
|
+
url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz
|
|
10
|
+
sha256: 1597c2080e2e32019293aab67836d1b944c1cc0b2fe3e7cd94e82d9119175742
|
|
11
|
+
|
|
12
|
+
build:
|
|
13
|
+
noarch: python
|
|
14
|
+
number: 0
|
|
15
|
+
script: {{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation
|
|
16
|
+
run_exports:
|
|
17
|
+
- {{ pin_subpackage('pylocuszoom', max_pin="x") }}
|
|
18
|
+
|
|
19
|
+
requirements:
|
|
20
|
+
host:
|
|
21
|
+
- python >=3.10
|
|
22
|
+
- pip
|
|
23
|
+
- hatchling
|
|
24
|
+
run:
|
|
25
|
+
- python >=3.10
|
|
26
|
+
- matplotlib-base >=3.5.0
|
|
27
|
+
- pandas >=1.4.0
|
|
28
|
+
- numpy >=1.21.0
|
|
29
|
+
- loguru >=0.7.0
|
|
30
|
+
- pyliftover >=0.4
|
|
31
|
+
- plotly >=5.15.0
|
|
32
|
+
- bokeh >=3.8.2
|
|
33
|
+
- python-kaleido >=0.2.0
|
|
34
|
+
- adjusttext >=0.8
|
|
35
|
+
- pydantic >=2.0.0
|
|
36
|
+
- requests >=2.25.0
|
|
37
|
+
- tqdm >=4.60.0
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
imports:
|
|
41
|
+
- pylocuszoom
|
|
42
|
+
commands:
|
|
43
|
+
- python -c "from pylocuszoom import LocusZoomPlotter"
|
|
44
|
+
|
|
45
|
+
about:
|
|
46
|
+
home: https://github.com/michael-denyer/pylocuszoom
|
|
47
|
+
license: GPL-3.0-or-later
|
|
48
|
+
license_family: GPL3
|
|
49
|
+
license_file: LICENSE
|
|
50
|
+
summary: Publication-ready GWAS visualization library with regional association plots, gene tracks, eQTL, PheWAS, fine-mapping, and forest plots
|
|
51
|
+
description: |
|
|
52
|
+
pyLocusZoom creates publication-quality genetic association visualizations:
|
|
53
|
+
regional association plots with LD coloring and recombination overlays,
|
|
54
|
+
stacked multi-GWAS comparisons, eQTL overlays, fine-mapping/SuSiE credible sets,
|
|
55
|
+
PheWAS (phenome-wide association) plots, and forest plots for meta-analysis.
|
|
56
|
+
Includes gene track visualization, file loaders for common formats (REGENIE, BOLT-LMM,
|
|
57
|
+
SAIGE, GEMMA, GTEx, SuSiE, FINEMAP), and three backends: matplotlib (static/publication),
|
|
58
|
+
plotly (interactive), and bokeh (dashboards).
|
|
59
|
+
dev_url: https://github.com/michael-denyer/pylocuszoom
|
|
60
|
+
|
|
61
|
+
extra:
|
|
62
|
+
recipe-maintainers:
|
|
63
|
+
- michael-denyer
|
|
@@ -773,6 +773,44 @@ plotter = LocusZoomPlotter(
|
|
|
773
773
|
|
|
774
774
|
Recombination maps must be named `chr{N}_recomb.tsv`.
|
|
775
775
|
|
|
776
|
+
### Automatic Gene Annotations from Ensembl
|
|
777
|
+
|
|
778
|
+
Instead of providing your own `genes_df`, enable automatic fetching from Ensembl:
|
|
779
|
+
|
|
780
|
+
```python
|
|
781
|
+
plotter = LocusZoomPlotter(species="human", auto_genes=True)
|
|
782
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
783
|
+
# Gene track populated automatically from Ensembl
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Supported Species:**
|
|
787
|
+
| Alias | Ensembl Name |
|
|
788
|
+
|-------|--------------|
|
|
789
|
+
| human | homo_sapiens |
|
|
790
|
+
| mouse | mus_musculus |
|
|
791
|
+
| rat | rattus_norvegicus |
|
|
792
|
+
| canine, dog | canis_lupus_familiaris |
|
|
793
|
+
| feline, cat | felis_catus |
|
|
794
|
+
|
|
795
|
+
Any valid Ensembl species name also works (e.g., `sus_scrofa` for pig).
|
|
796
|
+
|
|
797
|
+
**Region Limit:** Maximum 5Mb per request (Ensembl API limitation). For larger regions, provide `genes_df` directly.
|
|
798
|
+
|
|
799
|
+
**Error Handling:** By default, API errors result in warnings and an empty gene track. Use `raise_on_error=True` in low-level functions to get exceptions instead.
|
|
800
|
+
|
|
801
|
+
**Cache Location:**
|
|
802
|
+
- Linux/macOS: `~/.cache/snp-scope-plot/ensembl/{species}/`
|
|
803
|
+
- Windows: `%LOCALAPPDATA%/snp-scope-plot/ensembl/{species}/`
|
|
804
|
+
|
|
805
|
+
```python
|
|
806
|
+
# Clear cache when needed
|
|
807
|
+
from pylocuszoom import clear_ensembl_cache
|
|
808
|
+
clear_ensembl_cache() # Clear all
|
|
809
|
+
clear_ensembl_cache(species="human") # Clear specific species
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
**Note:** Recombination rates are NOT available from Ensembl for most species. Continue to provide recombination maps separately.
|
|
813
|
+
|
|
776
814
|
---
|
|
777
815
|
|
|
778
816
|
## Recipes & Examples
|