iqplot 0.3.8__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.
- iqplot/__init__.py +10 -0
- iqplot/cat.py +1838 -0
- iqplot/dist.py +2764 -0
- iqplot/utils.py +516 -0
- iqplot-0.3.8.dist-info/METADATA +41 -0
- iqplot-0.3.8.dist-info/RECORD +8 -0
- iqplot-0.3.8.dist-info/WHEEL +4 -0
- iqplot-0.3.8.dist-info/licenses/LICENSE +21 -0
iqplot/cat.py
ADDED
|
@@ -0,0 +1,1838 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
import colorcet
|
|
8
|
+
|
|
9
|
+
import bokeh.models
|
|
10
|
+
import bokeh.plotting
|
|
11
|
+
|
|
12
|
+
from . import utils
|
|
13
|
+
from .dist import histogram
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def strip(
|
|
17
|
+
data=None,
|
|
18
|
+
q=None,
|
|
19
|
+
cats=None,
|
|
20
|
+
q_axis="x",
|
|
21
|
+
palette=None,
|
|
22
|
+
order=None,
|
|
23
|
+
p=None,
|
|
24
|
+
show_legend=None,
|
|
25
|
+
legend_location="right",
|
|
26
|
+
legend_orientation="vertical",
|
|
27
|
+
legend_click_policy="hide",
|
|
28
|
+
color_column=None,
|
|
29
|
+
parcoord_column=None,
|
|
30
|
+
tooltips=None,
|
|
31
|
+
marker="circle",
|
|
32
|
+
spread=None,
|
|
33
|
+
cat_grid=False,
|
|
34
|
+
marker_kwargs=None,
|
|
35
|
+
jitter_kwargs=None,
|
|
36
|
+
swarm_kwargs=None,
|
|
37
|
+
parcoord_kwargs=None,
|
|
38
|
+
jitter=None,
|
|
39
|
+
horizontal=None,
|
|
40
|
+
val=None,
|
|
41
|
+
click_policy=None,
|
|
42
|
+
**kwargs,
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
Make a strip plot.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
data : Pandas DataFrame, 1D Numpy array, or xarray
|
|
50
|
+
DataFrame containing tidy data for plotting. If a Numpy array,
|
|
51
|
+
a single category is assumed and a strip plot generated from
|
|
52
|
+
data.
|
|
53
|
+
q : hashable
|
|
54
|
+
Name of column to use as quantitative variable if `data` is a
|
|
55
|
+
Pandas DataFrame. Otherwise, `q` is used as the quantitative
|
|
56
|
+
axis label.
|
|
57
|
+
cats : hashable or list of hashables
|
|
58
|
+
Name of column(s) to use as categorical variable(s).
|
|
59
|
+
q_axis : str, either 'x' or 'y', default 'x'
|
|
60
|
+
Axis along which the quantitative value varies.
|
|
61
|
+
palette : list of strings of hex colors, or single hex string
|
|
62
|
+
If a list, color palette to use. If a single string representing
|
|
63
|
+
a hex color, all glyphs are colored with that color. Default is
|
|
64
|
+
colorcet.b_glasbey_category10 from the colorcet package.
|
|
65
|
+
order : list or None
|
|
66
|
+
If not None, must be a list of unique group names when the input
|
|
67
|
+
data frame is grouped by `cats`. The order of the list specifies
|
|
68
|
+
the ordering of the categorical variables on the categorical
|
|
69
|
+
axis and legend. If None, the categories appear in the order in
|
|
70
|
+
which they appeared in the inputted data frame.
|
|
71
|
+
p : bokeh.plotting.Figure instance, or None (default)
|
|
72
|
+
If None, create a new figure. Otherwise, populate the existing
|
|
73
|
+
figure `p`.
|
|
74
|
+
show_legend : bool, default False
|
|
75
|
+
If True, display legend.
|
|
76
|
+
legend_location : str, default 'right'
|
|
77
|
+
Location of legend. If one of "right", "left", "above", or
|
|
78
|
+
"below", the legend is placed outside of the plot area. If one
|
|
79
|
+
of "top_left", "top_center", "top_right", "center_right",
|
|
80
|
+
"bottom_right", "bottom_center", "bottom_left", "center_left",
|
|
81
|
+
or "center", the legend is placed within the plot area. If a
|
|
82
|
+
2-tuple, legend is placed according to the coordinates in the
|
|
83
|
+
tuple.
|
|
84
|
+
legend_orientation : str, default 'vertical'
|
|
85
|
+
Either 'horizontal' or 'vertical'.
|
|
86
|
+
legend_click_policy : str, default 'hide'
|
|
87
|
+
Either 'hide', 'mute', or None; how the glyphs respond when the
|
|
88
|
+
corresponding category is clicked in the legend.
|
|
89
|
+
color_column : hashable, default None
|
|
90
|
+
Column of `data` to use in determining color of glyphs. If None,
|
|
91
|
+
then `cats` is used.
|
|
92
|
+
parcoord_column : hashable, default None
|
|
93
|
+
Column of `data` to use to construct a parallel coordinate plot.
|
|
94
|
+
Data points with like entries in the parcoord_column are
|
|
95
|
+
connected with lines.
|
|
96
|
+
tooltips : list of 2-tuples
|
|
97
|
+
Specification for tooltips as per Bokeh specifications. For
|
|
98
|
+
example, if we want `col1` and `col2` tooltips, we can use
|
|
99
|
+
`tooltips=[('label 1', '@col1'), ('label 2', '@col2')]`.
|
|
100
|
+
marker : str, default 'circle'
|
|
101
|
+
Name of marker to be used in the plot. Must be one of
|
|
102
|
+
['asterisk', 'circle', 'circle_cross', 'circle_x', 'cross',
|
|
103
|
+
'dash', 'diamond', 'diamond_cross', 'hex', 'inverted_triangle',
|
|
104
|
+
'square', 'square_cross', 'square_x', 'triangle', 'x'].
|
|
105
|
+
spread : str or None, default None
|
|
106
|
+
If 'jitter', spread points out using a jitter transform. If
|
|
107
|
+
'swarm', spread points in beeswarm style. If None or 'none', do
|
|
108
|
+
not spread.
|
|
109
|
+
cat_grid : bool, default False
|
|
110
|
+
If True, show grid lines for categorical axis.
|
|
111
|
+
marker_kwargs : dict
|
|
112
|
+
Keyword arguments to pass when adding markers to the plot.
|
|
113
|
+
["x", "y", "source", "marker", "cat", "legend_label"] are note
|
|
114
|
+
allowed because they are determined by other inputs.
|
|
115
|
+
jitter_kwargs : dict
|
|
116
|
+
Keyword arguments to be passed to `bokeh.transform.jitter()`. If
|
|
117
|
+
not specified, default is
|
|
118
|
+
`{'distribution': 'normal', 'width': 0.1}`. If the user
|
|
119
|
+
specifies `{'distribution': 'uniform'}`, the `'width'` entry is
|
|
120
|
+
adjusted to 0.4. Only active if `spread` is `'jitter'`.
|
|
121
|
+
swarm_kwargs : dict
|
|
122
|
+
Keyword arguments for use in generating swarm. Only active if
|
|
123
|
+
`spread` is `'swarm'`. Keys with allowed values are:
|
|
124
|
+
|
|
125
|
+
- 'corral': Either 'gutter' (default) or 'wrap'. This
|
|
126
|
+
specifies how points that are moved too far out are dealt
|
|
127
|
+
with. Using 'gutter', points are overlaid at the maximum
|
|
128
|
+
allowed distance. Using 'wrap', points are reflected inwards
|
|
129
|
+
from the maximal extent and possibly overlaid with other
|
|
130
|
+
points.
|
|
131
|
+
|
|
132
|
+
- 'priority': Either 'ascending' (default) or 'descending'.
|
|
133
|
+
Sort order when determining which points get moved in the
|
|
134
|
+
y-direction first.
|
|
135
|
+
|
|
136
|
+
- marker_pad_px : Gap between markers in units of pixels,
|
|
137
|
+
default 0.
|
|
138
|
+
parcoord_kwargs : dict
|
|
139
|
+
Keyword arguments to be passed to `p.line()` when making lines
|
|
140
|
+
for the parallel coordinate plot. Default is to have one-pixel
|
|
141
|
+
gray lines.
|
|
142
|
+
jitter : bool, default False
|
|
143
|
+
Deprecated, use `spread`.
|
|
144
|
+
horizontal : bool or None, default None
|
|
145
|
+
Deprecated. Use `q_axis`.
|
|
146
|
+
val : hashable
|
|
147
|
+
Deprecated, use `q`.
|
|
148
|
+
click_policy : str, default 'hide'
|
|
149
|
+
Deprecated. Use `legend_click_policy`.
|
|
150
|
+
kwargs
|
|
151
|
+
Any kwargs to be passed to `bokeh.plotting.figure()` when
|
|
152
|
+
instantiating the figure.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
output : bokeh.plotting.Figure instance
|
|
157
|
+
Plot populated with a strip plot.
|
|
158
|
+
"""
|
|
159
|
+
# Protect against mutability of dicts
|
|
160
|
+
jitter_kwargs = copy.copy(jitter_kwargs)
|
|
161
|
+
swarm_kwargs = copy.copy(swarm_kwargs)
|
|
162
|
+
marker_kwargs = copy.copy(marker_kwargs)
|
|
163
|
+
|
|
164
|
+
q, legend_click_policy, _ = utils._parse_deprecations(
|
|
165
|
+
q, q_axis, val, horizontal, "x", click_policy, legend_click_policy, None, None
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Hand check jitter deprecation
|
|
169
|
+
if jitter is not None:
|
|
170
|
+
if jitter:
|
|
171
|
+
if spread is None:
|
|
172
|
+
spread = "jitter"
|
|
173
|
+
warnings.warn("`jitter` is deprecated. Use spread='jitter'.")
|
|
174
|
+
if spread == "jitter":
|
|
175
|
+
warnings.warn("`jitter` is deprecated. Use spread='jitter'.")
|
|
176
|
+
else:
|
|
177
|
+
raise RuntimeError(
|
|
178
|
+
"`jitter` is deprecated. Use spread='jitter'. `jitter` and `spread` are in conflict."
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
if spread == "jitter":
|
|
182
|
+
raise RuntimeError(
|
|
183
|
+
"`jitter` is deprecated. Use `spread`. `jitter` and `spread` are in conflict."
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
warnings.warn("`jitter` is deprecated. Use spread='jitter'.")
|
|
187
|
+
|
|
188
|
+
# Check spread
|
|
189
|
+
if spread is not None:
|
|
190
|
+
spread = spread.lower()
|
|
191
|
+
if spread == "beeswarm":
|
|
192
|
+
raise RuntimeError("Did you mean `spread='swarm'`?")
|
|
193
|
+
if spread not in ("swarm", "jitter", "none", None):
|
|
194
|
+
raise RuntimeError(
|
|
195
|
+
"Invalid `spread`. Valid choices are 'swarm', 'jitter', and None."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if spread is not None and spread != "none" and parcoord_column is not None:
|
|
199
|
+
raise NotImplementedError(
|
|
200
|
+
"Parallel coordinate plots are not implemented with jitter or swarm spreading."
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if palette is None:
|
|
204
|
+
palette = colorcet.b_glasbey_category10
|
|
205
|
+
|
|
206
|
+
if show_legend is None:
|
|
207
|
+
if color_column is None:
|
|
208
|
+
show_legend = False
|
|
209
|
+
else:
|
|
210
|
+
show_legend = not _color_column_hexcodes(data, color_column)
|
|
211
|
+
|
|
212
|
+
# The legend for a strip plot comes from `color_column`, not `cats`,
|
|
213
|
+
# so only `order` is checked here.
|
|
214
|
+
utils._check_cats_none(cats, order)
|
|
215
|
+
|
|
216
|
+
data, q, cats, show_legend = utils._data_cats(data, q, cats, show_legend, None)
|
|
217
|
+
order = utils._order_to_str(order)
|
|
218
|
+
|
|
219
|
+
cats, cols = utils._check_cat_input(
|
|
220
|
+
data, cats, q, color_column, parcoord_column, tooltips, palette, order, kwargs
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
grouped = data.groupby(cats, sort=False)
|
|
224
|
+
|
|
225
|
+
if p is None:
|
|
226
|
+
p, factors, color_factors = _cat_figure(
|
|
227
|
+
data, grouped, q, order, color_column, q_axis, kwargs
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
if isinstance(p.x_range, bokeh.models.ranges.FactorRange) and q_axis == "x":
|
|
231
|
+
raise RuntimeError("`q_axis` is 'x', but `p` has a categorical x-axis.")
|
|
232
|
+
elif isinstance(p.y_range, bokeh.models.ranges.FactorRange) and q_axis == "y":
|
|
233
|
+
raise RuntimeError("`q_axis` is 'y', but `p` has a categorical y-axis.")
|
|
234
|
+
|
|
235
|
+
_, factors, color_factors = _get_cat_range(
|
|
236
|
+
data, grouped, order, color_column, q_axis
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if tooltips is not None:
|
|
240
|
+
p.add_tools(bokeh.models.HoverTool(tooltips=tooltips, name="hover_glyphs"))
|
|
241
|
+
|
|
242
|
+
if jitter_kwargs is None:
|
|
243
|
+
jitter_kwargs = dict(width=0.1, mean=0, distribution="normal")
|
|
244
|
+
elif not isinstance(jitter_kwargs, dict):
|
|
245
|
+
raise RuntimeError("`jitter_kwargs` must be a dict.")
|
|
246
|
+
elif "width" not in jitter_kwargs:
|
|
247
|
+
if (
|
|
248
|
+
"distribution" not in jitter_kwargs
|
|
249
|
+
or jitter_kwargs["distribution"] == "uniform"
|
|
250
|
+
):
|
|
251
|
+
jitter_kwargs["width"] = 0.4
|
|
252
|
+
else:
|
|
253
|
+
jitter_kwargs["width"] = 0.1
|
|
254
|
+
|
|
255
|
+
if swarm_kwargs is None:
|
|
256
|
+
swarm_kwargs = dict(corral="gutter", priority="ascending", marker_pad_px=0)
|
|
257
|
+
elif not isinstance(swarm_kwargs, dict):
|
|
258
|
+
raise RuntimeError("`swarm_kwargs` must be a dict.")
|
|
259
|
+
if "corral" not in swarm_kwargs:
|
|
260
|
+
swarm_kwargs["corral"] = "gutter"
|
|
261
|
+
if "priority" not in swarm_kwargs:
|
|
262
|
+
swarm_kwargs["priority"] = "ascending"
|
|
263
|
+
if "marker_pad_px" not in swarm_kwargs:
|
|
264
|
+
swarm_kwargs["marker_pad_px"] = 0
|
|
265
|
+
|
|
266
|
+
marker_kwargs = utils._check_marker_kwargs(marker_kwargs)
|
|
267
|
+
|
|
268
|
+
if "name" not in marker_kwargs:
|
|
269
|
+
marker_kwargs["name"] = "hover_glyphs"
|
|
270
|
+
if (
|
|
271
|
+
"color" not in marker_kwargs
|
|
272
|
+
and "fill_color" not in marker_kwargs
|
|
273
|
+
and "line_color" not in marker_kwargs
|
|
274
|
+
):
|
|
275
|
+
if color_column is None:
|
|
276
|
+
color_column = "cat"
|
|
277
|
+
if show_legend:
|
|
278
|
+
warnings.warn(
|
|
279
|
+
"`color_column` is not specified. No legend will be generated."
|
|
280
|
+
)
|
|
281
|
+
show_legend = False
|
|
282
|
+
if color_factors == "hex":
|
|
283
|
+
marker_kwargs["line_color"] = color_column
|
|
284
|
+
marker_kwargs["fill_color"] = color_column
|
|
285
|
+
if show_legend:
|
|
286
|
+
warnings.warn(
|
|
287
|
+
"`color_column` consists of hex colors. No legend will be generated."
|
|
288
|
+
)
|
|
289
|
+
show_legend = False
|
|
290
|
+
elif not show_legend:
|
|
291
|
+
marker_kwargs["fill_color"] = bokeh.transform.factor_cmap(
|
|
292
|
+
color_column, palette=palette, factors=color_factors
|
|
293
|
+
)
|
|
294
|
+
marker_kwargs["line_color"] = bokeh.transform.factor_cmap(
|
|
295
|
+
color_column, palette=palette, factors=color_factors
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if marker == "tick":
|
|
299
|
+
marker = "dash"
|
|
300
|
+
marker = utils._check_marker(marker)
|
|
301
|
+
|
|
302
|
+
if marker == "dash":
|
|
303
|
+
if spread == "swarm":
|
|
304
|
+
raise RuntimeError(
|
|
305
|
+
"Cannot have 'swarm' spreading with dash or tick markers."
|
|
306
|
+
)
|
|
307
|
+
if "angle" not in marker_kwargs and q_axis == "x":
|
|
308
|
+
marker_kwargs["angle"] = np.pi / 2
|
|
309
|
+
if "size" not in marker_kwargs:
|
|
310
|
+
if q_axis == "x":
|
|
311
|
+
marker_kwargs["size"] = p.frame_height * 0.25 / len(grouped)
|
|
312
|
+
else:
|
|
313
|
+
marker_kwargs["size"] = p.frame_width * 0.25 / len(grouped)
|
|
314
|
+
else:
|
|
315
|
+
if "size" not in marker_kwargs:
|
|
316
|
+
marker_kwargs["size"] = 4
|
|
317
|
+
if "line_width" not in marker_kwargs:
|
|
318
|
+
marker_kwargs["line_width"] = 1
|
|
319
|
+
|
|
320
|
+
source_dict = _cat_source_dict(data, cats, cols, color_column)
|
|
321
|
+
|
|
322
|
+
if spread == "swarm":
|
|
323
|
+
r = (marker_kwargs["size"] + marker_kwargs["line_width"]) / 2
|
|
324
|
+
|
|
325
|
+
if (
|
|
326
|
+
q_axis == "x" and "x_axis_type" in kwargs and kwargs["x_axis_type"] == "log"
|
|
327
|
+
) or (
|
|
328
|
+
q_axis == "y" and "y_axis_type" in kwargs and kwargs["y_axis_type"] == "log"
|
|
329
|
+
):
|
|
330
|
+
log_q = True
|
|
331
|
+
else:
|
|
332
|
+
log_q = False
|
|
333
|
+
|
|
334
|
+
if q_axis == "x" and np.all(utils._range_specified(p.x_range)):
|
|
335
|
+
if log_q:
|
|
336
|
+
q_range = [np.log10(p.x_range.start), np.log10(p.x_range.end)]
|
|
337
|
+
else:
|
|
338
|
+
q_range = [p.x_range.start, p.x_range.end]
|
|
339
|
+
elif q_axis == "y" and np.all(utils._range_specified(p.y_range)):
|
|
340
|
+
if log_q:
|
|
341
|
+
q_range = [np.log10(p.y_range.start), np.log10(p.y_range.end)]
|
|
342
|
+
else:
|
|
343
|
+
q_range = [p.y_range.start, p.y_range.end]
|
|
344
|
+
else:
|
|
345
|
+
if log_q:
|
|
346
|
+
q_range_width = np.log10(data[q]).max() - np.log10(data[q]).min()
|
|
347
|
+
q_range = [
|
|
348
|
+
np.log10(data[q]).min() - 0.05 * q_range_width,
|
|
349
|
+
np.log10(data[q]).max() + 0.05 * q_range_width,
|
|
350
|
+
]
|
|
351
|
+
else:
|
|
352
|
+
q_range_width = data[q].max() - data[q].min()
|
|
353
|
+
q_range = [
|
|
354
|
+
data[q].min() - 0.05 * q_range_width,
|
|
355
|
+
data[q].max() + 0.05 * q_range_width,
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
swarm_transform = (
|
|
359
|
+
grouped[q]
|
|
360
|
+
.transform(_swarm, p, r, q_range, q_axis, log_q, **swarm_kwargs)
|
|
361
|
+
.values
|
|
362
|
+
)
|
|
363
|
+
source_dict["__swarm"] = [
|
|
364
|
+
(*cat, y_val) if isinstance(cat, tuple) else (cat, y_val)
|
|
365
|
+
for cat, y_val in zip(source_dict["cat"], swarm_transform)
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
if q_axis == "x":
|
|
369
|
+
x = q
|
|
370
|
+
if spread == "jitter":
|
|
371
|
+
jitter_kwargs["range"] = p.y_range
|
|
372
|
+
y = bokeh.transform.jitter("cat", **jitter_kwargs)
|
|
373
|
+
elif spread == "swarm":
|
|
374
|
+
y = "__swarm"
|
|
375
|
+
else:
|
|
376
|
+
y = "cat"
|
|
377
|
+
if not cat_grid:
|
|
378
|
+
p.ygrid.grid_line_color = None
|
|
379
|
+
else:
|
|
380
|
+
y = q
|
|
381
|
+
if spread == "jitter":
|
|
382
|
+
jitter_kwargs["range"] = p.x_range
|
|
383
|
+
x = bokeh.transform.jitter("cat", **jitter_kwargs)
|
|
384
|
+
elif spread == "swarm":
|
|
385
|
+
x = "__swarm"
|
|
386
|
+
else:
|
|
387
|
+
x = "cat"
|
|
388
|
+
if not cat_grid:
|
|
389
|
+
p.xgrid.grid_line_color = None
|
|
390
|
+
|
|
391
|
+
if parcoord_column is not None:
|
|
392
|
+
source_pc = _parcoord_source(data, q, cats, q_axis, parcoord_column, factors)
|
|
393
|
+
|
|
394
|
+
if parcoord_kwargs is None:
|
|
395
|
+
line_color = "gray"
|
|
396
|
+
parcoord_kwargs = {}
|
|
397
|
+
elif not isinstance(parcoord_kwargs, dict):
|
|
398
|
+
raise RuntimeError("`parcoord_kwargs` must be a dict.")
|
|
399
|
+
|
|
400
|
+
if "color" in parcoord_kwargs and "line_color" not in parcoord_kwargs:
|
|
401
|
+
line_color = parcoord_kwargs.pop("color")
|
|
402
|
+
else:
|
|
403
|
+
line_color = parcoord_kwargs.pop("line_color", "gray")
|
|
404
|
+
|
|
405
|
+
p.multi_line(
|
|
406
|
+
source=source_pc, xs="xs", ys="ys", line_color=line_color, **parcoord_kwargs
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if color_factors == "hex" or color_column == "cat" or not show_legend:
|
|
410
|
+
p.scatter(
|
|
411
|
+
source=bokeh.models.ColumnDataSource(source_dict),
|
|
412
|
+
x=x,
|
|
413
|
+
y=y,
|
|
414
|
+
marker=marker,
|
|
415
|
+
**marker_kwargs,
|
|
416
|
+
)
|
|
417
|
+
else:
|
|
418
|
+
items = []
|
|
419
|
+
df = pd.DataFrame(source_dict)
|
|
420
|
+
for i, (name, g) in enumerate(df.groupby(color_column)):
|
|
421
|
+
marker_kwargs["color"] = palette[i % len(palette)]
|
|
422
|
+
mark = p.scatter(source=g, x=x, y=y, marker=marker, **marker_kwargs)
|
|
423
|
+
items.append((g["__label"].iloc[0], [mark]))
|
|
424
|
+
|
|
425
|
+
if len(p.legend) == 1:
|
|
426
|
+
for item in items:
|
|
427
|
+
p.legend.items.append(
|
|
428
|
+
bokeh.models.LegendItem(label=item[0], renderers=item[1])
|
|
429
|
+
)
|
|
430
|
+
else:
|
|
431
|
+
if len(p.legend) > 1:
|
|
432
|
+
warnings.warn(
|
|
433
|
+
"Ambiguous which legend to add glyphs to. Creating new legend."
|
|
434
|
+
)
|
|
435
|
+
if legend_location in ["right", "left", "above", "below"]:
|
|
436
|
+
legend = bokeh.models.Legend(
|
|
437
|
+
items=items,
|
|
438
|
+
location="center",
|
|
439
|
+
orientation=legend_orientation,
|
|
440
|
+
title=color_column,
|
|
441
|
+
)
|
|
442
|
+
p.add_layout(legend, legend_location)
|
|
443
|
+
elif legend_location in [
|
|
444
|
+
"top_left",
|
|
445
|
+
"top_center",
|
|
446
|
+
"top_right",
|
|
447
|
+
"center_right",
|
|
448
|
+
"bottom_right",
|
|
449
|
+
"bottom_center",
|
|
450
|
+
"bottom_left",
|
|
451
|
+
"center_left",
|
|
452
|
+
"center",
|
|
453
|
+
] or isinstance(legend_location, tuple):
|
|
454
|
+
legend = bokeh.models.Legend(
|
|
455
|
+
items=items,
|
|
456
|
+
location=legend_location,
|
|
457
|
+
orientation=legend_orientation,
|
|
458
|
+
title=color_column,
|
|
459
|
+
)
|
|
460
|
+
p.add_layout(legend, "center")
|
|
461
|
+
else:
|
|
462
|
+
raise RuntimeError(
|
|
463
|
+
'Invalid `legend_location`. Must be a 2-tuple specifying location or one of ["right", "left", "above", "below", "top_left", "top_center", "top_right", "center_right", "bottom_right", "bottom_center", "bottom_left", "center_left", "center"]'
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
p.legend.click_policy = legend_click_policy
|
|
467
|
+
|
|
468
|
+
return p
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def box(
|
|
472
|
+
data=None,
|
|
473
|
+
q=None,
|
|
474
|
+
cats=None,
|
|
475
|
+
q_axis="x",
|
|
476
|
+
palette=None,
|
|
477
|
+
order=None,
|
|
478
|
+
p=None,
|
|
479
|
+
whisker_caps=False,
|
|
480
|
+
display_points=True,
|
|
481
|
+
outlier_marker="circle",
|
|
482
|
+
min_data=5,
|
|
483
|
+
cat_grid=False,
|
|
484
|
+
box_kwargs=None,
|
|
485
|
+
median_kwargs=None,
|
|
486
|
+
whisker_kwargs=None,
|
|
487
|
+
outlier_kwargs=None,
|
|
488
|
+
display_outliers=None,
|
|
489
|
+
horizontal=None,
|
|
490
|
+
val=None,
|
|
491
|
+
**kwargs,
|
|
492
|
+
):
|
|
493
|
+
"""
|
|
494
|
+
Make a box-and-whisker plot.
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
data : Pandas DataFrame, 1D Numpy array, or xarray
|
|
499
|
+
DataFrame containing tidy data for plotting. If a Numpy array,
|
|
500
|
+
a single category is assumed and a box plot with a single box is
|
|
501
|
+
generated from data.
|
|
502
|
+
q : hashable
|
|
503
|
+
Name of column to use as quantitative variable if `data` is a
|
|
504
|
+
Pandas DataFrame. Otherwise, `q` is used as the quantitative
|
|
505
|
+
axis label.
|
|
506
|
+
cats : hashable or list of hashables
|
|
507
|
+
Name of column(s) to use as categorical variable(s).
|
|
508
|
+
q_axis : str, either 'x' or 'y', default 'x'
|
|
509
|
+
Axis along which the quantitative value varies.
|
|
510
|
+
palette : list of strings of hex colors, or single hex string
|
|
511
|
+
If a list, color palette to use. If a single string representing
|
|
512
|
+
a hex color, all glyphs are colored with that color. Default is
|
|
513
|
+
colorcet.b_glasbey_category10 from the colorcet package.
|
|
514
|
+
order : list or None
|
|
515
|
+
If not None, must be a list of unique group names when the input
|
|
516
|
+
data frame is grouped by `cats`. The order of the list specifies
|
|
517
|
+
the ordering of the categorical variables on the categorical
|
|
518
|
+
axis and legend. If None, the categories appear in the order in
|
|
519
|
+
which they appeared in the inputted data frame.
|
|
520
|
+
p : bokeh.plotting.Figure instance, or None (default)
|
|
521
|
+
If None, create a new figure. Otherwise, populate the existing
|
|
522
|
+
figure `p`.
|
|
523
|
+
whisker_caps : bool, default False
|
|
524
|
+
If True, put caps on whiskers. If False, omit caps.
|
|
525
|
+
display_points : bool, default True
|
|
526
|
+
If True, display outliers and any other points that arise from
|
|
527
|
+
categories with fewer than `min_data` data points; otherwise
|
|
528
|
+
suppress them. This should only be False when using the boxes
|
|
529
|
+
as annotation on another plot.
|
|
530
|
+
outlier_marker : str, default 'circle'
|
|
531
|
+
Name of marker to be used in the plot. Must be one of
|
|
532
|
+
['asterisk', 'circle', 'circle_cross', 'circle_x', 'cross',
|
|
533
|
+
'dash', 'diamond', 'diamond_cross', 'hex', 'inverted_triangle',
|
|
534
|
+
'square', 'square_cross', 'square_x', 'triangle', 'x'].
|
|
535
|
+
min_data : int, default 5
|
|
536
|
+
Minimum number of data points in a given category in order to
|
|
537
|
+
make a box and whisker. Otherwise, individual data points are
|
|
538
|
+
plotted as in a strip plot.
|
|
539
|
+
cat_grid : bool, default False
|
|
540
|
+
If True, display grid line for categorical axis.
|
|
541
|
+
box_kwargs : dict, default None
|
|
542
|
+
A dictionary of kwargs to be passed into `p.hbar()` or
|
|
543
|
+
`p.vbar()` when constructing the boxes for the box plot.
|
|
544
|
+
median_kwargs : dict, default None
|
|
545
|
+
A dictionary of kwargs to be passed into `p.hbar()` or
|
|
546
|
+
`p.vbar()` when constructing the median line for the box plot.
|
|
547
|
+
whisker_kwargs : dict, default None
|
|
548
|
+
A dictionary of kwargs to be passed into `p.segment()`
|
|
549
|
+
when constructing the whiskers for the box plot.
|
|
550
|
+
outlier_kwargs : dict, default None
|
|
551
|
+
A dictionary of kwargs to be passed into `p.scatter()`
|
|
552
|
+
when constructing the outliers for the box plot.
|
|
553
|
+
display_outliers : bool, default None
|
|
554
|
+
Deprecated. Use `display_points`.
|
|
555
|
+
horizontal : bool or None, default None
|
|
556
|
+
Deprecated. Use `q_axis`.
|
|
557
|
+
val : hashable
|
|
558
|
+
Deprecated, use `q`.
|
|
559
|
+
kwargs
|
|
560
|
+
Kwargs that are passed to bokeh.plotting.figure() in constructing
|
|
561
|
+
the figure.
|
|
562
|
+
|
|
563
|
+
Returns
|
|
564
|
+
-------
|
|
565
|
+
output : bokeh.plotting.Figure instance
|
|
566
|
+
Plot populated with box-and-whisker plot.
|
|
567
|
+
|
|
568
|
+
Notes
|
|
569
|
+
-----
|
|
570
|
+
Uses the Tukey convention for box plots. The top and bottom of
|
|
571
|
+
the box are respectively the 75th and 25th percentiles of the
|
|
572
|
+
data. The line in the middle of the box is the median. The top
|
|
573
|
+
whisker extends to the maximum of the set of data points that are
|
|
574
|
+
less than 1.5 times the IQR beyond the top of the box, with an
|
|
575
|
+
analogous definition for the lower whisker. Data points not
|
|
576
|
+
between the ends of the whiskers are considered outliers and are
|
|
577
|
+
plotted as individual points.
|
|
578
|
+
"""
|
|
579
|
+
# Protect against mutability of dicts
|
|
580
|
+
box_kwargs = copy.copy(box_kwargs)
|
|
581
|
+
median_kwargs = copy.copy(median_kwargs)
|
|
582
|
+
whisker_kwargs = copy.copy(whisker_kwargs)
|
|
583
|
+
outlier_kwargs = copy.copy(outlier_kwargs)
|
|
584
|
+
|
|
585
|
+
q, _, _ = utils._parse_deprecations(
|
|
586
|
+
q, q_axis, val, horizontal, "x", None, None, None, None
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
if display_outliers is not None:
|
|
590
|
+
warnings.warn(
|
|
591
|
+
f"`display_outliers` is deprecated. Use `display_points`. Using `display_points={display_outliers}.",
|
|
592
|
+
DeprecationWarning,
|
|
593
|
+
)
|
|
594
|
+
display_points = display_outliers
|
|
595
|
+
|
|
596
|
+
if palette is None:
|
|
597
|
+
palette = colorcet.b_glasbey_category10
|
|
598
|
+
|
|
599
|
+
# A box plot has no legend, so only `order` is checked here.
|
|
600
|
+
utils._check_cats_none(cats, order)
|
|
601
|
+
|
|
602
|
+
data, q, cats, _ = utils._data_cats(data, q, cats, False, None)
|
|
603
|
+
order = utils._order_to_str(order)
|
|
604
|
+
|
|
605
|
+
cats, cols = utils._check_cat_input(
|
|
606
|
+
data, cats, q, None, None, None, palette, order, box_kwargs
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if outlier_kwargs is None:
|
|
610
|
+
outlier_kwargs = dict()
|
|
611
|
+
elif not isinstance(outlier_kwargs, dict):
|
|
612
|
+
raise RuntimeError("`outlier_kwargs` must be a dict.")
|
|
613
|
+
|
|
614
|
+
if box_kwargs is None:
|
|
615
|
+
box_kwargs = {"line_color": None}
|
|
616
|
+
box_width = 0.4
|
|
617
|
+
elif not isinstance(box_kwargs, dict):
|
|
618
|
+
raise RuntimeError("`box_kwargs` must be a dict.")
|
|
619
|
+
else:
|
|
620
|
+
box_width = box_kwargs.pop("width", 0.4)
|
|
621
|
+
if "line_color" not in box_kwargs:
|
|
622
|
+
box_kwargs["line_color"] = None
|
|
623
|
+
|
|
624
|
+
if whisker_kwargs is None:
|
|
625
|
+
whisker_kwargs = {"line_color": "black"}
|
|
626
|
+
elif not isinstance(whisker_kwargs, dict):
|
|
627
|
+
raise RuntimeError("`whisker_kwargs` must be a dict.")
|
|
628
|
+
elif "line_color" not in whisker_kwargs and "color" not in whisker_kwargs:
|
|
629
|
+
whisker_kwargs["line_color"] = "black"
|
|
630
|
+
|
|
631
|
+
if median_kwargs is None:
|
|
632
|
+
median_kwargs = {"line_color": "white"}
|
|
633
|
+
elif not isinstance(median_kwargs, dict):
|
|
634
|
+
raise RuntimeError("`median_kwargs` must be a dict.")
|
|
635
|
+
elif "color" not in median_kwargs and "line_color" not in median_kwargs:
|
|
636
|
+
median_kwargs["line_color"] = "white"
|
|
637
|
+
|
|
638
|
+
if q_axis == "x":
|
|
639
|
+
if "height" in box_kwargs:
|
|
640
|
+
warnings.warn("'height' entry in `box_kwargs` ignored; using `box_width`.")
|
|
641
|
+
del box_kwargs["height"]
|
|
642
|
+
else:
|
|
643
|
+
if "width" in box_kwargs:
|
|
644
|
+
warnings.warn("'width' entry in `box_kwargs` ignored; using `box_width`.")
|
|
645
|
+
del box_kwargs["width"]
|
|
646
|
+
|
|
647
|
+
grouped = data.groupby(cats, sort=False)
|
|
648
|
+
|
|
649
|
+
if p is None:
|
|
650
|
+
p, factors, color_factors = _cat_figure(
|
|
651
|
+
data, grouped, q, order, None, q_axis, kwargs
|
|
652
|
+
)
|
|
653
|
+
else:
|
|
654
|
+
_, factors, color_factors = _get_cat_range(data, grouped, order, None, q_axis)
|
|
655
|
+
|
|
656
|
+
marker = utils._check_marker(outlier_marker)
|
|
657
|
+
|
|
658
|
+
source_box, source_outliers = _box_source(data, cats, q, cols, min_data)
|
|
659
|
+
|
|
660
|
+
if "color" in outlier_kwargs:
|
|
661
|
+
if "line_color" in outlier_kwargs or "fill_color" in outlier_kwargs:
|
|
662
|
+
raise RuntimeError(
|
|
663
|
+
"If `color` is in `outlier_kwargs`, `line_color` and `fill_color` cannot be."
|
|
664
|
+
)
|
|
665
|
+
else:
|
|
666
|
+
if "fill_color" in box_kwargs:
|
|
667
|
+
if "fill_color" not in outlier_kwargs:
|
|
668
|
+
outlier_kwargs["fill_color"] = box_kwargs["fill_color"]
|
|
669
|
+
if "line_color" not in outlier_kwargs:
|
|
670
|
+
outlier_kwargs["line_color"] = box_kwargs["fill_color"]
|
|
671
|
+
else:
|
|
672
|
+
if "fill_color" not in outlier_kwargs:
|
|
673
|
+
outlier_kwargs["fill_color"] = bokeh.transform.factor_cmap(
|
|
674
|
+
"cat", palette=palette, factors=factors
|
|
675
|
+
)
|
|
676
|
+
if "line_color" not in outlier_kwargs:
|
|
677
|
+
outlier_kwargs["line_color"] = bokeh.transform.factor_cmap(
|
|
678
|
+
"cat", palette=palette, factors=factors
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
if "fill_color" not in box_kwargs:
|
|
682
|
+
box_kwargs["fill_color"] = bokeh.transform.factor_cmap(
|
|
683
|
+
"cat", palette=palette, factors=factors
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
if q_axis == "x":
|
|
687
|
+
p.segment(
|
|
688
|
+
source=source_box,
|
|
689
|
+
y0="cat",
|
|
690
|
+
y1="cat",
|
|
691
|
+
x0="top",
|
|
692
|
+
x1="top_whisker",
|
|
693
|
+
**whisker_kwargs,
|
|
694
|
+
)
|
|
695
|
+
p.segment(
|
|
696
|
+
source=source_box,
|
|
697
|
+
y0="cat",
|
|
698
|
+
y1="cat",
|
|
699
|
+
x0="bottom",
|
|
700
|
+
x1="bottom_whisker",
|
|
701
|
+
**whisker_kwargs,
|
|
702
|
+
)
|
|
703
|
+
if whisker_caps:
|
|
704
|
+
p.hbar(
|
|
705
|
+
source=source_box,
|
|
706
|
+
y="cat",
|
|
707
|
+
left="top_whisker",
|
|
708
|
+
right="top_whisker",
|
|
709
|
+
height=box_width / 4,
|
|
710
|
+
**whisker_kwargs,
|
|
711
|
+
)
|
|
712
|
+
p.hbar(
|
|
713
|
+
source=source_box,
|
|
714
|
+
y="cat",
|
|
715
|
+
left="bottom_whisker",
|
|
716
|
+
right="bottom_whisker",
|
|
717
|
+
height=box_width / 4,
|
|
718
|
+
**whisker_kwargs,
|
|
719
|
+
)
|
|
720
|
+
p.hbar(
|
|
721
|
+
source=source_box,
|
|
722
|
+
y="cat",
|
|
723
|
+
left="bottom",
|
|
724
|
+
right="top",
|
|
725
|
+
height=box_width,
|
|
726
|
+
**box_kwargs,
|
|
727
|
+
)
|
|
728
|
+
p.hbar(
|
|
729
|
+
source=source_box,
|
|
730
|
+
y="cat",
|
|
731
|
+
left="middle",
|
|
732
|
+
right="middle",
|
|
733
|
+
height=box_width,
|
|
734
|
+
**median_kwargs,
|
|
735
|
+
)
|
|
736
|
+
if display_points:
|
|
737
|
+
p.scatter(
|
|
738
|
+
source=source_outliers, y="cat", x=q, marker=marker, **outlier_kwargs
|
|
739
|
+
)
|
|
740
|
+
if not cat_grid:
|
|
741
|
+
p.ygrid.grid_line_color = None
|
|
742
|
+
else:
|
|
743
|
+
p.segment(
|
|
744
|
+
source=source_box,
|
|
745
|
+
x0="cat",
|
|
746
|
+
x1="cat",
|
|
747
|
+
y0="top",
|
|
748
|
+
y1="top_whisker",
|
|
749
|
+
**whisker_kwargs,
|
|
750
|
+
)
|
|
751
|
+
p.segment(
|
|
752
|
+
source=source_box,
|
|
753
|
+
x0="cat",
|
|
754
|
+
x1="cat",
|
|
755
|
+
y0="bottom",
|
|
756
|
+
y1="bottom_whisker",
|
|
757
|
+
**whisker_kwargs,
|
|
758
|
+
)
|
|
759
|
+
if whisker_caps:
|
|
760
|
+
p.vbar(
|
|
761
|
+
source=source_box,
|
|
762
|
+
x="cat",
|
|
763
|
+
bottom="top_whisker",
|
|
764
|
+
top="top_whisker",
|
|
765
|
+
width=box_width / 4,
|
|
766
|
+
**whisker_kwargs,
|
|
767
|
+
)
|
|
768
|
+
p.vbar(
|
|
769
|
+
source=source_box,
|
|
770
|
+
x="cat",
|
|
771
|
+
bottom="bottom_whisker",
|
|
772
|
+
top="bottom_whisker",
|
|
773
|
+
width=box_width / 4,
|
|
774
|
+
**whisker_kwargs,
|
|
775
|
+
)
|
|
776
|
+
p.vbar(
|
|
777
|
+
source=source_box,
|
|
778
|
+
x="cat",
|
|
779
|
+
bottom="bottom",
|
|
780
|
+
top="top",
|
|
781
|
+
width=box_width,
|
|
782
|
+
**box_kwargs,
|
|
783
|
+
)
|
|
784
|
+
p.vbar(
|
|
785
|
+
source=source_box,
|
|
786
|
+
x="cat",
|
|
787
|
+
bottom="middle",
|
|
788
|
+
top="middle",
|
|
789
|
+
width=box_width,
|
|
790
|
+
**median_kwargs,
|
|
791
|
+
)
|
|
792
|
+
if display_points:
|
|
793
|
+
p.scatter(
|
|
794
|
+
source=source_outliers, x="cat", y=q, marker=marker, **outlier_kwargs
|
|
795
|
+
)
|
|
796
|
+
if not cat_grid:
|
|
797
|
+
p.xgrid.grid_line_color = None
|
|
798
|
+
|
|
799
|
+
return p
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def stripbox(
|
|
803
|
+
data=None,
|
|
804
|
+
q=None,
|
|
805
|
+
cats=None,
|
|
806
|
+
q_axis="x",
|
|
807
|
+
palette=None,
|
|
808
|
+
order=None,
|
|
809
|
+
p=None,
|
|
810
|
+
show_legend=False,
|
|
811
|
+
legend_location="right",
|
|
812
|
+
legend_orientation="vertical",
|
|
813
|
+
legend_click_policy="hide",
|
|
814
|
+
top_level="strip",
|
|
815
|
+
color_column=None,
|
|
816
|
+
parcoord_column=None,
|
|
817
|
+
tooltips=None,
|
|
818
|
+
marker="circle",
|
|
819
|
+
spread=None,
|
|
820
|
+
cat_grid=False,
|
|
821
|
+
marker_kwargs=None,
|
|
822
|
+
jitter_kwargs=None,
|
|
823
|
+
swarm_kwargs=None,
|
|
824
|
+
parcoord_kwargs=None,
|
|
825
|
+
whisker_caps=True,
|
|
826
|
+
min_data=5,
|
|
827
|
+
box_kwargs=None,
|
|
828
|
+
median_kwargs=None,
|
|
829
|
+
whisker_kwargs=None,
|
|
830
|
+
jitter=None,
|
|
831
|
+
horizontal=None,
|
|
832
|
+
val=None,
|
|
833
|
+
click_policy=None,
|
|
834
|
+
**kwargs,
|
|
835
|
+
):
|
|
836
|
+
"""
|
|
837
|
+
Make a strip plot with a box plot as annotation.
|
|
838
|
+
|
|
839
|
+
Parameters
|
|
840
|
+
----------
|
|
841
|
+
data : Pandas DataFrame, 1D Numpy array, or xarray
|
|
842
|
+
DataFrame containing tidy data for plotting. If a Numpy array,
|
|
843
|
+
a single category is assumed and a strip plot generated from
|
|
844
|
+
data.
|
|
845
|
+
q : hashable
|
|
846
|
+
Name of column to use as quantitative variable if `data` is a
|
|
847
|
+
Pandas DataFrame. Otherwise, `q` is used as the quantitative
|
|
848
|
+
axis label.
|
|
849
|
+
cats : hashable or list of hashables
|
|
850
|
+
Name of column(s) to use as categorical variable(s).
|
|
851
|
+
q_axis : str, either 'x' or 'y', default 'x'
|
|
852
|
+
Axis along which the quantitative value varies.
|
|
853
|
+
palette : list of strings of hex colors, or single hex string
|
|
854
|
+
If a list, color palette to use. If a single string representing
|
|
855
|
+
a hex color, all glyphs are colored with that color. Default is
|
|
856
|
+
colorcet.b_glasbey_category10 from the colorcet package.
|
|
857
|
+
order : list or None
|
|
858
|
+
If not None, must be a list of unique group names when the input
|
|
859
|
+
data frame is grouped by `cats`. The order of the list specifies
|
|
860
|
+
the ordering of the categorical variables on the categorical
|
|
861
|
+
axis and legend. If None, the categories appear in the order in
|
|
862
|
+
which they appeared in the inputted data frame.
|
|
863
|
+
p : bokeh.plotting.Figure instance, or None (default)
|
|
864
|
+
If None, create a new figure. Otherwise, populate the existing
|
|
865
|
+
figure `p`.
|
|
866
|
+
top_level : str, default 'strip'
|
|
867
|
+
If 'box', the box plot is overlaid. If 'strip', the strip plot
|
|
868
|
+
is overlaid.
|
|
869
|
+
show_legend : bool, default False
|
|
870
|
+
If True, display legend.
|
|
871
|
+
legend_location : str, default 'right'
|
|
872
|
+
Location of legend. If one of "right", "left", "above", or
|
|
873
|
+
"below", the legend is placed outside of the plot area. If one
|
|
874
|
+
of "top_left", "top_center", "top_right", "center_right",
|
|
875
|
+
"bottom_right", "bottom_center", "bottom_left", "center_left",
|
|
876
|
+
or "center", the legend is placed within the plot area. If a
|
|
877
|
+
2-tuple, legend is placed according to the coordinates in the
|
|
878
|
+
tuple.
|
|
879
|
+
legend_orientation : str, default 'vertical'
|
|
880
|
+
Either 'horizontal' or 'vertical'.
|
|
881
|
+
legend_click_policy : str, default 'hide'
|
|
882
|
+
Either 'hide', 'mute', or None; how the glyphs respond when the
|
|
883
|
+
corresponding category is clicked in the legend.
|
|
884
|
+
color_column : hashable, default None
|
|
885
|
+
Column of `data` to use in determining color of glyphs. The data
|
|
886
|
+
in the color_column are assumed to be categorical. If the data
|
|
887
|
+
in color_column consist entirely of hex colors, then those
|
|
888
|
+
colors are directly used to color the glyphs. If None,
|
|
889
|
+
then `cats` is used.
|
|
890
|
+
parcoord_column : hashable, default None
|
|
891
|
+
Column of `data` to use to construct a parallel coordinate plot.
|
|
892
|
+
Data points with like entries in the parcoord_column are
|
|
893
|
+
connected with lines in the strip plot.
|
|
894
|
+
tooltips : list of 2-tuples
|
|
895
|
+
Specification for tooltips as per Bokeh specifications. For
|
|
896
|
+
example, if we want `col1` and `col2` tooltips, we can use
|
|
897
|
+
`tooltips=[('label 1', '@col1'), ('label 2', '@col2')]`.
|
|
898
|
+
marker : str, default 'circle'
|
|
899
|
+
Name of marker to be used in the plot. Must be one of
|
|
900
|
+
['asterisk', 'circle', 'circle_cross', 'circle_x', 'cross',
|
|
901
|
+
'dash', 'diamond', 'diamond_cross', 'hex', 'inverted_triangle',
|
|
902
|
+
'square', 'square_cross', 'square_x', 'triangle', 'x'].
|
|
903
|
+
spread : str or None, default None
|
|
904
|
+
If 'jitter', spread points out using a jitter transform. If
|
|
905
|
+
'swarm', spread points in beeswarm style. If None or 'none', do
|
|
906
|
+
not spread.
|
|
907
|
+
cat_grid : bool, default False
|
|
908
|
+
If True, display grid line for categorical axis.
|
|
909
|
+
marker_kwargs : dict
|
|
910
|
+
Keyword arguments to pass when adding markers to the plot.
|
|
911
|
+
["x", "y", "marker", "source", "cat", "legend"] are not allowed
|
|
912
|
+
because they are determined by other inputs.
|
|
913
|
+
jitter_kwargs : dict
|
|
914
|
+
Keyword arguments to be passed to `bokeh.transform.jitter()`. If
|
|
915
|
+
not specified, default is
|
|
916
|
+
`{'distribution': 'normal', 'width': 0.1}`. If the user
|
|
917
|
+
specifies `{'distribution': 'uniform'}`, the `'width'` entry is
|
|
918
|
+
adjusted to 0.4. Only active if `spread` is `'jitter'`.
|
|
919
|
+
swarm_kwargs : dict
|
|
920
|
+
Keyword arguments for use in generating swarm. Only active if
|
|
921
|
+
`spread` is `'swarm'`. Keys with allowed values are:
|
|
922
|
+
|
|
923
|
+
- 'corral': Either 'gutter' (default) or 'wrap'. This
|
|
924
|
+
specifies how points that are moved too far out are dealt
|
|
925
|
+
with. Using 'gutter', points are overlaid at the maximum
|
|
926
|
+
allowed distance. Using 'wrap', points are reflected inwards
|
|
927
|
+
from the maximal extent and possibly overlaid with other
|
|
928
|
+
points.
|
|
929
|
+
|
|
930
|
+
- 'priority': Either 'ascending' (default) or 'descending'.
|
|
931
|
+
Sort order when determining which points get moved in the
|
|
932
|
+
y-direction first.
|
|
933
|
+
|
|
934
|
+
- marker_pad_px : Gap between markers in units of pixels,
|
|
935
|
+
default 0.
|
|
936
|
+
parcoord_kwargs : dict
|
|
937
|
+
Keyword arguments to be passed to `p.line()` when making lines
|
|
938
|
+
for the parallel coordinate plot. Default is to have one-pixel
|
|
939
|
+
gray lines.
|
|
940
|
+
whisker_caps : bool, default True
|
|
941
|
+
If True, put caps on whiskers. If False, omit caps.
|
|
942
|
+
min_data : int, default 5
|
|
943
|
+
Minimum number of data points in a given category in order to
|
|
944
|
+
make a box and whisker. Otherwise, individual data points are
|
|
945
|
+
plotted as in a strip plot.
|
|
946
|
+
box_kwargs : dict, default None
|
|
947
|
+
A dictionary of kwargs to be passed into `p.hbar()` or
|
|
948
|
+
`p.vbar()` when constructing the boxes for the box plot.
|
|
949
|
+
median_kwargs : dict, default None
|
|
950
|
+
A dictionary of kwargs to be passed into `p.hbar()` or
|
|
951
|
+
`p.vbar()` when constructing the median line for the box plot.
|
|
952
|
+
whisker_kwargs : dict, default None
|
|
953
|
+
A dictionary of kwargs to be passed into `p.segment()`
|
|
954
|
+
when constructing the whiskers for the box plot.
|
|
955
|
+
jitter : bool, default False
|
|
956
|
+
Deprecated, use `spread`.
|
|
957
|
+
horizontal : bool or None, default None
|
|
958
|
+
Deprecated. Use `q_axis`.
|
|
959
|
+
val : hashable
|
|
960
|
+
Deprecated, use `q`.
|
|
961
|
+
click_policy : str, default 'hide'
|
|
962
|
+
Deprecated. Use `legend_click_policy`.
|
|
963
|
+
kwargs
|
|
964
|
+
Any kwargs to be passed to `bokeh.plotting.figure()` when
|
|
965
|
+
instantiating the figure.
|
|
966
|
+
|
|
967
|
+
Returns
|
|
968
|
+
-------
|
|
969
|
+
output : bokeh.plotting.Figure instance
|
|
970
|
+
Plot populated with a strip-box plot.
|
|
971
|
+
"""
|
|
972
|
+
# display_points not allowed in kwargs
|
|
973
|
+
if "display_points" in kwargs:
|
|
974
|
+
raise ValueError("display_points not allowed as a kwarg for stripbox.")
|
|
975
|
+
|
|
976
|
+
# Protect against mutability of dicts
|
|
977
|
+
box_kwargs = copy.copy(box_kwargs)
|
|
978
|
+
median_kwargs = copy.copy(median_kwargs)
|
|
979
|
+
whisker_kwargs = copy.copy(whisker_kwargs)
|
|
980
|
+
jitter_kwargs = copy.copy(jitter_kwargs)
|
|
981
|
+
swarm_kwargs = copy.copy(swarm_kwargs)
|
|
982
|
+
marker_kwargs = copy.copy(marker_kwargs)
|
|
983
|
+
parcoord_kwargs = copy.copy(parcoord_kwargs)
|
|
984
|
+
|
|
985
|
+
# Set defaults
|
|
986
|
+
if box_kwargs is None:
|
|
987
|
+
box_kwargs = dict(line_color="gray", fill_alpha=0)
|
|
988
|
+
if "color" not in box_kwargs and "line_color" not in box_kwargs:
|
|
989
|
+
box_kwargs["line_color"] = "gray"
|
|
990
|
+
if ("fill_alpha" not in box_kwargs) and ("fill_color" not in box_kwargs):
|
|
991
|
+
box_kwargs["fill_alpha"] = 0
|
|
992
|
+
elif ("fill_color" in box_kwargs) and ("fill_alpha" not in box_kwargs):
|
|
993
|
+
box_kwargs["fill_alpha"] = 0.5
|
|
994
|
+
|
|
995
|
+
if median_kwargs is None:
|
|
996
|
+
median_kwargs = dict(line_color="gray")
|
|
997
|
+
if "color" not in median_kwargs and "line_color" not in median_kwargs:
|
|
998
|
+
median_kwargs["line_color"] = "gray"
|
|
999
|
+
|
|
1000
|
+
if whisker_kwargs is None:
|
|
1001
|
+
whisker_kwargs = dict(line_color="gray")
|
|
1002
|
+
if "color" not in whisker_kwargs and "line_color" not in whisker_kwargs:
|
|
1003
|
+
whisker_kwargs["line_color"] = "gray"
|
|
1004
|
+
|
|
1005
|
+
if top_level == "box":
|
|
1006
|
+
p = strip(
|
|
1007
|
+
data=data,
|
|
1008
|
+
q=q,
|
|
1009
|
+
cats=cats,
|
|
1010
|
+
q_axis=q_axis,
|
|
1011
|
+
palette=palette,
|
|
1012
|
+
order=order,
|
|
1013
|
+
p=p,
|
|
1014
|
+
show_legend=show_legend,
|
|
1015
|
+
legend_location=legend_location,
|
|
1016
|
+
legend_orientation=legend_orientation,
|
|
1017
|
+
legend_click_policy=legend_click_policy,
|
|
1018
|
+
color_column=color_column,
|
|
1019
|
+
parcoord_column=parcoord_column,
|
|
1020
|
+
tooltips=tooltips,
|
|
1021
|
+
marker=marker,
|
|
1022
|
+
spread=spread,
|
|
1023
|
+
cat_grid=cat_grid,
|
|
1024
|
+
marker_kwargs=marker_kwargs,
|
|
1025
|
+
jitter_kwargs=jitter_kwargs,
|
|
1026
|
+
swarm_kwargs=swarm_kwargs,
|
|
1027
|
+
parcoord_kwargs=parcoord_kwargs,
|
|
1028
|
+
jitter=jitter,
|
|
1029
|
+
horizontal=horizontal,
|
|
1030
|
+
val=val,
|
|
1031
|
+
click_policy=click_policy,
|
|
1032
|
+
**kwargs,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
p = box(
|
|
1036
|
+
data=data,
|
|
1037
|
+
q=q,
|
|
1038
|
+
cats=cats,
|
|
1039
|
+
q_axis=q_axis,
|
|
1040
|
+
palette=palette,
|
|
1041
|
+
order=order,
|
|
1042
|
+
p=p,
|
|
1043
|
+
display_points=False,
|
|
1044
|
+
whisker_caps=whisker_caps,
|
|
1045
|
+
min_data=min_data,
|
|
1046
|
+
box_kwargs=box_kwargs,
|
|
1047
|
+
median_kwargs=median_kwargs,
|
|
1048
|
+
whisker_kwargs=whisker_kwargs,
|
|
1049
|
+
horizontal=horizontal,
|
|
1050
|
+
val=val,
|
|
1051
|
+
)
|
|
1052
|
+
elif top_level == "strip":
|
|
1053
|
+
p = box(
|
|
1054
|
+
data=data,
|
|
1055
|
+
q=q,
|
|
1056
|
+
cats=cats,
|
|
1057
|
+
q_axis=q_axis,
|
|
1058
|
+
palette=palette,
|
|
1059
|
+
order=order,
|
|
1060
|
+
p=p,
|
|
1061
|
+
display_points=False,
|
|
1062
|
+
cat_grid=cat_grid,
|
|
1063
|
+
whisker_caps=whisker_caps,
|
|
1064
|
+
min_data=min_data,
|
|
1065
|
+
box_kwargs=box_kwargs,
|
|
1066
|
+
median_kwargs=median_kwargs,
|
|
1067
|
+
whisker_kwargs=whisker_kwargs,
|
|
1068
|
+
horizontal=horizontal,
|
|
1069
|
+
val=val,
|
|
1070
|
+
**kwargs,
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
p = strip(
|
|
1074
|
+
data=data,
|
|
1075
|
+
q=q,
|
|
1076
|
+
cats=cats,
|
|
1077
|
+
q_axis=q_axis,
|
|
1078
|
+
palette=palette,
|
|
1079
|
+
order=order,
|
|
1080
|
+
p=p,
|
|
1081
|
+
show_legend=show_legend,
|
|
1082
|
+
legend_location=legend_location,
|
|
1083
|
+
legend_orientation=legend_orientation,
|
|
1084
|
+
legend_click_policy=legend_click_policy,
|
|
1085
|
+
color_column=color_column,
|
|
1086
|
+
parcoord_column=parcoord_column,
|
|
1087
|
+
tooltips=tooltips,
|
|
1088
|
+
marker=marker,
|
|
1089
|
+
spread=spread,
|
|
1090
|
+
cat_grid=cat_grid,
|
|
1091
|
+
marker_kwargs=marker_kwargs,
|
|
1092
|
+
jitter_kwargs=jitter_kwargs,
|
|
1093
|
+
swarm_kwargs=swarm_kwargs,
|
|
1094
|
+
parcoord_kwargs=parcoord_kwargs,
|
|
1095
|
+
jitter=jitter,
|
|
1096
|
+
horizontal=horizontal,
|
|
1097
|
+
val=val,
|
|
1098
|
+
click_policy=click_policy,
|
|
1099
|
+
**kwargs,
|
|
1100
|
+
)
|
|
1101
|
+
else:
|
|
1102
|
+
raise RuntimeError("Invalid `top_level`. Allowed values are 'box' and 'strip'.")
|
|
1103
|
+
|
|
1104
|
+
return p
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
def striphistogram(
|
|
1108
|
+
data=None,
|
|
1109
|
+
q=None,
|
|
1110
|
+
cats=None,
|
|
1111
|
+
q_axis="x",
|
|
1112
|
+
palette=None,
|
|
1113
|
+
order=None,
|
|
1114
|
+
p=None,
|
|
1115
|
+
show_legend=None,
|
|
1116
|
+
legend_location="right",
|
|
1117
|
+
legend_orientation="vertical",
|
|
1118
|
+
legend_click_policy="hide",
|
|
1119
|
+
top_level="strip",
|
|
1120
|
+
color_column=None,
|
|
1121
|
+
parcoord_column=None,
|
|
1122
|
+
tooltips=None,
|
|
1123
|
+
marker="circle",
|
|
1124
|
+
spread=None,
|
|
1125
|
+
cat_grid=True,
|
|
1126
|
+
marker_kwargs=None,
|
|
1127
|
+
jitter_kwargs=None,
|
|
1128
|
+
swarm_kwargs=None,
|
|
1129
|
+
parcoord_kwargs=None,
|
|
1130
|
+
bins="freedman-diaconis",
|
|
1131
|
+
style=None,
|
|
1132
|
+
mirror=True,
|
|
1133
|
+
hist_height=0.75,
|
|
1134
|
+
conf_int=False,
|
|
1135
|
+
ptiles=(2.5, 97.5),
|
|
1136
|
+
n_bs_reps=10000,
|
|
1137
|
+
line_kwargs=None,
|
|
1138
|
+
fill_kwargs=None,
|
|
1139
|
+
conf_int_kwargs=None,
|
|
1140
|
+
kind=None,
|
|
1141
|
+
jitter=None,
|
|
1142
|
+
horizontal=None,
|
|
1143
|
+
val=None,
|
|
1144
|
+
click_policy=None,
|
|
1145
|
+
**kwargs,
|
|
1146
|
+
):
|
|
1147
|
+
"""
|
|
1148
|
+
Make a strip plot with a histogram as annotation.
|
|
1149
|
+
|
|
1150
|
+
Parameters
|
|
1151
|
+
----------
|
|
1152
|
+
data : Pandas DataFrame, 1D Numpy array, or xarray
|
|
1153
|
+
DataFrame containing tidy data for plotting. If a Numpy array,
|
|
1154
|
+
a single category is assumed and a strip plot generated from
|
|
1155
|
+
data.
|
|
1156
|
+
q : hashable
|
|
1157
|
+
Name of column to use as quantitative variable if `data` is a
|
|
1158
|
+
Pandas DataFrame. Otherwise, `q` is used as the quantitative
|
|
1159
|
+
axis label.
|
|
1160
|
+
cats : hashable or list of hashables
|
|
1161
|
+
Name of column(s) to use as categorical variable(s).
|
|
1162
|
+
q_axis : str, either 'x' or 'y', default 'x'
|
|
1163
|
+
Axis along which the quantitative value varies.
|
|
1164
|
+
palette : list of strings of hex colors, or single hex string
|
|
1165
|
+
If a list, color palette to use. If a single string representing
|
|
1166
|
+
a hex color, all glyphs are colored with that color. Default is
|
|
1167
|
+
colorcet.b_glasbey_category10 from the colorcet package.
|
|
1168
|
+
order : list or None
|
|
1169
|
+
If not None, must be a list of unique group names when the input
|
|
1170
|
+
data frame is grouped by `cats`. The order of the list specifies
|
|
1171
|
+
the ordering of the categorical variables on the categorical
|
|
1172
|
+
axis and legend. If None, the categories appear in the order in
|
|
1173
|
+
which they appeared in the inputted data frame.
|
|
1174
|
+
p : bokeh.plotting.Figure instance, or None (default)
|
|
1175
|
+
If None, create a new figure. Otherwise, populate the existing
|
|
1176
|
+
figure `p`.
|
|
1177
|
+
top_level : str, default 'strip'
|
|
1178
|
+
If 'histogram', the histogram is overlaid. If 'strip', the strip
|
|
1179
|
+
plot is overlaid.
|
|
1180
|
+
show_legend : bool, default False
|
|
1181
|
+
If True, display legend.
|
|
1182
|
+
legend_location : str, default 'right'
|
|
1183
|
+
Location of legend. If one of "right", "left", "above", or
|
|
1184
|
+
"below", the legend is placed outside of the plot area. If one
|
|
1185
|
+
of "top_left", "top_center", "top_right", "center_right",
|
|
1186
|
+
"bottom_right", "bottom_center", "bottom_left", "center_left",
|
|
1187
|
+
or "center", the legend is placed within the plot area. If a
|
|
1188
|
+
2-tuple, legend is placed according to the coordinates in the
|
|
1189
|
+
tuple.
|
|
1190
|
+
legend_orientation : str, default 'vertical'
|
|
1191
|
+
Either 'horizontal' or 'vertical'.
|
|
1192
|
+
legend_click_policy : str, default 'hide'
|
|
1193
|
+
Either 'hide', 'mute', or None; how the glyphs respond when the
|
|
1194
|
+
corresponding category is clicked in the legend.
|
|
1195
|
+
color_column : hashable, default None
|
|
1196
|
+
Column of `data` to use in determining color of glyphs. The data
|
|
1197
|
+
in the color_column are assumed to be categorical. If the data
|
|
1198
|
+
in color_column consist entirely of hex colors, then those
|
|
1199
|
+
colors are directly used to color the glyphs. If None,
|
|
1200
|
+
then `cats` is used.
|
|
1201
|
+
parcoord_column : hashable, default None
|
|
1202
|
+
Column of `data` to use to construct a parallel coordinate plot.
|
|
1203
|
+
Data points with like entries in the parcoord_column are
|
|
1204
|
+
connected with lines in the strip plot.
|
|
1205
|
+
tooltips : list of 2-tuples
|
|
1206
|
+
Specification for tooltips as per Bokeh specifications. For
|
|
1207
|
+
example, if we want `col1` and `col2` tooltips, we can use
|
|
1208
|
+
`tooltips=[('label 1', '@col1'), ('label 2', '@col2')]`.
|
|
1209
|
+
marker : str, default 'circle'
|
|
1210
|
+
Name of marker to be used in the plot. Must be one of
|
|
1211
|
+
['asterisk', 'circle', 'circle_cross', 'circle_x', 'cross',
|
|
1212
|
+
'dash', 'diamond', 'diamond_cross', 'hex', 'inverted_triangle',
|
|
1213
|
+
'square', 'square_cross', 'square_x', 'triangle', 'x'].
|
|
1214
|
+
spread : str or None, default None
|
|
1215
|
+
If 'jitter', spread points out using a jitter transform. If
|
|
1216
|
+
'swarm', spread points in beeswarm style. If None or 'none', do
|
|
1217
|
+
not spread.
|
|
1218
|
+
cat_grid : bool, default True
|
|
1219
|
+
If True, display grid line for categorical axis.
|
|
1220
|
+
marker_kwargs : dict
|
|
1221
|
+
Keyword arguments to pass when adding markers to the plot.
|
|
1222
|
+
["x", "y", "source", "marker", "cat", "legend"] are not allowed
|
|
1223
|
+
because they are determined by other inputs.
|
|
1224
|
+
jitter_kwargs : dict
|
|
1225
|
+
Keyword arguments to be passed to `bokeh.transform.jitter()`. If
|
|
1226
|
+
not specified, default is
|
|
1227
|
+
`{'distribution': 'normal', 'width': 0.1}`. If the user
|
|
1228
|
+
specifies `{'distribution': 'uniform'}`, the `'width'` entry is
|
|
1229
|
+
adjusted to 0.4. Only active if `spread` is `'jitter'`.
|
|
1230
|
+
swarm_kwargs : dict
|
|
1231
|
+
Keyword arguments for use in generating swarm. Only active if
|
|
1232
|
+
`spread` is `'swarm'`. Keys with allowed values are:
|
|
1233
|
+
|
|
1234
|
+
- 'corral': Either 'gutter' (default) or 'wrap'. This
|
|
1235
|
+
specifies how points that are moved too far out are dealt
|
|
1236
|
+
with. Using 'gutter', points are overlaid at the maximum
|
|
1237
|
+
allowed distance. Using 'wrap', points are reflected inwards
|
|
1238
|
+
from the maximal extent and possibly overlaid with other
|
|
1239
|
+
points.
|
|
1240
|
+
|
|
1241
|
+
- 'priority': Either 'ascending' (default) or 'descending'.
|
|
1242
|
+
Sort order when determining which points get moved in the
|
|
1243
|
+
y-direction first.
|
|
1244
|
+
|
|
1245
|
+
- marker_pad_px : Gap between markers in units of pixels,
|
|
1246
|
+
default 0.
|
|
1247
|
+
parcoord_kwargs : dict
|
|
1248
|
+
Keyword arguments to be passed to `p.line()` when making lines
|
|
1249
|
+
for the parallel coordinate plot. Default is to have one-pixel
|
|
1250
|
+
gray lines.
|
|
1251
|
+
bins : int, array_like, or str, default 'freedman-diaconis'
|
|
1252
|
+
If int or array_like, setting for `bins` kwarg to be passed to
|
|
1253
|
+
`np.histogram()`. If 'exact', then each unique value in the
|
|
1254
|
+
data gets its own bin. If 'integer', then integer data is
|
|
1255
|
+
assumed and each integer gets its own bin. If 'sqrt', uses the
|
|
1256
|
+
square root rule to determine number of bins. If
|
|
1257
|
+
`freedman-diaconis`, uses the Freedman-Diaconis rule for number
|
|
1258
|
+
of bins.
|
|
1259
|
+
style : None or one of ['step', 'step_filled']
|
|
1260
|
+
Default for overlaid histograms is 'step' and for stacked
|
|
1261
|
+
histograms 'step_filled'. The exception is when `conf_int` is
|
|
1262
|
+
True, in which case `style` must be 'step'.
|
|
1263
|
+
mirror : bool, default True
|
|
1264
|
+
If True, reflect the histogram through zero.
|
|
1265
|
+
hist_height : float, default 0.75
|
|
1266
|
+
Maximal height of histogram or its confidence interval as a
|
|
1267
|
+
fraction of available height along categorical axis. Only active
|
|
1268
|
+
when `arrangement` is 'stack'.
|
|
1269
|
+
conf_int : bool, default False
|
|
1270
|
+
If True, display confidence interval of the histogram.
|
|
1271
|
+
ptiles : list, default (2.5, 97.5)
|
|
1272
|
+
The percentiles to use for the confidence interval of the
|
|
1273
|
+
histogram. Ignored if `conf_int` is False.
|
|
1274
|
+
n_bs_reps : int, default 10,000
|
|
1275
|
+
Number of bootstrap replicates to do to compute confidence
|
|
1276
|
+
interval of histogram. Ignored if `conf_int` is False.
|
|
1277
|
+
line_kwargs : dict
|
|
1278
|
+
Keyword arguments to pass to `p.line()` in constructing the
|
|
1279
|
+
histograms. By default, {"line_width": 2}.
|
|
1280
|
+
fill_kwargs : dict
|
|
1281
|
+
Keyword arguments to pass to `p.patch()` when making the fill
|
|
1282
|
+
for the step-filled histogram or confidence intervals. Ignored
|
|
1283
|
+
if `style = 'step'` and `conf_int` is False. By default
|
|
1284
|
+
{"fill_alpha": 0.3, "line_alpha": 0}.
|
|
1285
|
+
jitter : bool, default False
|
|
1286
|
+
Deprecated, use `spread`.
|
|
1287
|
+
horizontal : bool or None, default None
|
|
1288
|
+
Deprecated. Use `q_axis`.
|
|
1289
|
+
val : hashable
|
|
1290
|
+
Deprecated, use `q`.
|
|
1291
|
+
click_policy : str, default 'hide'
|
|
1292
|
+
Deprecated. Use `legend_click_policy`.
|
|
1293
|
+
conf_int_kwargs : dict
|
|
1294
|
+
Deprecated. Use `fill_kwargs`.
|
|
1295
|
+
kind : str, default 'step_filled'
|
|
1296
|
+
Deprecated. Use `style`.
|
|
1297
|
+
kwargs
|
|
1298
|
+
Any kwargs to be passed to `bokeh.plotting.figure()` when
|
|
1299
|
+
instantiating the figure.
|
|
1300
|
+
|
|
1301
|
+
Returns
|
|
1302
|
+
-------
|
|
1303
|
+
output : bokeh.plotting.Figure instance
|
|
1304
|
+
Plot populated with a strip-histogram plot.
|
|
1305
|
+
|
|
1306
|
+
Notes
|
|
1307
|
+
-----
|
|
1308
|
+
.. Histograms are all normalized, as would be the case using the
|
|
1309
|
+
`density=True` kwargs of iqplot.histogram()`. This is necessary
|
|
1310
|
+
because there is no quantitative axis for the height of the
|
|
1311
|
+
histogram in a strip plot.
|
|
1312
|
+
"""
|
|
1313
|
+
# Protect against mutability of dicts
|
|
1314
|
+
jitter_kwargs = copy.copy(jitter_kwargs)
|
|
1315
|
+
swarm_kwargs = copy.copy(swarm_kwargs)
|
|
1316
|
+
marker_kwargs = copy.copy(marker_kwargs)
|
|
1317
|
+
parcoord_kwargs = copy.copy(parcoord_kwargs)
|
|
1318
|
+
line_kwargs = copy.copy(line_kwargs)
|
|
1319
|
+
fill_kwargs = copy.copy(fill_kwargs)
|
|
1320
|
+
|
|
1321
|
+
# Set defaults
|
|
1322
|
+
if color_column is not None and color_column != cats:
|
|
1323
|
+
if line_kwargs is None:
|
|
1324
|
+
line_kwargs = {}
|
|
1325
|
+
if fill_kwargs is None:
|
|
1326
|
+
fill_kwargs = {}
|
|
1327
|
+
if "color" not in line_kwargs and "line_color" not in line_kwargs:
|
|
1328
|
+
line_kwargs["line_color"] = "gray"
|
|
1329
|
+
if "color" not in fill_kwargs and "fill_color" not in fill_kwargs:
|
|
1330
|
+
fill_kwargs["fill_color"] = "gray"
|
|
1331
|
+
|
|
1332
|
+
if style is None:
|
|
1333
|
+
if conf_int:
|
|
1334
|
+
style = "step"
|
|
1335
|
+
else:
|
|
1336
|
+
style = "step_filled"
|
|
1337
|
+
|
|
1338
|
+
if top_level == "histogram":
|
|
1339
|
+
p = strip(
|
|
1340
|
+
data=data,
|
|
1341
|
+
q=q,
|
|
1342
|
+
cats=cats,
|
|
1343
|
+
q_axis=q_axis,
|
|
1344
|
+
palette=palette,
|
|
1345
|
+
order=order,
|
|
1346
|
+
p=p,
|
|
1347
|
+
show_legend=show_legend,
|
|
1348
|
+
color_column=color_column,
|
|
1349
|
+
parcoord_column=parcoord_column,
|
|
1350
|
+
tooltips=tooltips,
|
|
1351
|
+
marker=marker,
|
|
1352
|
+
spread=spread,
|
|
1353
|
+
cat_grid=cat_grid,
|
|
1354
|
+
marker_kwargs=marker_kwargs,
|
|
1355
|
+
jitter_kwargs=jitter_kwargs,
|
|
1356
|
+
swarm_kwargs=swarm_kwargs,
|
|
1357
|
+
parcoord_kwargs=parcoord_kwargs,
|
|
1358
|
+
jitter=jitter,
|
|
1359
|
+
horizontal=horizontal,
|
|
1360
|
+
val=val,
|
|
1361
|
+
click_policy=click_policy,
|
|
1362
|
+
**kwargs,
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
p = histogram(
|
|
1366
|
+
data=data,
|
|
1367
|
+
q=q,
|
|
1368
|
+
cats=cats,
|
|
1369
|
+
q_axis=q_axis,
|
|
1370
|
+
palette=palette,
|
|
1371
|
+
order=order,
|
|
1372
|
+
p=p,
|
|
1373
|
+
rug=False,
|
|
1374
|
+
show_legend=False,
|
|
1375
|
+
bins=bins,
|
|
1376
|
+
density=True,
|
|
1377
|
+
style=style,
|
|
1378
|
+
arrangement="stack",
|
|
1379
|
+
mirror=mirror,
|
|
1380
|
+
hist_height=hist_height,
|
|
1381
|
+
conf_int=conf_int,
|
|
1382
|
+
ptiles=ptiles,
|
|
1383
|
+
n_bs_reps=n_bs_reps,
|
|
1384
|
+
line_kwargs=line_kwargs,
|
|
1385
|
+
fill_kwargs=fill_kwargs,
|
|
1386
|
+
conf_int_kwargs=conf_int_kwargs,
|
|
1387
|
+
kind=kind,
|
|
1388
|
+
)
|
|
1389
|
+
elif top_level == "strip":
|
|
1390
|
+
p = histogram(
|
|
1391
|
+
data=data,
|
|
1392
|
+
q=q,
|
|
1393
|
+
cats=cats,
|
|
1394
|
+
q_axis=q_axis,
|
|
1395
|
+
palette=palette,
|
|
1396
|
+
order=order,
|
|
1397
|
+
p=p,
|
|
1398
|
+
rug=False,
|
|
1399
|
+
show_legend=False,
|
|
1400
|
+
bins=bins,
|
|
1401
|
+
density=True,
|
|
1402
|
+
style=style,
|
|
1403
|
+
arrangement="stack",
|
|
1404
|
+
mirror=mirror,
|
|
1405
|
+
hist_height=hist_height,
|
|
1406
|
+
conf_int=conf_int,
|
|
1407
|
+
ptiles=ptiles,
|
|
1408
|
+
n_bs_reps=n_bs_reps,
|
|
1409
|
+
line_kwargs=line_kwargs,
|
|
1410
|
+
fill_kwargs=fill_kwargs,
|
|
1411
|
+
conf_int_kwargs=conf_int_kwargs,
|
|
1412
|
+
kind=kind,
|
|
1413
|
+
**kwargs,
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
p = strip(
|
|
1417
|
+
data=data,
|
|
1418
|
+
q=q,
|
|
1419
|
+
cats=cats,
|
|
1420
|
+
q_axis=q_axis,
|
|
1421
|
+
palette=palette,
|
|
1422
|
+
order=order,
|
|
1423
|
+
p=p,
|
|
1424
|
+
cat_grid=cat_grid,
|
|
1425
|
+
show_legend=show_legend,
|
|
1426
|
+
color_column=color_column,
|
|
1427
|
+
parcoord_column=parcoord_column,
|
|
1428
|
+
tooltips=tooltips,
|
|
1429
|
+
marker=marker,
|
|
1430
|
+
spread=spread,
|
|
1431
|
+
marker_kwargs=marker_kwargs,
|
|
1432
|
+
jitter_kwargs=jitter_kwargs,
|
|
1433
|
+
swarm_kwargs=swarm_kwargs,
|
|
1434
|
+
parcoord_kwargs=parcoord_kwargs,
|
|
1435
|
+
jitter=jitter,
|
|
1436
|
+
horizontal=horizontal,
|
|
1437
|
+
val=val,
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
if not cat_grid:
|
|
1441
|
+
p.xgrid.grid_line_color = None
|
|
1442
|
+
else:
|
|
1443
|
+
raise RuntimeError(
|
|
1444
|
+
"Invalid `top_level`. Allowed values are 'histogram' and 'strip'."
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
return p
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
def _get_cat_range(df, grouped, order, color_column, q_axis):
|
|
1451
|
+
if order is None:
|
|
1452
|
+
if isinstance(list(grouped.groups.keys())[0], tuple):
|
|
1453
|
+
factors = tuple(
|
|
1454
|
+
[tuple([str(k) for k in key]) for key in grouped.groups.keys()]
|
|
1455
|
+
)
|
|
1456
|
+
else:
|
|
1457
|
+
factors = tuple([str(key) for key in grouped.groups.keys()])
|
|
1458
|
+
else:
|
|
1459
|
+
if isinstance(order[0], (list, tuple)):
|
|
1460
|
+
factors = tuple([tuple([str(k) for k in key]) for key in order])
|
|
1461
|
+
else:
|
|
1462
|
+
factors = tuple([str(entry) for entry in order])
|
|
1463
|
+
|
|
1464
|
+
if q_axis == "x":
|
|
1465
|
+
cat_range = bokeh.models.FactorRange(*(factors[::-1]))
|
|
1466
|
+
elif q_axis == "y":
|
|
1467
|
+
cat_range = bokeh.models.FactorRange(*factors)
|
|
1468
|
+
|
|
1469
|
+
if color_column is None:
|
|
1470
|
+
color_factors = factors
|
|
1471
|
+
elif _color_column_hexcodes(df, color_column):
|
|
1472
|
+
color_factors = "hex"
|
|
1473
|
+
else:
|
|
1474
|
+
color_factors = tuple(sorted(list(df[color_column].unique().astype(str))))
|
|
1475
|
+
|
|
1476
|
+
return cat_range, factors, color_factors
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
def _color_column_hexcodes(df, color_column):
|
|
1480
|
+
"""Return True if the color column consists of all hex codes."""
|
|
1481
|
+
try:
|
|
1482
|
+
return df[color_column].str.match(r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$").all()
|
|
1483
|
+
except:
|
|
1484
|
+
return False
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
def _cat_figure(df, grouped, q, order, color_column, q_axis, kwargs):
|
|
1488
|
+
cat_range, factors, color_factors = _get_cat_range(
|
|
1489
|
+
df, grouped, order, color_column, q_axis
|
|
1490
|
+
)
|
|
1491
|
+
|
|
1492
|
+
kwargs = utils._fig_dimensions(kwargs)
|
|
1493
|
+
|
|
1494
|
+
if q_axis == "x":
|
|
1495
|
+
if "x_axis_label" not in kwargs:
|
|
1496
|
+
kwargs["x_axis_label"] = q
|
|
1497
|
+
|
|
1498
|
+
if "y_axis_type" in kwargs:
|
|
1499
|
+
warnings.warn("`y_axis_type` specified for categorical axis. Ignoring.")
|
|
1500
|
+
del kwargs["y_axis_type"]
|
|
1501
|
+
|
|
1502
|
+
kwargs["y_range"] = cat_range
|
|
1503
|
+
elif q_axis == "y":
|
|
1504
|
+
if "y_axis_label" not in kwargs:
|
|
1505
|
+
kwargs["y_axis_label"] = q
|
|
1506
|
+
|
|
1507
|
+
if "x_axis_type" in kwargs:
|
|
1508
|
+
warnings.warn("`x_axis_type` specified for categorical axis. Ignoring.")
|
|
1509
|
+
del kwargs["x_axis_type"]
|
|
1510
|
+
|
|
1511
|
+
kwargs["x_range"] = cat_range
|
|
1512
|
+
|
|
1513
|
+
return bokeh.plotting.figure(**kwargs), factors, color_factors
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
def _cat_source_dict(df, cats, cols, color_column):
|
|
1517
|
+
cat_source, labels = utils._source_and_labels_from_cats(df, cats)
|
|
1518
|
+
|
|
1519
|
+
if isinstance(cols, (list, tuple, pd.Index)):
|
|
1520
|
+
source_dict = {col: list(df[col].values) for col in cols}
|
|
1521
|
+
else:
|
|
1522
|
+
source_dict = {cols: list(df[cols].values)}
|
|
1523
|
+
|
|
1524
|
+
source_dict["cat"] = cat_source
|
|
1525
|
+
if color_column in [None, "cat"]:
|
|
1526
|
+
source_dict["__label"] = labels
|
|
1527
|
+
else:
|
|
1528
|
+
source_dict["__label"] = list(df[color_column].astype(str).values)
|
|
1529
|
+
source_dict[color_column] = list(df[color_column].astype(str).values)
|
|
1530
|
+
|
|
1531
|
+
return source_dict
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
def _parcoord_source(data, q, cats, q_axis, parcoord_column, factors):
|
|
1535
|
+
if not isinstance(cats, (list, tuple)):
|
|
1536
|
+
cats = [cats]
|
|
1537
|
+
tuple_factors = False
|
|
1538
|
+
else:
|
|
1539
|
+
tuple_factors = True
|
|
1540
|
+
|
|
1541
|
+
grouped_parcoord = data.groupby(parcoord_column)
|
|
1542
|
+
xs = []
|
|
1543
|
+
ys = []
|
|
1544
|
+
for t, g in grouped_parcoord:
|
|
1545
|
+
xy = []
|
|
1546
|
+
for _, r in g.iterrows():
|
|
1547
|
+
if tuple_factors:
|
|
1548
|
+
xy.append([tuple([r[cat] for cat in cats]), r[q]])
|
|
1549
|
+
else:
|
|
1550
|
+
xy.append([r[cats[0]], r[q]])
|
|
1551
|
+
|
|
1552
|
+
if len(xy) > 1:
|
|
1553
|
+
xy.sort(key=lambda a: factors.index(a[0]))
|
|
1554
|
+
xs_pc = []
|
|
1555
|
+
ys_pc = []
|
|
1556
|
+
for pair in xy:
|
|
1557
|
+
xs_pc.append(pair[0])
|
|
1558
|
+
ys_pc.append(pair[1])
|
|
1559
|
+
|
|
1560
|
+
if q_axis == "y":
|
|
1561
|
+
xs.append(xs_pc)
|
|
1562
|
+
ys.append(ys_pc)
|
|
1563
|
+
else:
|
|
1564
|
+
xs.append(ys_pc)
|
|
1565
|
+
ys.append(xs_pc)
|
|
1566
|
+
|
|
1567
|
+
return bokeh.models.ColumnDataSource(dict(xs=xs, ys=ys))
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
def _outliers(data, min_data):
|
|
1571
|
+
if len(data) >= min_data:
|
|
1572
|
+
bottom, middle, top = np.percentile(data, [25, 50, 75])
|
|
1573
|
+
iqr = top - bottom
|
|
1574
|
+
outliers = data[(data > top + 1.5 * iqr) | (data < bottom - 1.5 * iqr)]
|
|
1575
|
+
return outliers
|
|
1576
|
+
else:
|
|
1577
|
+
return data
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def _box_and_whisker(data, min_data):
|
|
1581
|
+
if len(data) >= min_data:
|
|
1582
|
+
middle = data.median()
|
|
1583
|
+
bottom = data.quantile(0.25)
|
|
1584
|
+
top = data.quantile(0.75)
|
|
1585
|
+
iqr = top - bottom
|
|
1586
|
+
top_whisker = max(data[data <= top + 1.5 * iqr].max(), top)
|
|
1587
|
+
bottom_whisker = min(data[data >= bottom - 1.5 * iqr].min(), bottom)
|
|
1588
|
+
return pd.Series(
|
|
1589
|
+
{
|
|
1590
|
+
"middle": middle,
|
|
1591
|
+
"bottom": bottom,
|
|
1592
|
+
"top": top,
|
|
1593
|
+
"top_whisker": top_whisker,
|
|
1594
|
+
"bottom_whisker": bottom_whisker,
|
|
1595
|
+
}
|
|
1596
|
+
)
|
|
1597
|
+
else:
|
|
1598
|
+
return pd.Series(
|
|
1599
|
+
{
|
|
1600
|
+
"middle": np.nan,
|
|
1601
|
+
"bottom": np.nan,
|
|
1602
|
+
"top": np.nan,
|
|
1603
|
+
"top_whisker": np.nan,
|
|
1604
|
+
"bottom_whisker": np.nan,
|
|
1605
|
+
}
|
|
1606
|
+
)
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
def _box_source(df, cats, q, cols, min_data):
|
|
1610
|
+
"""Construct a data frame for making box plot."""
|
|
1611
|
+
# Need to reset index for use in slicing outliers
|
|
1612
|
+
df_source = df.reset_index(drop=True)
|
|
1613
|
+
|
|
1614
|
+
if cats is None:
|
|
1615
|
+
grouped = df_source
|
|
1616
|
+
else:
|
|
1617
|
+
grouped = df_source.groupby(cats, sort=False)
|
|
1618
|
+
|
|
1619
|
+
# Data frame for boxes and whiskers
|
|
1620
|
+
df_box = grouped[q].apply(_box_and_whisker, min_data).unstack().reset_index()
|
|
1621
|
+
df_box = df_box.dropna()
|
|
1622
|
+
|
|
1623
|
+
source_box = bokeh.models.ColumnDataSource(
|
|
1624
|
+
_cat_source_dict(
|
|
1625
|
+
df_box,
|
|
1626
|
+
cats,
|
|
1627
|
+
["middle", "bottom", "top", "top_whisker", "bottom_whisker"],
|
|
1628
|
+
None,
|
|
1629
|
+
)
|
|
1630
|
+
)
|
|
1631
|
+
|
|
1632
|
+
# Data frame for outliers
|
|
1633
|
+
s_outliers = grouped[q].apply(_outliers, min_data)
|
|
1634
|
+
|
|
1635
|
+
# If no cat has enough data, just use everything as an "outlier"
|
|
1636
|
+
if len(s_outliers) == len(df_source):
|
|
1637
|
+
df_outliers = df_source.copy()
|
|
1638
|
+
inds = df_source.index
|
|
1639
|
+
else:
|
|
1640
|
+
df_outliers = s_outliers.reset_index()
|
|
1641
|
+
inds = s_outliers.index.get_level_values(-1)
|
|
1642
|
+
|
|
1643
|
+
df_outliers.index = inds
|
|
1644
|
+
df_outliers[cols] = df_source.loc[inds, cols]
|
|
1645
|
+
|
|
1646
|
+
source_outliers = bokeh.models.ColumnDataSource(
|
|
1647
|
+
_cat_source_dict(df_outliers, cats, cols, None)
|
|
1648
|
+
)
|
|
1649
|
+
|
|
1650
|
+
return source_box, source_outliers
|
|
1651
|
+
|
|
1652
|
+
|
|
1653
|
+
def _out_every_interval(y, intervals, epsilon=1e-6):
|
|
1654
|
+
"""Check to see if a value `y` lies outside every interval in a
|
|
1655
|
+
list of 2-tuples `intervals`."""
|
|
1656
|
+
for interval in intervals:
|
|
1657
|
+
if y > interval[0] + epsilon and y < interval[1] - epsilon:
|
|
1658
|
+
return False
|
|
1659
|
+
|
|
1660
|
+
return True
|
|
1661
|
+
|
|
1662
|
+
|
|
1663
|
+
def _swarm_px(
|
|
1664
|
+
x,
|
|
1665
|
+
frame_width,
|
|
1666
|
+
r,
|
|
1667
|
+
x_range,
|
|
1668
|
+
max_y_px=np.inf,
|
|
1669
|
+
corral="gutter",
|
|
1670
|
+
priority="ascending",
|
|
1671
|
+
marker_pad_px=0,
|
|
1672
|
+
):
|
|
1673
|
+
"""Computes y-coordinates in pixel units for a swarm plot, where x
|
|
1674
|
+
is the quantitative axis.
|
|
1675
|
+
|
|
1676
|
+
Parameters
|
|
1677
|
+
----------
|
|
1678
|
+
x : array_like
|
|
1679
|
+
Array of values of quantitative variable.
|
|
1680
|
+
frame_width : int or float
|
|
1681
|
+
Width of plot frame in pixels.
|
|
1682
|
+
r : float
|
|
1683
|
+
Radius of marker, which is typically the (marker size + 1) / 2,
|
|
1684
|
+
where the +1 is due to the standard line width for a marker of
|
|
1685
|
+
one pixel.
|
|
1686
|
+
x_range : list
|
|
1687
|
+
List of length 2, where the first entry is the lower limit of
|
|
1688
|
+
the quantitative axis and the second entry is the upper limit of
|
|
1689
|
+
the quantitative axis.
|
|
1690
|
+
max_y_px : float, default np.inf
|
|
1691
|
+
Maximum allowed displacement. Any points with computed y-values
|
|
1692
|
+
beyond this will be corralled.
|
|
1693
|
+
corral : str, default 'gutter'
|
|
1694
|
+
Either 'gutter' or 'wrap'. How to corral points beyond the
|
|
1695
|
+
maximum displacement.
|
|
1696
|
+
priority : str, default 'ascending'
|
|
1697
|
+
Sort order when determining which points get moved in the
|
|
1698
|
+
y-direction first. Either 'ascending' or 'descending'.
|
|
1699
|
+
marker_pad_px : int or float
|
|
1700
|
+
Gap between markers in units of pixels.
|
|
1701
|
+
|
|
1702
|
+
Returns
|
|
1703
|
+
-------
|
|
1704
|
+
y : array_like
|
|
1705
|
+
Array of y-values in units of pixels.
|
|
1706
|
+
n_overrun : int
|
|
1707
|
+
Number of data points that overrun max_y_px.
|
|
1708
|
+
"""
|
|
1709
|
+
# Sort x according to priority
|
|
1710
|
+
if priority == "ascending":
|
|
1711
|
+
inds = [i[0] for i in sorted(enumerate(x), key=lambda x: x[1])]
|
|
1712
|
+
elif priority == "descending":
|
|
1713
|
+
inds = [i[0] for i in sorted(enumerate(x), key=lambda x: -x[1])]
|
|
1714
|
+
elif priority == "random":
|
|
1715
|
+
raise NotImplementedError("'random' priority is not yet implemented.")
|
|
1716
|
+
else:
|
|
1717
|
+
raise NotImplementedError("Custom `priority` not yet implemented.")
|
|
1718
|
+
|
|
1719
|
+
x_pixels = (x[inds] - x_range[0]) * frame_width / (x_range[1] - x_range[0])
|
|
1720
|
+
y_pixels = np.inf * np.ones_like(x_pixels)
|
|
1721
|
+
|
|
1722
|
+
for i in range(len(x_pixels)):
|
|
1723
|
+
intervals = []
|
|
1724
|
+
|
|
1725
|
+
# Scan points to the right
|
|
1726
|
+
for j in range(i + 1, len(x_pixels)):
|
|
1727
|
+
dist = abs(x_pixels[i] - x_pixels[j])
|
|
1728
|
+
if dist > 2 * r:
|
|
1729
|
+
if priority in ["ascending", "descending"]:
|
|
1730
|
+
break
|
|
1731
|
+
else:
|
|
1732
|
+
continue
|
|
1733
|
+
if y_pixels[j] < np.inf:
|
|
1734
|
+
offset = np.sqrt(4 * r**2 - dist**2) + marker_pad_px
|
|
1735
|
+
intervals.append([y_pixels[j] - offset, y_pixels[j] + offset])
|
|
1736
|
+
|
|
1737
|
+
# Scan points to the left
|
|
1738
|
+
for j in range(i - 1, -1, -1):
|
|
1739
|
+
dist = abs(x_pixels[i] - x_pixels[j])
|
|
1740
|
+
if dist > 2 * r:
|
|
1741
|
+
if priority in ["ascending", "descending"]:
|
|
1742
|
+
break
|
|
1743
|
+
else:
|
|
1744
|
+
continue
|
|
1745
|
+
if y_pixels[j] < np.inf:
|
|
1746
|
+
offset = np.sqrt(4 * r**2 - dist**2) + marker_pad_px
|
|
1747
|
+
intervals.append([y_pixels[j] - offset, y_pixels[j] + offset])
|
|
1748
|
+
|
|
1749
|
+
# Any y-position must be outside all intervals and should be at the edge of one of the intervals
|
|
1750
|
+
# Need to find the first candidate that satisfies this
|
|
1751
|
+
y_cand = 0
|
|
1752
|
+
if len(intervals) > 0:
|
|
1753
|
+
candidates = sorted(np.array(intervals).flatten(), key=abs)
|
|
1754
|
+
for cand in candidates:
|
|
1755
|
+
if _out_every_interval(cand, intervals):
|
|
1756
|
+
y_cand = cand
|
|
1757
|
+
break
|
|
1758
|
+
y_pixels[i] = y_cand
|
|
1759
|
+
|
|
1760
|
+
# Clean up points landing too far out
|
|
1761
|
+
n_overrun = 0
|
|
1762
|
+
for i in range(len(x)):
|
|
1763
|
+
if abs(y_pixels[i]) > max_y_px:
|
|
1764
|
+
n_overrun += 1
|
|
1765
|
+
if corral == "gutter":
|
|
1766
|
+
y_pixels[i] = np.sign(y_pixels[i]) * max_y_px
|
|
1767
|
+
elif corral == "wrap":
|
|
1768
|
+
y_pixels[i] = np.sign(y_pixels[i]) * 2 * max_y_px - y_pixels[i]
|
|
1769
|
+
|
|
1770
|
+
# Build output for y in pixels
|
|
1771
|
+
y_out = np.empty_like(x)
|
|
1772
|
+
for i in range(len(x)):
|
|
1773
|
+
y_out[inds[i]] = y_pixels[i]
|
|
1774
|
+
|
|
1775
|
+
return y_out, n_overrun
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def _swarm(
|
|
1779
|
+
x,
|
|
1780
|
+
p,
|
|
1781
|
+
r,
|
|
1782
|
+
x_range,
|
|
1783
|
+
q_axis,
|
|
1784
|
+
log_q,
|
|
1785
|
+
corral="gutter",
|
|
1786
|
+
priority="ascending",
|
|
1787
|
+
marker_pad_px=0,
|
|
1788
|
+
):
|
|
1789
|
+
if q_axis == "x":
|
|
1790
|
+
extra_padding = 0
|
|
1791
|
+
if isinstance(p.y_range.factors[0], tuple):
|
|
1792
|
+
if len(p.y_range.factors[0]) >= 2:
|
|
1793
|
+
extra_padding += p.y_range.group_padding
|
|
1794
|
+
if len(p.y_range.factors[0]) > 2:
|
|
1795
|
+
extra_padding += (
|
|
1796
|
+
len(p.y_range.factors[0]) - 2
|
|
1797
|
+
) * p.y_range.subgroup_padding
|
|
1798
|
+
h = p.frame_height
|
|
1799
|
+
w = p.frame_width
|
|
1800
|
+
n_factors = len(p.y_range.factors) + extra_padding
|
|
1801
|
+
else:
|
|
1802
|
+
extra_padding = 0
|
|
1803
|
+
if isinstance(p.x_range.factors[0], tuple):
|
|
1804
|
+
if len(p.x_range.factors[0]) >= 2:
|
|
1805
|
+
extra_padding += p.x_range.group_padding
|
|
1806
|
+
if len(p.x_range.factors[0]) > 2:
|
|
1807
|
+
extra_padding += (
|
|
1808
|
+
len(p.x_range.factors[0]) - 2
|
|
1809
|
+
) * p.x_range.subgroup_padding
|
|
1810
|
+
w = p.frame_height
|
|
1811
|
+
h = p.frame_width
|
|
1812
|
+
n_factors = len(p.x_range.factors) + extra_padding
|
|
1813
|
+
|
|
1814
|
+
max_y_px = h / n_factors / 2 - 2 * r
|
|
1815
|
+
|
|
1816
|
+
# Make adjustments in case we have log axes
|
|
1817
|
+
xvals = np.array(x)
|
|
1818
|
+
if log_q:
|
|
1819
|
+
xvals = np.log10(xvals)
|
|
1820
|
+
|
|
1821
|
+
y_pixels, n_overrun = _swarm_px(
|
|
1822
|
+
xvals,
|
|
1823
|
+
w,
|
|
1824
|
+
r,
|
|
1825
|
+
x_range,
|
|
1826
|
+
max_y_px=max_y_px,
|
|
1827
|
+
corral=corral,
|
|
1828
|
+
priority=priority,
|
|
1829
|
+
marker_pad_px=marker_pad_px,
|
|
1830
|
+
)
|
|
1831
|
+
|
|
1832
|
+
if n_overrun > 0:
|
|
1833
|
+
hw = "height" if q_axis == "x" else "width"
|
|
1834
|
+
warnings.warn(
|
|
1835
|
+
f"{n_overrun} data points exceed maximum {hw}. Consider using spread='jitter' or increasing the frame {hw}."
|
|
1836
|
+
)
|
|
1837
|
+
|
|
1838
|
+
return y_pixels / h * n_factors
|