lessPython 0.2.0__py3-none-any.whl → 0.3.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 +5 -5
- lessPy/Chart.py +386 -88
- lessPy/Correlation.py +2 -2
- lessPy/Logit.py +2 -2
- lessPy/Regression.py +6 -4
- lessPy/X.py +279 -42
- lessPy/XY.py +814 -158
- lessPy/__init__.py +12 -2
- lessPy/_themes.py +5007 -0
- lessPy/bc_items_plotly.py +31 -18
- lessPy/bc_plotly.py +23 -7
- lessPy/bubble_plotly.py +17 -2
- lessPy/corCFA.py +3 -3
- lessPy/corProp.py +2 -2
- lessPy/corReflect.py +2 -2
- lessPy/corReorder.py +2 -2
- lessPy/corScree.py +3 -3
- lessPy/data/StockPrice.csv +1401 -1386
- lessPy/details.py +17 -4
- lessPy/dn_plotly.py +65 -45
- lessPy/freq_poly_plotly.py +12 -5
- lessPy/getColors.py +247 -20
- lessPy/hier_plotly.py +36 -9
- lessPy/hs_plotly.py +16 -4
- lessPy/plotly_utils.py +78 -9
- lessPy/plt_mat_plotly.py +74 -2
- lessPy/plt_time.py +10 -3
- lessPy/prob_norm.py +3 -3
- lessPy/prob_tcut.py +3 -3
- lessPy/prob_znorm.py +3 -1
- lessPy/rcolors.py +461 -0
- lessPy/simCImean.py +3 -3
- lessPy/simCLT.py +3 -3
- lessPy/simFlips.py +3 -3
- lessPy/simMeans.py +3 -3
- lessPy/stats_out.py +257 -11
- lessPy/style.py +160 -0
- lessPy/sunflower_plotly.py +140 -0
- lessPy/ttest.py +5 -5
- lessPy/utils.py +43 -6
- lessPy/vbs_plotly.py +16 -5
- lessPy/x_console.py +763 -0
- {lesspython-0.2.0.dist-info → lesspython-0.3.0.dist-info}/METADATA +6 -2
- lesspython-0.3.0.dist-info/RECORD +89 -0
- lesspython-0.2.0.dist-info/RECORD +0 -84
- {lesspython-0.2.0.dist-info → lesspython-0.3.0.dist-info}/WHEEL +0 -0
- {lesspython-0.2.0.dist-info → lesspython-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {lesspython-0.2.0.dist-info → lesspython-0.3.0.dist-info}/top_level.txt +0 -0
lessPy/Chart.py
CHANGED
|
@@ -35,14 +35,18 @@ from .plt_add import plt_add
|
|
|
35
35
|
from .bubble_plotly import bubble_plotly
|
|
36
36
|
from .dot_plotly import dot_plotly
|
|
37
37
|
from .hier_plotly import (
|
|
38
|
-
hier_aggregate, hier_color_resolve,
|
|
38
|
+
hier_aggregate, hier_color_resolve, hier_color_resolve_byfac,
|
|
39
|
+
hier_plotly,
|
|
39
40
|
)
|
|
40
41
|
from .pie_plotly import pie_plotly
|
|
41
42
|
from .plt_plotly import plt_plotly
|
|
42
43
|
from .profile_plotly import profile_facet_plotly
|
|
43
44
|
from .radar_plotly import radar_plotly
|
|
44
45
|
from .plotly_utils import build_title, font_scaled
|
|
45
|
-
from .stats_out import
|
|
46
|
+
from .stats_out import (
|
|
47
|
+
CALL_STATS, ChartStats, attach_stats, chart_stats,
|
|
48
|
+
chart_stats_data, resolve_quiet,
|
|
49
|
+
)
|
|
46
50
|
from .utils import (
|
|
47
51
|
STAT_FUN, STAT_LBL, category_order as _category_order,
|
|
48
52
|
facet_values, get_column as _get_column, get_option, pretty,
|
|
@@ -62,6 +66,36 @@ _THEME_PALETTE = {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
|
|
69
|
+
# each theme's single bar color, a bar chart without by drawn in it
|
|
70
|
+
# (R: style(theme)$bar$bar_fill_discrete, alpha from trans_bar_fill)
|
|
71
|
+
_THEME_BAR_FILL = {
|
|
72
|
+
"lightbronze": "#7B8C96", "dodgerblue": "#1874CDE6",
|
|
73
|
+
"darkred": "#8B0000E6", "gray": "#595959E6", "gold": "#CD9B1DE6",
|
|
74
|
+
"darkgreen": "#006400E6", "blue": "#4876FFE6", "red": "#CD2626E6",
|
|
75
|
+
"rose": "#CD9B9BE6", "green": "#006400E6", "purple": "#9B30FFE6",
|
|
76
|
+
"sienna": "#CD6839E6", "brown": "#8B6969E6", "orange": "#FFA500E6",
|
|
77
|
+
"white": "#FFFFFFE6", "slatered": "#A35763E6",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _color_range(pal, n):
|
|
82
|
+
"""n colors of a named palette, alpha dropped. Two grays are set
|
|
83
|
+
further apart than the palette spaces them, gray60 and gray30,
|
|
84
|
+
as in R. R analog: .color_range()"""
|
|
85
|
+
from .getColors import getColors
|
|
86
|
+
if pal == "grays" and n == 2:
|
|
87
|
+
return ["#999999", "#4D4D4D"]
|
|
88
|
+
cols = getColors(pal, n=max(1, n), quiet=True)
|
|
89
|
+
return [c[:7] if len(c) == 9 and c.endswith("FF") else c
|
|
90
|
+
for c in cols]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _palette_names():
|
|
94
|
+
from .getColors import _SEQ_NAMES, _SEQ_R, _VIRIDIS
|
|
95
|
+
return set(_SEQ_NAMES) | set(_SEQ_R) | set(_VIRIDIS) | {
|
|
96
|
+
"hues", "Okabe-Ito", "Tableau", "distinct"}
|
|
97
|
+
|
|
98
|
+
|
|
65
99
|
def _theme_fill(theme, n):
|
|
66
100
|
"""A theme's fill: an n-color sequential palette in the theme's
|
|
67
101
|
hue. R analog: the theme branch of the fill assignment."""
|
|
@@ -80,9 +114,10 @@ def _facet_table(x_s, y_s, by_s, stat, is_agg, x_order, by_order,
|
|
|
80
114
|
"""Frequency or stat table for one facet level, on the global
|
|
81
115
|
category set(s) so panels stay aligned; absent cells fill 0,
|
|
82
116
|
as in R's xtabs. Series without by, DataFrame (by x cats)
|
|
83
|
-
with. proportion normalizes the per-panel counts
|
|
84
|
-
|
|
85
|
-
faceted radar and bubble
|
|
117
|
+
with. proportion normalizes the per-panel counts to sum to 1,
|
|
118
|
+
the faceted stat_x="proportion": the whole panel without by,
|
|
119
|
+
each by row with it. Used by the faceted radar and bubble
|
|
120
|
+
paths."""
|
|
86
121
|
if by_s is None:
|
|
87
122
|
if y_s is None:
|
|
88
123
|
t = x_s.groupby(x_s, observed=True).size() \
|
|
@@ -108,8 +143,12 @@ def _facet_table(x_s, y_s, by_s, stat, is_agg, x_order, by_order,
|
|
|
108
143
|
t = STAT_FUN[stat](
|
|
109
144
|
df.groupby(["by", "x"], observed=True)["y"]
|
|
110
145
|
).unstack("x")
|
|
111
|
-
|
|
112
|
-
|
|
146
|
+
t = t.reindex(index=by_order, columns=x_order).fillna(0)
|
|
147
|
+
if proportion and y_s is None:
|
|
148
|
+
# each group's distribution over x: every by row sums to 1
|
|
149
|
+
tot = t.sum(axis=1)
|
|
150
|
+
t = t.div(tot.where(tot != 0), axis=0).fillna(0)
|
|
151
|
+
return t
|
|
113
152
|
|
|
114
153
|
_FORMS = ("bar", "radar", "bubble", "dot", "pie", "icicle", "treemap",
|
|
115
154
|
"profile")
|
|
@@ -170,7 +209,7 @@ def _dot_origin_grid(vals, origin_in=None, is_counts=None):
|
|
|
170
209
|
def _chart_items(data, items, y, by, facet, form, stat, sort,
|
|
171
210
|
sort_miss, stack100, horiz, fill, theme, labels,
|
|
172
211
|
labels_size, gap, labels_cut, xlab, ylab, main,
|
|
173
|
-
legend_title,
|
|
212
|
+
legend_title, power, radius, one_plot,
|
|
174
213
|
n_col, n_row, axis_fmt, axis_x_pre, rotate_x,
|
|
175
214
|
rotate_y, quiet):
|
|
176
215
|
"""The multi-item stacked chart and its faceted form. R analog:
|
|
@@ -185,22 +224,74 @@ def _chart_items(data, items, y, by, facet, form, stat, sort,
|
|
|
185
224
|
if facet is not None and form != "bar":
|
|
186
225
|
raise ValueError(
|
|
187
226
|
'facet with multiple x variables requires form="bar"')
|
|
188
|
-
if form
|
|
189
|
-
raise NotImplementedError(
|
|
190
|
-
"the bubble plot frequency matrix of multiple x "
|
|
191
|
-
'variables is not yet ported; use form="bar"')
|
|
192
|
-
if form != "bar":
|
|
227
|
+
if form not in ("bar", "bubble"):
|
|
193
228
|
raise ValueError(
|
|
194
229
|
'Multiple x variables only available for form="bar"')
|
|
195
230
|
if y is not None or stat is not None:
|
|
196
231
|
raise ValueError(
|
|
197
232
|
"A multi-item chart counts the responses to each item, "
|
|
198
233
|
"so y and stat do not apply")
|
|
199
|
-
if not horiz:
|
|
200
|
-
raise NotImplementedError(
|
|
201
|
-
"the vertical multi-item chart is not yet ported")
|
|
202
234
|
cols = [_get_column(data, it, "x") for it in items]
|
|
235
|
+
|
|
236
|
+
# one_plot: the items compose one chart when they share one set of
|
|
237
|
+
# responses, decided from the data unless given; otherwise each
|
|
238
|
+
# variable is its own bar chart. R analog: Chart.R one_plot test
|
|
239
|
+
sets = [set(c.dropna().unique()) for c in cols]
|
|
240
|
+
widest = max(sets, key=len)
|
|
241
|
+
shared = not any(st - widest for st in sets)
|
|
242
|
+
if one_plot is None:
|
|
243
|
+
one_plot = shared
|
|
244
|
+
if not one_plot:
|
|
245
|
+
if facet is not None:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
"facet requires the multi-item stacked chart, which "
|
|
248
|
+
"needs the x variables to share the same response "
|
|
249
|
+
"categories: a separate chart per variable is not a "
|
|
250
|
+
"single composition, so there are no panels to "
|
|
251
|
+
"condition")
|
|
252
|
+
if form != "bar":
|
|
253
|
+
raise ValueError(
|
|
254
|
+
'A separate chart per variable is a bar chart, so it '
|
|
255
|
+
'needs form="bar"')
|
|
256
|
+
return _chart_per_variable(data, items, cols, fill, xlab,
|
|
257
|
+
main, quiet)
|
|
258
|
+
|
|
203
259
|
frq, wm, numeric = items_table(cols, items)
|
|
260
|
+
|
|
261
|
+
if form == "bubble":
|
|
262
|
+
# the bubble plot frequency matrix: one row of bubbles per
|
|
263
|
+
# item, one column per response, each bubble sized by its
|
|
264
|
+
# count, the items in the order given from the bottom unless
|
|
265
|
+
# sorted by their mean. R analog: .dpmat.main()
|
|
266
|
+
order = list(items)
|
|
267
|
+
if sort != "0":
|
|
268
|
+
order = list(wm.sort_values(ascending=(sort == "+"),
|
|
269
|
+
kind="stable").index)
|
|
270
|
+
_set_stats(ChartStats(n_dim=2, freq=frq[order].T,
|
|
271
|
+
mean=wm[order]),
|
|
272
|
+
items_stats_lines(
|
|
273
|
+
frq[order], wm[order],
|
|
274
|
+
"Frequencies of Responses by Variable", numeric,
|
|
275
|
+
with_sum=True), quiet)
|
|
276
|
+
# the matrix lists its first row at the top, so reverse the
|
|
277
|
+
# order to draw the first item at the bottom, as R does
|
|
278
|
+
mat = frq[order[::-1]].T.astype(float)
|
|
279
|
+
mat.index.name, mat.columns.name = "Item", "Response"
|
|
280
|
+
cut = power / 2.5 * float(np.nanmax(mat.to_numpy()))
|
|
281
|
+
if fill is None:
|
|
282
|
+
fill = get_option("bar_fill_cont", "#96AAC3")
|
|
283
|
+
return bubble_plotly(
|
|
284
|
+
mat, x_name="Response", y_name="Count", by_name="Item",
|
|
285
|
+
x_lab="" if xlab is None else xlab,
|
|
286
|
+
y_lab="" if ylab is None else ylab,
|
|
287
|
+
main=main or None,
|
|
288
|
+
fill=(list(fill)[::-1] if isinstance(fill, (list, tuple))
|
|
289
|
+
else [fill] * len(order)),
|
|
290
|
+
border=get_option("pt_color", "#324E5C"),
|
|
291
|
+
opacity=0.6, power=power, radius=radius, digits_d=0,
|
|
292
|
+
labels="input" if labels is None else labels,
|
|
293
|
+
labels_color=get_option("bubble_text_color", "#F7F2E6"),
|
|
294
|
+
label_min_value=cut)
|
|
204
295
|
fill_use = (fill if isinstance(fill, (list, tuple))
|
|
205
296
|
else [fill] * len(frq.index) if fill is not None
|
|
206
297
|
else items_fill(theme, len(frq.index)))
|
|
@@ -217,10 +308,11 @@ def _chart_items(data, items, y, by, facet, form, stat, sort,
|
|
|
217
308
|
if sort != "0":
|
|
218
309
|
order = list(wm.sort_values(ascending=(sort == "+"),
|
|
219
310
|
kind="stable").index)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
311
|
+
# the table lists the items top of the chart first
|
|
312
|
+
_set_stats(ChartStats(n_dim=2, freq=frq[order[::-1]].T,
|
|
313
|
+
mean=wm[order[::-1]]),
|
|
314
|
+
_items_print(frq[order[::-1]], wm[order[::-1]],
|
|
315
|
+
numeric), quiet)
|
|
224
316
|
return bc_items_plotly(
|
|
225
317
|
frq, order, fill_use,
|
|
226
318
|
labels="%" if labels is None else labels,
|
|
@@ -230,7 +322,7 @@ def _chart_items(data, items, y, by, facet, form, stat, sort,
|
|
|
230
322
|
y_lab="" if ylab is None else ylab,
|
|
231
323
|
legend_title=leg, main=main or None,
|
|
232
324
|
axis_fmt=axis_fmt, axis_x_pre=axis_x_pre,
|
|
233
|
-
rotate_x=rotate_x, rotate_y=rotate_y)
|
|
325
|
+
rotate_x=rotate_x, rotate_y=rotate_y, horiz=horiz)
|
|
234
326
|
|
|
235
327
|
# faceted: a panel is a fraction of the width of the single
|
|
236
328
|
# chart, too narrow for segment labels, and the paneled chart
|
|
@@ -262,24 +354,27 @@ def _chart_items(data, items, y, by, facet, form, stat, sort,
|
|
|
262
354
|
facet_order = _category_order(fac[keep])
|
|
263
355
|
resp = list(frq.index)
|
|
264
356
|
tbls = {}
|
|
357
|
+
rep_lines, panels = [], {}
|
|
265
358
|
for lv in facet_order:
|
|
266
359
|
m = keep & (fac == lv)
|
|
267
360
|
f_lv, w_lv, _ = items_table([c[m] for c in cols], items) \
|
|
268
361
|
if all(c[m].notna().any() for c in cols) else (
|
|
269
362
|
pd.DataFrame(0, index=resp, columns=items), None, None)
|
|
270
363
|
f_lv = f_lv.reindex(index=resp, fill_value=0)
|
|
271
|
-
if
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
364
|
+
if w_lv is None:
|
|
365
|
+
w_lv = pd.Series(np.nan, index=items)
|
|
366
|
+
rep_lines += _items_print(
|
|
367
|
+
f_lv, w_lv, numeric,
|
|
368
|
+
f"Frequencies of Responses by Variable, "
|
|
369
|
+
f"{facet_name}: {lv}")
|
|
370
|
+
panels[lv] = ChartStats(freq=f_lv.T, mean=w_lv)
|
|
278
371
|
t = f_lv.astype(float)
|
|
279
372
|
if stack100:
|
|
280
373
|
tot = t.sum(axis=0)
|
|
281
374
|
t = t.div(tot.where(tot != 0), axis=1).fillna(0)
|
|
282
|
-
|
|
375
|
+
# first item at the top, or at the left of a vertical chart
|
|
376
|
+
tbls[lv] = t[items[::-1]] if horiz else t[items]
|
|
377
|
+
_set_stats(ChartStats(n_dim=3, panels=panels), rep_lines, quiet)
|
|
283
378
|
tbl = pd.DataFrame([tbls[lv].sum(axis=0) for lv in facet_order],
|
|
284
379
|
index=facet_order)
|
|
285
380
|
tbl.index.name = facet_name
|
|
@@ -296,7 +391,85 @@ def _chart_items(data, items, y, by, facet, form, stat, sort,
|
|
|
296
391
|
rotate_x=rotate_x, rotate_y=rotate_y, main=main,
|
|
297
392
|
by_tbls=tbls, by_name=leg, beside=False,
|
|
298
393
|
val_name=("Proportion" if stack100 else "Count"),
|
|
299
|
-
legend_title=leg)
|
|
394
|
+
legend_title=leg, horiz=horiz)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _chart_per_variable(data, items, cols, fill, xlab, main, quiet):
|
|
398
|
+
"""One bar chart of counts per categorical variable, as panels of
|
|
399
|
+
one figure, each followed at the console by its frequencies and
|
|
400
|
+
chi-square test. Numeric variables are passed over, as are a
|
|
401
|
+
variable that identifies each case and one with a single value.
|
|
402
|
+
R analog: bc.data.frame()"""
|
|
403
|
+
from plotly.subplots import make_subplots
|
|
404
|
+
import plotly.graph_objects as go
|
|
405
|
+
from .plotly_utils import (BASE_COLORS, auto_text_color,
|
|
406
|
+
axis_format, plotly_style, to_hex)
|
|
407
|
+
from .stats_out import _counts_1d
|
|
408
|
+
show = not resolve_quiet(quiet)
|
|
409
|
+
keep = []
|
|
410
|
+
for nm, c in zip(items, cols):
|
|
411
|
+
if is_numeric_dtype(c) and not isinstance(
|
|
412
|
+
c.dtype, pd.CategoricalDtype):
|
|
413
|
+
continue # R: .is.num.cat(x, 0) FALSE
|
|
414
|
+
v = c.dropna()
|
|
415
|
+
if v.nunique() >= len(v): # identifies each case
|
|
416
|
+
continue
|
|
417
|
+
if v.nunique() == 1:
|
|
418
|
+
if show:
|
|
419
|
+
print(f"\nVariable {nm} has only one value.\n"
|
|
420
|
+
" No bar chart produced.\n")
|
|
421
|
+
continue
|
|
422
|
+
keep.append((nm, c))
|
|
423
|
+
if not keep:
|
|
424
|
+
raise ValueError(
|
|
425
|
+
"No categorical variables, so no bar charts.\n\n"
|
|
426
|
+
"If you have integer variables that are categorical, "
|
|
427
|
+
"convert them to pandas Categorical, e.g.\n"
|
|
428
|
+
" d['Plan'] = d['Plan'].astype('category')")
|
|
429
|
+
|
|
430
|
+
n = len(keep)
|
|
431
|
+
n_col = min(3, n)
|
|
432
|
+
n_row = math.ceil(n / n_col)
|
|
433
|
+
fig = make_subplots(rows=n_row, cols=n_col,
|
|
434
|
+
subplot_titles=[nm for nm, _ in keep],
|
|
435
|
+
horizontal_spacing=0.08,
|
|
436
|
+
vertical_spacing=max(0.08, 0.3 / n_row))
|
|
437
|
+
st = plotly_style()
|
|
438
|
+
rep_lines, per_var = [], {}
|
|
439
|
+
for k, (nm, c) in enumerate(keep):
|
|
440
|
+
rep_lines += _counts_1d(c.dropna(), nm,
|
|
441
|
+
int(c.isna().sum())) + [""]
|
|
442
|
+
per_var[nm] = chart_stats_data(c.dropna(), None, None, None,
|
|
443
|
+
n_miss=int(c.isna().sum()))
|
|
444
|
+
order = _category_order(c.dropna())
|
|
445
|
+
cnt = c.value_counts().reindex(order, fill_value=0)
|
|
446
|
+
f = (fill if isinstance(fill, (list, tuple))
|
|
447
|
+
else [fill] if fill is not None else BASE_COLORS)
|
|
448
|
+
cols_k = [to_hex(f[i % len(f)]) for i in range(len(cnt))]
|
|
449
|
+
r, cc = k // n_col + 1, k % n_col + 1
|
|
450
|
+
fig.add_trace(go.Bar(
|
|
451
|
+
x=[str(v) for v in cnt.index], y=cnt.to_numpy(),
|
|
452
|
+
marker=dict(color=cols_k), text=[str(v) for v in cnt],
|
|
453
|
+
textposition="inside", showlegend=False,
|
|
454
|
+
textfont=dict(color=auto_text_color(cols_k)),
|
|
455
|
+
hovertemplate=f"{nm}: %{{x}}<br>Count: %{{y}}"
|
|
456
|
+
"<extra></extra>"), row=r, col=cc)
|
|
457
|
+
ticks = pretty(0.0, float(cnt.max()))
|
|
458
|
+
fig.update_yaxes(row=r, col=cc, tickvals=ticks,
|
|
459
|
+
ticktext=axis_format(ticks, 0),
|
|
460
|
+
range=[0, ticks[-1] * 1.04],
|
|
461
|
+
gridcolor="#E5E5E5",
|
|
462
|
+
title_text="Count" if cc == 1 else "")
|
|
463
|
+
fig.update_xaxes(row=r, col=cc, type="category",
|
|
464
|
+
title_text=xlab or "")
|
|
465
|
+
_set_stats(ChartStats(n_dim=1, variables=per_var), rep_lines,
|
|
466
|
+
quiet)
|
|
467
|
+
fig.update_layout(
|
|
468
|
+
template=None, height=140 + 260 * n_row,
|
|
469
|
+
plot_bgcolor=to_hex(st["panel_fill"]),
|
|
470
|
+
paper_bgcolor=to_hex(st["window_fill"]),
|
|
471
|
+
title=(dict(text=main, x=0.5) if main else None))
|
|
472
|
+
return fig
|
|
300
473
|
|
|
301
474
|
|
|
302
475
|
def _items_print(frq, wm, numeric, title=None):
|
|
@@ -305,6 +478,15 @@ def _items_print(frq, wm, numeric, title=None):
|
|
|
305
478
|
numeric)
|
|
306
479
|
|
|
307
480
|
|
|
481
|
+
def _set_stats(st, lines, quiet):
|
|
482
|
+
"""Keep the statistics of this call, with the report as their
|
|
483
|
+
text, and display the report unless quiet."""
|
|
484
|
+
st["text"] = "\n".join(lines)
|
|
485
|
+
CALL_STATS.set(st)
|
|
486
|
+
if not resolve_quiet(quiet):
|
|
487
|
+
print(st["text"])
|
|
488
|
+
|
|
489
|
+
|
|
308
490
|
def _profile_fill(theme):
|
|
309
491
|
"""One hue per series from the theme, as R's profile does:
|
|
310
492
|
.color_range(.get_fill(theme), n), the qualitative hues for the
|
|
@@ -352,7 +534,7 @@ def _dot_diff_lines(cats, ydf, x_name):
|
|
|
352
534
|
|
|
353
535
|
|
|
354
536
|
def _dot_paired(cats, ydf, sort, sort_miss, origin_x, show_diff,
|
|
355
|
-
x_name, **render):
|
|
537
|
+
x_name, quiet=None, **render):
|
|
356
538
|
"""Order a multi-series dot plot and draw it. Two series are a
|
|
357
539
|
pair, read for the gap between them, so by default they are
|
|
358
540
|
ordered by that difference, ascending so the largest positive
|
|
@@ -370,8 +552,23 @@ def _dot_paired(cats, ydf, sort, sort_miss, origin_x, show_diff,
|
|
|
370
552
|
kind="stable")
|
|
371
553
|
cats = [cats[i] for i in order]
|
|
372
554
|
ydf = ydf.iloc[order]
|
|
373
|
-
if dif_pair
|
|
374
|
-
|
|
555
|
+
if dif_pair:
|
|
556
|
+
# the difference the display presents, kept with the figure; it
|
|
557
|
+
# is reported where R lists it (show_diff), unless quiet
|
|
558
|
+
dif = pd.Series(
|
|
559
|
+
(ydf.iloc[:, 1] - ydf.iloc[:, 0]).to_numpy(dtype=float),
|
|
560
|
+
index=cats, name=f"{ydf.columns[1]} - {ydf.columns[0]}")
|
|
561
|
+
st = CALL_STATS.get()
|
|
562
|
+
if st is None:
|
|
563
|
+
st = ChartStats(n_dim=2, values=ydf.set_axis(cats), text="")
|
|
564
|
+
CALL_STATS.set(st)
|
|
565
|
+
st["diff"] = dif
|
|
566
|
+
if show_diff:
|
|
567
|
+
lines = _dot_diff_lines(cats, ydf, x_name)
|
|
568
|
+
st["text"] = "\n".join(
|
|
569
|
+
[t for t in (st.get("text", ""), "\n".join(lines)) if t])
|
|
570
|
+
if not resolve_quiet(quiet):
|
|
571
|
+
print("\n".join(lines))
|
|
375
572
|
origin, gridT = _dot_origin_grid(
|
|
376
573
|
ydf.to_numpy(dtype=float).ravel(),
|
|
377
574
|
origin_in=0 if (dif_pair and origin_x is None) else origin_x)
|
|
@@ -396,7 +593,7 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
396
593
|
theme=None,
|
|
397
594
|
labels=None, labels_position=None,
|
|
398
595
|
labels_color=None, labels_size=None, labels_decimals=None,
|
|
399
|
-
labels_cut=None, segments=None,
|
|
596
|
+
labels_cut=None, segments=None, one_plot=None,
|
|
400
597
|
legend_title=None, legend_position=None,
|
|
401
598
|
legend_labels=None, legend_horiz=False,
|
|
402
599
|
legend_size=None, legend_abbrev=None, legend_adjust=0,
|
|
@@ -453,6 +650,10 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
453
650
|
|
|
454
651
|
Variables are strings naming columns of the DataFrame `data`.
|
|
455
652
|
Returns a plotly Figure; call .show() to display from a script.
|
|
653
|
+
The statistics behind the chart come with it as fig.stats, read
|
|
654
|
+
as attributes or by name: fig.stats.freq, fig.stats.p_value,
|
|
655
|
+
fig.stats.text (the console report, kept when quiet=True), named
|
|
656
|
+
as R's Chart() names its returned list.
|
|
456
657
|
"""
|
|
457
658
|
|
|
458
659
|
# ----- validate parameters ------------------------------------
|
|
@@ -566,6 +767,13 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
566
767
|
raise ValueError('labels_position must be "in" or "out"')
|
|
567
768
|
if axis_fmt not in ("K", ",", ".", ""):
|
|
568
769
|
raise ValueError('axis_fmt must be "K", ",", "." or ""')
|
|
770
|
+
# proportions of a bar chart with by are taken within each bar,
|
|
771
|
+
# the 100% stacked chart. R analog: .bc.main()
|
|
772
|
+
# if (!is.null(by) && prop) stack100 <- TRUE
|
|
773
|
+
if (stat_x == "proportion" and by is not None and form == "bar"
|
|
774
|
+
and facet is None and y is None):
|
|
775
|
+
stack100 = True
|
|
776
|
+
stat_x = "count"
|
|
569
777
|
if stack100:
|
|
570
778
|
if form != "bar":
|
|
571
779
|
raise ValueError('stack100 rescales the bars of a bar '
|
|
@@ -662,9 +870,6 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
662
870
|
if sort != "0":
|
|
663
871
|
raise ValueError("Sort not applicable to Trellis "
|
|
664
872
|
"(faceted bar) charts")
|
|
665
|
-
if stat_x == "proportion" and by is not None:
|
|
666
|
-
raise NotImplementedError(
|
|
667
|
-
'stat_x="proportion" with by= is not yet ported')
|
|
668
873
|
|
|
669
874
|
# ----- resolve variables and filter ---------------------------
|
|
670
875
|
if filter is not None:
|
|
@@ -692,7 +897,8 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
692
897
|
stat=stat, sort=sort, sort_miss=sort_miss,
|
|
693
898
|
stack100=stack100, horiz=horiz, fill=fill, theme=theme,
|
|
694
899
|
labels=labels, labels_size=labels_size, gap=gap,
|
|
695
|
-
labels_cut=labels_cut,
|
|
900
|
+
labels_cut=labels_cut, power=power, radius=radius,
|
|
901
|
+
one_plot=one_plot,
|
|
696
902
|
xlab=xlab, ylab=ylab, main=main,
|
|
697
903
|
legend_title=legend_title, n_col=n_col, n_row=n_row,
|
|
698
904
|
axis_fmt=axis_fmt, axis_x_pre=axis_x_pre,
|
|
@@ -737,7 +943,7 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
737
943
|
x_lab_dot = (xlab if xlab else ylab if ylab else val_lab)
|
|
738
944
|
return _dot_paired(
|
|
739
945
|
cats, ydf.reset_index(drop=True), sort, sort_miss,
|
|
740
|
-
origin_x, show_diff=
|
|
946
|
+
origin_x, show_diff=True, quiet=quiet, x_name=x,
|
|
741
947
|
fill=fill, border=color, pt_size=pt_size,
|
|
742
948
|
x_lab=x_lab_dot, y_lab=x,
|
|
743
949
|
digits_d=2 if digits_d is None else digits_d,
|
|
@@ -814,12 +1020,53 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
814
1020
|
facet_order = (_category_order(facet_ser)
|
|
815
1021
|
if facet_ser is not None else None)
|
|
816
1022
|
|
|
817
|
-
# theme
|
|
818
|
-
#
|
|
819
|
-
if theme is
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1023
|
+
# a theme set by style(), or by this call's theme=, which the
|
|
1024
|
+
# wrapper has applied for the call
|
|
1025
|
+
if theme is None:
|
|
1026
|
+
theme = get_option("theme")
|
|
1027
|
+
|
|
1028
|
+
# an ordered category is ordinal, so its default fill is the
|
|
1029
|
+
# theme's sequential palette, light to dark in the order of the
|
|
1030
|
+
# levels, not distinct hues. Fill encodes x without by and by
|
|
1031
|
+
# with it, so the ordering of that variable is the one that
|
|
1032
|
+
# applies. R analog: Chart.R is.ordered() fill
|
|
1033
|
+
if (form == "bar" and fill is None and facet_ser is None
|
|
1034
|
+
and fill_split is None and not fill_scaled):
|
|
1035
|
+
enc = by_ser if by_ser is not None else x_ser
|
|
1036
|
+
if (isinstance(enc.dtype, pd.CategoricalDtype)
|
|
1037
|
+
and enc.cat.ordered):
|
|
1038
|
+
fill = _color_range(
|
|
1039
|
+
_THEME_PALETTE.get(theme, "blues"),
|
|
1040
|
+
len(by_order) if by_order is not None
|
|
1041
|
+
else len(x_order))
|
|
1042
|
+
|
|
1043
|
+
# theme=: a bar chart without by is drawn in the theme's single
|
|
1044
|
+
# bar color, which fill_scaled then varies in luminance; with
|
|
1045
|
+
# by, the levels span the theme's sequential palette, darkest
|
|
1046
|
+
# first. The default theme keeps the qualitative hues.
|
|
1047
|
+
# R analog: Chart.R fill block (theme.seq) and .bc.main()
|
|
1048
|
+
if (theme is not None and fill is None
|
|
1049
|
+
and theme not in ("colors", "hues")):
|
|
1050
|
+
if theme not in _THEME_PALETTE:
|
|
1051
|
+
raise ValueError(
|
|
1052
|
+
f"unknown theme '{theme}'; one of: "
|
|
1053
|
+
f"{', '.join(sorted(_THEME_PALETTE))}")
|
|
1054
|
+
if by_order is not None:
|
|
1055
|
+
fill = list(reversed(_color_range(_THEME_PALETTE[theme],
|
|
1056
|
+
len(by_order))))
|
|
1057
|
+
elif form == "bar":
|
|
1058
|
+
fill = [_THEME_BAR_FILL.get(theme, "#595959E6")] \
|
|
1059
|
+
* len(x_order)
|
|
1060
|
+
else:
|
|
1061
|
+
fill = _theme_fill(theme, len(x_order))
|
|
1062
|
+
|
|
1063
|
+
# a palette name in fill, such as "reds", names a range of colors,
|
|
1064
|
+
# one per bar, or per level of by. R analog: .bc.main()
|
|
1065
|
+
# .color_range(fill, n.levels)
|
|
1066
|
+
if (form == "bar" and isinstance(fill, str) and not fill_scaled
|
|
1067
|
+
and fill in _palette_names()):
|
|
1068
|
+
fill = _color_range(fill, len(by_order) if by_order is not None
|
|
1069
|
+
else len(x_order))
|
|
823
1070
|
|
|
824
1071
|
# explicit panel-grid layout: n_col wins, else derive from
|
|
825
1072
|
# n_row; None keeps each renderer's own default (single-column
|
|
@@ -915,35 +1162,50 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
915
1162
|
f"have a numerical variable y='{y}', so specify "
|
|
916
1163
|
'stat to define the aggregation, e.g., stat="mean"')
|
|
917
1164
|
|
|
918
|
-
# accompanying statistics: frequencies, or the stat of y
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1165
|
+
# accompanying statistics: frequencies, or the stat of y. They are
|
|
1166
|
+
# computed whether or not displayed, and returned with the figure
|
|
1167
|
+
# as fig.stats, as R's Chart() returns its list
|
|
1168
|
+
y_out = (y_ser if y_ser is not None
|
|
1169
|
+
and stat in STAT_FUN else None)
|
|
1170
|
+
if by_more_sers and (y_ser is None or y_out is not None):
|
|
1171
|
+
from .stats_out import nested_stats
|
|
1172
|
+
lines = nested_stats(
|
|
1173
|
+
[x_ser, by_ser] + by_more_sers, [x, by] + by_more,
|
|
1174
|
+
y_out, stat, y, 2 if digits_d is None else digits_d)
|
|
1175
|
+
cols_n = [x_ser, by_ser] + by_more_sers
|
|
1176
|
+
df_n = pd.DataFrame({nm: c for nm, c in
|
|
1177
|
+
zip([x, by] + by_more, cols_n)})
|
|
1178
|
+
_set_stats(ChartStats(
|
|
1179
|
+
n_dim=len(cols_n),
|
|
1180
|
+
freq=df_n.groupby([x, by] + by_more, observed=True).size()
|
|
1181
|
+
if y_out is None else None),
|
|
1182
|
+
lines, quiet)
|
|
1183
|
+
elif y_ser is None or y_out is not None:
|
|
1184
|
+
var_lbl = data.attrs.get("variable_labels", {}) or {}
|
|
1185
|
+
lines = chart_stats(
|
|
1186
|
+
x_ser, by_ser, y_out, stat, x, by, y,
|
|
1187
|
+
2 if digits_d is None else digits_d,
|
|
1188
|
+
stack100=stack100, n_miss=n_miss_x,
|
|
1189
|
+
x_lbl=var_lbl.get(x), y_lbl=var_lbl.get(y),
|
|
1190
|
+
facet_ser=facet_ser, facet_name=facet_name,
|
|
1191
|
+
form=form)
|
|
1192
|
+
_set_stats(chart_stats_data(x_ser, by_ser, y_out, stat,
|
|
1193
|
+
facet_ser=facet_ser,
|
|
1194
|
+
n_miss=n_miss_x),
|
|
1195
|
+
lines, quiet)
|
|
1196
|
+
|
|
1197
|
+
# labels default for aggregated data, the value of the statistic;
|
|
1198
|
+
# bars of counts are labeled with their percentages, as lessR's
|
|
1199
|
+
# do (getOption("values"), "%"): of the total, or of each bar for
|
|
1200
|
+
# the 100% stacked chart. R analog: Chart.R ~716, .bc.main()
|
|
941
1201
|
if labels is None and (stat is not None or
|
|
942
1202
|
(is_agg and y_ser is not None)):
|
|
943
1203
|
# the bars carry a statistic, so the label is its value;
|
|
944
1204
|
# beside only arranges the bars, and a percentage of a sum
|
|
945
1205
|
# of means is not a quantity
|
|
946
1206
|
labels = "input"
|
|
1207
|
+
elif labels is None and form == "bar" and y_ser is None:
|
|
1208
|
+
labels = "%"
|
|
947
1209
|
|
|
948
1210
|
# ----- hierarchical forms aggregate per nesting level ---------
|
|
949
1211
|
# from the raw columns, not from the flat table built below
|
|
@@ -962,6 +1224,10 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
962
1224
|
facet_order=facet_order)
|
|
963
1225
|
fill_vec = hier_color_resolve(x_order, fill, x=x_ser,
|
|
964
1226
|
y=y_ser, stat=stat)
|
|
1227
|
+
fill_vec_byfac = (None if facet_ser is None else
|
|
1228
|
+
hier_color_resolve_byfac(
|
|
1229
|
+
x_order, fill, x_ser, y_ser, stat,
|
|
1230
|
+
facet_ser, facet_order))
|
|
965
1231
|
if main is None:
|
|
966
1232
|
main = build_title(x, by_name=by_label, y_name=y,
|
|
967
1233
|
stat=stat, facet_name=facet_name)
|
|
@@ -974,6 +1240,7 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
974
1240
|
# keep the inner rings meaningful.
|
|
975
1241
|
return hier_plotly(
|
|
976
1242
|
agg, fill_vec, type=hier_type,
|
|
1243
|
+
fill_vec_byfac=fill_vec_byfac,
|
|
977
1244
|
x_name=x, by_name=by, facet_name=facet_name,
|
|
978
1245
|
main=main, border=color, digits_d=digits_d,
|
|
979
1246
|
labels="%" if labels is None else labels,
|
|
@@ -1021,6 +1288,11 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1021
1288
|
columns=x_order)
|
|
1022
1289
|
if y_ser is None:
|
|
1023
1290
|
t = t.fillna(0)
|
|
1291
|
+
if stat_x == "proportion" and not stack100:
|
|
1292
|
+
# every cell of the panel as a share of the panel,
|
|
1293
|
+
# as lattice's prop.table(margin=panel)
|
|
1294
|
+
tot = t.to_numpy().sum()
|
|
1295
|
+
t = t / tot if tot > 0 else t
|
|
1024
1296
|
if stack100:
|
|
1025
1297
|
# each bar scaled to its own total compares
|
|
1026
1298
|
# composition rather than magnitude
|
|
@@ -1048,7 +1320,9 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1048
1320
|
elif y_ser is not None:
|
|
1049
1321
|
val_name = y
|
|
1050
1322
|
elif by_ser is not None:
|
|
1051
|
-
|
|
1323
|
+
# named for x, as lattice's "Count of" / "Proportion of"
|
|
1324
|
+
val_name = ("Proportion of " if stat_x == "proportion"
|
|
1325
|
+
else "Count of ") + x
|
|
1052
1326
|
else:
|
|
1053
1327
|
val_name = None # Count/Proportion of x
|
|
1054
1328
|
return bc_facet_plotly(
|
|
@@ -1070,9 +1344,6 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1070
1344
|
)
|
|
1071
1345
|
|
|
1072
1346
|
if facet_ser is not None and form == "radar":
|
|
1073
|
-
if stat_x == "proportion" and by_ser is not None:
|
|
1074
|
-
raise NotImplementedError(
|
|
1075
|
-
'stat_x="proportion" with by= is not yet ported')
|
|
1076
1347
|
facets = {
|
|
1077
1348
|
str(lv): _facet_table(
|
|
1078
1349
|
x_ser[facet_ser == lv],
|
|
@@ -1174,6 +1445,10 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1174
1445
|
g = df.groupby(["f", "g", "x"], observed=True)["y"]
|
|
1175
1446
|
t = (g.sum() if y_ser is None
|
|
1176
1447
|
else STAT_FUN[stat](g) if agg_y else g.mean())
|
|
1448
|
+
if y_ser is None and stat_x == "proportion":
|
|
1449
|
+
# each series is its group's distribution over x in that
|
|
1450
|
+
# panel; without by, the panel's own distribution
|
|
1451
|
+
t = t / t.groupby(level=["f", "g"]).transform("sum")
|
|
1177
1452
|
cells = {}
|
|
1178
1453
|
for (f, gg, xx), v in t.items():
|
|
1179
1454
|
nm = None if by_ser is None else gg
|
|
@@ -1194,8 +1469,12 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1194
1469
|
digits_d = 0 if y_ser is None else 2
|
|
1195
1470
|
from .plotly_utils import axis_format
|
|
1196
1471
|
y_lab = (ylab if ylab is not None
|
|
1472
|
+
else f"Proportion of {x}" if (y_ser is None
|
|
1473
|
+
and stat_x == "proportion")
|
|
1197
1474
|
else "Count" if y_ser is None
|
|
1198
1475
|
else f"{STAT_LBL[stat]} of {y}" if agg_y else y)
|
|
1476
|
+
if y_ser is None and stat_x == "proportion" and digits_d == 0:
|
|
1477
|
+
digits_d = 2
|
|
1199
1478
|
return profile_facet_plotly(
|
|
1200
1479
|
cells, x_lv, series, y_tick,
|
|
1201
1480
|
axis_format(y_tick, digits_d, axis_fmt, axis_y_pre),
|
|
@@ -1220,9 +1499,6 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1220
1499
|
agg_y = not is_agg and stat is not None
|
|
1221
1500
|
cats_l, vals_l, fac_l = [], [], []
|
|
1222
1501
|
if by_ser is not None:
|
|
1223
|
-
if prop:
|
|
1224
|
-
raise ValueError(
|
|
1225
|
-
'stat_x="proportion" with by= is not yet ported')
|
|
1226
1502
|
rows = []
|
|
1227
1503
|
for lv in facet_order:
|
|
1228
1504
|
m = facet_ser == lv
|
|
@@ -1236,6 +1512,10 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1236
1512
|
wide = (t.unstack("by")
|
|
1237
1513
|
.reindex(index=[str(c) for c in x_order],
|
|
1238
1514
|
columns=[str(b) for b in by_order]))
|
|
1515
|
+
if prop and y_ser is None:
|
|
1516
|
+
# each group's distribution over x in this panel
|
|
1517
|
+
tot = wide.sum(axis=0)
|
|
1518
|
+
wide = wide.div(tot.where(tot != 0), axis=1)
|
|
1239
1519
|
rows.append(wide)
|
|
1240
1520
|
cats_l += list(wide.index)
|
|
1241
1521
|
fac_l += [str(lv)] * len(wide)
|
|
@@ -1266,8 +1546,8 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1266
1546
|
# an aggregated value is labeled by its statistic, as the
|
|
1267
1547
|
# unfaceted chart is: "Mean of Salary", not "Salary"
|
|
1268
1548
|
if y_ser is None:
|
|
1269
|
-
val_lab = ("
|
|
1270
|
-
else
|
|
1549
|
+
val_lab = (f"Proportion of {x}" if prop
|
|
1550
|
+
else "Count" if by_ser is not None
|
|
1271
1551
|
else f"Count of {x}")
|
|
1272
1552
|
else:
|
|
1273
1553
|
val_lab = f"{STAT_LBL[stat]} of {y}" if agg_y else y
|
|
@@ -1317,9 +1597,6 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1317
1597
|
|
|
1318
1598
|
# ----- build the table the renderer receives ------------------
|
|
1319
1599
|
if y_ser is None: # counts of x
|
|
1320
|
-
if stat_x == "proportion" and by_ser is not None:
|
|
1321
|
-
raise NotImplementedError(
|
|
1322
|
-
'stat_x="proportion" with by= is not yet ported')
|
|
1323
1600
|
|
|
1324
1601
|
if by_ser is None:
|
|
1325
1602
|
tbl = (x_ser.groupby(x_ser).size()
|
|
@@ -1339,11 +1616,20 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1339
1616
|
tbl = pd.crosstab(by_ser, x_ser)
|
|
1340
1617
|
tbl = tbl.reindex(index=by_order, columns=x_order,
|
|
1341
1618
|
fill_value=0)
|
|
1342
|
-
|
|
1619
|
+
if stat_x == "proportion":
|
|
1620
|
+
# each group's distribution over x, so the groups are
|
|
1621
|
+
# compared whatever their sizes: every by row sums
|
|
1622
|
+
# to 1 (the bar chart took its own path above, the
|
|
1623
|
+
# 100% stacked chart within each bar)
|
|
1624
|
+
tot = tbl.sum(axis=1)
|
|
1625
|
+
tbl = tbl.div(tot.where(tot != 0), axis=0).fillna(0)
|
|
1626
|
+
y_name = "Proportion"
|
|
1627
|
+
else:
|
|
1628
|
+
y_name = "Count"
|
|
1343
1629
|
if ylab is None:
|
|
1344
|
-
ylab = f"
|
|
1630
|
+
ylab = f"{y_name} of {x}"
|
|
1345
1631
|
if digits_d is None:
|
|
1346
|
-
digits_d = 0
|
|
1632
|
+
digits_d = 0 if stat_x == "count" else 2
|
|
1347
1633
|
|
|
1348
1634
|
elif stat == "deviation": # R analog: .agg()
|
|
1349
1635
|
if by_ser is not None:
|
|
@@ -1468,6 +1754,7 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1468
1754
|
labels_decimals=labels_decimals,
|
|
1469
1755
|
labels_size=0.90 if labels_size is None
|
|
1470
1756
|
else labels_size,
|
|
1757
|
+
shares=(y_ser is None and stat_x == "proportion"),
|
|
1471
1758
|
)
|
|
1472
1759
|
|
|
1473
1760
|
if form == "radar":
|
|
@@ -1564,11 +1851,13 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1564
1851
|
ydf.columns = [str(c) for c in ydf.columns]
|
|
1565
1852
|
cats = [str(c) for c in ydf.index]
|
|
1566
1853
|
val_lab = (xlab if xlab else ylab_user if ylab_user
|
|
1567
|
-
else
|
|
1854
|
+
else ylab if (y_ser is not None
|
|
1855
|
+
or stat_x == "proportion")
|
|
1856
|
+
else "Count")
|
|
1568
1857
|
return _dot_paired(
|
|
1569
1858
|
cats, ydf.reset_index(drop=True), sort, sort_miss,
|
|
1570
1859
|
origin_x,
|
|
1571
|
-
show_diff=
|
|
1860
|
+
show_diff=y_ser is None, quiet=quiet,
|
|
1572
1861
|
x_name=x,
|
|
1573
1862
|
fill=fill, border=color, pt_size=pt_size,
|
|
1574
1863
|
x_lab=val_lab, y_lab=x, digits_d=digits_d,
|
|
@@ -1650,8 +1939,16 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1650
1939
|
if (fill_split is not None or fill_scaled) \
|
|
1651
1940
|
and isinstance(tbl, pd.Series):
|
|
1652
1941
|
from .getColors import bar_fill_colors
|
|
1653
|
-
fill
|
|
1654
|
-
|
|
1942
|
+
# one fill repeated over the bars is a single color; more
|
|
1943
|
+
# than two leave the hue to the theme, as R's .bc.main()
|
|
1944
|
+
f_in = fill
|
|
1945
|
+
if isinstance(fill, (list, tuple)):
|
|
1946
|
+
uniq = list(dict.fromkeys(fill))
|
|
1947
|
+
f_in = (uniq[0] if len(uniq) == 1
|
|
1948
|
+
else None if len(uniq) > 2 else uniq)
|
|
1949
|
+
fill = bar_fill_colors(tbl.to_numpy(dtype=float), f_in,
|
|
1950
|
+
fill_split, fill_scaled, fill_chroma,
|
|
1951
|
+
theme=theme)
|
|
1655
1952
|
|
|
1656
1953
|
fig = bc_plotly(
|
|
1657
1954
|
tbl,
|
|
@@ -1688,4 +1985,5 @@ def Chart(x, y=None, data=None, filter=None, by=None, facet=None,
|
|
|
1688
1985
|
|
|
1689
1986
|
|
|
1690
1987
|
# font_size= scales all text of the returned figure
|
|
1691
|
-
|
|
1988
|
+
from .style import with_theme as _with_theme # noqa: E402
|
|
1989
|
+
Chart = font_scaled(attach_stats(_with_theme(Chart, own_param=True)))
|