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.
Files changed (82) hide show
  1. lessPy/ANOVA.py +680 -0
  2. lessPy/Chart.py +1055 -0
  3. lessPy/Correlation.py +236 -0
  4. lessPy/Flows.py +116 -0
  5. lessPy/Logit.py +615 -0
  6. lessPy/Prop_test.py +267 -0
  7. lessPy/Regression.py +1491 -0
  8. lessPy/VariableLabels.py +119 -0
  9. lessPy/X.py +426 -0
  10. lessPy/XY.py +2007 -0
  11. lessPy/__init__.py +60 -0
  12. lessPy/anova_rmd.py +227 -0
  13. lessPy/bc_plotly.py +575 -0
  14. lessPy/bubble_plotly.py +470 -0
  15. lessPy/corCFA.py +316 -0
  16. lessPy/corEFA.py +220 -0
  17. lessPy/corPrint.py +45 -0
  18. lessPy/corProp.py +73 -0
  19. lessPy/corRead.py +48 -0
  20. lessPy/corReflect.py +72 -0
  21. lessPy/corReorder.py +161 -0
  22. lessPy/corScree.py +87 -0
  23. lessPy/data/Anova_1way.csv +25 -0
  24. lessPy/data/Anova_2way.csv +49 -0
  25. lessPy/data/Anova_rb.csv +8 -0
  26. lessPy/data/Anova_rbf.csv +49 -0
  27. lessPy/data/Anova_sp.csv +57 -0
  28. lessPy/data/BodyMeas.csv +341 -0
  29. lessPy/data/Cars93.csv +94 -0
  30. lessPy/data/Employee.csv +38 -0
  31. lessPy/data/Employee_lbl.csv +9 -0
  32. lessPy/data/FreqTable99.csv +5 -0
  33. lessPy/data/Jackets.csv +1026 -0
  34. lessPy/data/Learn.csv +35 -0
  35. lessPy/data/Mach4.csv +352 -0
  36. lessPy/data/Mach4_lbl.csv +21 -0
  37. lessPy/data/Reading.csv +101 -0
  38. lessPy/data/StockPrice.csv +1489 -0
  39. lessPy/data/WeightLoss.csv +11 -0
  40. lessPy/datasets.py +46 -0
  41. lessPy/date_infer.py +112 -0
  42. lessPy/details.py +314 -0
  43. lessPy/dn_plotly.py +495 -0
  44. lessPy/dot_plotly.py +385 -0
  45. lessPy/freq_poly_plotly.py +324 -0
  46. lessPy/getColors.py +399 -0
  47. lessPy/hier_plotly.py +352 -0
  48. lessPy/hs_plotly.py +395 -0
  49. lessPy/logit_rmd.py +410 -0
  50. lessPy/order_by.py +94 -0
  51. lessPy/pie_plotly.py +292 -0
  52. lessPy/pivot.py +158 -0
  53. lessPy/plotly_utils.py +787 -0
  54. lessPy/plt_add.py +129 -0
  55. lessPy/plt_contour.py +192 -0
  56. lessPy/plt_contour_facet.py +194 -0
  57. lessPy/plt_forecast.py +677 -0
  58. lessPy/plt_mat_plotly.py +201 -0
  59. lessPy/plt_plotly.py +216 -0
  60. lessPy/plt_smooth.py +170 -0
  61. lessPy/plt_time.py +143 -0
  62. lessPy/prob_norm.py +111 -0
  63. lessPy/prob_tcut.py +131 -0
  64. lessPy/prob_znorm.py +110 -0
  65. lessPy/radar_plotly.py +201 -0
  66. lessPy/reg_rmd.py +754 -0
  67. lessPy/rename.py +33 -0
  68. lessPy/reshape.py +95 -0
  69. lessPy/showColors.py +130 -0
  70. lessPy/simCImean.py +165 -0
  71. lessPy/simCLT.py +265 -0
  72. lessPy/simFlips.py +104 -0
  73. lessPy/simMeans.py +146 -0
  74. lessPy/stats_out.py +189 -0
  75. lessPy/ttest.py +641 -0
  76. lessPy/utils.py +235 -0
  77. lessPy/vbs_plotly.py +545 -0
  78. lesspython-0.1.0.dist-info/METADATA +93 -0
  79. lesspython-0.1.0.dist-info/RECORD +82 -0
  80. lesspython-0.1.0.dist-info/WHEEL +5 -0
  81. lesspython-0.1.0.dist-info/licenses/LICENSE +338 -0
  82. lesspython-0.1.0.dist-info/top_level.txt +1 -0
