pylocuszoom 0.5.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.5.0 → pylocuszoom-0.8.0}/.github/workflows/ci.yml +1 -8
- pylocuszoom-0.8.0/.github/workflows/publish.yml +110 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/.gitignore +2 -0
- pylocuszoom-0.8.0/.pre-commit-config.yaml +17 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/CHANGELOG.md +67 -1
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/PKG-INFO +97 -28
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/README.md +89 -24
- pylocuszoom-0.8.0/bioconda/meta.yaml +63 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/docs/USER_GUIDE.md +197 -1
- pylocuszoom-0.8.0/examples/eqtl_bokeh.html +61 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/eqtl_overlay.png +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/eqtl_plotly.html +1 -1
- pylocuszoom-0.8.0/examples/finemapping_bokeh.html +61 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/finemapping_plot.png +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/finemapping_plotly.html +1 -1
- pylocuszoom-0.8.0/examples/forest_plot.png +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/generate_readme_plots.py +76 -2
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/getting_started.ipynb +167 -56
- pylocuszoom-0.8.0/examples/phewas_plot.png +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/examples/regional_plot.png +0 -0
- pylocuszoom-0.8.0/examples/stacked_plot.png +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/pyproject.toml +26 -4
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/__init__.py +38 -2
- pylocuszoom-0.8.0/src/pylocuszoom/backends/__init__.py +147 -0
- pylocuszoom-0.8.0/src/pylocuszoom/backends/base.py +777 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/bokeh_backend.py +192 -34
- pylocuszoom-0.8.0/src/pylocuszoom/backends/hover.py +198 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/matplotlib_backend.py +332 -3
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/backends/plotly_backend.py +187 -38
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/colors.py +41 -0
- pylocuszoom-0.8.0/src/pylocuszoom/ensembl.py +476 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/eqtl.py +15 -19
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/finemapping.py +17 -26
- pylocuszoom-0.8.0/src/pylocuszoom/forest.py +35 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/gene_track.py +161 -135
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/loaders.py +38 -18
- pylocuszoom-0.8.0/src/pylocuszoom/phewas.py +34 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/plotter.py +370 -190
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/recombination.py +64 -34
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/schemas.py +37 -26
- {pylocuszoom-0.5.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.5.0 → pylocuszoom-0.8.0}/tests/test_colors.py +25 -0
- pylocuszoom-0.8.0/tests/test_ensembl.py +466 -0
- pylocuszoom-0.8.0/tests/test_ensembl_integration.py +77 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_finemapping.py +3 -7
- pylocuszoom-0.8.0/tests/test_forest.py +54 -0
- pylocuszoom-0.8.0/tests/test_hover.py +351 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_loaders.py +134 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_notebook_backends.py +133 -0
- pylocuszoom-0.8.0/tests/test_phewas.py +51 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_plotter.py +257 -0
- pylocuszoom-0.8.0/tests/test_utils.py +162 -0
- pylocuszoom-0.8.0/tests/test_validation.py +343 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/uv.lock +187 -2
- pylocuszoom-0.5.0/.github/workflows/publish.yml +0 -23
- pylocuszoom-0.5.0/.pre-commit-config.yaml +0 -7
- pylocuszoom-0.5.0/bioconda/meta.yaml +0 -54
- pylocuszoom-0.5.0/examples/eqtl_bokeh.html +0 -61
- pylocuszoom-0.5.0/examples/finemapping_bokeh.html +0 -61
- pylocuszoom-0.5.0/examples/stacked_plot.png +0 -0
- pylocuszoom-0.5.0/src/pylocuszoom/backends/__init__.py +0 -48
- pylocuszoom-0.5.0/src/pylocuszoom/backends/base.py +0 -388
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/.gitattributes +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/CONTRIBUTING.md +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/LICENSE.md +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/docs/ARCHITECTURE.md +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/logo.svg +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/labels.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/ld.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/logging.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/py.typed +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/src/pylocuszoom/reference_data/__init__.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/conftest.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_gene_track.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_labels.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_ld.py +0 -0
- {pylocuszoom-0.5.0 → pylocuszoom-0.8.0}/tests/test_logging.py +0 -0
- {pylocuszoom-0.5.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,110 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
outputs:
|
|
14
|
+
version: ${{ steps.version.outputs.version }}
|
|
15
|
+
sha256: ${{ steps.sha256.outputs.sha256 }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
|
+
|
|
22
|
+
- name: Get version
|
|
23
|
+
id: version
|
|
24
|
+
run: |
|
|
25
|
+
VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
|
|
26
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
27
|
+
|
|
28
|
+
- name: Build package
|
|
29
|
+
run: uv build
|
|
30
|
+
|
|
31
|
+
- name: Publish to PyPI
|
|
32
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
33
|
+
|
|
34
|
+
- name: Wait for PyPI availability
|
|
35
|
+
env:
|
|
36
|
+
PKG_VERSION: ${{ steps.version.outputs.version }}
|
|
37
|
+
run: |
|
|
38
|
+
echo "Waiting for pylocuszoom $PKG_VERSION to be available on PyPI..."
|
|
39
|
+
for i in {1..30}; do
|
|
40
|
+
if curl -s "https://pypi.org/pypi/pylocuszoom/$PKG_VERSION/json" | grep -q '"version"'; then
|
|
41
|
+
echo "Package available on PyPI"
|
|
42
|
+
break
|
|
43
|
+
fi
|
|
44
|
+
echo "Attempt $i: Package not yet available, waiting 10s..."
|
|
45
|
+
sleep 10
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
- name: Get SHA256
|
|
49
|
+
id: sha256
|
|
50
|
+
env:
|
|
51
|
+
PKG_VERSION: ${{ steps.version.outputs.version }}
|
|
52
|
+
run: |
|
|
53
|
+
URL="https://pypi.io/packages/source/p/pylocuszoom/pylocuszoom-$PKG_VERSION.tar.gz"
|
|
54
|
+
SHA256=$(curl -sL "$URL" | sha256sum | cut -d' ' -f1)
|
|
55
|
+
echo "sha256=$SHA256" >> "$GITHUB_OUTPUT"
|
|
56
|
+
echo "SHA256: $SHA256"
|
|
57
|
+
|
|
58
|
+
update-bioconda:
|
|
59
|
+
needs: publish
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
with:
|
|
64
|
+
ref: main
|
|
65
|
+
|
|
66
|
+
- name: Update bioconda/meta.yaml
|
|
67
|
+
env:
|
|
68
|
+
PKG_VERSION: ${{ needs.publish.outputs.version }}
|
|
69
|
+
PKG_SHA256: ${{ needs.publish.outputs.sha256 }}
|
|
70
|
+
run: |
|
|
71
|
+
# Update version
|
|
72
|
+
sed -i "s/{% set version = \".*\" %}/{% set version = \"$PKG_VERSION\" %}/" bioconda/meta.yaml
|
|
73
|
+
|
|
74
|
+
# Update sha256
|
|
75
|
+
sed -i "s/sha256: .*/sha256: $PKG_SHA256/" bioconda/meta.yaml
|
|
76
|
+
|
|
77
|
+
# Update plotly version requirement
|
|
78
|
+
sed -i "s/plotly >=5.0.0/plotly >=5.15.0/" bioconda/meta.yaml
|
|
79
|
+
|
|
80
|
+
# Add new dependencies if missing
|
|
81
|
+
if ! grep -q "pydantic" bioconda/meta.yaml; then
|
|
82
|
+
sed -i '/adjusttext/a\ - pydantic >=2.0.0' bioconda/meta.yaml
|
|
83
|
+
fi
|
|
84
|
+
if ! grep -q "requests" bioconda/meta.yaml; then
|
|
85
|
+
sed -i '/pydantic/a\ - requests >=2.25.0' bioconda/meta.yaml
|
|
86
|
+
fi
|
|
87
|
+
if ! grep -q "tqdm" bioconda/meta.yaml; then
|
|
88
|
+
sed -i '/requests/a\ - tqdm >=4.60.0' bioconda/meta.yaml
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
cat bioconda/meta.yaml
|
|
92
|
+
|
|
93
|
+
- name: Create Pull Request
|
|
94
|
+
uses: peter-evans/create-pull-request@v6
|
|
95
|
+
with:
|
|
96
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
97
|
+
commit-message: "chore: update bioconda recipe for new release"
|
|
98
|
+
branch: bioconda-update
|
|
99
|
+
title: "Update bioconda recipe"
|
|
100
|
+
body: |
|
|
101
|
+
Automated update of bioconda/meta.yaml after PyPI release.
|
|
102
|
+
|
|
103
|
+
**Next steps:**
|
|
104
|
+
1. Review this PR
|
|
105
|
+
2. Merge to main
|
|
106
|
+
3. Copy `bioconda/meta.yaml` to your fork of bioconda-recipes
|
|
107
|
+
4. Submit PR to bioconda-recipes
|
|
108
|
+
labels: |
|
|
109
|
+
bioconda
|
|
110
|
+
automated
|
|
@@ -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
|
|
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
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
|
+
|
|
49
|
+
## [0.6.0] - 2026-01-27
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- `plot_phewas()` method for phenome-wide association study plots
|
|
53
|
+
- `plot_forest()` method for forest plots (meta-analysis visualization)
|
|
54
|
+
- PheWAS category color palette with 12 distinct colors
|
|
55
|
+
- Forest plot and PheWAS validation utilities
|
|
56
|
+
- Backend methods: `axvline()`, `hbar()`, `errorbar_h()` for new plot types
|
|
57
|
+
- Example plots for PheWAS and forest plots
|
|
58
|
+
- Progress bars (tqdm) for recombination map and liftover chain downloads
|
|
59
|
+
- `requests` and `tqdm` as core dependencies for reliable downloads with progress
|
|
60
|
+
- `pytest-randomly` and `pytest-xdist` as dev dependencies for test randomization and parallel execution
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- Bumped minimum Plotly version to 5.15.0 (required for multiple legends feature)
|
|
64
|
+
- eQTL loaders now output `effect_size` column instead of `effect` for plotter compatibility
|
|
65
|
+
- Download functions now use `requests` with streaming and progress bars instead of `urllib`
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
- SAIGE loader now prefers SPA-adjusted p-values (`p.value.NA`) over raw p-values when both present
|
|
69
|
+
- BED loader now handles BED12 format and files with more than 6 columns
|
|
70
|
+
- eQTL panel in `plot_stacked()` now filters by chromosome in addition to position
|
|
71
|
+
- Validation errors for non-numeric p-values or positions now show clear "must be numeric" message instead of runtime errors
|
|
72
|
+
|
|
10
73
|
## [0.5.0] - 2026-01-27
|
|
11
74
|
|
|
12
75
|
### Added
|
|
@@ -118,7 +181,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
118
181
|
- bokeh >= 3.8.2
|
|
119
182
|
- kaleido >= 0.2.0
|
|
120
183
|
|
|
121
|
-
[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
|
|
187
|
+
[0.6.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.5.0...v0.6.0
|
|
122
188
|
[0.5.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.4.0...v0.5.0
|
|
123
189
|
[0.4.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.3.0...v0.4.0
|
|
124
190
|
[0.3.0]: https://github.com/michael-denyer/pyLocusZoom/compare/v0.2.0...v0.3.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
|
|
@@ -26,13 +26,17 @@ Requires-Dist: loguru>=0.7.0
|
|
|
26
26
|
Requires-Dist: matplotlib>=3.5.0
|
|
27
27
|
Requires-Dist: numpy>=1.21.0
|
|
28
28
|
Requires-Dist: pandas>=1.4.0
|
|
29
|
-
Requires-Dist: plotly>=5.
|
|
29
|
+
Requires-Dist: plotly>=5.15.0
|
|
30
30
|
Requires-Dist: pydantic>=2.0.0
|
|
31
31
|
Requires-Dist: pyliftover>=0.4
|
|
32
|
+
Requires-Dist: requests>=2.25.0
|
|
33
|
+
Requires-Dist: tqdm>=4.60.0
|
|
32
34
|
Provides-Extra: all
|
|
33
35
|
Requires-Dist: pyspark>=3.0.0; extra == 'all'
|
|
34
36
|
Provides-Extra: dev
|
|
35
37
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-randomly>=3.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-xdist>=3.0.0; extra == 'dev'
|
|
36
40
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
37
41
|
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
38
42
|
Provides-Extra: spark
|
|
@@ -40,20 +44,18 @@ Requires-Dist: pyspark>=3.0.0; extra == 'spark'
|
|
|
40
44
|
Description-Content-Type: text/markdown
|
|
41
45
|
|
|
42
46
|
[](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml)
|
|
43
|
-
[](https://codecov.io/gh/michael-denyer/pyLocusZoom)
|
|
44
47
|
[](https://pypi.org/project/pylocuszoom/)
|
|
45
|
-
[](https://anaconda.org/bioconda/pylocuszoom)
|
|
46
48
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
47
49
|
[](https://www.python.org/downloads/)
|
|
48
50
|
[](https://github.com/astral-sh/ruff)
|
|
49
51
|
[](https://matplotlib.org/)
|
|
50
|
-
[](https://plotly.com/python/)
|
|
51
53
|
[](https://bokeh.org/)
|
|
52
54
|
[](https://pandas.pydata.org/)
|
|
53
55
|
<img src="logo.svg" alt="pyLocusZoom logo" width="120" align="right">
|
|
54
56
|
# pyLocusZoom
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
Designed for publication-ready GWAS visualization with regional association plots, gene tracks, eQTL, PheWAS, fine-mapping, and forest plots.
|
|
57
59
|
|
|
58
60
|
Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.com/myles-lewis/locuszoomr).
|
|
59
61
|
|
|
@@ -64,18 +66,22 @@ Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.c
|
|
|
64
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
|
|
65
67
|
- **LD coloring**: SNPs colored by linkage disequilibrium (R²) with lead variant
|
|
66
68
|
- **Gene tracks**: Annotated gene/exon positions below the association plot
|
|
67
|
-
- **Recombination rate**:
|
|
68
|
-
- **SNP labels (matplotlib)**: Automatic labeling of
|
|
69
|
-
- **
|
|
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
|
|
70
72
|
|
|
71
|
-

|
|
73
|
+

|
|
74
|
+
*Regional association plot with LD coloring, gene/exon track, and top SNP labels (recombination overlay disabled in example).*
|
|
72
75
|
|
|
73
76
|
2. **Stacked plots**: Compare multiple GWAS/phenotypes vertically
|
|
74
77
|
3. **eQTL plot**: Expression QTL data aligned with association plots and gene tracks
|
|
75
78
|
4. **Fine-mapping plots**: Visualize SuSiE credible sets with posterior inclusion probabilities
|
|
76
|
-
5. **
|
|
77
|
-
6. **
|
|
78
|
-
7. **
|
|
79
|
+
5. **PheWAS plots**: Phenome-wide association study visualization across multiple phenotypes
|
|
80
|
+
6. **Forest plots**: Meta-analysis effect size visualization with confidence intervals
|
|
81
|
+
7. **Multiple backends**: matplotlib (publication-ready), plotly (interactive), bokeh (dashboard integration)
|
|
82
|
+
8. **Pandas and PySpark support**: Works with both Pandas and PySpark DataFrames for large-scale genomics data
|
|
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)
|
|
79
85
|
|
|
80
86
|
## Installation
|
|
81
87
|
|
|
@@ -175,28 +181,46 @@ fig = plotter.plot(
|
|
|
175
181
|
)
|
|
176
182
|
```
|
|
177
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
|
+
|
|
178
199
|
## Backends
|
|
179
200
|
|
|
180
|
-
pyLocusZoom supports multiple rendering backends:
|
|
201
|
+
pyLocusZoom supports multiple rendering backends (set at initialization):
|
|
181
202
|
|
|
182
203
|
```python
|
|
183
204
|
# Static publication-quality plot (default)
|
|
184
|
-
|
|
205
|
+
plotter = LocusZoomPlotter(species="canine", backend="matplotlib")
|
|
206
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
185
207
|
fig.savefig("plot.png", dpi=150)
|
|
186
208
|
|
|
187
209
|
# Interactive Plotly (hover tooltips, pan/zoom)
|
|
188
|
-
|
|
210
|
+
plotter = LocusZoomPlotter(species="canine", backend="plotly")
|
|
211
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
189
212
|
fig.write_html("plot.html")
|
|
190
213
|
|
|
191
214
|
# Interactive Bokeh (dashboard-ready)
|
|
192
|
-
|
|
215
|
+
plotter = LocusZoomPlotter(species="canine", backend="bokeh")
|
|
216
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
193
217
|
```
|
|
194
218
|
|
|
195
219
|
| Backend | Output | Best For | Features |
|
|
196
220
|
|---------|--------|----------|----------|
|
|
197
|
-
| `matplotlib` | Static PNG/PDF/SVG |
|
|
198
|
-
| `plotly` | Interactive HTML | Web reports,
|
|
199
|
-
| `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 |
|
|
200
224
|
|
|
201
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.
|
|
202
226
|
|
|
@@ -215,7 +239,8 @@ fig = plotter.plot_stacked(
|
|
|
215
239
|
)
|
|
216
240
|
```
|
|
217
241
|
|
|
218
|
-

|
|
242
|
+

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

|
|
266
|
+
*eQTL overlay with effect direction (up/down triangles) and magnitude binning.*
|
|
241
267
|
|
|
242
268
|
## Fine-mapping Visualization
|
|
243
269
|
|
|
@@ -260,19 +286,62 @@ fig = plotter.plot_stacked(
|
|
|
260
286
|
```
|
|
261
287
|
|
|
262
288
|

|
|
289
|
+
*Fine-mapping visualization with PIP line and credible set coloring (CS1/CS2).*
|
|
290
|
+
|
|
291
|
+
## PheWAS Plots
|
|
292
|
+
|
|
293
|
+
Visualize associations of a single variant across multiple phenotypes:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
phewas_df = pd.DataFrame({
|
|
297
|
+
"phenotype": ["Height", "BMI", "T2D", "CAD", "HDL"],
|
|
298
|
+
"p_value": [1e-15, 0.05, 1e-8, 1e-3, 1e-10],
|
|
299
|
+
"category": ["Anthropometric", "Anthropometric", "Metabolic", "Cardiovascular", "Lipids"],
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
fig = plotter.plot_phewas(
|
|
303
|
+
phewas_df,
|
|
304
|
+
variant_id="rs12345",
|
|
305
|
+
category_col="category",
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+

|
|
310
|
+
*PheWAS plot showing associations across phenotype categories with significance threshold.*
|
|
311
|
+
|
|
312
|
+
## Forest Plots
|
|
313
|
+
|
|
314
|
+
Create forest plots for meta-analysis visualization:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
forest_df = pd.DataFrame({
|
|
318
|
+
"study": ["Study A", "Study B", "Study C", "Meta-analysis"],
|
|
319
|
+
"effect": [0.45, 0.52, 0.38, 0.46],
|
|
320
|
+
"ci_lower": [0.30, 0.35, 0.20, 0.40],
|
|
321
|
+
"ci_upper": [0.60, 0.69, 0.56, 0.52],
|
|
322
|
+
"weight": [25, 35, 20, 100],
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
fig = plotter.plot_forest(
|
|
326
|
+
forest_df,
|
|
327
|
+
variant_id="rs12345",
|
|
328
|
+
weight_col="weight",
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+

|
|
333
|
+
*Forest plot with effect sizes, confidence intervals, and weight-proportional markers.*
|
|
263
334
|
|
|
264
335
|
## PySpark Support
|
|
265
336
|
|
|
266
|
-
For large-scale genomics data,
|
|
337
|
+
For large-scale genomics data, convert PySpark DataFrames with `to_pandas()` before plotting:
|
|
267
338
|
|
|
268
339
|
```python
|
|
269
340
|
from pylocuszoom import LocusZoomPlotter, to_pandas
|
|
270
341
|
|
|
271
|
-
# PySpark DataFrame (
|
|
272
|
-
fig = plotter.plot(spark_gwas_df, chrom=1, start=1000000, end=2000000)
|
|
273
|
-
|
|
274
|
-
# Or convert manually with sampling for very large data
|
|
342
|
+
# Convert PySpark DataFrame (optionally sampled for very large data)
|
|
275
343
|
pandas_df = to_pandas(spark_gwas_df, sample_size=100000)
|
|
344
|
+
fig = plotter.plot(pandas_df, chrom=1, start=1000000, end=2000000)
|
|
276
345
|
```
|
|
277
346
|
|
|
278
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,18 +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
|
-
5. **
|
|
36
|
-
6. **
|
|
37
|
-
7. **
|
|
34
|
+
5. **PheWAS plots**: Phenome-wide association study visualization across multiple phenotypes
|
|
35
|
+
6. **Forest plots**: Meta-analysis effect size visualization with confidence intervals
|
|
36
|
+
7. **Multiple backends**: matplotlib (publication-ready), plotly (interactive), bokeh (dashboard integration)
|
|
37
|
+
8. **Pandas and PySpark support**: Works with both Pandas and PySpark DataFrames for large-scale genomics data
|
|
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)
|
|
38
40
|
|
|
39
41
|
## Installation
|
|
40
42
|
|
|
@@ -134,28 +136,46 @@ fig = plotter.plot(
|
|
|
134
136
|
)
|
|
135
137
|
```
|
|
136
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
|
+
|
|
137
154
|
## Backends
|
|
138
155
|
|
|
139
|
-
pyLocusZoom supports multiple rendering backends:
|
|
156
|
+
pyLocusZoom supports multiple rendering backends (set at initialization):
|
|
140
157
|
|
|
141
158
|
```python
|
|
142
159
|
# Static publication-quality plot (default)
|
|
143
|
-
|
|
160
|
+
plotter = LocusZoomPlotter(species="canine", backend="matplotlib")
|
|
161
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
144
162
|
fig.savefig("plot.png", dpi=150)
|
|
145
163
|
|
|
146
164
|
# Interactive Plotly (hover tooltips, pan/zoom)
|
|
147
|
-
|
|
165
|
+
plotter = LocusZoomPlotter(species="canine", backend="plotly")
|
|
166
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
148
167
|
fig.write_html("plot.html")
|
|
149
168
|
|
|
150
169
|
# Interactive Bokeh (dashboard-ready)
|
|
151
|
-
|
|
170
|
+
plotter = LocusZoomPlotter(species="canine", backend="bokeh")
|
|
171
|
+
fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
|
|
152
172
|
```
|
|
153
173
|
|
|
154
174
|
| Backend | Output | Best For | Features |
|
|
155
175
|
|---------|--------|----------|----------|
|
|
156
|
-
| `matplotlib` | Static PNG/PDF/SVG |
|
|
157
|
-
| `plotly` | Interactive HTML | Web reports,
|
|
158
|
-
| `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 |
|
|
159
179
|
|
|
160
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.
|
|
161
181
|
|
|
@@ -174,7 +194,8 @@ fig = plotter.plot_stacked(
|
|
|
174
194
|
)
|
|
175
195
|
```
|
|
176
196
|
|
|
177
|
-

|
|
197
|
+

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

|
|
221
|
+
*eQTL overlay with effect direction (up/down triangles) and magnitude binning.*
|
|
200
222
|
|
|
201
223
|
## Fine-mapping Visualization
|
|
202
224
|
|
|
@@ -219,19 +241,62 @@ fig = plotter.plot_stacked(
|
|
|
219
241
|
```
|
|
220
242
|
|
|
221
243
|

|
|
244
|
+
*Fine-mapping visualization with PIP line and credible set coloring (CS1/CS2).*
|
|
245
|
+
|
|
246
|
+
## PheWAS Plots
|
|
247
|
+
|
|
248
|
+
Visualize associations of a single variant across multiple phenotypes:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
phewas_df = pd.DataFrame({
|
|
252
|
+
"phenotype": ["Height", "BMI", "T2D", "CAD", "HDL"],
|
|
253
|
+
"p_value": [1e-15, 0.05, 1e-8, 1e-3, 1e-10],
|
|
254
|
+
"category": ["Anthropometric", "Anthropometric", "Metabolic", "Cardiovascular", "Lipids"],
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
fig = plotter.plot_phewas(
|
|
258
|
+
phewas_df,
|
|
259
|
+
variant_id="rs12345",
|
|
260
|
+
category_col="category",
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+

|
|
265
|
+
*PheWAS plot showing associations across phenotype categories with significance threshold.*
|
|
266
|
+
|
|
267
|
+
## Forest Plots
|
|
268
|
+
|
|
269
|
+
Create forest plots for meta-analysis visualization:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
forest_df = pd.DataFrame({
|
|
273
|
+
"study": ["Study A", "Study B", "Study C", "Meta-analysis"],
|
|
274
|
+
"effect": [0.45, 0.52, 0.38, 0.46],
|
|
275
|
+
"ci_lower": [0.30, 0.35, 0.20, 0.40],
|
|
276
|
+
"ci_upper": [0.60, 0.69, 0.56, 0.52],
|
|
277
|
+
"weight": [25, 35, 20, 100],
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
fig = plotter.plot_forest(
|
|
281
|
+
forest_df,
|
|
282
|
+
variant_id="rs12345",
|
|
283
|
+
weight_col="weight",
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+

|
|
288
|
+
*Forest plot with effect sizes, confidence intervals, and weight-proportional markers.*
|
|
222
289
|
|
|
223
290
|
## PySpark Support
|
|
224
291
|
|
|
225
|
-
For large-scale genomics data,
|
|
292
|
+
For large-scale genomics data, convert PySpark DataFrames with `to_pandas()` before plotting:
|
|
226
293
|
|
|
227
294
|
```python
|
|
228
295
|
from pylocuszoom import LocusZoomPlotter, to_pandas
|
|
229
296
|
|
|
230
|
-
# PySpark DataFrame (
|
|
231
|
-
fig = plotter.plot(spark_gwas_df, chrom=1, start=1000000, end=2000000)
|
|
232
|
-
|
|
233
|
-
# Or convert manually with sampling for very large data
|
|
297
|
+
# Convert PySpark DataFrame (optionally sampled for very large data)
|
|
234
298
|
pandas_df = to_pandas(spark_gwas_df, sample_size=100000)
|
|
299
|
+
fig = plotter.plot(pandas_df, chrom=1, start=1000000, end=2000000)
|
|
235
300
|
```
|
|
236
301
|
|
|
237
302
|
Install PySpark support: `uv add pylocuszoom[spark]`
|