lessPython 0.2.0__py3-none-any.whl → 0.3.1__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 (59) hide show
  1. lessPy/ANOVA.py +74 -15
  2. lessPy/Chart.py +431 -88
  3. lessPy/Correlation.py +40 -11
  4. lessPy/Flows.py +3 -3
  5. lessPy/Logit.py +15 -11
  6. lessPy/Regression.py +27 -21
  7. lessPy/X.py +280 -42
  8. lessPy/XY.py +1332 -239
  9. lessPy/__init__.py +12 -2
  10. lessPy/_themes.py +5007 -0
  11. lessPy/bc_items_plotly.py +33 -20
  12. lessPy/bc_plotly.py +27 -10
  13. lessPy/bubble_plotly.py +33 -17
  14. lessPy/corCFA.py +9 -5
  15. lessPy/corProp.py +8 -4
  16. lessPy/corReflect.py +8 -4
  17. lessPy/corReorder.py +8 -4
  18. lessPy/corScree.py +12 -8
  19. lessPy/data/StockPrice.csv +1401 -1386
  20. lessPy/details.py +17 -4
  21. lessPy/dn_plotly.py +74 -54
  22. lessPy/dot_plotly.py +9 -8
  23. lessPy/freq_poly_plotly.py +18 -11
  24. lessPy/getColors.py +247 -20
  25. lessPy/hier_plotly.py +41 -14
  26. lessPy/hs_plotly.py +23 -11
  27. lessPy/pie_plotly.py +1 -1
  28. lessPy/pivot.py +39 -9
  29. lessPy/plotly_utils.py +162 -29
  30. lessPy/plt_add.py +2 -7
  31. lessPy/plt_contour.py +6 -6
  32. lessPy/plt_contour_facet.py +3 -3
  33. lessPy/plt_mat_plotly.py +85 -12
  34. lessPy/plt_plotly.py +34 -18
  35. lessPy/plt_smooth.py +4 -4
  36. lessPy/plt_time.py +10 -3
  37. lessPy/prob_norm.py +7 -3
  38. lessPy/prob_tcut.py +7 -3
  39. lessPy/prob_znorm.py +7 -1
  40. lessPy/profile_plotly.py +5 -4
  41. lessPy/radar_plotly.py +4 -3
  42. lessPy/rcolors.py +461 -0
  43. lessPy/simCImean.py +7 -3
  44. lessPy/simCLT.py +7 -3
  45. lessPy/simFlips.py +7 -3
  46. lessPy/simMeans.py +7 -3
  47. lessPy/stats_out.py +257 -11
  48. lessPy/style.py +167 -0
  49. lessPy/sunflower_plotly.py +141 -0
  50. lessPy/ttest.py +17 -13
  51. lessPy/utils.py +146 -6
  52. lessPy/vbs_plotly.py +39 -27
  53. lessPy/x_console.py +763 -0
  54. {lesspython-0.2.0.dist-info → lesspython-0.3.1.dist-info}/METADATA +13 -2
  55. lesspython-0.3.1.dist-info/RECORD +89 -0
  56. {lesspython-0.2.0.dist-info → lesspython-0.3.1.dist-info}/WHEEL +1 -1
  57. lesspython-0.2.0.dist-info/RECORD +0 -84
  58. {lesspython-0.2.0.dist-info → lesspython-0.3.1.dist-info}/licenses/LICENSE +0 -0
  59. {lesspython-0.2.0.dist-info → lesspython-0.3.1.dist-info}/top_level.txt +0 -0
