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/hier_plotly.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# hier_plotly.py — analog of hier.plotly.R (.hier.plotly) plus its
|
|
2
|
+
# data preparation .hier_aggregate() and a simplified
|
|
3
|
+
# .hier_color_resolve() (both in zzz_plotly.R; placed here per the
|
|
4
|
+
# single-caller convention — only the hier path uses them).
|
|
5
|
+
#
|
|
6
|
+
# Renders the three hierarchical part-of-whole views from RAW
|
|
7
|
+
# (case-level) data: sunburst, treemap, icicle. Unlike the other
|
|
8
|
+
# renderers, aggregation happens here at every nesting level, so
|
|
9
|
+
# the input is the raw x (and optional by / y) columns, not a
|
|
10
|
+
# pre-built table.
|
|
11
|
+
#
|
|
12
|
+
# Node model (plotly ids/labels/parents/values):
|
|
13
|
+
# level 1: one node per x category, parent "" (the tree roots)
|
|
14
|
+
# level 2: one node per (x, by) combination, nested inside x
|
|
15
|
+
# values are set on leaves only, branchvalues="remainder"
|
|
16
|
+
#
|
|
17
|
+
# Deliberate deviations from the R source:
|
|
18
|
+
# - Theme-dependent sequential palettes (.scale.clr) are not
|
|
19
|
+
# ported; fills default to BASE_COLORS keyed by top-level
|
|
20
|
+
# category (lessR's default "colors" theme). For the same
|
|
21
|
+
# reason the per-facet luminance remap (fill_vec_byfac) has
|
|
22
|
+
# no counterpart: every facet panel uses the global fills.
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
import pandas as pd
|
|
26
|
+
import plotly.graph_objects as go
|
|
27
|
+
|
|
28
|
+
from .utils import STAT_FUN, get_option
|
|
29
|
+
from .plotly_utils import (
|
|
30
|
+
BASE_COLORS, facet_layout, plotly_style, to_hex,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_LABEL_VALUES = ("input", "%", "prop", "off")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def hier_aggregate(x, by=None, y=None, stat=None, facet=None,
|
|
37
|
+
x_name="X", by_name=None, y_name="y",
|
|
38
|
+
facet_name=None, facet_order=None):
|
|
39
|
+
"""Build the plotly node table (ids/labels/parents/values/
|
|
40
|
+
customdata/root) for sunburst/treemap/icicle. With facet=,
|
|
41
|
+
one node table per facet level (nd_byfac), each carrying a
|
|
42
|
+
facet line in its hover meta; nd is then None.
|
|
43
|
+
R analog: .hier_aggregate()"""
|
|
44
|
+
path = pd.DataFrame({x_name: x.astype(str).values})
|
|
45
|
+
lvl_names = [x_name]
|
|
46
|
+
if by is not None:
|
|
47
|
+
by_col = by_name or "by"
|
|
48
|
+
path[by_col] = by.astype(str).values
|
|
49
|
+
lvl_names.append(by_col)
|
|
50
|
+
n_levels = len(lvl_names)
|
|
51
|
+
|
|
52
|
+
if y is None:
|
|
53
|
+
vals = np.ones(len(path))
|
|
54
|
+
else:
|
|
55
|
+
vals = y.to_numpy(dtype=float)
|
|
56
|
+
vals = np.where(np.isfinite(vals), vals, 0.0)
|
|
57
|
+
|
|
58
|
+
STAT = (stat or "sum").lower()
|
|
59
|
+
stat_fun = STAT_FUN[STAT]
|
|
60
|
+
non_additive = STAT != "sum"
|
|
61
|
+
|
|
62
|
+
if y is None:
|
|
63
|
+
stat_label = "Count"
|
|
64
|
+
elif non_additive:
|
|
65
|
+
stat_label = f"{STAT.capitalize()} of {y_name}"
|
|
66
|
+
else:
|
|
67
|
+
stat_label = y_name
|
|
68
|
+
|
|
69
|
+
df = path.copy()
|
|
70
|
+
df["..val"] = vals
|
|
71
|
+
|
|
72
|
+
def build_nodes(sub, facet_label=None):
|
|
73
|
+
ids, labels, parents, values, custom, root = \
|
|
74
|
+
[], [], [], [], [], []
|
|
75
|
+
for lvl in range(1, n_levels + 1):
|
|
76
|
+
cols = lvl_names[:lvl]
|
|
77
|
+
g = sub.groupby(cols, sort=True)
|
|
78
|
+
agg_stat = g["..val"].apply(stat_fun)
|
|
79
|
+
node_n = g.size()
|
|
80
|
+
|
|
81
|
+
for key, sv in agg_stat.items():
|
|
82
|
+
key = (key,) if lvl == 1 else key
|
|
83
|
+
label = str(key[-1])
|
|
84
|
+
if lvl == 1:
|
|
85
|
+
parent = ""
|
|
86
|
+
node_id = f"I_{label}"
|
|
87
|
+
else:
|
|
88
|
+
parent = "I_" + "_".join(
|
|
89
|
+
str(k) for k in key[:-1])
|
|
90
|
+
node_id = f"{parent}_{label}"
|
|
91
|
+
meta_parts = [f"{lvl_names[k]}: {key[k]}"
|
|
92
|
+
for k in range(lvl)]
|
|
93
|
+
if facet_label is not None:
|
|
94
|
+
meta_parts.append(
|
|
95
|
+
f"{facet_name or 'Facet'}: {facet_label}")
|
|
96
|
+
ids.append(node_id)
|
|
97
|
+
labels.append(label)
|
|
98
|
+
parents.append(parent)
|
|
99
|
+
values.append(float(sv) if lvl == n_levels
|
|
100
|
+
else 0.0)
|
|
101
|
+
custom.append(dict(meta="<br>".join(meta_parts),
|
|
102
|
+
stat=float(sv),
|
|
103
|
+
n=int(node_n[key if lvl > 1
|
|
104
|
+
else key[0]]),
|
|
105
|
+
level=lvl))
|
|
106
|
+
root.append(str(key[0]))
|
|
107
|
+
return dict(ids=ids, labels=labels, parents=parents,
|
|
108
|
+
values=values, customdata=custom, root=root)
|
|
109
|
+
|
|
110
|
+
if facet is None:
|
|
111
|
+
nd = build_nodes(df)
|
|
112
|
+
nd_byfac = None
|
|
113
|
+
else:
|
|
114
|
+
fac = facet.astype(str).to_numpy()
|
|
115
|
+
if facet_order is None:
|
|
116
|
+
facet_order = sorted(set(fac))
|
|
117
|
+
nd = None
|
|
118
|
+
nd_byfac = {str(lv): build_nodes(df[fac == str(lv)],
|
|
119
|
+
facet_label=lv)
|
|
120
|
+
for lv in facet_order}
|
|
121
|
+
|
|
122
|
+
return dict(nd=nd, nd_byfac=nd_byfac, stat_label=stat_label,
|
|
123
|
+
non_additive=non_additive, STAT=STAT,
|
|
124
|
+
n_levels=n_levels, has_y=y is not None)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def hier_color_resolve(top_levels, fill=None):
|
|
128
|
+
"""Hex color per top-level category, inherited down the tree.
|
|
129
|
+
R analog: .hier_color_resolve(), without the theme-dependent
|
|
130
|
+
sequential-palette branch."""
|
|
131
|
+
if fill is None:
|
|
132
|
+
fill = BASE_COLORS
|
|
133
|
+
if isinstance(fill, dict):
|
|
134
|
+
base = list(fill.values()) or ["#CCCCCC"]
|
|
135
|
+
return {lv: to_hex(fill.get(lv, base[i % len(base)]))
|
|
136
|
+
for i, lv in enumerate(top_levels)}
|
|
137
|
+
if not isinstance(fill, (list, tuple)):
|
|
138
|
+
fill = [fill]
|
|
139
|
+
return {lv: to_hex(fill[i % len(fill)])
|
|
140
|
+
for i, lv in enumerate(top_levels)}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def hier_plotly(agg, fill_vec, type="sunburst",
|
|
144
|
+
x_name="X", by_name=None, facet_name=None,
|
|
145
|
+
main=None, border=None, digits_d=None,
|
|
146
|
+
labels=None, labels_color="white",
|
|
147
|
+
labels_size=0.75, n_col=None, style_opts=None):
|
|
148
|
+
|
|
149
|
+
if labels is None:
|
|
150
|
+
labels = "input" # R analog: match.arg default
|
|
151
|
+
if labels not in _LABEL_VALUES:
|
|
152
|
+
raise ValueError(f"labels must be one of {_LABEL_VALUES}")
|
|
153
|
+
if type not in ("sunburst", "treemap", "icicle"):
|
|
154
|
+
raise ValueError('type must be "sunburst", "treemap", '
|
|
155
|
+
'or "icicle"')
|
|
156
|
+
if style_opts is None:
|
|
157
|
+
style_opts = plotly_style()
|
|
158
|
+
|
|
159
|
+
nd = agg["nd"]
|
|
160
|
+
nd_byfac = agg.get("nd_byfac")
|
|
161
|
+
non_additive = agg["non_additive"]
|
|
162
|
+
n_levels = agg["n_levels"]
|
|
163
|
+
has_y = agg["has_y"]
|
|
164
|
+
stat_label = agg["stat_label"]
|
|
165
|
+
|
|
166
|
+
title_size = round(16 * get_option("main_size", 1))
|
|
167
|
+
label_fsize = 18 * labels_size * get_option("axis_size", 0.9)
|
|
168
|
+
border_hex = to_hex("white" if border is None else border)
|
|
169
|
+
|
|
170
|
+
digits = 2 if digits_d is None else max(0, int(digits_d))
|
|
171
|
+
if non_additive and digits == 0:
|
|
172
|
+
digits += 2 # means etc. need decimals
|
|
173
|
+
|
|
174
|
+
fmt_spec = f"%{{customdata.stat:,.{digits}f}}"
|
|
175
|
+
|
|
176
|
+
# texttemplate reaches customdata on parent nodes (value 0),
|
|
177
|
+
# where textinfo+text would suppress the text
|
|
178
|
+
use_template = ((non_additive and labels != "off")
|
|
179
|
+
or labels == "prop"
|
|
180
|
+
or labels == "input")
|
|
181
|
+
if labels == "input":
|
|
182
|
+
txtinfo = "none" # value drawn via texttemplate
|
|
183
|
+
elif labels == "%":
|
|
184
|
+
txtinfo = "label+percent root"
|
|
185
|
+
elif labels == "prop":
|
|
186
|
+
txtinfo = "none"
|
|
187
|
+
else: # "off": name only, no value
|
|
188
|
+
txtinfo = "label"
|
|
189
|
+
|
|
190
|
+
if use_template and labels != "prop":
|
|
191
|
+
texttemplate = f"%{{label}}<br>{fmt_spec}"
|
|
192
|
+
elif labels == "prop":
|
|
193
|
+
texttemplate = "%{label}<br>%{percentRoot:.3f}"
|
|
194
|
+
else:
|
|
195
|
+
texttemplate = None
|
|
196
|
+
|
|
197
|
+
hover_lines = ["%{customdata.meta}",
|
|
198
|
+
f"{stat_label}: {fmt_spec}"]
|
|
199
|
+
if not non_additive:
|
|
200
|
+
hover_lines.append("% of parent: %{percentParent:.2%}")
|
|
201
|
+
hover_lines.append("% of total: %{percentRoot:.2%}")
|
|
202
|
+
hover = "<br>".join(hover_lines) + "<extra></extra>"
|
|
203
|
+
|
|
204
|
+
trace_cls = {"sunburst": go.Sunburst,
|
|
205
|
+
"treemap": go.Treemap,
|
|
206
|
+
"icicle": go.Icicle}[type]
|
|
207
|
+
|
|
208
|
+
kw = {}
|
|
209
|
+
if txtinfo is not None and texttemplate is None:
|
|
210
|
+
kw["textinfo"] = txtinfo
|
|
211
|
+
if texttemplate is not None:
|
|
212
|
+
kw["texttemplate"] = texttemplate
|
|
213
|
+
|
|
214
|
+
def make_trace(nd_i, dom_x, dom_y):
|
|
215
|
+
node_cols = [fill_vec.get(r, list(fill_vec.values())[0])
|
|
216
|
+
for r in nd_i["root"]]
|
|
217
|
+
return trace_cls(
|
|
218
|
+
ids=nd_i["ids"],
|
|
219
|
+
labels=nd_i["labels"],
|
|
220
|
+
parents=nd_i["parents"],
|
|
221
|
+
values=nd_i["values"],
|
|
222
|
+
customdata=nd_i["customdata"],
|
|
223
|
+
domain=dict(x=list(dom_x), y=list(dom_y)),
|
|
224
|
+
branchvalues="remainder",
|
|
225
|
+
textfont=dict(size=label_fsize, color=labels_color),
|
|
226
|
+
hovertemplate=hover,
|
|
227
|
+
marker=dict(colors=node_cols,
|
|
228
|
+
line=dict(color=border_hex, width=1)),
|
|
229
|
+
**kw,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
lab_color = to_hex(get_option("lab_color", "black"))
|
|
233
|
+
|
|
234
|
+
# ---- faceted grid: one panel per facet level -------------------
|
|
235
|
+
# R analog: hier.plotly.R faceted section
|
|
236
|
+
if nd_byfac is not None:
|
|
237
|
+
fac_levels = list(nd_byfac.keys())
|
|
238
|
+
|
|
239
|
+
if type == "treemap":
|
|
240
|
+
def dom_adj(d):
|
|
241
|
+
span = d["y"][1] - d["y"][0]
|
|
242
|
+
y0 = max(0.0, d["y"][0] - 0.06)
|
|
243
|
+
y1 = y0 + span
|
|
244
|
+
if y1 > 1:
|
|
245
|
+
y1, y0 = 1.0, 1.0 - span
|
|
246
|
+
d["y"] = (y0, y1)
|
|
247
|
+
return d
|
|
248
|
+
|
|
249
|
+
def ann_adj(i, a, d):
|
|
250
|
+
a["y"] = min(d["y"][1] - 0.025, 0.99)
|
|
251
|
+
a["x"] = (d["x"][0] + d["x"][1]) / 2
|
|
252
|
+
return a
|
|
253
|
+
rev_dom = True
|
|
254
|
+
elif type == "icicle":
|
|
255
|
+
def dom_adj(d):
|
|
256
|
+
span = d["y"][1] - d["y"][0] * 1.12
|
|
257
|
+
y1 = max(0.0, d["y"][1] - 0.10)
|
|
258
|
+
y0 = max(0.0, y1 - span)
|
|
259
|
+
if y1 > 1:
|
|
260
|
+
y1, y0 = 1.0, 1.0 - span
|
|
261
|
+
d["y"] = (y0, y1)
|
|
262
|
+
return d
|
|
263
|
+
|
|
264
|
+
def ann_adj(i, a, d):
|
|
265
|
+
a["y"] = min(d["y"][1] + 0.02, 0.99)
|
|
266
|
+
a["x"] = a["x"] + 0.10
|
|
267
|
+
a["xanchor"] = "right"
|
|
268
|
+
return a
|
|
269
|
+
rev_dom = False
|
|
270
|
+
else: # sunburst
|
|
271
|
+
def dom_adj(d):
|
|
272
|
+
span = d["y"][1] - d["y"][0]
|
|
273
|
+
y1 = min(1.0, d["y"][1] - 0.06)
|
|
274
|
+
y0 = max(0.0, y1 - span)
|
|
275
|
+
d["y"] = (y0, y1)
|
|
276
|
+
return d
|
|
277
|
+
|
|
278
|
+
def ann_adj(i, a, d):
|
|
279
|
+
a["y"] = min(d["y"][1] - 0.025, 0.99)
|
|
280
|
+
a["x"] = (d["x"][0] + d["x"][1]) / 2
|
|
281
|
+
return a
|
|
282
|
+
rev_dom = False
|
|
283
|
+
|
|
284
|
+
domains, anns = facet_layout(
|
|
285
|
+
fac_levels, facet_name or "",
|
|
286
|
+
domain_adjust=dom_adj, reverse_domains=rev_dom,
|
|
287
|
+
y_base=-0.018, y_row_shift=-0.003, yanchor="bottom",
|
|
288
|
+
ann_adjust=ann_adj, n_col=n_col)
|
|
289
|
+
|
|
290
|
+
fig = go.Figure()
|
|
291
|
+
for i, lv in enumerate(fac_levels):
|
|
292
|
+
nd_i = nd_byfac[lv]
|
|
293
|
+
if nd_i["ids"]:
|
|
294
|
+
fig.add_trace(make_trace(nd_i, domains[i]["x"],
|
|
295
|
+
domains[i]["y"]))
|
|
296
|
+
|
|
297
|
+
t_margin = round(title_size * 2.8)
|
|
298
|
+
fig.update_layout(
|
|
299
|
+
annotations=anns,
|
|
300
|
+
margin=dict(
|
|
301
|
+
t=t_margin - 5 if type == "treemap" else t_margin,
|
|
302
|
+
b=8,
|
|
303
|
+
l=0 if type == "icicle" else 10,
|
|
304
|
+
r=30 if type == "icicle" else 20),
|
|
305
|
+
title=dict(
|
|
306
|
+
text=main,
|
|
307
|
+
font=dict(size=title_size, color=lab_color),
|
|
308
|
+
x=0.55 if type == "icicle" else 0.50,
|
|
309
|
+
xanchor="center"),
|
|
310
|
+
template=None,
|
|
311
|
+
paper_bgcolor=to_hex(style_opts["window_fill"]),
|
|
312
|
+
)
|
|
313
|
+
return fig
|
|
314
|
+
|
|
315
|
+
# ---- single panel ----------------------------------------------
|
|
316
|
+
dom_y_top = 0.95 if type == "sunburst" else 1
|
|
317
|
+
fig = go.Figure(make_trace(nd, [0, 1], [0, dom_y_top]))
|
|
318
|
+
|
|
319
|
+
if type == "icicle":
|
|
320
|
+
title_x = 0.64 if n_levels > 1 else 0.71
|
|
321
|
+
else:
|
|
322
|
+
title_x = 0.50
|
|
323
|
+
|
|
324
|
+
if type in ("treemap", "icicle") and main:
|
|
325
|
+
# plotly's built-in title expands the top margin from the
|
|
326
|
+
# trace label font, squeezing the chart; an annotation in
|
|
327
|
+
# paper coordinates gives full control
|
|
328
|
+
fig.update_layout(
|
|
329
|
+
margin=dict(t=round(title_size * 2.1), b=8,
|
|
330
|
+
l=20, r=30 if type == "icicle" else 20),
|
|
331
|
+
annotations=[dict(
|
|
332
|
+
text=main, x=title_x, xref="paper",
|
|
333
|
+
xanchor="center", y=1, yref="paper",
|
|
334
|
+
yanchor="bottom", showarrow=False,
|
|
335
|
+
font=dict(size=title_size, color=lab_color))],
|
|
336
|
+
)
|
|
337
|
+
else:
|
|
338
|
+
fig.update_layout(
|
|
339
|
+
margin=dict(t=round(title_size * 2.0), b=8, l=20,
|
|
340
|
+
r=30 if type == "icicle" else 20),
|
|
341
|
+
title=dict(
|
|
342
|
+
text=main,
|
|
343
|
+
font=dict(size=title_size, color=lab_color),
|
|
344
|
+
x=title_x, xanchor="center",
|
|
345
|
+
y=0.97 if not has_y else 1.00, yanchor="top"),
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
fig.update_layout(
|
|
349
|
+
template=None,
|
|
350
|
+
paper_bgcolor=to_hex(style_opts["window_fill"]),
|
|
351
|
+
)
|
|
352
|
+
return fig
|