lessPython 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lessPy/ANOVA.py +680 -0
- lessPy/Chart.py +1055 -0
- lessPy/Correlation.py +236 -0
- lessPy/Flows.py +116 -0
- lessPy/Logit.py +615 -0
- lessPy/Prop_test.py +267 -0
- lessPy/Regression.py +1491 -0
- lessPy/VariableLabels.py +119 -0
- lessPy/X.py +426 -0
- lessPy/XY.py +2007 -0
- lessPy/__init__.py +60 -0
- lessPy/anova_rmd.py +227 -0
- lessPy/bc_plotly.py +575 -0
- lessPy/bubble_plotly.py +470 -0
- lessPy/corCFA.py +316 -0
- lessPy/corEFA.py +220 -0
- lessPy/corPrint.py +45 -0
- lessPy/corProp.py +73 -0
- lessPy/corRead.py +48 -0
- lessPy/corReflect.py +72 -0
- lessPy/corReorder.py +161 -0
- lessPy/corScree.py +87 -0
- lessPy/data/Anova_1way.csv +25 -0
- lessPy/data/Anova_2way.csv +49 -0
- lessPy/data/Anova_rb.csv +8 -0
- lessPy/data/Anova_rbf.csv +49 -0
- lessPy/data/Anova_sp.csv +57 -0
- lessPy/data/BodyMeas.csv +341 -0
- lessPy/data/Cars93.csv +94 -0
- lessPy/data/Employee.csv +38 -0
- lessPy/data/Employee_lbl.csv +9 -0
- lessPy/data/FreqTable99.csv +5 -0
- lessPy/data/Jackets.csv +1026 -0
- lessPy/data/Learn.csv +35 -0
- lessPy/data/Mach4.csv +352 -0
- lessPy/data/Mach4_lbl.csv +21 -0
- lessPy/data/Reading.csv +101 -0
- lessPy/data/StockPrice.csv +1489 -0
- lessPy/data/WeightLoss.csv +11 -0
- lessPy/datasets.py +46 -0
- lessPy/date_infer.py +112 -0
- lessPy/details.py +314 -0
- lessPy/dn_plotly.py +495 -0
- lessPy/dot_plotly.py +385 -0
- lessPy/freq_poly_plotly.py +324 -0
- lessPy/getColors.py +399 -0
- lessPy/hier_plotly.py +352 -0
- lessPy/hs_plotly.py +395 -0
- lessPy/logit_rmd.py +410 -0
- lessPy/order_by.py +94 -0
- lessPy/pie_plotly.py +292 -0
- lessPy/pivot.py +158 -0
- lessPy/plotly_utils.py +787 -0
- lessPy/plt_add.py +129 -0
- lessPy/plt_contour.py +192 -0
- lessPy/plt_contour_facet.py +194 -0
- lessPy/plt_forecast.py +677 -0
- lessPy/plt_mat_plotly.py +201 -0
- lessPy/plt_plotly.py +216 -0
- lessPy/plt_smooth.py +170 -0
- lessPy/plt_time.py +143 -0
- lessPy/prob_norm.py +111 -0
- lessPy/prob_tcut.py +131 -0
- lessPy/prob_znorm.py +110 -0
- lessPy/radar_plotly.py +201 -0
- lessPy/reg_rmd.py +754 -0
- lessPy/rename.py +33 -0
- lessPy/reshape.py +95 -0
- lessPy/showColors.py +130 -0
- lessPy/simCImean.py +165 -0
- lessPy/simCLT.py +265 -0
- lessPy/simFlips.py +104 -0
- lessPy/simMeans.py +146 -0
- lessPy/stats_out.py +189 -0
- lessPy/ttest.py +641 -0
- lessPy/utils.py +235 -0
- lessPy/vbs_plotly.py +545 -0
- lesspython-0.1.0.dist-info/METADATA +93 -0
- lesspython-0.1.0.dist-info/RECORD +82 -0
- lesspython-0.1.0.dist-info/WHEEL +5 -0
- lesspython-0.1.0.dist-info/licenses/LICENSE +338 -0
- lesspython-0.1.0.dist-info/top_level.txt +1 -0
lessPy/hs_plotly.py
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# hs_plotly.py — analog of hs.plotly.R
|
|
2
|
+
#
|
|
3
|
+
# Renders a histogram from RAW numeric data plus pre-computed bin
|
|
4
|
+
# breaks (binning policy — Sturges, bin_width, etc. — is decided
|
|
5
|
+
# upstream in X(), as in lessR where .hst.main supplies h$breaks).
|
|
6
|
+
# Optional by= draws one translucent series per group, overlaid
|
|
7
|
+
# (default) or stacked.
|
|
8
|
+
#
|
|
9
|
+
# Small extension over the R source: proportion=True scales each
|
|
10
|
+
# group's counts to proportions (R's plotly path always passes
|
|
11
|
+
# counts and handles proportions only in the base-R renderer).
|
|
12
|
+
#
|
|
13
|
+
# facet= draws one panel per level on shared bins and a shared
|
|
14
|
+
# count scale (~ .bar.lattice T.type="hist"), first level in the
|
|
15
|
+
# bottom panel; proportions are per panel. With by=, each panel
|
|
16
|
+
# overlays (or stacks) one translucent series per group, and
|
|
17
|
+
# proportions and hover shares are per group within a panel.
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import pandas as pd
|
|
23
|
+
import plotly.graph_objects as go
|
|
24
|
+
|
|
25
|
+
from .utils import get_option, pretty
|
|
26
|
+
from .plotly_utils import (
|
|
27
|
+
auto_opacity, axis_format, axis_num, facet_fig,
|
|
28
|
+
facet_panels, finish_facet, get_tick_fmt,
|
|
29
|
+
legend_style, make_trans, plot_border, plotly_style, to_hex,
|
|
30
|
+
x_grid,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def hs_plotly(x, by=None, x_name=None, by_name=None,
|
|
35
|
+
facet=None, facet_order=None, facet_name=None,
|
|
36
|
+
facet2=None, facet2_order=None, facet2_name=None,
|
|
37
|
+
breaks=None, freq=True, proportion=False,
|
|
38
|
+
fill=None, border=None,
|
|
39
|
+
cumulate="off", reg="snow2", counts=False,
|
|
40
|
+
x_lab=None, y_lab=None,
|
|
41
|
+
ax=None, gridT1=None,
|
|
42
|
+
digits_d=2, position="overlay", main=None,
|
|
43
|
+
n_col=1, axis_fmt="K", axis_x_pre="",
|
|
44
|
+
axis_y_pre="", style_opts=None):
|
|
45
|
+
|
|
46
|
+
if position not in ("overlay", "stack"):
|
|
47
|
+
raise ValueError('position must be "overlay" or "stack"')
|
|
48
|
+
if breaks is None or len(breaks) < 2:
|
|
49
|
+
raise ValueError("breaks (bin edges) required, length >= 2")
|
|
50
|
+
if style_opts is None:
|
|
51
|
+
style_opts = plotly_style()
|
|
52
|
+
if facet is not None:
|
|
53
|
+
return _hs_facet(x, facet, facet_order, x_name,
|
|
54
|
+
facet_name, breaks, proportion, fill,
|
|
55
|
+
border, x_lab, y_lab, digits_d, main,
|
|
56
|
+
style_opts, by=by, by_name=by_name,
|
|
57
|
+
position=position, n_col=n_col,
|
|
58
|
+
axis_fmt=axis_fmt,
|
|
59
|
+
axis_x_pre=axis_x_pre,
|
|
60
|
+
axis_y_pre=axis_y_pre,
|
|
61
|
+
facet2=facet2,
|
|
62
|
+
facet2_order=facet2_order,
|
|
63
|
+
facet2_name=facet2_name)
|
|
64
|
+
fill_miss = fill is None
|
|
65
|
+
if fill_miss:
|
|
66
|
+
fill = get_option("bar_fill_cont", "#96AAC3")
|
|
67
|
+
if border is None:
|
|
68
|
+
border = get_option("bar_color_cont", "#8496AF")
|
|
69
|
+
if x_name is None:
|
|
70
|
+
x_name = getattr(x, "name", None) or "x"
|
|
71
|
+
if x_lab is None:
|
|
72
|
+
x_lab = x_name
|
|
73
|
+
if y_lab is None:
|
|
74
|
+
y_lab = f"Count of {x_name}"
|
|
75
|
+
|
|
76
|
+
x = np.asarray(x, dtype=float)
|
|
77
|
+
breaks = [float(b) for b in breaks]
|
|
78
|
+
|
|
79
|
+
# --- bin geometry ---
|
|
80
|
+
left = np.array(breaks[:-1])
|
|
81
|
+
right = np.array(breaks[1:])
|
|
82
|
+
mids = (left + right) / 2
|
|
83
|
+
widths = right - left
|
|
84
|
+
n_bins = len(mids)
|
|
85
|
+
|
|
86
|
+
# --- groups ---
|
|
87
|
+
if by is None:
|
|
88
|
+
groups = ["Series 1"]
|
|
89
|
+
else:
|
|
90
|
+
by = pd.Series(by).astype(str)
|
|
91
|
+
groups = sorted(by.dropna().unique().tolist())
|
|
92
|
+
G = len(groups)
|
|
93
|
+
|
|
94
|
+
# groups need distinguishable hues: default to the palette
|
|
95
|
+
if fill_miss and G > 1:
|
|
96
|
+
from .plotly_utils import BASE_COLORS
|
|
97
|
+
fill = BASE_COLORS
|
|
98
|
+
|
|
99
|
+
# --- counts per bin (right-closed bins, as in R's hist) ---
|
|
100
|
+
def bin_counts(xg):
|
|
101
|
+
cats = pd.cut(xg, breaks, right=True, include_lowest=True)
|
|
102
|
+
return cats.value_counts(sort=False).to_numpy(dtype=float)
|
|
103
|
+
|
|
104
|
+
y_mat = np.zeros((n_bins, G))
|
|
105
|
+
for g, gname in enumerate(groups):
|
|
106
|
+
xg = x if by is None else x[(by == gname).to_numpy()]
|
|
107
|
+
xg = xg[np.isfinite(xg)]
|
|
108
|
+
y_mat[:, g] = bin_counts(pd.Series(xg))
|
|
109
|
+
|
|
110
|
+
# density: normalize each group to unit area; proportion: to 1
|
|
111
|
+
if not freq:
|
|
112
|
+
for g in range(G):
|
|
113
|
+
n_g = y_mat[:, g].sum()
|
|
114
|
+
if n_g > 0:
|
|
115
|
+
y_mat[:, g] = y_mat[:, g] / (n_g * widths)
|
|
116
|
+
elif proportion:
|
|
117
|
+
for g in range(G):
|
|
118
|
+
n_g = y_mat[:, g].sum()
|
|
119
|
+
if n_g > 0:
|
|
120
|
+
y_mat[:, g] = y_mat[:, g] / n_g
|
|
121
|
+
|
|
122
|
+
# cumulative histogram: cumsum the (single) series; "both"
|
|
123
|
+
# overdraws the regular bars in the reg color (hst.main.R)
|
|
124
|
+
y_orig = None
|
|
125
|
+
if cumulate in ("on", "both"):
|
|
126
|
+
y_orig = y_mat[:, 0].copy()
|
|
127
|
+
y_mat[:, 0] = np.cumsum(y_mat[:, 0])
|
|
128
|
+
y_lab = f"Cumulative {y_lab}"
|
|
129
|
+
|
|
130
|
+
rng_txt = [f"({lv:g}, {rv:g}]" for lv, rv in zip(left, right)]
|
|
131
|
+
|
|
132
|
+
def hover_group(yv):
|
|
133
|
+
if freq and not proportion:
|
|
134
|
+
amt = yv
|
|
135
|
+
cum_lbl = "Cumulative"
|
|
136
|
+
else:
|
|
137
|
+
amt = yv * widths if not freq else yv
|
|
138
|
+
cum_lbl = ("Cumulative (area)" if not freq
|
|
139
|
+
else "Cumulative")
|
|
140
|
+
total = amt.sum()
|
|
141
|
+
share = amt / total if total > 0 else np.full(n_bins, np.nan)
|
|
142
|
+
cum = np.cumsum(amt)
|
|
143
|
+
cum_pct = cum / total if total > 0 else share
|
|
144
|
+
return [
|
|
145
|
+
(f"Bin: {rng_txt[i]}"
|
|
146
|
+
f"<br>{y_lab}: {round(yv[i], digits_d):g}"
|
|
147
|
+
f"<br>% of total: {100 * share[i]:.1f}%"
|
|
148
|
+
f"<br>{cum_lbl}: {round(cum[i], digits_d):g}"
|
|
149
|
+
f"<br>Cumulative %: {100 * cum_pct[i]:.1f}%")
|
|
150
|
+
for i in range(n_bins)
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
# colors
|
|
154
|
+
alpha_fill = auto_opacity(
|
|
155
|
+
G if G > 1 else 1,
|
|
156
|
+
"stack" if (G > 1 and position == "stack") else "overlay")
|
|
157
|
+
fills = [make_trans(
|
|
158
|
+
fill[g % len(fill)] if isinstance(fill, (list, tuple))
|
|
159
|
+
else fill, alpha_fill) for g in range(G)]
|
|
160
|
+
borders = [to_hex(
|
|
161
|
+
border[g % len(border)] if isinstance(border, (list, tuple))
|
|
162
|
+
else border) for g in range(G)]
|
|
163
|
+
|
|
164
|
+
fig = go.Figure()
|
|
165
|
+
for g, gname in enumerate(groups):
|
|
166
|
+
yv = y_mat[:, g]
|
|
167
|
+
if cumulate != "off": # shares of a cumsum are
|
|
168
|
+
hover = [ # not meaningful
|
|
169
|
+
f"Bin: {rng_txt[i]}<br>{y_lab}: "
|
|
170
|
+
f"{round(yv[i], digits_d):g}"
|
|
171
|
+
for i in range(n_bins)]
|
|
172
|
+
else:
|
|
173
|
+
hover = hover_group(yv)
|
|
174
|
+
if G > 1 and by_name:
|
|
175
|
+
hover = [f"{by_name}: {gname}<br>{h}" for h in hover]
|
|
176
|
+
extra = {}
|
|
177
|
+
if counts: # labels above the bars
|
|
178
|
+
extra = dict(
|
|
179
|
+
text=[f"{round(v, digits_d):g}" for v in yv],
|
|
180
|
+
textposition="outside", cliponaxis=False,
|
|
181
|
+
textfont=dict(color=to_hex(get_option(
|
|
182
|
+
"lab_color", "black"))))
|
|
183
|
+
fig.add_trace(go.Bar(
|
|
184
|
+
x=mids,
|
|
185
|
+
y=yv,
|
|
186
|
+
name=gname if G > 1 else None,
|
|
187
|
+
width=widths * 0.98,
|
|
188
|
+
marker=dict(
|
|
189
|
+
color=fills[g],
|
|
190
|
+
line=dict(color=borders[g], width=1),
|
|
191
|
+
),
|
|
192
|
+
hoverinfo="text",
|
|
193
|
+
hovertext=hover,
|
|
194
|
+
showlegend=G > 1,
|
|
195
|
+
**extra,
|
|
196
|
+
))
|
|
197
|
+
if cumulate == "both": # regular bars on top
|
|
198
|
+
reg_hex = "#EEE9E9" if reg == "snow2" else to_hex(reg)
|
|
199
|
+
fig.add_trace(go.Bar(
|
|
200
|
+
x=mids, y=y_orig, width=widths * 0.98,
|
|
201
|
+
marker=dict(color=reg_hex,
|
|
202
|
+
line=dict(color=borders[0], width=1)),
|
|
203
|
+
hoverinfo="skip", showlegend=False))
|
|
204
|
+
|
|
205
|
+
# axes, grid, layout
|
|
206
|
+
if ax is None:
|
|
207
|
+
from .utils import pretty
|
|
208
|
+
fx = x[np.isfinite(x)]
|
|
209
|
+
axT1 = pretty(float(fx.min()), float(fx.max()))
|
|
210
|
+
ymax = (y_mat.sum(axis=1).max()
|
|
211
|
+
if (G > 1 and position == "stack")
|
|
212
|
+
else y_mat.max())
|
|
213
|
+
axT2 = pretty(0, float(ymax))
|
|
214
|
+
ax = dict(
|
|
215
|
+
axT1=axT1,
|
|
216
|
+
axL1=axis_format(axT1, digits_d, axis_fmt,
|
|
217
|
+
axis_x_pre),
|
|
218
|
+
axT2=axT2,
|
|
219
|
+
axL2=axis_format(axT2, digits_d, axis_fmt,
|
|
220
|
+
axis_y_pre))
|
|
221
|
+
if gridT1 is None:
|
|
222
|
+
gridT1 = breaks # grid at the bin boundaries
|
|
223
|
+
|
|
224
|
+
ax_x = axis_num(x_lab, ax["axT1"], ax["axL1"])
|
|
225
|
+
ax_y = axis_num(y_lab, ax["axT2"], ax["axL2"])
|
|
226
|
+
ax_y.update(showgrid=True,
|
|
227
|
+
gridcolor=to_hex(style_opts["grid_col"]),
|
|
228
|
+
gridwidth=1, griddash="dot")
|
|
229
|
+
|
|
230
|
+
fig.update_layout(
|
|
231
|
+
xaxis=ax_x,
|
|
232
|
+
yaxis=ax_y,
|
|
233
|
+
shapes=x_grid(gridT1) + plot_border(),
|
|
234
|
+
template=None,
|
|
235
|
+
barmode=("stack" if (G > 1 and position == "stack")
|
|
236
|
+
else "overlay"),
|
|
237
|
+
bargap=0,
|
|
238
|
+
plot_bgcolor=to_hex(style_opts["panel_fill"]),
|
|
239
|
+
paper_bgcolor=to_hex(style_opts["window_fill"]),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if G > 1:
|
|
243
|
+
fig.update_layout(legend=legend_style(by_name, style_opts))
|
|
244
|
+
|
|
245
|
+
if main:
|
|
246
|
+
title_size = round(16 * get_option("main_size", 1))
|
|
247
|
+
fig.update_layout(
|
|
248
|
+
title=dict(text=main, x=0.5, xanchor="center",
|
|
249
|
+
y=0.99, yanchor="top",
|
|
250
|
+
font=dict(size=title_size,
|
|
251
|
+
color=to_hex(get_option(
|
|
252
|
+
"lab_color", "black")))),
|
|
253
|
+
margin=dict(t=round(title_size * 2.2)))
|
|
254
|
+
return fig
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _hs_facet(x, facet, facet_order, x_name, facet_name, breaks,
|
|
258
|
+
proportion, fill, border, x_lab, y_lab, digits_d,
|
|
259
|
+
main, style_opts, by=None, by_name=None,
|
|
260
|
+
position="overlay", n_col=1, axis_fmt="K",
|
|
261
|
+
axis_x_pre="", axis_y_pre="",
|
|
262
|
+
facet2=None, facet2_order=None, facet2_name=None):
|
|
263
|
+
"""One histogram panel per facet level: shared bins, shared
|
|
264
|
+
count scale, per-panel proportions. ~ .bar.lattice hist.
|
|
265
|
+
With by=, one translucent series per group within each panel,
|
|
266
|
+
overlaid (default) or stacked, legend from the bottom panel;
|
|
267
|
+
proportions are then per group within a panel. facet2: the
|
|
268
|
+
two-facet grid, rows = facet2 levels."""
|
|
269
|
+
x = np.asarray(x, dtype=float)
|
|
270
|
+
ok = np.isfinite(x)
|
|
271
|
+
x, facet = x[ok], np.asarray(facet)[ok]
|
|
272
|
+
if facet2 is not None:
|
|
273
|
+
facet2 = np.asarray(facet2)[ok]
|
|
274
|
+
if facet_order is None:
|
|
275
|
+
facet_order = sorted(set(facet))
|
|
276
|
+
labels, pos, sel, n_row_g, n_col = facet_panels(
|
|
277
|
+
facet, facet_order, facet2, facet2_order,
|
|
278
|
+
facet_name, facet2_name, n_col)
|
|
279
|
+
|
|
280
|
+
if by is None:
|
|
281
|
+
groups = [None]
|
|
282
|
+
else:
|
|
283
|
+
by = pd.Series(by).astype(str).to_numpy()[ok]
|
|
284
|
+
groups = sorted(set(by))
|
|
285
|
+
G = len(groups)
|
|
286
|
+
stack = G > 1 and position == "stack"
|
|
287
|
+
|
|
288
|
+
if fill is None:
|
|
289
|
+
if G > 1:
|
|
290
|
+
from .plotly_utils import BASE_COLORS
|
|
291
|
+
fill = BASE_COLORS
|
|
292
|
+
else:
|
|
293
|
+
fill = get_option("bar_fill_cont", "#96AAC3")
|
|
294
|
+
if border is None:
|
|
295
|
+
border = get_option("bar_color_cont", "#8496AF")
|
|
296
|
+
if x_lab is None:
|
|
297
|
+
x_lab = x_name
|
|
298
|
+
if y_lab is None:
|
|
299
|
+
y_lab = (f"Proportion of {x_name}" if proportion
|
|
300
|
+
else f"Count of {x_name}")
|
|
301
|
+
|
|
302
|
+
breaks = [float(b) for b in breaks]
|
|
303
|
+
left = np.array(breaks[:-1])
|
|
304
|
+
right = np.array(breaks[1:])
|
|
305
|
+
mids = (left + right) / 2
|
|
306
|
+
widths = right - left
|
|
307
|
+
n_bins = len(mids)
|
|
308
|
+
rng_txt = [f"({lv:g}, {rv:g}]"
|
|
309
|
+
for lv, rv in zip(left, right)]
|
|
310
|
+
|
|
311
|
+
# y_mats[i][:, g]: bin heights for facet panel i, group g
|
|
312
|
+
y_mats = []
|
|
313
|
+
for in_lvl in sel:
|
|
314
|
+
y_mat = np.zeros((n_bins, G))
|
|
315
|
+
for g, gname in enumerate(groups):
|
|
316
|
+
sel = (in_lvl if gname is None
|
|
317
|
+
else in_lvl & (by == gname))
|
|
318
|
+
cats = pd.cut(pd.Series(x[sel]), breaks, right=True,
|
|
319
|
+
include_lowest=True)
|
|
320
|
+
yv = cats.value_counts(sort=False).to_numpy(
|
|
321
|
+
dtype=float)
|
|
322
|
+
if proportion and yv.sum() > 0:
|
|
323
|
+
yv = yv / yv.sum()
|
|
324
|
+
y_mat[:, g] = yv
|
|
325
|
+
y_mats.append(y_mat)
|
|
326
|
+
|
|
327
|
+
ymax = max(float(m.sum(axis=1).max() if stack else m.max())
|
|
328
|
+
for m in y_mats)
|
|
329
|
+
axT1 = pretty(float(x.min()), float(x.max()))
|
|
330
|
+
axT2 = pretty(0, ymax)
|
|
331
|
+
fmt2 = get_tick_fmt(axT2, digits_d)
|
|
332
|
+
ax = dict(axT1=axT1,
|
|
333
|
+
axL1=axis_format(axT1, digits_d, axis_fmt,
|
|
334
|
+
axis_x_pre),
|
|
335
|
+
axT2=axT2,
|
|
336
|
+
axL2=[f"{v:.{digits_d}f}" if fmt2 else f"{v:g}"
|
|
337
|
+
for v in axT2])
|
|
338
|
+
|
|
339
|
+
alpha_fill = auto_opacity(G if G > 1 else 1,
|
|
340
|
+
"stack" if stack else "overlay")
|
|
341
|
+
fills = [make_trans(
|
|
342
|
+
fill[g % len(fill)] if isinstance(fill, (list, tuple))
|
|
343
|
+
else fill, alpha_fill) for g in range(G)]
|
|
344
|
+
borders = [to_hex(
|
|
345
|
+
border[g % len(border)] if isinstance(border, (list, tuple))
|
|
346
|
+
else border) for g in range(G)]
|
|
347
|
+
|
|
348
|
+
pct_lbl = "% of panel" if G == 1 else "% of group"
|
|
349
|
+
fig = facet_fig(n_row_g, n_col)
|
|
350
|
+
for i, lab in enumerate(labels):
|
|
351
|
+
p_row, p_col = pos[i]
|
|
352
|
+
panel_txt = (lab if facet2 is not None
|
|
353
|
+
else f"{facet_name}: {lab}")
|
|
354
|
+
for g, gname in enumerate(groups):
|
|
355
|
+
yv = y_mats[i][:, g]
|
|
356
|
+
total = yv.sum()
|
|
357
|
+
hover = [
|
|
358
|
+
(f"Bin: {rng_txt[j]}"
|
|
359
|
+
f"<br>{y_lab}: {round(yv[j], digits_d):g}"
|
|
360
|
+
f"<br>{pct_lbl}: "
|
|
361
|
+
f"{100 * yv[j] / total if total else 0:.1f}%"
|
|
362
|
+
f"<br>{panel_txt}")
|
|
363
|
+
for j in range(n_bins)
|
|
364
|
+
]
|
|
365
|
+
if gname is not None and by_name:
|
|
366
|
+
hover = [f"{by_name}: {gname}<br>{h}"
|
|
367
|
+
for h in hover]
|
|
368
|
+
fig.add_trace(go.Bar(
|
|
369
|
+
x=mids, y=yv, width=widths * 0.98,
|
|
370
|
+
name=gname if G > 1 else None,
|
|
371
|
+
legendgroup=gname if G > 1 else None,
|
|
372
|
+
marker=dict(color=fills[g],
|
|
373
|
+
line=dict(color=borders[g], width=1)),
|
|
374
|
+
hoverinfo="text", hovertext=hover,
|
|
375
|
+
showlegend=G > 1 and i == 0,
|
|
376
|
+
), row=p_row, col=p_col) # first level bottom
|
|
377
|
+
|
|
378
|
+
fig.update_layout(bargap=0,
|
|
379
|
+
barmode="stack" if stack else "overlay")
|
|
380
|
+
finish_facet(fig, labels, ax, x_lab, y_lab,
|
|
381
|
+
gridT1=breaks, style_opts=style_opts,
|
|
382
|
+
n_col=n_col, pos=pos)
|
|
383
|
+
if G > 1:
|
|
384
|
+
fig.update_layout(legend=legend_style(by_name,
|
|
385
|
+
style_opts))
|
|
386
|
+
if main:
|
|
387
|
+
title_size = round(16 * get_option("main_size", 1))
|
|
388
|
+
fig.update_layout(
|
|
389
|
+
title=dict(text=main, x=0.5, xanchor="center",
|
|
390
|
+
y=0.99, yanchor="top",
|
|
391
|
+
font=dict(size=title_size,
|
|
392
|
+
color=to_hex(get_option(
|
|
393
|
+
"lab_color", "black")))),
|
|
394
|
+
margin=dict(t=round(title_size * 2.2)))
|
|
395
|
+
return fig
|