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