plot-misc 2.2.1__py3-none-any.whl → 2.3.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.
- plot_misc/__init__.py +11 -0
- plot_misc/_version.py +1 -1
- plot_misc/constants.py +6 -0
- plot_misc/example_data/examples.py +48 -0
- plot_misc/forest.py +0 -14
- plot_misc/heatmap.py +223 -11
- plot_misc/machine_learning.py +10 -14
- plot_misc/survival.py +1 -0
- plot_misc/utils/formatting.py +1 -1
- plot_misc/utils/utils.py +275 -135
- plot_misc/volcano.py +31 -12
- {plot_misc-2.2.1.dist-info → plot_misc-2.3.0.dist-info}/METADATA +26 -10
- {plot_misc-2.2.1.dist-info → plot_misc-2.3.0.dist-info}/RECORD +16 -16
- {plot_misc-2.2.1.dist-info → plot_misc-2.3.0.dist-info}/WHEEL +1 -1
- {plot_misc-2.2.1.dist-info → plot_misc-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {plot_misc-2.2.1.dist-info → plot_misc-2.3.0.dist-info}/top_level.txt +0 -0
plot_misc/__init__.py
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
plot-misc: matplotlib-based plotting archetypes for scientific figures.
|
|
3
|
+
|
|
4
|
+
A curated collection of user-oriented plotting functions and
|
|
5
|
+
classes built on top of `matplotlib`. Each function returns standard
|
|
6
|
+
matplotlib `Figure`/`Axes` objects, so results can be further customised
|
|
7
|
+
with familiar matplotlib methods. Per the package design callables are limited
|
|
8
|
+
to creating illustrations, and should data be internally calculated this is
|
|
9
|
+
done with options for user overwrites, while making the derived data available
|
|
10
|
+
for inspection and re-use.
|
|
11
|
+
"""
|
|
1
12
|
from ._version import __version__
|
plot_misc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.
|
|
1
|
+
__version__ = '2.3.0'
|
plot_misc/constants.py
CHANGED
|
@@ -58,6 +58,8 @@ class UtilsNames(object):
|
|
|
58
58
|
annot_pval = 'matrix_pvalue'
|
|
59
59
|
annot_effect = 'matrix_point_estimate'
|
|
60
60
|
value_point = 'curated_matrix_point_estimate_value'
|
|
61
|
+
value_unsigned_log = 'curated_matrix_value_unsigned_log'
|
|
62
|
+
value_raw = 'curated_matrix_value_raw'
|
|
61
63
|
value_original = 'crude_point_estimate'
|
|
62
64
|
source_data = 'source_data'
|
|
63
65
|
mat_point = 'point'
|
|
@@ -67,8 +69,12 @@ class UtilsNames(object):
|
|
|
67
69
|
mat_outcome = 'outcome'
|
|
68
70
|
mat_exposure_list = ['IL2ra', 'IP10', 'SCF', 'TRAIL']
|
|
69
71
|
mat_outcome_list = ['HDL-C', 'LDL-C']
|
|
72
|
+
mat_annot_symbol = 'symbol'
|
|
70
73
|
mat_annot_star = 'star'
|
|
71
74
|
mat_annot_pval = 'pvalues'
|
|
75
|
+
mat_annot_pval_signed = 'pvalues_signed'
|
|
76
|
+
mat_annot_pval_unsigned = 'pvalues_unsigned'
|
|
77
|
+
mat_annot_pval_raw = 'pvalues_raw'
|
|
72
78
|
mat_annot_point = 'point_estimates'
|
|
73
79
|
mat_annot_none = '`NoneType`'
|
|
74
80
|
roc_false_positive = 'false_positive'
|
|
@@ -508,6 +508,54 @@ def heatmap_pvalue_matrix(**kwargs):
|
|
|
508
508
|
data.index.name = UtilsNames.mat_outcome
|
|
509
509
|
return data
|
|
510
510
|
|
|
511
|
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
512
|
+
@dataset
|
|
513
|
+
def qc_matrix(seed=2026):
|
|
514
|
+
"""
|
|
515
|
+
Creates a dummy quality-control (QC) data set to showcase
|
|
516
|
+
`plot_misc.heatmap.masked_heatmap`.
|
|
517
|
+
|
|
518
|
+
The values are signed, standardised QC deviations for a set of samples
|
|
519
|
+
(rows) across several QC metrics (columns). The accompanying indicator
|
|
520
|
+
flags the cells that failed QC (an absolute deviation above 2), i.e. the
|
|
521
|
+
cells `masked_heatmap` should highlight; the passing cells are left to the
|
|
522
|
+
background layer.
|
|
523
|
+
|
|
524
|
+
Parameters
|
|
525
|
+
----------
|
|
526
|
+
seed : `int`, default 2026
|
|
527
|
+
Seed for the random number generator, ensuring a reproducible matrix.
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
-------
|
|
531
|
+
values : `pd.DataFrame`
|
|
532
|
+
Signed standardised QC deviations of shape (12, 6).
|
|
533
|
+
indicator : `pd.DataFrame`
|
|
534
|
+
A binary (0/1) table of the same shape as `values`, equal to 1 where
|
|
535
|
+
the metric failed QC and 0 otherwise.
|
|
536
|
+
"""
|
|
537
|
+
rng = np.random.default_rng(seed)
|
|
538
|
+
samples = ['Sample_{:02d}'.format(i) for i in range(1, 13)]
|
|
539
|
+
metrics = [
|
|
540
|
+
'CallRate', 'Heterozygosity', 'Contamination', 'MeanDepth',
|
|
541
|
+
'DuplicationRate', 'InsertSize',
|
|
542
|
+
]
|
|
543
|
+
values = pd.DataFrame(
|
|
544
|
+
rng.normal(loc=0.0, scale=1.3, size=(len(samples), len(metrics))),
|
|
545
|
+
index=samples, columns=metrics,
|
|
546
|
+
)
|
|
547
|
+
# inject a handful of unambiguous QC failures so the showcase always has
|
|
548
|
+
# highlighted cells regardless of the random draw
|
|
549
|
+
values.iloc[0, 2] = 3.4
|
|
550
|
+
values.iloc[3, 0] = -3.1
|
|
551
|
+
values.iloc[5, 4] = 2.8
|
|
552
|
+
values.iloc[7, 1] = -2.6
|
|
553
|
+
values.iloc[9, 5] = 3.0
|
|
554
|
+
values.iloc[11, 3] = -2.9
|
|
555
|
+
values = values.round(3)
|
|
556
|
+
indicator = (values.abs() > 2).astype(int)
|
|
557
|
+
return values, indicator
|
|
558
|
+
|
|
511
559
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
512
560
|
@dataset
|
|
513
561
|
def load_calibration_data(**kwargs):
|
plot_misc/forest.py
CHANGED
|
@@ -982,20 +982,6 @@ class EmpiricalSupport(object):
|
|
|
982
982
|
results_ : `EmpiricalSupportResults`
|
|
983
983
|
An EmpiricalSupportResults instance.
|
|
984
984
|
|
|
985
|
-
Methods
|
|
986
|
-
-------
|
|
987
|
-
calc_empirical_support(estimate, standard_error, alpha)
|
|
988
|
-
Computes the range of confidence intervals and compatibility metrics
|
|
989
|
-
over the supplied alpha values.
|
|
990
|
-
|
|
991
|
-
plot_tree(...)
|
|
992
|
-
Creates a 'tree plot' summarising the parameter space supported by
|
|
993
|
-
the data, with options for CI and estimate annotations.
|
|
994
|
-
|
|
995
|
-
_plot_empirical_support(...)
|
|
996
|
-
Generates a visualisation of confidence intervals and their overlap
|
|
997
|
-
across varying alpha values.
|
|
998
|
-
|
|
999
985
|
Notes
|
|
1000
986
|
-----
|
|
1001
987
|
This implementation is based on the concept of compatibility (or
|
plot_misc/heatmap.py
CHANGED
|
@@ -11,6 +11,10 @@ heatmap(data, row_labels, col_labels, ...)
|
|
|
11
11
|
Draws a standard heatmap using matplotlib's `imshow`, with options for
|
|
12
12
|
gridlines, tick formatting, and embedded colourbars.
|
|
13
13
|
|
|
14
|
+
masked_heatmap(data, indicator, row_labels, col_labels, ...)
|
|
15
|
+
Draws a two-layer heatmap: a single-colour background and, on top of it,
|
|
16
|
+
the heatmap restricted to the cells flagged by a binary indicator table.
|
|
17
|
+
|
|
14
18
|
annotate_heatmap(im, data=None, valfmt=None, ...)
|
|
15
19
|
Adds text annotations to an existing heatmap image (AxesImage object),
|
|
16
20
|
with configurable formatting and colour thresholding.
|
|
@@ -22,9 +26,8 @@ from the example published in the official matplotlib gallery [1]_.
|
|
|
22
26
|
|
|
23
27
|
References
|
|
24
28
|
----------
|
|
25
|
-
.. [1] Matplotlib contributors. "Creating annotated heatmaps."
|
|
26
|
-
|
|
27
|
-
https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html
|
|
29
|
+
.. [1] Matplotlib contributors. "Creating annotated heatmaps." Matplotlib
|
|
30
|
+
Gallery. https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html
|
|
28
31
|
"""
|
|
29
32
|
|
|
30
33
|
# modules
|
|
@@ -32,9 +35,13 @@ import numpy as np
|
|
|
32
35
|
import pandas as pd
|
|
33
36
|
import matplotlib
|
|
34
37
|
import matplotlib.pyplot as plt
|
|
38
|
+
from matplotlib.colors import ListedColormap
|
|
39
|
+
from matplotlib.patches import Rectangle
|
|
35
40
|
from plot_misc.utils.utils import _update_kwargs
|
|
36
41
|
from plot_misc.errors import (
|
|
37
42
|
is_type,
|
|
43
|
+
is_df,
|
|
44
|
+
InputValidationError,
|
|
38
45
|
)
|
|
39
46
|
from plot_misc.constants import Real
|
|
40
47
|
from typing import Any
|
|
@@ -45,6 +52,7 @@ def heatmap(data:pd.DataFrame | np.ndarray, row_labels:list[str] | np.ndarray,
|
|
|
45
52
|
grid_linestyle:str='-', grid_linewidth:float=3,
|
|
46
53
|
cbar_bool:bool=False, cbar_label:str="",
|
|
47
54
|
ax:plt.Axes | None = None,
|
|
55
|
+
figsize:tuple[float,float] | None = None,
|
|
48
56
|
grid_kw:dict[Any,Any] | None = None,
|
|
49
57
|
cbar_kw:dict[Any,Any] | None = None,
|
|
50
58
|
**kwargs:Any,
|
|
@@ -78,6 +86,8 @@ def heatmap(data:pd.DataFrame | np.ndarray, row_labels:list[str] | np.ndarray,
|
|
|
78
86
|
ax : `plt.Axes` or `None`, default None
|
|
79
87
|
A `matplotlib.axes.Axes` instance to which the heatmap is plotted. If
|
|
80
88
|
not provided, use current axes or create a new one.
|
|
89
|
+
figsize : `tuple` [`float`, `float`] or `None`, default `None`
|
|
90
|
+
Figure size in inches (width, height). Ignored if `ax` is provided.
|
|
81
91
|
grid_kw : `dict` [`str`,`any`] or `None`, default None
|
|
82
92
|
A dictionary with arguments to `matplotlib.Axes.grid`.
|
|
83
93
|
cbar_kw : `dict` [`str`, `any`] or `None`, default `None`
|
|
@@ -105,10 +115,20 @@ def heatmap(data:pd.DataFrame | np.ndarray, row_labels:list[str] | np.ndarray,
|
|
|
105
115
|
Matplotlib Gallery.
|
|
106
116
|
https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html
|
|
107
117
|
"""
|
|
108
|
-
|
|
118
|
+
# check in put
|
|
119
|
+
is_type(data, (pd.DataFrame, np.ndarray))
|
|
120
|
+
is_type(row_labels, (list, np.ndarray))
|
|
121
|
+
is_type(col_labels, (list, np.ndarray))
|
|
122
|
+
is_type(grid_col, str)
|
|
123
|
+
is_type(grid_linestyle, str)
|
|
124
|
+
is_type(grid_linewidth, Real)
|
|
125
|
+
is_type(cbar_bool, bool)
|
|
126
|
+
is_type(cbar_label, str)
|
|
109
127
|
# create a axes if needed
|
|
110
|
-
if
|
|
111
|
-
ax = plt.
|
|
128
|
+
if ax is None:
|
|
129
|
+
_, ax = plt.subplots(figsize=figsize)
|
|
130
|
+
else:
|
|
131
|
+
f = ax.figure
|
|
112
132
|
# check input
|
|
113
133
|
if isinstance(data, pd.DataFrame):
|
|
114
134
|
matrix = data.copy().to_numpy()
|
|
@@ -117,10 +137,6 @@ def heatmap(data:pd.DataFrame | np.ndarray, row_labels:list[str] | np.ndarray,
|
|
|
117
137
|
# copy
|
|
118
138
|
row_lab = row_labels
|
|
119
139
|
col_lab = col_labels
|
|
120
|
-
# check additional input
|
|
121
|
-
is_type(row_lab, (list, np.array))
|
|
122
|
-
is_type(col_lab, (list, np.array))
|
|
123
|
-
is_type(cbar_label, str)
|
|
124
140
|
# map None to dict
|
|
125
141
|
grid_kw = grid_kw or {}
|
|
126
142
|
cbar_kw = cbar_kw or {}
|
|
@@ -156,12 +172,201 @@ def heatmap(data:pd.DataFrame | np.ndarray, row_labels:list[str] | np.ndarray,
|
|
|
156
172
|
new_grid_kwargs = _update_kwargs(
|
|
157
173
|
update_dict=grid_kw, which="minor", color=grid_col,
|
|
158
174
|
linestyle=grid_linestyle, linewidth=grid_linewidth,
|
|
159
|
-
)
|
|
175
|
+
clip_on=False,)
|
|
160
176
|
ax.grid(**new_grid_kwargs)
|
|
161
177
|
ax.tick_params(which="minor", bottom=False, left=False)
|
|
162
178
|
# return stuff
|
|
163
179
|
return im, cbar
|
|
164
180
|
|
|
181
|
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
182
|
+
def masked_heatmap(data:pd.DataFrame | np.ndarray,
|
|
183
|
+
indicator:pd.DataFrame | np.ndarray,
|
|
184
|
+
row_labels:list[str] | np.ndarray,
|
|
185
|
+
col_labels:list[str] | np.ndarray,
|
|
186
|
+
background_col:str='white', background_gridcol:str='white',
|
|
187
|
+
background_linestyle:str='-', background_linewidth:float=0.5,
|
|
188
|
+
background_zorder:Real = 1,
|
|
189
|
+
outline_col:str='black', outline_linestyle:str='-',
|
|
190
|
+
outline_linewidth:float=1.5, outline_zorder:Real = 2,
|
|
191
|
+
frame: bool=False,
|
|
192
|
+
cbar_bool:bool=False, cbar_label:str="",
|
|
193
|
+
ax:plt.Axes | None = None,
|
|
194
|
+
figsize:tuple[float,float] | None = None,
|
|
195
|
+
grid_kw:dict[Any,Any] | None = None,
|
|
196
|
+
cbar_kw:dict[Any,Any] | None = None,
|
|
197
|
+
background_kw:dict[Any,Any] | None = None,
|
|
198
|
+
outline_kw:dict[Any,Any] | None = None,
|
|
199
|
+
**kwargs: Any,
|
|
200
|
+
) -> tuple[matplotlib.image.AxesImage,
|
|
201
|
+
matplotlib.colorbar.Colorbar]:
|
|
202
|
+
"""
|
|
203
|
+
Plot a two-layer heatmap masked by a binary indicator table.
|
|
204
|
+
|
|
205
|
+
The function draws two layers. First a single-colour background covering
|
|
206
|
+
every cell (carrying an optional grid lattice). Second, the heatmap of
|
|
207
|
+
`data`, restricted to the cells where `indicator` equals 1.
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
data : `pd.DataFrame` or `np.ndarray`
|
|
212
|
+
A 2D array of shape (M, N) containing the values to plot.
|
|
213
|
+
indicator : `pd.DataFrame` or `np.ndarray`
|
|
214
|
+
A binary (0/1, booleans accepted) array of the same shape as `data`.
|
|
215
|
+
Only cells equal to 1 are drawn and outlined.
|
|
216
|
+
row_labels : `list` [`str`] or `np.ndarray`
|
|
217
|
+
A list or array of length M with the labels for the rows.
|
|
218
|
+
col_labels : `list` [`str`] or `np.ndarray`
|
|
219
|
+
A list or array of length N with the labels for the columns.
|
|
220
|
+
background_col : `str`, default 'white'
|
|
221
|
+
The fill colour of the background layer.
|
|
222
|
+
background_gridcol : `str`, default 'white'
|
|
223
|
+
The colour of the background grid lattice lines.
|
|
224
|
+
background_linestyle : `str`, default '-'
|
|
225
|
+
The linestyle of the background grid lattice.
|
|
226
|
+
background_linewidth : `float`, default 0.5
|
|
227
|
+
The width of the background grid lattice. Set to 0 to suppress it.
|
|
228
|
+
background_zorder : `int`, `float` default `1`
|
|
229
|
+
The draw order of the background grid lattice.
|
|
230
|
+
outline_col : `str`, default 'black'
|
|
231
|
+
The edge colour of the per-cell outlines drawn on `indicator == 1`
|
|
232
|
+
cells.
|
|
233
|
+
outline_linestyle : `str`, default '-'
|
|
234
|
+
The linestyle of the per-cell outlines.
|
|
235
|
+
outline_linewidth : `float`, default 1.5
|
|
236
|
+
The width of the per-cell outlines. Set to 0 to suppress them.
|
|
237
|
+
outline_zorder : `int`, `float`, default `2`
|
|
238
|
+
The draw order of the per-cell outlines.
|
|
239
|
+
frame : `bool`, default `False`
|
|
240
|
+
Whether to plot the spines.
|
|
241
|
+
cbar_bool : `bool`, default `False`
|
|
242
|
+
If `True`, add a colourbar (built from the masked heatmap layer).
|
|
243
|
+
cbar_label : `str`, default ""
|
|
244
|
+
The label for the colourbar.
|
|
245
|
+
ax : `plt.Axes` or `None`, default `None`
|
|
246
|
+
A `matplotlib.axes.Axes` instance to draw on. If `None`, a new figure
|
|
247
|
+
and axes are created.
|
|
248
|
+
figsize : `tuple` [`float`, `float`] or `None`, default `None`
|
|
249
|
+
Figure size in inches (width, height). Ignored if `ax` is provided.
|
|
250
|
+
grid_kw : `dict` [`str`, `any`] or `None`, default `None`
|
|
251
|
+
Additional arguments forwarded to `matplotlib.Axes.grid` for the
|
|
252
|
+
background lattice.
|
|
253
|
+
outline_kw : `dict` [`str`, `any`] or `None`, default `None`
|
|
254
|
+
Additional arguments forwarded to each `matplotlib.patches.Rectangle`
|
|
255
|
+
outline. Outlines default to `clip_on=False` so the borders of cells on
|
|
256
|
+
the matrix boundary are not clipped by the axes edge; pass
|
|
257
|
+
`{'clip_on': True}` to restore clipping.
|
|
258
|
+
cbar_kw : `dict` [`str`, `any`] or `None`, default `None`
|
|
259
|
+
A dictionary with arguments to `matplotlib.Figure.colorbar`.
|
|
260
|
+
background_kw : `dict` [`str`, `any`] or `None`, default `None`,
|
|
261
|
+
A dictionary with arguments to `heatmap.heatmap`.
|
|
262
|
+
**kwargs : `any`,
|
|
263
|
+
All other arguments passed to `masking ax.imshow`.
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
im : `matplotlib.image.AxesImage`
|
|
268
|
+
The masked (foreground) heatmap image object.
|
|
269
|
+
cbar : `matplotlib.colorbar.Colorbar` or `None`
|
|
270
|
+
The colourbar object if `cbar_bool` is `True`, otherwise `None`.
|
|
271
|
+
|
|
272
|
+
Notes
|
|
273
|
+
-----
|
|
274
|
+
The masking is achieved by a separate imshow call setting the cells to
|
|
275
|
+
transparent, revealing the background.
|
|
276
|
+
|
|
277
|
+
The returned `im` mirrors the contract of `heatmap` and can therefore be
|
|
278
|
+
annotated using `annotate_heatmap`.
|
|
279
|
+
"""
|
|
280
|
+
# create an axes if needed
|
|
281
|
+
if ax is None:
|
|
282
|
+
_, ax = plt.subplots(figsize=figsize)
|
|
283
|
+
else:
|
|
284
|
+
f = ax.figure
|
|
285
|
+
# check input types
|
|
286
|
+
is_type(data, (pd.DataFrame, np.ndarray))
|
|
287
|
+
is_type(indicator, (pd.DataFrame, np.ndarray))
|
|
288
|
+
_ = [is_type(k, (dict, type(None))) for k in\
|
|
289
|
+
(grid_kw, cbar_kw, outline_kw, background_kw)]
|
|
290
|
+
# the indicator must match the data shape exactly (full 2D shape, not just
|
|
291
|
+
# the row count)
|
|
292
|
+
if np.shape(data) != np.shape(indicator):
|
|
293
|
+
raise InputValidationError(
|
|
294
|
+
f"`indicator` shape {np.shape(indicator)} does not match `data` "
|
|
295
|
+
f"shape {np.shape(data)}."
|
|
296
|
+
)
|
|
297
|
+
# coerce the data and indicator to numpy arrays
|
|
298
|
+
if isinstance(data, pd.DataFrame):
|
|
299
|
+
matrix = data.copy().to_numpy()
|
|
300
|
+
else:
|
|
301
|
+
matrix = data
|
|
302
|
+
if isinstance(indicator, pd.DataFrame):
|
|
303
|
+
flag = indicator.copy().to_numpy()
|
|
304
|
+
else:
|
|
305
|
+
flag = indicator
|
|
306
|
+
# flag should only contain 0 and 1
|
|
307
|
+
unique_flags = set(np.unique(flag).tolist())
|
|
308
|
+
if not unique_flags.issubset({0, 1}):
|
|
309
|
+
raise InputValidationError(
|
|
310
|
+
f"`indicator` must only contain binary (0/1) values, got "
|
|
311
|
+
f"{sorted(unique_flags)}."
|
|
312
|
+
)
|
|
313
|
+
# setup the kwargs None to dict
|
|
314
|
+
background_kw = background_kw or {}
|
|
315
|
+
# masking_kw = masking_kw or {}
|
|
316
|
+
grid_kw = grid_kw or {}
|
|
317
|
+
cbar_kw = cbar_kw or {}
|
|
318
|
+
outline_kw = outline_kw or {}
|
|
319
|
+
outline_kw = _update_kwargs(update_dict=outline_kw,
|
|
320
|
+
zorder=outline_zorder)
|
|
321
|
+
# the background grid lattice carries the background draw order
|
|
322
|
+
grid_kw = _update_kwargs(update_dict=grid_kw, zorder=background_zorder)
|
|
323
|
+
# ### Layer 1: a single-colour background covering every cell. Reusing
|
|
324
|
+
# `heatmap` keeps a single source of truth for the tick, label, spine and
|
|
325
|
+
# grid (lattice) cosmetics.
|
|
326
|
+
background = np.zeros_like(matrix, dtype=float)
|
|
327
|
+
layer1_kwargs = _update_kwargs(
|
|
328
|
+
update_dict=background_kw,
|
|
329
|
+
data=background, row_labels=row_labels, col_labels=col_labels,
|
|
330
|
+
grid_col=background_gridcol, grid_linestyle=background_linestyle,
|
|
331
|
+
grid_linewidth=background_linewidth, cbar_bool=False, ax=ax,
|
|
332
|
+
grid_kw=grid_kw, cmap=ListedColormap([background_col]),
|
|
333
|
+
)
|
|
334
|
+
heatmap(**layer1_kwargs, )
|
|
335
|
+
# ### Layer 2: the heatmap, masked so only `indicator == 1` cells are drawn.
|
|
336
|
+
masked = np.ma.masked_where(flag == 0, matrix)
|
|
337
|
+
# creating an alpha matrix.
|
|
338
|
+
# user_alpha = masking_kw.pop('alpha', 1.0)
|
|
339
|
+
user_alpha = kwargs.pop('alpha', 1.0)
|
|
340
|
+
alpha = (flag == 1).astype(float) * np.asarray(user_alpha, dtype=float)
|
|
341
|
+
layer2_kwargs = _update_kwargs(
|
|
342
|
+
update_dict=kwargs, alpha=alpha,
|
|
343
|
+
)
|
|
344
|
+
im = ax.imshow(masked, **layer2_kwargs)
|
|
345
|
+
# Create colorbar from the foreground (masked) layer
|
|
346
|
+
if cbar_bool:
|
|
347
|
+
cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw)
|
|
348
|
+
cbar.ax.set_ylabel(cbar_label, rotation=-90, va="bottom")
|
|
349
|
+
else:
|
|
350
|
+
cbar = None
|
|
351
|
+
# ### Outline each `indicator == 1` cell. Zero cells get no patch, so they
|
|
352
|
+
# carry no outline; a zero `outline_linewidth` hides the borders.
|
|
353
|
+
rect_kw = _update_kwargs(update_dict=outline_kw, facecolor='none',
|
|
354
|
+
edgecolor=outline_col,
|
|
355
|
+
linestyle=outline_linestyle,
|
|
356
|
+
linewidth=outline_linewidth,
|
|
357
|
+
clip_on=False,
|
|
358
|
+
)
|
|
359
|
+
rows, cols = np.where(flag == 1)
|
|
360
|
+
# NOTE the 0.5 and 1.0 are imshow fixed convention and should be hardcoded
|
|
361
|
+
for i, j in zip(rows, cols):
|
|
362
|
+
ax.add_patch(Rectangle((j-.5, i-.5), 1, 1, **rect_kw))
|
|
363
|
+
# Show the spines
|
|
364
|
+
if frame:
|
|
365
|
+
for spine in ax.spines.values():
|
|
366
|
+
spine.set_visible(True)
|
|
367
|
+
# return stuff
|
|
368
|
+
return im, cbar
|
|
369
|
+
|
|
165
370
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
166
371
|
def annotate_heatmap(
|
|
167
372
|
im:plt.Axes.imshow,
|
|
@@ -210,6 +415,10 @@ def annotate_heatmap(
|
|
|
210
415
|
|
|
211
416
|
# mapping data to matrix
|
|
212
417
|
values = im.get_array()
|
|
418
|
+
# masked cells (e.g. from `masked_heatmap`) are not drawn and must not be
|
|
419
|
+
# annotated; `getmaskarray` yields a full boolean mask for masked arrays and
|
|
420
|
+
# an all-False mask for plain arrays, leaving the unmasked path unchanged
|
|
421
|
+
mask = np.ma.getmaskarray(values)
|
|
213
422
|
if data is None:
|
|
214
423
|
matrix = im.get_array()
|
|
215
424
|
elif isinstance(data, pd.DataFrame):
|
|
@@ -239,6 +448,9 @@ def annotate_heatmap(
|
|
|
239
448
|
texts = []
|
|
240
449
|
for i in range(matrix.shape[0]):
|
|
241
450
|
for j in range(matrix.shape[1]):
|
|
451
|
+
# skip masked cells, which carry no drawn value to annotate
|
|
452
|
+
if mask[i, j]:
|
|
453
|
+
continue
|
|
242
454
|
# only run if threshold exists
|
|
243
455
|
if threshold is not None:
|
|
244
456
|
kw.update(color=textcolors[int(abs(values[i, j]) >= threshold)])
|
plot_misc/machine_learning.py
CHANGED
|
@@ -55,8 +55,13 @@ from typing import (
|
|
|
55
55
|
Any,
|
|
56
56
|
Callable,
|
|
57
57
|
Union,
|
|
58
|
-
Self,
|
|
59
58
|
)
|
|
59
|
+
# `typing.Self` was added in Python 3.11; fall back to typing_extensions on 3.10
|
|
60
|
+
# (the minimum supported version per pyproject `requires-python`).
|
|
61
|
+
if sys.version_info >= (3, 11):
|
|
62
|
+
from typing import Self
|
|
63
|
+
else:
|
|
64
|
+
from typing_extensions import Self
|
|
60
65
|
from statsmodels.nonparametric.smoothers_lowess import lowess
|
|
61
66
|
# from packaging import version
|
|
62
67
|
# if version.parse('3.4.0') < version.parse(mpl._version.version):
|
|
@@ -665,15 +670,6 @@ class DecisionCurve(object):
|
|
|
665
670
|
include at least one predicted risk score (between 0 and 1) and a
|
|
666
671
|
binary outcome variable.
|
|
667
672
|
|
|
668
|
-
Methods
|
|
669
|
-
-------
|
|
670
|
-
calc_net_benefit(...)
|
|
671
|
-
Computes the net benefit across a range of thresholds for one or more
|
|
672
|
-
models.
|
|
673
|
-
plot(...)
|
|
674
|
-
Visualises the decision curves, with optional smoothing and style
|
|
675
|
-
customisation.
|
|
676
|
-
|
|
677
673
|
Notes
|
|
678
674
|
-----
|
|
679
675
|
This implementation is adapted from the `dcurves` Python package
|
|
@@ -763,8 +759,8 @@ class DecisionCurve(object):
|
|
|
763
759
|
These rates are scaled by the assumed prevalence to allow valid
|
|
764
760
|
comparisons across populations with different case/control ratios.
|
|
765
761
|
|
|
766
|
-
Code adapted from
|
|
767
|
-
`
|
|
762
|
+
Code adapted from the
|
|
763
|
+
`dcurves true/false rate calculation <https://github.com/MSKCC-Epi-Bio/dcurves/blob/main/dcurves/dca.py>`_.
|
|
768
764
|
|
|
769
765
|
Hash: 007c64b
|
|
770
766
|
"""
|
|
@@ -861,8 +857,8 @@ class DecisionCurve(object):
|
|
|
861
857
|
|
|
862
858
|
The resulting table can be visualised using the `plot()` method.
|
|
863
859
|
|
|
864
|
-
Code adapted from
|
|
865
|
-
`
|
|
860
|
+
Code adapted from the
|
|
861
|
+
`dcurves net benefit calculation <https://github.com/MSKCC-Epi-Bio/dcurves/blob/main/dcurves/dca.py>`_
|
|
866
862
|
|
|
867
863
|
Hash: 007c64b
|
|
868
864
|
"""
|
plot_misc/survival.py
CHANGED
|
@@ -292,6 +292,7 @@ def extract_follow_up(data: pd.DataFrame,
|
|
|
292
292
|
-------
|
|
293
293
|
pd.DataFrame
|
|
294
294
|
DataFrame with the following columns:
|
|
295
|
+
|
|
295
296
|
- 'time': Requested time points (as integers)
|
|
296
297
|
- '{output_col}_at_risk': Number at risk at each time point
|
|
297
298
|
- '{output_col}_at_risk_format': Formatted at-risk numbers with
|
plot_misc/utils/formatting.py
CHANGED
|
@@ -16,7 +16,7 @@ format_estimates(point, se=None, lower=None, upper=None, alpha=0.05, ...)
|
|
|
16
16
|
sci_notation(number, sig_fig=2, ...)
|
|
17
17
|
Converts a float into scientific notation with superscript exponents.
|
|
18
18
|
|
|
19
|
-
format_roc(observed, predicted,
|
|
19
|
+
format_roc(observed, predicted, ...)
|
|
20
20
|
Computes ROC curve data and returns it as a tidy DataFrame.
|
|
21
21
|
|
|
22
22
|
string_interval(limits, int_notation, middle, lower_lim, inq_space, sep)
|