pylocuszoom 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,120 @@
1
+ """pyLocusZoom - Regional association plots for GWAS results.
2
+
3
+ This package provides LocusZoom-style regional association plots with:
4
+ - LD coloring based on R² with lead variant
5
+ - Gene and exon tracks
6
+ - Recombination rate overlays (dog built-in, or user-provided)
7
+ - Automatic SNP labeling
8
+ - Multiple backends: matplotlib (static), plotly (interactive), bokeh (dashboards)
9
+ - eQTL overlay support
10
+ - PySpark DataFrame support for large-scale data
11
+
12
+ Example:
13
+ >>> from pylocuszoom import LocusZoomPlotter
14
+ >>> plotter = LocusZoomPlotter(species="dog")
15
+ >>> fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
16
+ >>> fig.savefig("regional_plot.png", dpi=150)
17
+
18
+ Interactive example:
19
+ >>> plotter = LocusZoomPlotter(species="dog", backend="plotly")
20
+ >>> fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
21
+ >>> fig.write_html("regional_plot.html")
22
+
23
+ Stacked plots:
24
+ >>> fig = plotter.plot_stacked(
25
+ ... [gwas_height, gwas_bmi],
26
+ ... chrom=1, start=1000000, end=2000000,
27
+ ... panel_labels=["Height", "BMI"],
28
+ ... )
29
+
30
+ Species Support:
31
+ - Dog (Canis lupus familiaris): Full features including built-in recombination maps
32
+ - Cat (Felis catus): LD coloring and gene tracks (user provides recombination data)
33
+ - Custom: User provides all reference data
34
+ """
35
+
36
+ __version__ = "0.1.0"
37
+
38
+ # Main plotter class
39
+ from .plotter import LocusZoomPlotter
40
+
41
+ # Backend types
42
+ from .backends import BackendType, get_backend
43
+
44
+ # Colors and LD
45
+ from .colors import LEAD_SNP_COLOR, get_ld_bin, get_ld_color, get_ld_color_palette
46
+
47
+ # Gene track
48
+ from .gene_track import get_nearest_gene, plot_gene_track
49
+
50
+ # Labels
51
+ from .labels import add_snp_labels
52
+
53
+ # LD calculation
54
+ from .ld import calculate_ld
55
+
56
+ # Logging configuration
57
+ from .logging import disable_logging, enable_logging
58
+
59
+ # Reference data management
60
+ from .recombination import (
61
+ add_recombination_overlay,
62
+ download_dog_recombination_maps,
63
+ get_recombination_rate_for_region,
64
+ load_recombination_map,
65
+ )
66
+
67
+ # eQTL support
68
+ from .eqtl import (
69
+ EQTLValidationError,
70
+ calculate_colocalization_overlap,
71
+ filter_eqtl_by_gene,
72
+ filter_eqtl_by_region,
73
+ get_eqtl_genes,
74
+ prepare_eqtl_for_plotting,
75
+ validate_eqtl_df,
76
+ )
77
+
78
+ # Validation utilities
79
+ from .utils import ValidationError, to_pandas
80
+
81
+ __all__ = [
82
+ # Core
83
+ "__version__",
84
+ "LocusZoomPlotter",
85
+ # Backends
86
+ "BackendType",
87
+ "get_backend",
88
+ # Reference data
89
+ "download_dog_recombination_maps",
90
+ # Colors
91
+ "get_ld_color",
92
+ "get_ld_bin",
93
+ "get_ld_color_palette",
94
+ "LEAD_SNP_COLOR",
95
+ # Gene track
96
+ "get_nearest_gene",
97
+ "plot_gene_track",
98
+ # LD
99
+ "calculate_ld",
100
+ # Labels
101
+ "add_snp_labels",
102
+ # Recombination
103
+ "add_recombination_overlay",
104
+ "get_recombination_rate_for_region",
105
+ "load_recombination_map",
106
+ # eQTL
107
+ "validate_eqtl_df",
108
+ "filter_eqtl_by_gene",
109
+ "filter_eqtl_by_region",
110
+ "prepare_eqtl_for_plotting",
111
+ "get_eqtl_genes",
112
+ "calculate_colocalization_overlap",
113
+ "EQTLValidationError",
114
+ # Logging
115
+ "enable_logging",
116
+ "disable_logging",
117
+ # Validation & Utils
118
+ "ValidationError",
119
+ "to_pandas",
120
+ ]
@@ -0,0 +1,52 @@
1
+ """Pluggable plotting backends for pyLocusZoom.
2
+
3
+ Supports matplotlib (default), plotly, and bokeh backends.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING, Literal
7
+
8
+ from .base import PlotBackend
9
+ from .matplotlib_backend import MatplotlibBackend
10
+
11
+ if TYPE_CHECKING:
12
+ from .bokeh_backend import BokehBackend
13
+ from .plotly_backend import PlotlyBackend
14
+
15
+ BackendType = Literal["matplotlib", "plotly", "bokeh"]
16
+
17
+ _BACKENDS: dict[str, type[PlotBackend]] = {
18
+ "matplotlib": MatplotlibBackend,
19
+ }
20
+
21
+
22
+ def get_backend(name: BackendType) -> PlotBackend:
23
+ """Get a backend instance by name.
24
+
25
+ Args:
26
+ name: Backend name ('matplotlib', 'plotly', or 'bokeh').
27
+
28
+ Returns:
29
+ Instantiated backend.
30
+
31
+ Raises:
32
+ ValueError: If backend name is invalid.
33
+ ImportError: If backend dependencies are not installed.
34
+ """
35
+ if name == "plotly":
36
+ from .plotly_backend import PlotlyBackend
37
+
38
+ _BACKENDS["plotly"] = PlotlyBackend
39
+ elif name == "bokeh":
40
+ from .bokeh_backend import BokehBackend
41
+
42
+ _BACKENDS["bokeh"] = BokehBackend
43
+
44
+ if name not in _BACKENDS:
45
+ raise ValueError(
46
+ f"Unknown backend: {name}. Available: matplotlib, plotly, bokeh"
47
+ )
48
+
49
+ return _BACKENDS[name]()
50
+
51
+
52
+ __all__ = ["PlotBackend", "BackendType", "get_backend", "MatplotlibBackend"]
@@ -0,0 +1,341 @@
1
+ """Base protocol for plotting backends.
2
+
3
+ Defines the interface that matplotlib, plotly, and bokeh backends must implement.
4
+ """
5
+
6
+ from typing import Any, Dict, List, Optional, Protocol, Tuple, Union
7
+
8
+ import pandas as pd
9
+
10
+
11
+ class PlotBackend(Protocol):
12
+ """Protocol defining the backend interface for LocusZoom plots.
13
+
14
+ All backends (matplotlib, plotly, bokeh) must implement these methods
15
+ to enable consistent plotting across different rendering engines.
16
+ """
17
+
18
+ def create_figure(
19
+ self,
20
+ n_panels: int,
21
+ height_ratios: List[float],
22
+ figsize: Tuple[float, float],
23
+ sharex: bool = True,
24
+ ) -> Tuple[Any, List[Any]]:
25
+ """Create a figure with multiple panels (subplots).
26
+
27
+ Args:
28
+ n_panels: Number of vertical panels.
29
+ height_ratios: Relative heights for each panel.
30
+ figsize: Figure size as (width, height).
31
+ sharex: Whether panels share the x-axis.
32
+
33
+ Returns:
34
+ Tuple of (figure, list of axes/panels).
35
+ """
36
+ ...
37
+
38
+ def scatter(
39
+ self,
40
+ ax: Any,
41
+ x: pd.Series,
42
+ y: pd.Series,
43
+ colors: Union[str, List[str], pd.Series],
44
+ sizes: Union[float, List[float], pd.Series] = 60,
45
+ marker: str = "o",
46
+ edgecolor: str = "black",
47
+ linewidth: float = 0.5,
48
+ zorder: int = 2,
49
+ hover_data: Optional[pd.DataFrame] = None,
50
+ label: Optional[str] = None,
51
+ ) -> Any:
52
+ """Create a scatter plot on the given axes.
53
+
54
+ Args:
55
+ ax: Axes or panel to plot on.
56
+ x: X-axis values (positions).
57
+ y: Y-axis values (-log10 p-values).
58
+ colors: Point colors (single color or per-point).
59
+ sizes: Point sizes.
60
+ marker: Marker style.
61
+ edgecolor: Marker edge color.
62
+ linewidth: Marker edge width.
63
+ zorder: Drawing order.
64
+ hover_data: DataFrame with columns for hover tooltips.
65
+ label: Legend label.
66
+
67
+ Returns:
68
+ The scatter plot object.
69
+ """
70
+ ...
71
+
72
+ def line(
73
+ self,
74
+ ax: Any,
75
+ x: pd.Series,
76
+ y: pd.Series,
77
+ color: str = "blue",
78
+ linewidth: float = 1.5,
79
+ alpha: float = 1.0,
80
+ linestyle: str = "-",
81
+ zorder: int = 1,
82
+ label: Optional[str] = None,
83
+ ) -> Any:
84
+ """Create a line plot on the given axes.
85
+
86
+ Args:
87
+ ax: Axes or panel to plot on.
88
+ x: X-axis values.
89
+ y: Y-axis values.
90
+ color: Line color.
91
+ linewidth: Line width.
92
+ alpha: Transparency.
93
+ linestyle: Line style ('-', '--', ':', '-.').
94
+ zorder: Drawing order.
95
+ label: Legend label.
96
+
97
+ Returns:
98
+ The line plot object.
99
+ """
100
+ ...
101
+
102
+ def fill_between(
103
+ self,
104
+ ax: Any,
105
+ x: pd.Series,
106
+ y1: Union[float, pd.Series],
107
+ y2: Union[float, pd.Series],
108
+ color: str = "blue",
109
+ alpha: float = 0.3,
110
+ zorder: int = 0,
111
+ ) -> Any:
112
+ """Fill area between two y-values.
113
+
114
+ Args:
115
+ ax: Axes or panel to plot on.
116
+ x: X-axis values.
117
+ y1: Lower y boundary.
118
+ y2: Upper y boundary.
119
+ color: Fill color.
120
+ alpha: Transparency.
121
+ zorder: Drawing order.
122
+
123
+ Returns:
124
+ The fill object.
125
+ """
126
+ ...
127
+
128
+ def axhline(
129
+ self,
130
+ ax: Any,
131
+ y: float,
132
+ color: str = "grey",
133
+ linestyle: str = "--",
134
+ linewidth: float = 1.0,
135
+ zorder: int = 1,
136
+ ) -> Any:
137
+ """Add a horizontal line across the axes.
138
+
139
+ Args:
140
+ ax: Axes or panel.
141
+ y: Y-value for the line.
142
+ color: Line color.
143
+ linestyle: Line style.
144
+ linewidth: Line width.
145
+ zorder: Drawing order.
146
+
147
+ Returns:
148
+ The line object.
149
+ """
150
+ ...
151
+
152
+ def add_text(
153
+ self,
154
+ ax: Any,
155
+ x: float,
156
+ y: float,
157
+ text: str,
158
+ fontsize: int = 10,
159
+ ha: str = "center",
160
+ va: str = "bottom",
161
+ rotation: float = 0,
162
+ color: str = "black",
163
+ ) -> Any:
164
+ """Add text annotation to axes.
165
+
166
+ Args:
167
+ ax: Axes or panel.
168
+ x: X position.
169
+ y: Y position.
170
+ text: Text content.
171
+ fontsize: Font size.
172
+ ha: Horizontal alignment.
173
+ va: Vertical alignment.
174
+ rotation: Text rotation in degrees.
175
+ color: Text color.
176
+
177
+ Returns:
178
+ The text object.
179
+ """
180
+ ...
181
+
182
+ def add_rectangle(
183
+ self,
184
+ ax: Any,
185
+ xy: Tuple[float, float],
186
+ width: float,
187
+ height: float,
188
+ facecolor: str = "blue",
189
+ edgecolor: str = "black",
190
+ linewidth: float = 0.5,
191
+ zorder: int = 2,
192
+ ) -> Any:
193
+ """Add a rectangle patch to axes.
194
+
195
+ Args:
196
+ ax: Axes or panel.
197
+ xy: Bottom-left corner coordinates.
198
+ width: Rectangle width.
199
+ height: Rectangle height.
200
+ facecolor: Fill color.
201
+ edgecolor: Edge color.
202
+ linewidth: Edge width.
203
+ zorder: Drawing order.
204
+
205
+ Returns:
206
+ The rectangle object.
207
+ """
208
+ ...
209
+
210
+ def set_xlim(self, ax: Any, left: float, right: float) -> None:
211
+ """Set x-axis limits.
212
+
213
+ Args:
214
+ ax: Axes or panel.
215
+ left: Minimum x value.
216
+ right: Maximum x value.
217
+ """
218
+ ...
219
+
220
+ def set_ylim(self, ax: Any, bottom: float, top: float) -> None:
221
+ """Set y-axis limits.
222
+
223
+ Args:
224
+ ax: Axes or panel.
225
+ bottom: Minimum y value.
226
+ top: Maximum y value.
227
+ """
228
+ ...
229
+
230
+ def set_xlabel(self, ax: Any, label: str, fontsize: int = 12) -> None:
231
+ """Set x-axis label.
232
+
233
+ Args:
234
+ ax: Axes or panel.
235
+ label: Label text.
236
+ fontsize: Font size.
237
+ """
238
+ ...
239
+
240
+ def set_ylabel(self, ax: Any, label: str, fontsize: int = 12) -> None:
241
+ """Set y-axis label.
242
+
243
+ Args:
244
+ ax: Axes or panel.
245
+ label: Label text.
246
+ fontsize: Font size.
247
+ """
248
+ ...
249
+
250
+ def set_title(self, ax: Any, title: str, fontsize: int = 14) -> None:
251
+ """Set panel title.
252
+
253
+ Args:
254
+ ax: Axes or panel.
255
+ title: Title text.
256
+ fontsize: Font size.
257
+ """
258
+ ...
259
+
260
+ def create_twin_axis(self, ax: Any) -> Any:
261
+ """Create a secondary y-axis sharing the same x-axis.
262
+
263
+ Args:
264
+ ax: Primary axes.
265
+
266
+ Returns:
267
+ Secondary axes for overlay (e.g., recombination rate).
268
+ """
269
+ ...
270
+
271
+ def add_legend(
272
+ self,
273
+ ax: Any,
274
+ handles: List[Any],
275
+ labels: List[str],
276
+ loc: str = "upper left",
277
+ title: Optional[str] = None,
278
+ ) -> Any:
279
+ """Add a legend to the axes.
280
+
281
+ Args:
282
+ ax: Axes or panel.
283
+ handles: Legend handle objects.
284
+ labels: Legend labels.
285
+ loc: Legend location.
286
+ title: Legend title.
287
+
288
+ Returns:
289
+ The legend object.
290
+ """
291
+ ...
292
+
293
+ def hide_spines(self, ax: Any, spines: List[str]) -> None:
294
+ """Hide specified axis spines.
295
+
296
+ Args:
297
+ ax: Axes or panel.
298
+ spines: List of spine names ('top', 'right', 'bottom', 'left').
299
+ """
300
+ ...
301
+
302
+ def format_xaxis_mb(self, ax: Any) -> None:
303
+ """Format x-axis to show megabase values.
304
+
305
+ Args:
306
+ ax: Axes or panel.
307
+ """
308
+ ...
309
+
310
+ def save(
311
+ self,
312
+ fig: Any,
313
+ path: str,
314
+ dpi: int = 150,
315
+ bbox_inches: str = "tight",
316
+ ) -> None:
317
+ """Save figure to file.
318
+
319
+ Args:
320
+ fig: Figure object.
321
+ path: Output file path (.png, .pdf, .html).
322
+ dpi: Resolution for raster formats.
323
+ bbox_inches: Bounding box adjustment.
324
+ """
325
+ ...
326
+
327
+ def show(self, fig: Any) -> None:
328
+ """Display the figure.
329
+
330
+ Args:
331
+ fig: Figure object.
332
+ """
333
+ ...
334
+
335
+ def close(self, fig: Any) -> None:
336
+ """Close the figure and free resources.
337
+
338
+ Args:
339
+ fig: Figure object.
340
+ """
341
+ ...