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/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
|