lessPy/details.py CHANGED
@@ -129,6 +129,11 @@ def details(data=None, n_mcut=1, max_lines=30, miss_show=30,
129
129
 
130
130
  names = list(data.columns)
131
131
  types = [_type_label(data[c]) for c in names]
132
+ # whole numbers that pandas stores as float64 (a pandas integer
133
+ # column cannot hold NaN): reported as integer, as lessR does,
134
+ # but flagged so the report states what pandas actually stores
135
+ stored_float = [t == "integer" and pdt.is_float_dtype(data[c].dtype)
136
+ for c, t in zip(names, types)]
132
137
  n_val = [int(data[c].notna().sum()) for c in names]
133
138
  n_miss = [int(data[c].isna().sum()) for c in names]
134
139
  n_uniq = [int(data[c].dropna().nunique()) for c in names]
@@ -137,7 +142,8 @@ def details(data=None, n_mcut=1, max_lines=30, miss_show=30,
137
142
  _print_types(types)
138
143
  first_last = [_first_last(data[c], n_obs) for c in names]
139
144
  maybe_id, id_col = _print_table(names, types, n_val, n_miss,
140
- n_uniq, first_last, n_var)
145
+ n_uniq, first_last, n_var,
146
+ stored_float)
141
147
 
142
148
  if maybe_id is not None and not var_labels:
143
149
  _print_id_note(maybe_id, id_col)
@@ -196,10 +202,13 @@ def _print_types(types):
196
202
 
197
203
 
198
204
  def _print_table(names, types, n_val, n_miss, n_uniq, first_last,
199
- n_var):
205
+ n_var, stored_float=None):
206
+ if stored_float is None:
207
+ stored_float = [False] * n_var
208
+ shown = [t + "*" if f else t for t, f in zip(types, stored_float)]
200
209
  w_num = max(2, len(str(n_var)))
201
210
  w_nam = max(len("Variable"), *(len(x) for x in names))
202
- w_typ = max(len("Type"), *(len(t) for t in types))
211
+ w_typ = max(len("Type"), *(len(t) for t in shown))
203
212
  w_val = max(len("Values"), *(len(str(x)) for x in n_val))
204
213
  w_mis = max(len("Missing"), *(len(str(x)) for x in n_miss))
205
214
  w_uni = max(len("Unique"), *(len(str(x)) for x in n_uniq))
@@ -215,7 +224,7 @@ def _print_table(names, types, n_val, n_miss, n_uniq, first_last,
215
224
  maybe_id, id_col = None, 0
216
225
  for i in range(n_var):
217
226
  print(f"{i + 1:>{w_num}} {names[i]:<{w_nam}} "
218
- f"{types[i]:<{w_typ}} {n_val[i]:>{w_val}} "
227
+ f"{shown[i]:<{w_typ}} {n_val[i]:>{w_val}} "
219
228
  f"{n_miss[i]:>{w_mis}} {n_uniq[i]:>{w_uni}} "
220
229
  f"{first_last[i]}")
221
230
  if (n_uniq[i] == n_val[i]
@@ -223,6 +232,10 @@ def _print_table(names, types, n_val, n_miss, n_uniq, first_last,
223
232
  "character")):
224
233
  maybe_id, id_col = names[i], i + 1
225
234
  _dash(len(hdr))
235
+ if any(stored_float):
236
+ print("* Whole numbers stored by Pandas as float64 (double), "
237
+ "usually because\n the column had missing values, NaN, "
238
+ "which a Pandas integer column\n cannot hold.")
226
239
  return maybe_id, id_col
227
240
 
228
241
 
lessPy/dn_plotly.py CHANGED
@@ -58,11 +58,13 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
58
58
  from .utils import bw_nrd0
59
59
  bw = bw_nrd0(x)
60
60
  h = bw * adjust
61
+ from .stats_out import record_stats
62
+ record_stats(bw=float(h))
61
63
 
62
64
  if facet is not None:
63
65
  if fill_miss and by is not None:
64
- from .plotly_utils import BASE_COLORS
65
- fill = BASE_COLORS
66
+ from .plotly_utils import by_colors
67
+ fill = by_colors(len(pd.Series(by).dropna().unique()))
66
68
  return _dn_facet(x, facet, facet_order, x_name,
67
69
  facet_name, fill, x_lab, y_lab, main,
68
70
  h, n, fill_area, style_opts,
@@ -91,8 +93,8 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
91
93
 
92
94
  # groups need distinguishable hues: default to the palette
93
95
  if fill_miss and G > 1:
94
- from .plotly_utils import BASE_COLORS
95
- fill = BASE_COLORS
96
+ from .plotly_utils import by_colors
97
+ fill = by_colors(G)
96
98
 
97
99
  def group_x(gname):
98
100
  xg = x if by is None else x[(by == gname).to_numpy()]
@@ -124,6 +126,11 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
124
126
  dx = grid[1] - grid[0]
125
127
 
126
128
  # --- densities per group -------------------------------------
129
+ # R analog: dn.plotly.R — kind "normal"/"both" adds the normal
130
+ # fit to each group by its own mean and sd on the shared grid;
131
+ # a group of identical values has no normal to draw
132
+ do_gen = kind in ("general", "both")
133
+ do_nrm = kind in ("normal", "both")
127
134
  dens = []
128
135
  ymax = 0.0
129
136
  for gname in groups:
@@ -133,19 +140,21 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
133
140
  [[0], np.cumsum((y[1:] + y[:-1]) * 0.5 * dx)])
134
141
  if cum[-1] > 0:
135
142
  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))
