plot-misc 2.0.2__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.
Files changed (35) hide show
  1. plot_misc/__init__.py +1 -0
  2. plot_misc/_version.py +1 -0
  3. plot_misc/barchart.py +523 -0
  4. plot_misc/constants.py +118 -0
  5. plot_misc/errors.py +328 -0
  6. plot_misc/example_data/__init__.py +1 -0
  7. plot_misc/example_data/example_datasets/bar_points.tsv.gz +0 -0
  8. plot_misc/example_data/example_datasets/barchart.tsv.gz +0 -0
  9. plot_misc/example_data/example_datasets/calibration_bins.tsv.gz +0 -0
  10. plot_misc/example_data/example_datasets/calibration_data.tsv.gz +0 -0
  11. plot_misc/example_data/example_datasets/forest_data.tsv.gz +0 -0
  12. plot_misc/example_data/example_datasets/group_bar.tsv.gz +0 -0
  13. plot_misc/example_data/example_datasets/heatmap_data.tsv.gz +0 -0
  14. plot_misc/example_data/example_datasets/incidence_matrix_data.tsv.gz +0 -0
  15. plot_misc/example_data/example_datasets/lollipop_data.tsv.gz +0 -0
  16. plot_misc/example_data/example_datasets/mace_associations.tsv.gz +0 -0
  17. plot_misc/example_data/example_datasets/net_benefit.tsv.gz +0 -0
  18. plot_misc/example_data/example_datasets/string_data.txt +1 -0
  19. plot_misc/example_data/example_datasets/volcano.tsv.gz +0 -0
  20. plot_misc/example_data/examples.py +637 -0
  21. plot_misc/forest.py +1478 -0
  22. plot_misc/heatmap.py +369 -0
  23. plot_misc/incidencematrix.py +394 -0
  24. plot_misc/machine_learning.py +1143 -0
  25. plot_misc/piechart.py +197 -0
  26. plot_misc/utils/__init__.py +1 -0
  27. plot_misc/utils/colour.py +171 -0
  28. plot_misc/utils/formatting.py +369 -0
  29. plot_misc/utils/utils.py +1151 -0
  30. plot_misc/volcano.py +203 -0
  31. plot_misc-2.0.2.dist-info/METADATA +107 -0
  32. plot_misc-2.0.2.dist-info/RECORD +35 -0
  33. plot_misc-2.0.2.dist-info/WHEEL +5 -0
  34. plot_misc-2.0.2.dist-info/licenses/LICENSE +18 -0
  35. plot_misc-2.0.2.dist-info/top_level.txt +1 -0
