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.
Files changed (81) hide show
  1. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/workflows/ci.yml +1 -8
  2. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/workflows/publish.yml +2 -0
  3. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.gitignore +2 -0
  4. pylocuszoom-0.8.0/.pre-commit-config.yaml +17 -0
  5. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/CHANGELOG.md +44 -1
  6. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/PKG-INFO +46 -25
  7. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/README.md +43 -22
  8. pylocuszoom-0.8.0/bioconda/meta.yaml +63 -0
  9. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/docs/USER_GUIDE.md +38 -0
  10. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/eqtl_bokeh.html +5 -5
  11. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/eqtl_overlay.png +0 -0
  12. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/eqtl_plotly.html +1 -1
  13. pylocuszoom-0.8.0/examples/finemapping_bokeh.html +61 -0
  14. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/finemapping_plot.png +0 -0
  15. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/finemapping_plotly.html +1 -1
  16. pylocuszoom-0.8.0/examples/forest_plot.png +0 -0
  17. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/generate_readme_plots.py +31 -7
  18. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/getting_started.ipynb +167 -56
  19. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/regional_plot.png +0 -0
  20. pylocuszoom-0.8.0/examples/stacked_plot.png +0 -0
  21. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/pyproject.toml +22 -4
  22. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/__init__.py +15 -0
  23. pylocuszoom-0.8.0/src/pylocuszoom/backends/__init__.py +147 -0
  24. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/base.py +363 -60
  25. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/bokeh_backend.py +77 -15
  26. pylocuszoom-0.8.0/src/pylocuszoom/backends/hover.py +198 -0
  27. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/matplotlib_backend.py +263 -3
  28. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/plotly_backend.py +73 -16
  29. pylocuszoom-0.8.0/src/pylocuszoom/ensembl.py +476 -0
  30. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/eqtl.py +15 -19
  31. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/finemapping.py +17 -26
  32. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/forest.py +9 -11
  33. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/gene_track.py +161 -135
  34. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/loaders.py +3 -1
  35. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/phewas.py +10 -11
  36. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/plotter.py +120 -194
  37. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/recombination.py +19 -3
  38. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/utils.py +52 -0
  39. pylocuszoom-0.8.0/src/pylocuszoom/validation.py +172 -0
  40. pylocuszoom-0.8.0/tests/test_backends.py +294 -0
  41. pylocuszoom-0.8.0/tests/test_ensembl.py +466 -0
  42. pylocuszoom-0.8.0/tests/test_ensembl_integration.py +77 -0
  43. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_finemapping.py +3 -7
  44. pylocuszoom-0.8.0/tests/test_hover.py +351 -0
  45. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_plotter.py +101 -0
  46. pylocuszoom-0.8.0/tests/test_utils.py +162 -0
  47. pylocuszoom-0.8.0/tests/test_validation.py +343 -0
  48. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/uv.lock +1 -1
  49. pylocuszoom-0.6.0/.pre-commit-config.yaml +0 -7
  50. pylocuszoom-0.6.0/bioconda/meta.yaml +0 -54
  51. pylocuszoom-0.6.0/examples/finemapping_bokeh.html +0 -61
  52. pylocuszoom-0.6.0/examples/forest_plot.png +0 -0
  53. pylocuszoom-0.6.0/examples/stacked_plot.png +0 -0
  54. pylocuszoom-0.6.0/src/pylocuszoom/backends/__init__.py +0 -48
  55. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.gitattributes +0 -0
  56. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  57. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  58. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  59. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/CONTRIBUTING.md +0 -0
  60. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/LICENSE.md +0 -0
  61. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/docs/ARCHITECTURE.md +0 -0
  62. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/examples/phewas_plot.png +0 -0
  63. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/logo.svg +0 -0
  64. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/colors.py +0 -0
  65. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/labels.py +0 -0
  66. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/ld.py +0 -0
  67. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/logging.py +0 -0
  68. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/py.typed +0 -0
  69. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/reference_data/__init__.py +0 -0
  70. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/src/pylocuszoom/schemas.py +0 -0
  71. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/conftest.py +0 -0
  72. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_colors.py +0 -0
  73. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_forest.py +0 -0
  74. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_gene_track.py +0 -0
  75. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_labels.py +0 -0
  76. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_ld.py +0 -0
  77. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_loaders.py +0 -0
  78. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_logging.py +0 -0
  79. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_notebook_backends.py +0 -0
  80. {pylocuszoom-0.6.0 → pylocuszoom-0.8.0}/tests/test_phewas.py +0 -0
  81. {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=xml
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
@@ -60,6 +60,8 @@ jobs:
60
60
  runs-on: ubuntu-latest
61
61
  steps:
62
62
  - uses: actions/checkout@v4
63
+ with:
64
+ ref: main
63
65
 
64
66
  - name: Update bioconda/meta.yaml
65
67
  env:
@@ -26,3 +26,5 @@ htmlcov/
26
26
 
27
27
  # Project instructions (private)
28
28
  CLAUDE.md
29
+ docs/plans/
30
+ .planning/
@@ -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.6.0...HEAD
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.6.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 :: 3 - Alpha
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
  [![CI](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml/badge.svg)](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml)
47
- [![codecov](https://codecov.io/gh/michael-denyer/pyLocusZoom/graph/badge.svg)](https://codecov.io/gh/michael-denyer/pyLocusZoom)
48
47
  [![PyPI](https://img.shields.io/pypi/v/pylocuszoom)](https://pypi.org/project/pylocuszoom/)
49
- [![Bioconda](https://img.shields.io/conda/vn/bioconda/pylocuszoom)](https://anaconda.org/bioconda/pylocuszoom)
50
48
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg)](https://www.gnu.org/licenses/gpl-3.0)
51
49
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
52
50
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
53
51
  [![Matplotlib](https://img.shields.io/badge/Matplotlib-3.5+-11557c.svg)](https://matplotlib.org/)
54
- [![Plotly](https://img.shields.io/badge/Plotly-5.0+-3F4F75.svg)](https://plotly.com/python/)
52
+ [![Plotly](https://img.shields.io/badge/Plotly-5.15+-3F4F75.svg)](https://plotly.com/python/)
55
53
  [![Bokeh](https://img.shields.io/badge/Bokeh-3.8+-E6526F.svg)](https://bokeh.org/)
56
54
  [![Pandas](https://img.shields.io/badge/Pandas-1.4+-150458.svg)](https://pandas.pydata.org/)
57
55
  <img src="logo.svg" alt="pyLocusZoom logo" width="120" align="right">
58
56
  # pyLocusZoom
59
57
 
60
- Publication-ready regional association plots with LD coloring, gene tracks, and recombination overlays.
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**: Overlay showing recombination rate across region (*Canis lupus familiaris* only)
72
- - **SNP labels (matplotlib)**: Automatic labeling of lead SNPs with RS ID
73
- - **Tooltips (Bokeh and Plotly)**: Mouseover for detailed SNP data
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
- ![Example regional association plot](examples/regional_plot.png)
73
+ ![Example regional association plot with LD coloring and gene track](examples/regional_plot.png)
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 charting libraries**: matplotlib (static), plotly (interactive), bokeh (dashboards)
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
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="matplotlib")
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
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="plotly")
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
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="bokeh")
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 | Publications, presentations | Full feature set with SNP labels |
204
- | `plotly` | Interactive HTML | Web reports, data exploration | Hover tooltips, pan/zoom |
205
- | `bokeh` | Interactive HTML | Dashboards, web apps | Hover tooltips, pan/zoom |
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
- ![Example stacked plot](examples/stacked_plot.png)
242
+ ![Example stacked plot comparing two phenotypes](examples/stacked_plot.png)
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
  ![Example eQTL overlay plot](examples/eqtl_overlay.png)
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
  ![Example fine-mapping plot](examples/finemapping_plot.png)
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
  ![Example PheWAS plot](examples/phewas_plot.png)
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
  ![Example forest plot](examples/forest_plot.png)
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, pass PySpark DataFrames directly:
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 (automatically converted)
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
  [![CI](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml/badge.svg)](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml)
2
- [![codecov](https://codecov.io/gh/michael-denyer/pyLocusZoom/graph/badge.svg)](https://codecov.io/gh/michael-denyer/pyLocusZoom)
3
2
  [![PyPI](https://img.shields.io/pypi/v/pylocuszoom)](https://pypi.org/project/pylocuszoom/)
4
- [![Bioconda](https://img.shields.io/conda/vn/bioconda/pylocuszoom)](https://anaconda.org/bioconda/pylocuszoom)
5
3
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg)](https://www.gnu.org/licenses/gpl-3.0)
6
4
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
7
5
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
8
6
  [![Matplotlib](https://img.shields.io/badge/Matplotlib-3.5+-11557c.svg)](https://matplotlib.org/)
9
- [![Plotly](https://img.shields.io/badge/Plotly-5.0+-3F4F75.svg)](https://plotly.com/python/)
7
+ [![Plotly](https://img.shields.io/badge/Plotly-5.15+-3F4F75.svg)](https://plotly.com/python/)
10
8
  [![Bokeh](https://img.shields.io/badge/Bokeh-3.8+-E6526F.svg)](https://bokeh.org/)
11
9
  [![Pandas](https://img.shields.io/badge/Pandas-1.4+-150458.svg)](https://pandas.pydata.org/)
12
10
  <img src="logo.svg" alt="pyLocusZoom logo" width="120" align="right">
13
11
  # pyLocusZoom
14
12
 
15
- Publication-ready regional association plots with LD coloring, gene tracks, and recombination overlays.
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**: Overlay showing recombination rate across region (*Canis lupus familiaris* only)
27
- - **SNP labels (matplotlib)**: Automatic labeling of lead SNPs with RS ID
28
- - **Tooltips (Bokeh and Plotly)**: Mouseover for detailed SNP data
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
- ![Example regional association plot](examples/regional_plot.png)
28
+ ![Example regional association plot with LD coloring and gene track](examples/regional_plot.png)
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 charting libraries**: matplotlib (static), plotly (interactive), bokeh (dashboards)
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
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="matplotlib")
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
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="plotly")
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
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="bokeh")
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 | Publications, presentations | Full feature set with SNP labels |
159
- | `plotly` | Interactive HTML | Web reports, data exploration | Hover tooltips, pan/zoom |
160
- | `bokeh` | Interactive HTML | Dashboards, web apps | Hover tooltips, pan/zoom |
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
- ![Example stacked plot](examples/stacked_plot.png)
197
+ ![Example stacked plot comparing two phenotypes](examples/stacked_plot.png)
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
  ![Example eQTL overlay plot](examples/eqtl_overlay.png)
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
  ![Example fine-mapping plot](examples/finemapping_plot.png)
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
  ![Example PheWAS plot](examples/phewas_plot.png)
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
  ![Example forest plot](examples/forest_plot.png)
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, pass PySpark DataFrames directly:
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 (automatically converted)
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