143
+ sd = float(xg.std(ddof=1))
144
+ nrm = None
145
+ if do_nrm and np.isfinite(sd) and sd > 0:
146
+ z = (grid - float(xg.mean())) / sd
147
+ nrm = np.exp(-0.5 * z * z) / (sd * np.sqrt(2 * np.pi))
148
+ dens.append(dict(y=y, cum=cum, mn=float(xg.mean()), nrm=nrm))
149
+ # only a curve that is drawn sets the height of the y-axis
150
+ if do_gen:
151
+ ymax = max(ymax, float(y.max()))
152
+ if nrm is not None:
153
+ ymax = max(ymax, float(nrm.max()))
154
+
155
+ # --- background histogram -------------------------------------
156
+ # show_histogram draws a faint density-scaled histogram behind
157
+ # the curves (dn.main.R)
149
158
  hist_y = None
150
159
  if show_histogram and hist_edges is not None:
151
160
  e = np.asarray(hist_edges, dtype=float)
@@ -155,10 +164,6 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
155
164
  hist_w = np.diff(e)
156
165
  hist_y = cnt / (tot * hist_w) # density scale
157
166
  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
167
  if hist_y is not None:
163
168
  ymax = max(ymax, float(hist_y.max()))
164
169
 
@@ -195,12 +200,16 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
195
200
  if fill_hist is None else fill_hist),
196
201
  line=dict(width=0)),
197
202
  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]
203
+ # the normal curve: its own color for a single distribution, but
204
+ # the group's color when there are groups to keep apart, dashed
205
+ # beside that group's general curve (dn.plotly.R)
206
+ nrm_fill = (None if fill_normal in (None, "transparent")
207
+ else as_plotly_color(fill_normal))
208
+ for g, gname in enumerate(groups):
209
+ d = dens[g]
210
+ pre = (f"{by_name}: {gname}<br>"
211
+ if G > 1 and by_name else "")
212
+ if do_gen:
204
213
  fig.add_trace(go.Scatter(
205
214
  mode="lines",
206
215
  x=grid, y=d["y"],
@@ -209,29 +218,40 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
209
218
  fill="tozeroy" if fill_area else "none",
210
219
  fillcolor=fill_rgba[g] if fill_area else None,
211
220
  hoverinfo="text",
212
- hovertext=hv,
221
+ hovertext=[pre + t for t in hover(d)],
213
222
  showlegend=G > 1,
214
223
  ))
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)
224
+ if d["nrm"] is not None:
225
+ nrm_col = line_rgba[g] if G > 1 else to_hex(color_normal)
226
+ filled = fill_area and G == 1 and nrm_fill is not None
219
227
  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,
228
+ mode="lines", x=grid, y=d["nrm"],
229
+ name=(None if G == 1 else
230
+ f"{gname} normal" if do_gen else gname),
231
+ showlegend=G > 1 and not do_gen,
232
+ line=dict(color=nrm_col, width=1.4,
233
+ dash="dash" if do_gen and G > 1
234
+ else "solid"),
235
+ fill="tozeroy" if filled else "none",
236
+ fillcolor=nrm_fill if filled else None,
237
+ hoverinfo="text",
238
+ hovertext=[
239
+ f"{pre}{x_name}: {grid[i]:.6g}"
240
+ f"<br>Normal density="
241
+ f"{round(float(d['nrm'][i]), 6):g}"
242
+ for i in range(n)],
224
243
  ))
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))
244
+ # vertical line at the group mean, up to whichever curve
245
+ # is drawn
246
+ curve = d["y"] if do_gen or d["nrm"] is None else d["nrm"]
247
+ y_mn = float(np.interp(d["mn"], grid, curve))
228
248
  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))