plot_misc/heatmap.py ADDED
@@ -0,0 +1,369 @@
1
+ """
2
+ Heatmap drawing and annotation tools built on top of matplotlib and seaborn.
3
+
4
+ This module provides flexible functions to create and annotate heatmaps using
5
+ either `matplotlib` or `seaborn`, with extensive support for customisation and
6
+ publication-quality output. It includes functionality for standard heatmaps,
7
+ clustered heatmaps, and embedded annotations with control over grid styling,
8
+ tick labelling, and colourbar presentation.
9
+
10
+ Functions
11
+ ---------
12
+ heatmap(data, row_labels, col_labels, ...)
13
+ Draws a standard heatmap using matplotlib's `imshow`, with options for
14
+ gridlines, tick formatting, and embedded colourbars.
15
+
16
+ annotate_heatmap(im, data=None, valfmt=None, ...)
17
+ Adds text annotations to an existing heatmap image (AxesImage object),
18
+ with configurable formatting and colour thresholding.
19
+
20
+ clustermap(data, cmap='Spectral', annot=None, ...)
21
+ Wraps seaborn's `clustermap` with additional layout and styling options
22
+ suitable for compact or publication figures.
23
+
24
+ Notes
25
+ -----
26
+ The base structure of the `heatmap` and `annotate_heatmap` functions is derived
27
+ from the example published in the official matplotlib gallery [1]_.
28
+
29
+ References
30
+ ----------
31
+ .. [1] https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html
32
+ """
33
+
34
+ # modules
35
+ import numpy as np
36
+ import pandas as pd
37
+ import seaborn as sns
38
+ import matplotlib
39
+ import matplotlib.pyplot as plt
40
+ from plot_misc.utils.utils import _update_kwargs
41
+ from plot_misc.errors import (
42
+ is_type,
43
+ )
44
+ from plot_misc.constants import Real
45
+ from typing import (
46
+ Any,
47
+ Optional,
48
+ )
49
+
50
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51
+ def heatmap(data:pd.DataFrame | np.ndarray, row_labels:list[str] | np.ndarray,
52
+ col_labels:list[str] | np.ndarray, grid_col:str='white',
53
+ grid_linestyle:str='-', grid_linewidth:float=3,
54
+ cbar_bool:bool=False, cbar_label:str="",
55
+ ax:plt.Axes | None = None,
56
+ grid_kw:dict[Any,Any] | None = None,
57
+ cbar_kw:dict[Any,Any] | None = None,
58
+ **kwargs:Optional[Any],
59
+ ) -> tuple[matplotlib.image.AxesImage,
60
+ matplotlib.colorbar.Colorbar]:
61
+ """
62
+ Plot a heatmap with row and column labels using matplotlib.
63
+
64
+ This function draws a heatmap using `imshow`, with options to configure
65
+ grid lines, colourbars, and axis labels. It accepts both NumPy arrays
66
+ and pandas DataFrames as input.
67
+
68
+ Parameters
69
+ ----------
70
+ data : `pd.DataFrame` or `np.array`
71
+ A 2D array of shape (M, N) containing the values to plot.
72
+ row_labels : `list` [`str`] or `np.ndarray`
73
+ A list or array of length M with the labels for the rows.
74
+ col_labels : `list` [`str`] or `np.ndarray`
75
+ A list or array of length N with the labels for the rows.
76
+ grid_col : `str`, default 'white'
77
+ The colour of the grid lines
78
+ grid_linestyle : `str`, default '-'
79
+ The linestyle of the grid lines
80
+ grid_linewidth : `float`, default 3
81
+ The width of the grid lines.
82
+ cbar_bool : `bool`, default `False`
83
+ If `True`, add a colourbar to the figure.
84
+ cbar_label : `str`, default " "
85
+ The label for the colorbar.
86
+ ax : `plt.Axes` or `None`, default NoneType
87
+ A `matplotlib.axes.Axes` instance to which the heatmap is plotted. If
88
+ not provided, use current axes or create a new one.
89
+ grid_kw : `dict` [`str`,`any`] or `None`, default None
90
+ A dictionary with arguments to `matplotlib.Axes.grid`.
91
+ cbar_kw : `dict` [`str`, `any`] or `None`, default `None`
92
+ A dictionary with arguments to `matplotlib.Figure.colorbar`.
93
+ **kwargs : `any`
94
+ All other arguments are forwarded to `imshow`.
95
+
96
+ Returns
97
+ -------
98
+ im : `matplotlib.image.AxesImage`
99
+ The heatmap image object.
100
+ cbar : `matplotlib.colorbar.Colorbar` or `None`
101
+ The colourbar object if `cbar_bool` is `True`, otherwise `None`.
102
+
103
+ Notes
104
+ -----
105
+ The returned objects can be used to annotate the cells using for example
106
+ `annotate_heatmap`.
107
+
108
+ This function is adapted from the matplotlib gallery example [1]_.
109
+ """
110
+
111
+ # create a axes if needed
112
+ if not ax:
113
+ ax = plt.gca()
114
+ # check input
115
+ if isinstance(data, pd.DataFrame):
116
+ matrix = data.copy().to_numpy()
117
+ else:
118
+ matrix = data
119
+ # copy
120
+ row_lab = row_labels
121
+ col_lab = col_labels
122
+ # check additional input
123
+ is_type(row_lab, (list, np.array))
124
+ is_type(col_lab, (list, np.array))
125
+ is_type(cbar_label, str)
126
+ # map None to dict
127
+ grid_kw = grid_kw or {}
128
+ cbar_kw = cbar_kw or {}
129
+ # ### Plot the heatmap
130
+ im = ax.imshow(matrix, **kwargs)
131
+ # Create colorbar
132
+ if cbar_bool == True:
133
+ # NOTE if the kwargs for colobar is extended use `_update_kwargs
134
+ cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw)
135
+ cbar.ax.set_ylabel(cbar_label, rotation=-90, va="bottom")
136
+ else:
137
+ cbar = None
138
+ # Show all ticks and label them with the respective list entries.
139
+ ax.set_xticks(np.arange(matrix.shape[1]))
140
+ ax.set_xticklabels(col_lab)
141
+ ax.set_yticks(np.arange(matrix.shape[0]))
142
+ ax.set_yticklabels(row_lab)
143
+ # Let the horizontal axes labeling appear on top.
144
+ # ax.tick_params(top=True, bottom=False,
145
+ # labeltop=True, labelbottom=False)
146
+ # Rotate the tick labels and set their alignment.
147
+ plt.setp(ax.get_xticklabels(), rotation=30, ha="right",
148
+ rotation_mode="anchor")
149
+ # Turn spines off and create white grid.
150
+ ax.spines['top'].set_visible(False)
151
+ ax.spines['bottom'].set_visible(False)
152
+ ax.spines['right'].set_visible(False)
153
+ ax.spines['left'].set_visible(False)
154
+ # set tick marks
155
+ ax.set_xticks(np.arange(matrix.shape[1]+1)-.5, minor=True)
156
+ ax.set_yticks(np.arange(matrix.shape[0]+1)-.5, minor=True)
157
+ # grid
158
+ new_grid_kwargs = _update_kwargs(
159
+ update_dict=grid_kw, which="minor", color=grid_col,
160
+ linestyle=grid_linestyle, linewidth=grid_linewidth,
161
+ )
162
+ ax.grid(**new_grid_kwargs)
163
+ ax.tick_params(which="minor", bottom=False, left=False)
164
+ # return stuff
165
+ return im, cbar
166
+
167
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
168
+ def annotate_heatmap(
169
+ im:plt.Axes.imshow,
170
+ data:pd.DataFrame | np.ndarray | None = None,
171
+ valfmt:str | matplotlib.ticker.Formatter | None = None,
172
+ textcolors:tuple[str,str] | list[str,str]=("black","white"),
173
+ threshold: float | None = None,
174
+ **kwargs:Optional[Any],
175
+ ) -> list[plt.Text]:
176
+ """
177
+ Annotate each cell in a heatmap image with its value.
178
+
179
+ This function adds text annotations to an existing `AxesImage` object,
180
+ such as those created by the `heatmap` function. The text colour may
181
+ be adjusted dynamically based on a threshold value and the image’s colour
182
+ map.
183
+
184
+ Parameters
185
+ ----------
186
+ im : `plt.Axes.imshow`
187
+ The AxesImage to be labeled.
188
+ data : `pd.DataFrame`, `np.array`, or `None`, default `Nonetype`
189
+ A 2D numpy array of shape (M, N). If `None`, the function uses the
190
+ array embedded in `im`.
191
+ valfmt : `str`, `matplotlib.ticker.Formatter` or `None`, default `NoneType`
192
+ The format of the annotations inside the heatmap. This should either
193
+ use the string format method, e.g. "$ {x:.2f}" - (note the `x` is needs
194
+ to be included to represent the numerical), or be a
195
+ `matplotlib.ticker.Formatter`.
196
+ textcolors : `list` or `tuple` [`str`, `str`], default `('black', 'white')`
197
+ A pair of colors. The first is used for values below a threshold,
198
+ the second for those above.
199
+ threshold : `float` or `None`, default `NoneType`
200
+ The absolute value in data units according to which the colors from
201
+ textcolors are applied. If None (the default) uses the middle of the
202
+ colormap as separation.
203
+ **kwargs : `any`
204
+ All other arguments are forwarded to each call to `text` used to create
205
+ the text labels.
206
+
207
+ Returns
208
+ -------
209
+ texts : `list` of `matplotlib.text.Text`
210
+ A list of text annotation objects added to the heatmap.
211
+ """
212
+
213
+ # mapping data to matrix
214
+ values = im.get_array()
215
+ if data is None:
216
+ matrix = im.get_array()
217
+ elif isinstance(data, pd.DataFrame):
218
+ matrix = data.copy().to_numpy()
219
+ else:
220
+ matrix = data
221
+ # Normalize the threshold to the images color range.
222
+ if threshold is not None:
223
+ threshold = im.norm(threshold)
224
+ else:
225
+ # this will value is matrix is a string
226
+ try:
227
+ threshold = im.norm(values.max())/2.
228
+ except np.core._exceptions.UFuncTypeError:
229
+ threshold = None
230
+ # Set default alignment to center, but allow it to be
231
+ # overwritten by text_kw.
232
+ kw = _update_kwargs(update_dict=kwargs,
233
+ horizontalalignment="center",
234
+ verticalalignment="center",
235
+ )
236
+ # Get the formatter in case a string is supplied
237
+ if not valfmt is None:
238
+ if isinstance(valfmt, str):
239
+ valfmt = matplotlib.ticker.StrMethodFormatter(valfmt)
240
+ # Loop over the data and create a `Text` for each "pixel".
241
+ # Change the text's color depending on the data.
242
+ texts = []
243
+ for i in range(matrix.shape[0]):
244
+ for j in range(matrix.shape[1]):
245
+ # only run if threshold exists
246
+ if not threshold is None:
247
+ kw.update(color=textcolors[int(im.norm(abs(values[i, j])) > threshold)])
248
+ # format text or not
249
+ if not valfmt is None:
250
+ text = im.axes.text(j, i, valfmt(matrix[i, j], None), **kw)
251
+ else:
252
+ text = im.axes.text(j, i, matrix[i,j], **kw)
253
+ texts.append(text)
254
+ # returning stuff
255
+ return texts
256
+
257
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258
+ def clustermap(data:pd.DataFrame,
259
+ cmap:matplotlib.cm.get_cmap=matplotlib.colormaps.get_cmap('Spectral'),
260
+ annot:pd.DataFrame | None = None,
261
+ fsize:tuple[Real,Real]=(15, 15),
262
+ linewidths:float=1.0,
263
+ cpos: tuple[float] | None =(0.09, 0.02, 0.03, 0.10),
264
+ annotsize:float=6, clab:str='', clabfs:float=7, fmt:str=".3",
265
+ clabpos:str='left', clabtsize:float=5, xticklabsize:float=8,
266
+ yticklabsize:float=6, yticks:bool=True, xticks:bool=True,
267
+ cbar_dict_kw:dict[Any,Any] | None = None,
268
+ tree_dict_kw:dict[Any,Any] | None = None,
269
+ annot_dict_kw:dict[Any,Any] | None = None,
270
+ clustermap_dict_kw:dict[Any,Any] | None = None,
271
+ ) -> sns.matrix.ClusterGrid:
272
+ """
273
+ Plot a clustered heatmap using seaborn's clustermap API.
274
+
275
+ Wraps `seaborn.clustermap` with additional controls for layout,
276
+ annotation, and appearance. Allows clustering of both rows and columns
277
+ and supports annotated values, tick styling, and custom colourbars.
278
+
279
+ Parameters
280
+ ----------
281
+ data : `pd.DataFrame`
282
+ A datafarme of shape (M, N).
283
+ fsize : `tuple` [`real`, `real`], default `(15, 15)`
284
+ figure size in inches
285
+ linewidths : `float`, default 1.0
286
+ Width of the gridlines between cells.
287
+ annot : `pd.DataFrame` or `None`, default `NoneType`
288
+ An opional dataframe used for annotation.
289
+ annotsize : `float`, default 6.0
290
+ Font size for annotations, will be parsed to
291
+ `matplotlib.axes.Axes.text`.
292
+ fmt : `str`, default '.3'
293
+ String formatting code to use when adding annotations.
294
+ cmap : `matplotlib.colormaps`, default 'viridis'
295
+ matplotlib colormaps
296
+ cpos : `tuple` [`float`, `float`, `float`, `float`]
297
+ Default (0.09,0.02.0.03,0.10). Position of the colourbar in figure
298
+ coordinates: `(left, bottom, width, height)`. Set to `None` to disable
299
+ the colourbar.
300
+ clab : `str`, default ' '
301
+ colour guide y-axis title.
302
+ clabsf : `float`, default 7.0.
303
+ Font size for the colourbar label.
304
+ clabpos : `str`, `left`
305
+ Position of the colourbar label (e.g., `'left'`, `'right'`).
306
+ clabtsize : `float`, default 5.0
307
+ Font size for the colourbar tick labels.
308
+ xticklabsize : `float`, default 8.0
309
+ Font size for x-axis tick labels.
310
+ yticklabsize : `float`, default 6.0
311
+ Font size for y-axis tick labels.
312
+ yticks : `bool`, default `True`
313
+ Whether to display y-axis tick marks.
314
+ xticks : `bool`, default `True`
315
+ Whether to display x-axis tick marks.
316
+ cbar_dict_kw : `dict` [`any`, `any`], optional
317
+ Keyword arguments passed to `Figure.colorbar()`.
318
+ tree_dict_kw : `dict` [`any`, `any`], optional
319
+ Keyword arguments passed to dendrogram tree plotting.
320
+ annot_dict_kw : `dict` [`any`, `any`], optional
321
+ Keyword arguments passed to annotation text formatting.
322
+ clustermap_dict_kw : `dict` [`any`, `any`], optional
323
+ Keyword arguments passed to `seaborn.clustermap()`.
324
+
325
+ Returns
326
+ -------
327
+ cm : `seaborn.matrix.ClusterGrid`
328
+ A seaborn cluster grid object with the full figure layout.
329
+ """
330
+ # #### constants
331
+ # map None to dict
332
+ cbar_dict_kw = cbar_dict_kw or {}
333
+ tree_dict_kw = tree_dict_kw or {}
334
+ annot_dict_kw = annot_dict_kw or {}
335
+ clustermap_dict_kw = clustermap_dict_kw or {}
336
+ # update keyword dictionaries
337
+ annot_kw = _update_kwargs(update_dict=annot_dict_kw,
338
+ size=annotsize,
339
+ )
340
+ clustermap_kw = _update_kwargs(update_dict=clustermap_dict_kw,
341
+ fmt=fmt, linewidths=linewidths,
342
+ figsize=(fsize[0], fsize[1]),
343
+ cbar_pos=cpos, cmap=cmap,
344
+ annot=annot,
345
+ )
346
+ # make figure
347
+ cm = sns.clustermap(data,
348
+ cbar_kws=cbar_dict_kw,
349
+ tree_kws=tree_dict_kw,
350
+ annot_kws=annot_kw,
351
+ **clustermap_kw,
352
+ )
353
+ # cbar labels
354
+ cm.ax_cbar.axes.yaxis.set_label_text(clab, fontsize=clabfs)
355
+ cm.ax_cbar.axes.yaxis.set_label_position(clabpos)
356
+ cm.ax_cbar.tick_params(labelsize=clabtsize)
357
+ # heatmap tick labels
358
+ cm.ax_heatmap.set_xticklabels(cm.ax_heatmap.get_xmajorticklabels(),
359
+ fontsize = xticklabsize)
360
+ cm.ax_heatmap.set_yticklabels(cm.ax_heatmap.get_ymajorticklabels(),
361
+ fontsize = yticklabsize)
362
+ # removing axis labels
363
+ cm.ax_heatmap.set_ylabel("")
364
+ cm.ax_heatmap.set_xlabel("")
365
+ # add both xy ticks
366
+ cm.ax_heatmap.tick_params('both', reset=False, bottom=xticks, right=yticks)
367
+ # return
368
+ return cm
369
+