lessPy/dn_plotly.py ADDED
@@ -0,0 +1,495 @@
1
+ # dn_plotly.py — analog of dn.plotly.R
2
+ #
3
+ # Renders a kernel density curve (Gaussian kernel) from RAW numeric
4
+ # data, with a vertical line at each group's mean. Optional by=
5
+ # overlays one translucent curve per group with a legend.
6
+ #
7
+ # The KDE itself is computed here with numpy (R uses stats::density;
8
+ # the bandwidth arrives from X() via bw_nrd0, R's default rule), on
9
+ # a shared grid of n points extended cut*bw beyond the data range —
10
+ # the same geometry as R's density(cut = 3).
11
+ #
12
+ # facet= draws one panel per level: shared bandwidth, support, and
13
+ # density scale, mean line per panel, first level in the bottom
14
+ # panel. An EXTENSION beyond R, where X() still stops with
15
+ # "Facets not yet working with density" (X.R line ~220). With
16
+ # by=, each panel overlays one curve per group present in it.
17
+
18
+ import math
19
+
20
+ import numpy as np
21
+ import pandas as pd
22
+ import plotly.graph_objects as go
23
+
24
+ from .utils import get_option, kde, pretty
25
+ from .plotly_utils import (
26
+ as_plotly_color, auto_opacity, axis_format, axis_num,
27
+ facet_fig, facet_panels, finish_facet, legend_style,
28
+ make_trans, plot_border, plotly_style, to_hex, x_grid,
29
+ )
30
+
31
+ def dn_plotly(x, by=None, x_name=None, by_name=None,
32
+ facet=None, facet_order=None, facet_name=None,
33
+ facet2=None, facet2_order=None, facet2_name=None,
34
+ fill=None, x_lab=None, y_lab="Density", main=None,
35
+ bw=None, adjust=1, n=512, from_=None, to=None,
36
+ full_curve=True, fill_area=True,
37
+ kind="general", fill_normal=None,
38
+ color_normal="gray20",
39
+ show_histogram=False, fill_hist=None,
40
+ hist_edges=None,
41
+ rug=False, color_rug="black", size_rug=0.5,
42
+ n_col=1, axis_fmt="K", axis_x_pre="",
43
+ digits_d=3, style_opts=None):
44
+
45
+ if style_opts is None:
46
+ style_opts = plotly_style()
47
+ if x_name is None:
48
+ x_name = getattr(x, "name", None) or "x"
49
+ if x_lab is None:
50
+ x_lab = x_name
51
+ fill_miss = fill is None
52
+ if fill_miss:
53
+ # R analog: X.R density default fill rgb(80,150,200)
54
+ fill = "#5096C8"
55
+
56
+ x = np.asarray(x, dtype=float)
57
+ if bw is None:
58
+ from .utils import bw_nrd0
59
+ bw = bw_nrd0(x)
60
+ h = bw * adjust
61
+
62
+ if facet is not None:
63
+ if fill_miss and by is not None:
64
+ from .plotly_utils import BASE_COLORS
65
+ fill = BASE_COLORS
66
+ return _dn_facet(x, facet, facet_order, x_name,
67
+ facet_name, fill, x_lab, y_lab, main,
68
+ h, n, fill_area, style_opts,
69
+ by=by, by_name=by_name, n_col=n_col,
70
+ facet2=facet2,
71
+ facet2_order=facet2_order,
72
+ facet2_name=facet2_name,
73
+ kind=kind, fill_normal=fill_normal,
74
+ color_normal=color_normal,
75
+ show_histogram=show_histogram,
76
+ fill_hist=fill_hist,
77
+ hist_edges=hist_edges,
78
+ rug=rug, color_rug=color_rug,
79
+ size_rug=size_rug,
80
+ axis_fmt=axis_fmt,
81
+ axis_x_pre=axis_x_pre,
82
+ digits_d=digits_d)
83
+
84
+ # --- groups ------------------------------------------------
85
+ if by is None:
86
+ groups = ["Series 1"]
87
+ else:
88
+ by = pd.Series(by).astype(str)
89
+ groups = sorted(by.dropna().unique().tolist())
90
+ G = len(groups)
91
+
92
+ # groups need distinguishable hues: default to the palette
93
+ if fill_miss and G > 1:
94
+ from .plotly_utils import BASE_COLORS
95
+ fill = BASE_COLORS
96
+
97
+ def group_x(gname):
98
+ xg = x if by is None else x[(by == gname).to_numpy()]
99
+ return xg[np.isfinite(xg)]
100
+
101
+ for gname in groups:
102
+ n_ok = len(group_x(gname))
103
+ if n_ok < 2:
104
+ raise ValueError(
105
+ f"density requires at least 2 finite observations; "
106
+ f"group '{gname}' has {n_ok}")
107
+
108
+ # --- shared support (R density: cut = 3 -> +- 3*bw) ---------
109
+ if full_curve:
110
+ ends = []
111
+ for gname in groups:
112
+ xg = group_x(gname)
113
+ ends += [xg.min() - 3 * h, xg.max() + 3 * h]
114
+ lo = min(ends) if from_ is None else from_
115
+ hi = max(ends) if to is None else to
116
+ else:
117
+ fx = x[np.isfinite(x)]
118
+ lo = fx.min() if from_ is None else from_
119
+ hi = fx.max() if to is None else to
120
+ if hi <= lo:
121
+ hi = lo + max(abs(lo) * 1e-4, 1e-6)
122
+ n = max(2, int(n))
123
+ grid = np.linspace(lo, hi, n)
124
+ dx = grid[1] - grid[0]
125
+
126
+ # --- densities per group -------------------------------------
127
+ dens = []
128
+ ymax = 0.0
129
+ for gname in groups:
130
+ xg = group_x(gname)
131
+ y = kde(xg, grid, h)
132
+ cum = np.concatenate(
133
+ [[0], np.cumsum((y[1:] + y[:-1]) * 0.5 * dx)])
134
+ if cum[-1] > 0:
135
+ cum = cum / cum[-1]
136
+ dens.append(dict(y=y, cum=cum, mn=float(xg.mean())))
137
+ ymax = max(ymax, float(y.max()))
138
+
139
+ # --- normal curve and background histogram --------------------
140
+ # R analog: dn.main.R — kind "normal"/"both" adds the normal
141
+ # curve at the sample mean and sd; show_histogram draws a
142
+ # faint density-scaled histogram behind the curves
143
+ d_nrm = None
144
+ if kind in ("normal", "both"):
145
+ xg0 = group_x(groups[0])
146
+ mu, sd = float(xg0.mean()), float(xg0.std(ddof=1))
147
+ z = (grid - mu) / sd
148
+ d_nrm = np.exp(-0.5 * z * z) / (sd * np.sqrt(2 * np.pi))
149
+ hist_y = None
150
+ if show_histogram and hist_edges is not None:
151
+ e = np.asarray(hist_edges, dtype=float)
152
+ cnt, _ = np.histogram(group_x(groups[0]), bins=e)
153
+ tot = cnt.sum()
154
+ if tot > 0:
155
+ hist_w = np.diff(e)
156
+ hist_y = cnt / (tot * hist_w) # density scale
157
+ hist_mids = (e[:-1] + e[1:]) / 2
158
+ if kind == "normal": # general curve not drawn
159
+ ymax = 0.0
160
+ if d_nrm is not None:
161
+ ymax = max(ymax, float(d_nrm.max()))
162
+ if hist_y is not None:
163
+ ymax = max(ymax, float(hist_y.max()))
164
+
165
+ # --- ticks ----------------------------------------------------
166
+ gridT1 = pretty(float(lo), float(hi))
167
+ gridL1 = axis_format(gridT1, digits_d, axis_fmt,
168
+ axis_x_pre)
169
+ gridT2 = pretty(0, ymax, n=6)
170
+ gridL2 = [f"{v:g}" for v in gridT2]
171
+
172
+ # --- colors ---------------------------------------------------
173
+ alpha_fill = auto_opacity(G, "fill")
174
+ alpha_line = auto_opacity(G, "lines") if fill_area else 1
175
+ fill_list = [fill[g % len(fill)]
176
+ if isinstance(fill, (list, tuple)) else fill
177
+ for g in range(G)]
178
+ fill_rgba = [make_trans(c, alpha_fill) for c in fill_list]
179
+ line_rgba = [make_trans(c, alpha_line) for c in fill_list]
180
+
181
+ def hover(d):
182
+ return [
183
+ (f"{x_name}: {grid[i]:.6g}"
184
+ f"<br>Density={round(float(d['y'][i]), 6):g}"
185
+ f"<br>Cumulative %: {100 * d['cum'][i]:.1f}%")
186
+ for i in range(n)
187
+ ]
188
+
189
+ fig = go.Figure()
190
+ if hist_y is not None: # faint histogram behind
191
+ fig.add_trace(go.Bar(
192
+ x=hist_mids, y=hist_y, width=hist_w,
193
+ marker=dict(color=as_plotly_color(
194
+ get_option("se_fill", "#1A1A1A19")
195
+ if fill_hist is None else fill_hist),
196
+ line=dict(width=0)),
197
+ hoverinfo="skip", showlegend=False))
198
+ if kind in ("general", "both"):
199
+ for g, gname in enumerate(groups):
200
+ d = dens[g]
201
+ hv = hover(d)
202
+ if G > 1 and by_name:
203
+ hv = [f"{by_name}: {gname}<br>{t}" for t in hv]
204
+ fig.add_trace(go.Scatter(
205
+ mode="lines",
206
+ x=grid, y=d["y"],
207
+ name=gname if G > 1 else None,
208
+ line=dict(color=line_rgba[g], width=1.4),
209
+ fill="tozeroy" if fill_area else "none",
210
+ fillcolor=fill_rgba[g] if fill_area else None,
211
+ hoverinfo="text",
212
+ hovertext=hv,
213
+ showlegend=G > 1,
214
+ ))
215
+ # vertical line at the group mean, up to the curve
216
+ y_mn = float(np.interp(d["mn"], grid, d["y"]))
217
+ mean_line = dict(color=line_rgba[g], dash="dash",
218
+ width=1.4 if fill_area else 0.7)
219
+ fig.add_trace(go.Scatter(
220
+ mode="lines",
221
+ x=[d["mn"], d["mn"]], y=[0, y_mn],
222
+ line=mean_line,
223
+ hoverinfo="skip", showlegend=False,
224
+ ))
225
+ if d_nrm is not None: # normal curve
226
+ nrm_fill = (None if fill_normal in (None, "transparent")
227
+ else as_plotly_color(fill_normal))
228
+ fig.add_trace(go.Scatter(
229
+ mode="lines", x=grid, y=d_nrm,
230
+ line=dict(color=to_hex(color_normal),
231
+ width=1.35 if nrm_fill is None else 1),
232
+ fill="none" if nrm_fill is None else "tozeroy",
233
+ fillcolor=nrm_fill,
234
+ hoverinfo="skip", showlegend=False))
235
+ if rug: # ticks at each data value
236
+ xr, yr = [], []
237
+ y1r = -0.05 * ymax
238
+ for v in group_x(groups[0]):
239
+ xr += [float(v), float(v), None]
240
+ yr += [0.0, y1r, None]
241
+ fig.add_trace(go.Scatter(
242
+ mode="lines", x=xr, y=yr,
243
+ line=dict(color=to_hex(color_rug),
244
+ width=max(0.5, float(size_rug))),
245
+ hoverinfo="skip", showlegend=False))
246
+
247
+ # --- axes, grid, layout ----------------------------------------
248
+ axis_shapes = []
249
+ if not fill_area: # lines only: neutral x-axis baseline
250
+ axis_shapes = [dict(
251
+ type="line", xref="paper", yref="paper",
252
+ x0=0, x1=1, y0=0, y1=0,
253
+ line=dict(color=to_hex("gray50"), width=1))]
254
+
255
+ ax_x = axis_num(x_lab, gridT1, gridL1)
256
+ ax_y = axis_num(y_lab, gridT2, gridL2)
257
+ ax_y.update(showgrid=True,
258
+ gridcolor=to_hex(style_opts["grid_col"]),
259
+ gridwidth=1, griddash="dot")
260
+
261
+ fig.update_layout(
262
+ xaxis=ax_x,
263
+ yaxis=ax_y,
264
+ shapes=x_grid(gridT1) + plot_border() + axis_shapes,
265
+ template=None,
266
+ bargap=0,
267
+ plot_bgcolor=to_hex(style_opts["panel_fill"]),
268
+ paper_bgcolor=to_hex(style_opts["window_fill"]),
269
+ )
270
+
271
+ if G > 1:
272
+ fig.update_layout(legend=legend_style(by_name, style_opts))
273
+
274
+ if main:
275
+ title_size = round(16 * get_option("main_size", 1))
276
+ fig.update_layout(
277
+ title=dict(text=main, x=0.5, xanchor="center",
278
+ y=0.99, yanchor="top",
279
+ font=dict(size=title_size,
280
+ color=to_hex(get_option(
281
+ "lab_color", "black")))),
282
+ margin=dict(t=round(title_size * 2.2)))
283
+ return fig
284
+
285
+
286
+ def _dn_facet(x, facet, facet_order, x_name, facet_name, fill,
287
+ x_lab, y_lab, main, h, n, fill_area, style_opts,
288
+ by=None, by_name=None, n_col=1,
289
+ facet2=None, facet2_order=None, facet2_name=None,
290
+ kind="general", fill_normal=None,
291
+ color_normal="gray20",
292
+ show_histogram=False, fill_hist=None,
293
+ hist_edges=None,
294
+ rug=False, color_rug="black", size_rug=0.5,
295
+ axis_fmt="K", axis_x_pre="", digits_d=3):
296
+ """One density panel per facet level: common bandwidth,
297
+ support, and density scale; mean line per curve. With by=,
298
+ one translucent curve per group within each panel, legend
299
+ from the first panel showing a group; a group absent from a
300
+ panel draws no curve there. A cell too thin to estimate
301
+ (< 2 finite values) draws an empty panel; only if no cell
302
+ can plot does the call stop, as in R's .plt.dist.facet.
303
+ facet2: the two-facet grid, rows = facet2 levels. Without
304
+ by=, each panel takes the single-panel embellishments, as
305
+ in R: kind= normal curve from the panel's mean and sd,
306
+ show_histogram backdrop on shared bins, rug."""
307
+ ok = np.isfinite(x)
308
+ x, facet = x[ok], np.asarray(facet)[ok]
309
+ if facet2 is not None:
310
+ facet2 = np.asarray(facet2)[ok]
311
+ if facet_order is None:
312
+ facet_order = sorted(set(facet))
313
+ labels, pos, sel, n_row_g, n_col = facet_panels(
314
+ facet, facet_order, facet2, facet2_order,
315
+ facet_name, facet2_name, n_col)
316
+ n_f = len(labels)
317
+
318
+ if by is None:
319
+ groups = [None]
320
+ else:
321
+ by = pd.Series(by).astype(str).to_numpy()[ok]
322
+ groups = sorted(set(by))
323
+ G = len(groups)
324
+
325
+ def cell(i, gname):
326
+ m = sel[i]
327
+ if gname is not None:
328
+ m = m & (by == gname)
329
+ return x[m]
330
+
331
+ if not any(len(cell(i, g)) >= 2
332
+ for i in range(n_f) for g in groups):
333
+ raise ValueError(
334
+ "No facet cell has enough data to plot.")
335
+
336
+ lo = x.min() - 3 * h
337
+ hi = x.max() + 3 * h
338
+ grid = np.linspace(lo, hi, max(2, int(n)))
339
+ dx = grid[1] - grid[0]
340
+
341
+ # curves[i][g] is None for an absent or too-thin cell
342
+ curves = [[None] * G for _ in range(n_f)]
343
+ ymax = 0.0
344
+ for i in range(n_f):
345
+ for g, gname in enumerate(groups):
346
+ xg = cell(i, gname)
347
+ if len(xg) < 2:
348
+ continue
349
+ y = kde(xg, grid, h)
350
+ cum = np.concatenate(
351
+ [[0], np.cumsum((y[1:] + y[:-1]) * 0.5 * dx)])
352
+ if cum[-1] > 0:
353
+ cum = cum / cum[-1]
354
+ curves[i][g] = dict(y=y, cum=cum,
355
+ mn=float(xg.mean()))
356
+ ymax = max(ymax, float(y.max()))
357
+
358
+ # per-panel embellishments, single series only (R's
359
+ # .plt.dist.facet has no grouping): normal curve from the
360
+ # panel's mean/sd, density-scaled histogram on shared bins
361
+ solo = G == 1
362
+ d_nrms = [None] * n_f
363
+ if solo and kind in ("normal", "both"):
364
+ for i in range(n_f):
365
+ xg = cell(i, None)
366
+ if len(xg) < 2:
367
+ continue
368
+ mu = float(xg.mean())
369
+ sd = float(xg.std(ddof=1))
370
+ if sd == 0:
371
+ continue
372
+ z = (grid - mu) / sd
373
+ d_nrms[i] = (np.exp(-0.5 * z * z)
374
+ / (sd * np.sqrt(2 * np.pi)))
375
+ ymax = max(ymax, float(d_nrms[i].max()))
376
+ hist_ys = [None] * n_f
377
+ hist_mids = hist_w = None
378
+ if solo and show_histogram and hist_edges is not None:
379
+ e = np.asarray(hist_edges, dtype=float)
380
+ hist_w = np.diff(e)
381
+ hist_mids = (e[:-1] + e[1:]) / 2
382
+ for i in range(n_f):
383
+ cnt, _ = np.histogram(cell(i, None), bins=e)
384
+ tot = cnt.sum()
385
+ if tot > 0:
386
+ hist_ys[i] = cnt / (tot * hist_w)
387
+ ymax = max(ymax, float(hist_ys[i].max()))
388
+
389
+ axT1 = pretty(float(lo), float(hi))
390
+ axT2 = pretty(0, ymax, n=6)
391
+ ax = dict(axT1=axT1,
392
+ axL1=axis_format(axT1, digits_d, axis_fmt,
393
+ axis_x_pre),
394
+ axT2=axT2, axL2=[f"{v:g}" for v in axT2])
395
+
396
+ alpha_fill = auto_opacity(G, "fill")
397
+ alpha_line = auto_opacity(G, "lines") if fill_area else 1
398
+ fill_list = [fill[g % len(fill)]
399
+ if isinstance(fill, (list, tuple)) else fill
400
+ for g in range(G)]
401
+ fill_rgba = [make_trans(c, alpha_fill) for c in fill_list]
402
+ line_rgba = [make_trans(c, alpha_line) for c in fill_list]
403
+
404
+ fig = facet_fig(n_row_g, n_col)
405
+ seen = set() # legend once per group
406
+ for i, lab in enumerate(labels):
407
+ row, p_col = pos[i]
408
+ panel_txt = (lab if facet2 is not None
409
+ else f"{facet_name}: {lab}")
410
+ if hist_ys[i] is not None: # faint histogram behind
411
+ fig.add_trace(go.Bar(
412
+ x=hist_mids, y=hist_ys[i], width=hist_w,
413
+ marker=dict(color=as_plotly_color(
414
+ get_option("se_fill", "#1A1A1A19")
415
+ if fill_hist is None else fill_hist),
416
+ line=dict(width=0)),
417
+ hoverinfo="skip", showlegend=False,
418
+ ), row=row, col=p_col)
419
+ for g, gname in enumerate(groups):
420
+ c = curves[i][g]
421
+ if c is None or kind == "normal":
422
+ continue
423
+ hover = [
424
+ (f"{x_name}: {grid[j]:.6g}"
425
+ f"<br>Density={round(float(c['y'][j]), 6):g}"
426
+ f"<br>Cumulative %: {100 * c['cum'][j]:.1f}%"
427
+ f"<br>{panel_txt}")
428
+ for j in range(len(grid))
429
+ ]
430
+ if gname is not None and by_name:
431
+ hover = [f"{by_name}: {gname}<br>{t}"
432
+ for t in hover]
433
+ show = G > 1 and gname not in seen
434
+ seen.add(gname)
435
+ fig.add_trace(go.Scatter(
436
+ mode="lines", x=grid, y=c["y"],
437
+ name=gname if G > 1 else None,
438
+ legendgroup=gname if G > 1 else None,
439
+ line=dict(color=line_rgba[g], width=1.4),
440
+ fill="tozeroy" if fill_area else "none",
441
+ fillcolor=fill_rgba[g] if fill_area else None,
442
+ hoverinfo="text", hovertext=hover,
443
+ showlegend=show,
444
+ ), row=row, col=p_col)
445
+ y_mn = float(np.interp(c["mn"], grid, c["y"]))
446
+ fig.add_trace(go.Scatter(
447
+ mode="lines", x=[c["mn"], c["mn"]], y=[0, y_mn],
448
+ line=dict(color=line_rgba[g], dash="dash",
449
+ width=1.4 if fill_area else 0.7),
450
+ hoverinfo="skip", showlegend=False,
451
+ ), row=row, col=p_col)
452
+ if d_nrms[i] is not None: # normal curve
453
+ nrm_fill = (None
454
+ if fill_normal in (None, "transparent")
455
+ else as_plotly_color(fill_normal))
456
+ fig.add_trace(go.Scatter(
457
+ mode="lines", x=grid, y=d_nrms[i],
458
+ line=dict(color=to_hex(color_normal),
459
+ width=1.35 if nrm_fill is None else 1),
460
+ fill="none" if nrm_fill is None else "tozeroy",
461
+ fillcolor=nrm_fill,
462
+ hoverinfo="skip", showlegend=False,
463
+ ), row=row, col=p_col)
464
+ if solo and rug: # ticks at each data value
465
+ xr, yr = [], []
466
+ y1r = -0.05 * ymax
467
+ for v in cell(i, None):
468
+ xr += [float(v), float(v), None]
469
+ yr += [0.0, y1r, None]
470
+ fig.add_trace(go.Scatter(
471
+ mode="lines", x=xr, y=yr,
472
+ line=dict(color=to_hex(color_rug),
473
+ width=max(0.5, float(size_rug))),
474
+ hoverinfo="skip", showlegend=False,
475
+ ), row=row, col=p_col)
476
+
477
+ fig.update_layout(bargap=0)
478
+ finish_facet(fig, labels, ax, x_lab, y_lab, n_col=n_col,
479
+ gridT1=axT1, style_opts=style_opts, pos=pos)
480
+ if solo and rug: # room below zero for the rug ticks
481
+ top = float(ax["axT2"][-1])
482
+ fig.update_yaxes(range=[-0.06 * top, top * 1.04])
483
+ if G > 1:
484
+ fig.update_layout(legend=legend_style(by_name,
485
+ style_opts))
486
+ if main:
487
+ title_size = round(16 * get_option("main_size", 1))
488
+ fig.update_layout(
489
+ title=dict(text=main, x=0.5, xanchor="center",
490
+ y=0.99, yanchor="top",
491
+ font=dict(size=title_size,
492
+ color=to_hex(get_option(
493
+ "lab_color", "black")))),
494
+ margin=dict(t=round(title_size * 2.2)))
495
+ return fig