249
+ mode="lines",
250
+ x=[d["mn"], d["mn"]], y=[0, y_mn],
251
+ line=dict(color=line_rgba[g], dash="dash",
252
+ width=1.4 if fill_area else 0.7),
253
+ hoverinfo="skip", showlegend=False,
254
+ ))
235
255
  if rug: # ticks at each data value
236
256
  xr, yr = [], []
237
257
  y1r = -0.05 * ymax
@@ -240,7 +260,7 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
240
260
  yr += [0.0, y1r, None]
241
261
  fig.add_trace(go.Scatter(
242
262
  mode="lines", x=xr, y=yr,
243
- line=dict(color=to_hex(color_rug),
263
+ line=dict(color=as_plotly_color(color_rug),
244
264
  width=max(0.5, float(size_rug))),
245
265
  hoverinfo="skip", showlegend=False))
246
266
 
@@ -250,12 +270,12 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
250
270
  axis_shapes = [dict(
251
271
  type="line", xref="paper", yref="paper",
252
272
  x0=0, x1=1, y0=0, y1=0,
253
- line=dict(color=to_hex("gray50"), width=1))]
273
+ line=dict(color=as_plotly_color("gray50"), width=1))]
254
274
 
255
275
  ax_x = axis_num(x_lab, gridT1, gridL1)
256
276
  ax_y = axis_num(y_lab, gridT2, gridL2)
257
277
  ax_y.update(showgrid=True,
258
- gridcolor=to_hex(style_opts["grid_col"]),
278
+ gridcolor=as_plotly_color(style_opts["grid_col"]),
259
279
  gridwidth=1, griddash="dot")
260
280
 
261
281
  fig.update_layout(
@@ -264,8 +284,8 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
264
284
  shapes=x_grid(gridT1) + plot_border() + axis_shapes,
265
285
  template=None,
266
286
  bargap=0,
267
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
268
- paper_bgcolor=to_hex(style_opts["window_fill"]),
287
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
288
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
269
289
  )
270
290
 
271
291
  if G > 1:
@@ -277,7 +297,7 @@ def dn_plotly(x, by=None, x_name=None, by_name=None,
277
297
  title=dict(text=main, x=0.5, xanchor="center",
278
298
  y=0.99, yanchor="top",
279
299
  font=dict(size=title_size,
280
- color=to_hex(get_option(
300
+ color=as_plotly_color(get_option(
281
301
  "lab_color", "black")))),
282
302
  margin=dict(t=round(title_size * 2.2)))
283
303
  return fig
@@ -455,7 +475,7 @@ def _dn_facet(x, facet, facet_order, x_name, facet_name, fill,
455
475
  else as_plotly_color(fill_normal))
456
476
  fig.add_trace(go.Scatter(
457
477
  mode="lines", x=grid, y=d_nrms[i],
458
- line=dict(color=to_hex(color_normal),
478
+ line=dict(color=as_plotly_color(color_normal),
459
479
  width=1.35 if nrm_fill is None else 1),
460
480
  fill="none" if nrm_fill is None else "tozeroy",
461
481
  fillcolor=nrm_fill,
@@ -469,7 +489,7 @@ def _dn_facet(x, facet, facet_order, x_name, facet_name, fill,
469
489
  yr += [0.0, y1r, None]
470
490
  fig.add_trace(go.Scatter(
471
491
  mode="lines", x=xr, y=yr,
472
- line=dict(color=to_hex(color_rug),
492
+ line=dict(color=as_plotly_color(color_rug),
473
493
  width=max(0.5, float(size_rug))),
474
494
  hoverinfo="skip", showlegend=False,
475
495
  ), row=row, col=p_col)
@@ -489,7 +509,7 @@ def _dn_facet(x, facet, facet_order, x_name, facet_name, fill,
489
509
  title=dict(text=main, x=0.5, xanchor="center",
490
510
  y=0.99, yanchor="top",
491
511
  font=dict(size=title_size,
492
- color=to_hex(get_option(
512
+ color=as_plotly_color(get_option(
493
513
  "lab_color", "black")))),
494
514
  margin=dict(t=round(title_size * 2.2)))
495
515
  return fig
lessPy/dot_plotly.py CHANGED
@@ -19,6 +19,7 @@ import plotly.graph_objects as go
19
19
 
20
20
  from .utils import get_option
21
21
  from .plotly_utils import (
22
+ as_plotly_color,
22
23
  BASE_COLORS, axis_cat, axis_format, axis_num, facet_layout,
23
24
  get_tick_fmt, make_trans, plot_border, plotly_style, to_hex,
24
25
  x_grid, y_grid,
@@ -128,7 +129,7 @@ def _dot_title(main):
128
129
  return dict(text=main, x=0.5, xanchor="center",
129
130
  y=0.98, yanchor="top",
130
131
  font=dict(size=round(16 * get_option("main_size", 1)),
131
- color=to_hex(get_option("lab_color",
132
+ color=as_plotly_color(get_option("lab_color",
132
133
  "black"))))
133
134
 
134
135
 
@@ -169,7 +170,7 @@ def dot_plotly(x, y, orientation="v",
169
170
  else np.asarray(list(vals), dtype=float))
170
171
  flat = flat[np.isfinite(flat)]
171
172
  val_max = float(flat.max()) if flat.size else None
172
- seg_color = to_hex(style_opts["segment_color"])
173
+ seg_color = as_plotly_color(style_opts["segment_color"])
173
174
 
174
175
  def val_ticks(orient):
175
176
  # value-axis labels through the axis_fmt policies ("60K"),
@@ -240,8 +241,8 @@ def dot_plotly(x, y, orientation="v",
240
241
  font=dict(size=round(
241
242
  16 * get_option("axis_size", 0.9)))),
242
243
  template=None,
243
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
244
- paper_bgcolor=to_hex(style_opts["window_fill"]),
244
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
245
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
245
246
  )
246
247
  return fig
247
248
 
@@ -374,8 +375,8 @@ def dot_plotly(x, y, orientation="v",
374
375
  16 * get_option("axis_size", 0.9)))),
375
376
  title=_dot_title(main),
376
377
  template=None,
377
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
378
- paper_bgcolor=to_hex(style_opts["window_fill"]),
378
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
379
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
379
380
  **axes,
380
381
  )
381
382
  return fig
@@ -450,7 +451,7 @@ def dot_plotly(x, y, orientation="v",
450
451
  margin=margin,
451
452
  title=_dot_title(main),
452
453
  template=None,
453
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
454
- paper_bgcolor=to_hex(style_opts["window_fill"]),
454
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
455
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
455
456
  )
456
457
  return fig
@@ -25,7 +25,7 @@ import plotly.graph_objects as go
25
25
 
26
26
  from .utils import get_option, pretty
27
27
  from .plotly_utils import (
28
- auto_opacity, axis_format, axis_num, facet_fig,
28
+ as_plotly_color, auto_opacity, axis_format, axis_num, facet_fig,
29
29
  facet_panels, finish_facet, get_tick_fmt,
30
30
  legend_style, make_trans, plot_border, plotly_style, to_hex,
31
31
  x_grid,
@@ -48,6 +48,7 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
48
48
  facet2=None, facet2_order=None,
49
49
  facet2_name=None,
50
50
  breaks=None, proportion=False,
51
+ cumulate=False,
51
52
  fill=None, fill_area=True,
52
53
  x_lab=None, y_lab=None, main=None,
53
54
  n_col=1, axis_fmt="K", axis_x_pre="",
@@ -65,6 +66,8 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
65
66
  if y_lab is None:
66
67
  y_lab = (f"Proportion of {x_name}" if proportion
67
68
  else f"Count of {x_name}")
69
+ if cumulate:
70
+ y_lab = f"Cumulative {y_lab}"
68
71
 
69
72
  if facet is not None:
70
73
  return _fp_facet(x, facet, facet_order, x_name,
@@ -90,8 +93,8 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
90
93
  # groups need distinguishable hues: default to the palette
91
94
  if fill is None:
92
95
  if G > 1:
93
- from .plotly_utils import BASE_COLORS
94
- fill = BASE_COLORS
96
+ from .plotly_utils import by_colors
97
+ fill = by_colors(G)
95
98
  else:
96
99
  fill = get_option("bar_fill_cont", "#96AAC3")
97
100
 
@@ -107,6 +110,8 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
107
110
  xg = x if by is None else x[(by == gname).to_numpy()]
108
111
  xg = xg[np.isfinite(xg)]
109
112
  yv = _poly_counts(xg, breaks, proportion)
113
+ if cumulate: # running total over the bins
114
+ yv = np.cumsum(yv)
110
115
  polys.append(yv)
111
116
  ymax = max(ymax, float(yv.max()))
112
117
 
@@ -133,7 +138,9 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
133
138
  for g, gname in enumerate(groups):
134
139
  xx = np.concatenate([[mids[0] - step], mids,
135
140
  [mids[-1] + step]])
136
- yy = np.concatenate([[0], polys[g], [0]])
141
+ # a cumulative polygon (ogive) holds its total at the right
142
+ yy = np.concatenate([[0], polys[g],
143
+ [polys[g][-1] if cumulate else 0]])
137
144
  hover = [
138
145
  (f"{x_name}: {xv:.6g}"
139
146
  f"<br>{y_lab}: {round(float(yv), digits_d):g}")
@@ -162,7 +169,7 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
162
169
  ax_x = axis_num(x_lab, gridT1, gridL1)
163
170
  ax_y = axis_num(y_lab, gridT2, gridL2)
164
171
  ax_y.update(showgrid=True,
165
- gridcolor=to_hex(style_opts["grid_col"]),
172
+ gridcolor=as_plotly_color(style_opts["grid_col"]),
166
173
  gridwidth=1, griddash="dot")
167
174
 
168
175
  fig.update_layout(
@@ -170,8 +177,8 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
170
177
  yaxis=ax_y,
171
178
  shapes=x_grid(gridT1) + plot_border(),
172
179
  template=None,
173
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
174
- paper_bgcolor=to_hex(style_opts["window_fill"]),
180
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
181
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
175
182
  )
176
183
 
177
184
  if G > 1:
@@ -183,7 +190,7 @@ def freq_poly_plotly(x, by=None, x_name=None, by_name=None,
183
190
  title=dict(text=main, x=0.5, xanchor="center",
184
191
  y=0.99, yanchor="top",
185
192
  font=dict(size=title_size,
186
- color=to_hex(get_option(
193
+ color=as_plotly_color(get_option(
187
194
  "lab_color", "black")))),
188
195
  margin=dict(t=round(title_size * 2.2)))
189
196
  return fig
@@ -221,8 +228,8 @@ def _fp_facet(x, facet, facet_order, x_name, facet_name, breaks,
221
228
 
222
229
  if fill is None:
223
230
  if G > 1:
224
- from .plotly_utils import BASE_COLORS
225
- fill = BASE_COLORS
231
+ from .plotly_utils import by_colors
232
+ fill = by_colors(G)
226
233
  else:
227
234
  fill = get_option("bar_fill_cont", "#96AAC3")
228
235
 
@@ -318,7 +325,7 @@ def _fp_facet(x, facet, facet_order, x_name, facet_name, breaks,
318
325
  title=dict(text=main, x=0.5, xanchor="center",
319
326
  y=0.99, yanchor="top",
320
327
  font=dict(size=title_size,
321
- color=to_hex(get_option(
328
+ color=as_plotly_color(get_option(
322
329
  "lab_color", "black")))),
323
330
  margin=dict(t=round(title_size * 2.2)))
324
331
  return fig