lessPython 0.3.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 (50) hide show
  1. lessPy/ANOVA.py +70 -11
  2. lessPy/Chart.py +49 -4
  3. lessPy/Correlation.py +39 -10
  4. lessPy/Flows.py +3 -3
  5. lessPy/Logit.py +14 -10
  6. lessPy/Regression.py +22 -18
  7. lessPy/X.py +3 -2
  8. lessPy/XY.py +528 -91
  9. lessPy/bc_items_plotly.py +2 -2
  10. lessPy/bc_plotly.py +6 -5
  11. lessPy/bubble_plotly.py +16 -15
  12. lessPy/corCFA.py +7 -3
  13. lessPy/corProp.py +7 -3
  14. lessPy/corReflect.py +7 -3
  15. lessPy/corReorder.py +7 -3
  16. lessPy/corScree.py +10 -6
  17. lessPy/dn_plotly.py +9 -9
  18. lessPy/dot_plotly.py +9 -8
  19. lessPy/freq_poly_plotly.py +6 -6
  20. lessPy/hier_plotly.py +5 -5
  21. lessPy/hs_plotly.py +7 -7
  22. lessPy/pie_plotly.py +1 -1
  23. lessPy/pivot.py +39 -9
  24. lessPy/plotly_utils.py +90 -26
  25. lessPy/plt_add.py +2 -7
  26. lessPy/plt_contour.py +6 -6
  27. lessPy/plt_contour_facet.py +3 -3
  28. lessPy/plt_mat_plotly.py +11 -10
  29. lessPy/plt_plotly.py +34 -18
  30. lessPy/plt_smooth.py +4 -4
  31. lessPy/prob_norm.py +5 -1
  32. lessPy/prob_tcut.py +5 -1
  33. lessPy/prob_znorm.py +5 -1
  34. lessPy/profile_plotly.py +5 -4
  35. lessPy/radar_plotly.py +4 -3
  36. lessPy/simCImean.py +5 -1
  37. lessPy/simCLT.py +5 -1
  38. lessPy/simFlips.py +5 -1
  39. lessPy/simMeans.py +5 -1
  40. lessPy/style.py +8 -1
  41. lessPy/sunflower_plotly.py +6 -5
  42. lessPy/ttest.py +13 -9
  43. lessPy/utils.py +103 -0
  44. lessPy/vbs_plotly.py +23 -22
  45. {lesspython-0.3.0.dist-info → lesspython-0.3.1.dist-info}/METADATA +8 -1
  46. lesspython-0.3.1.dist-info/RECORD +89 -0
  47. {lesspython-0.3.0.dist-info → lesspython-0.3.1.dist-info}/WHEEL +1 -1
  48. lesspython-0.3.0.dist-info/RECORD +0 -89
  49. {lesspython-0.3.0.dist-info → lesspython-0.3.1.dist-info}/licenses/LICENSE +0 -0
  50. {lesspython-0.3.0.dist-info → lesspython-0.3.1.dist-info}/top_level.txt +0 -0
lessPy/pivot.py CHANGED
@@ -45,8 +45,10 @@ def pivot(data, compute, variable=None, by=None, filter=None,
45
45
  """Aggregate a numeric variable over by groups, or tabulate
46
46
  frequencies. compute is a statistic name or a list of names
47
47
  ("mean", ["mean","sd"], "table"); variable is the numeric
48
- column to aggregate; by is the grouping column(s). Returns a
49
- long-form DataFrame. R analog: pivot()"""
48
+ column to aggregate, or a list of them, each then with its own
49
+ {variable}_n, {variable}_na, and statistic columns; by is the
50
+ grouping column(s). Returns a long-form DataFrame.
51
+ R analog: pivot()"""
50
52
  if filter is not None:
51
53
  data = data.query(filter)
52
54
  computes = [compute] if isinstance(compute, str) \
@@ -72,16 +74,44 @@ def pivot(data, compute, variable=None, by=None, filter=None,
72
74
  raise ValueError(
73
75
  f"unknown compute {unknown}; use "
74
76
  f"{', '.join(_STAT)}, or \"table\"")
75
- v = get_column(data, variable, "variable")
76
- if not pd.api.types.is_numeric_dtype(v):
77
- raise TypeError(
78
- f"the variable to aggregate '{variable}' is "
79
- f"{v.dtype}: it must be numeric. Put categorical "
80
- "variables in by=.")
77
+ variables = (list(variable) if isinstance(variable, (list, tuple))
78
+ else [variable])
79
+ for var in variables:
80
+ v = get_column(data, var, "variable")
81
+ if not pd.api.types.is_numeric_dtype(v):
82
+ raise TypeError(
83
+ f"the variable to aggregate '{var}' is "
84
+ f"{v.dtype}: it must be numeric. Put categorical "
85
+ "variables in by=.")
81
86
  if not by:
82
87
  raise ValueError("by= is required: the grouping "
83
88
  "column(s)")
84
- return _pivot_agg(data, variable, by, computes, show_n, sort)
89
+ if len(variables) == 1:
90
+ return _pivot_agg(data, variables[0], by, computes, show_n,
91
+ sort)
92
+ if len(computes) > 1: # R pivot.R
93
+ raise ValueError(
94
+ "Cannot have multiple compute functions, multiple "
95
+ "variables,\n and do aggregation with a by variable. "
96
+ "Choose two of the three")
97
+ # several variables: each its own n, na, and statistic, side by
98
+ # side over the same by groups, as R's Sales_n, Sales_na,
99
+ # Sales_sum, Profit_n, ...
100
+ parts = []
101
+ for var in variables:
102
+ a = _pivot_agg(data, var, by, computes, True, None)
103
+ parts.append(a.drop(columns=by).rename(
104
+ columns={"n": f"{var}_n", "na": f"{var}_na"}))
105
+ out = pd.concat([a[by]] + parts, axis=1)
106
+ if sort is not None:
107
+ stat_col = out.columns[-1]
108
+ out = out.sort_values(
109
+ stat_col, ascending=(sort == "+"),
110
+ na_position="last", kind="stable").reset_index(drop=True)
111
+ if not show_n:
112
+ out = out.drop(columns=[c for v_ in variables
113
+ for c in (f"{v_}_n", f"{v_}_na")])
114
+ return out
85
115
 
86
116
 
87
117
  def _cat_frame(data, by):
lessPy/plotly_utils.py CHANGED
@@ -264,14 +264,14 @@ def legend_style(by_name, style_opts, title=None, position=None,
264
264
  title=dict(text=abbrev(text, abbrev_n) or "",
265
265
  font=dict(size=round(
266
266
  15 * get_option("lab_size", 1) * mult),
267
- color=to_hex(style_opts["lab_color"]))),
267
+ color=as_plotly_color(style_opts["lab_color"]))),
268
268
  # the legend reads in the theme's label color, light on the
269
269
  # black sub_theme as the axis labels are
270
270
  font=dict(size=round(
271
271
  16 * get_option("axis_size", 0.9) * mult),
272
- color=to_hex(style_opts["lab_color"])),
273
- bgcolor=to_hex(style_opts["window_fill"]),
274
- bordercolor=to_hex(style_opts["legend_border"]),
272
+ color=as_plotly_color(style_opts["lab_color"])),
273
+ bgcolor=as_plotly_color(style_opts["window_fill"]),
274
+ bordercolor=as_plotly_color(style_opts["legend_border"]),
275
275
  borderwidth=1,
276
276
  )
277
277
  # placement stays plotly's own unless asked for, so the callers
@@ -360,6 +360,17 @@ def axis_format(vals, digits_d=2, axis_fmt="K", prefix=""):
360
360
  return base
361
361
 
362
362
 
363
+ _R_DASH = {"dashed": "dash", "dotted": "dot",
364
+ "dotdash": "dashdot", "longdash": "longdash",
365
+ "twodash": "longdashdot"}
366
+
367
+
368
+ def r_dash(lty):
369
+ """An R line type name as plotly's dash, such as "dotted" to
370
+ "dot"; plotly's own names and None pass through. R analog: lty"""
371
+ return _R_DASH.get(lty, lty) if isinstance(lty, str) else lty
372
+
373
+
363
374
  def grid_style():
364
375
  """R analog: .grid_style(). A sub_theme sets the grid through
365
376
  grid_x_color / grid_y_color (white on the gray panel of "default"),
@@ -367,9 +378,9 @@ def grid_style():
367
378
  clr = (get_option("grid_y_color") or get_option("grid_x_color")
368
379
  or get_option("grid_col", "gray85"))
369
380
  return {
370
- "color": to_hex(clr),
381
+ "color": as_plotly_color(clr),
371
382
  "width": get_option("grid_lwd", 0.5),
372
- "dash": get_option("grid_lty", None),
383
+ "dash": r_dash(get_option("grid_lty", None)),
373
384
  }
374
385
 
375
386
 
@@ -416,24 +427,24 @@ def axis_base():
416
427
  return {
417
428
  "zeroline": False,
418
429
  "showline": True,
419
- "linecolor": to_hex(ax_line),
430
+ "linecolor": as_plotly_color(ax_line),
420
431
  "linewidth": get_option("axis_lwd", 1),
421
432
  "ticks": "outside",
422
433
  "ticklen": 4,
423
- "tickcolor": to_hex(ax_line),
434
+ "tickcolor": as_plotly_color(ax_line),
424
435
  "automargin": True,
425
436
  "tickfont": {
426
- "color": to_hex(ax_text),
437
+ "color": as_plotly_color(ax_text),
427
438
  "size": 16 * get_option("axis_size", 0.9),
428
439
  },
429
440
  "title": {
430
441
  "font": {
431
- "color": to_hex(get_option("lab_color", "black")),
442
+ "color": as_plotly_color(get_option("lab_color", "black")),
432
443
  "size": 15 * get_option("lab_size", 1),
433
444
  },
434
445
  "standoff": 10,
435
446
  },
436
- "gridcolor": to_hex(get_option("grid_color", "gray90")),
447
+ "gridcolor": as_plotly_color(get_option("grid_color", "gray90")),
437
448
  "gridwidth": get_option("grid_lwd", 1),
438
449
  }
439
450
 
@@ -503,7 +514,7 @@ def add_subtitle(fig, sub):
503
514
  title_size = round(16 * get_option("main_size", 1))
504
515
  size = round(title_size * get_option("sub_size", 0.75) / 0.75
505
516
  * 0.75)
506
- color = to_hex(get_option("sub_color",
517
+ color = as_plotly_color(get_option("sub_color",
507
518
  get_option("main_color",
508
519
  get_option("lab_color",
509
520
  "black"))))
@@ -558,13 +569,66 @@ def font_scaled(func):
558
569
  return wrapper
559
570
 
560
571
 
561
- def sym_at(shape, i):
562
- """Per-group marker symbol. A scalar shape is used for every
563
- group (by= differentiates by color only, the default); a list/
564
- tuple gives one symbol per group, recycled if short."""
572
+ # R's pch codes 0-25 as plotly symbols; 21-25 are lessR's named
573
+ # shapes. R analog: .plt.shapes(), and points() for the open codes
574
+ _R_PCH = {
575
+ 0: "square-open", 1: "circle-open", 2: "triangle-up-open",
576
+ 3: "cross-thin-open", 4: "x-thin-open", 5: "diamond-open",
577
+ 6: "triangle-down-open", 7: "square-x-open",
578
+ 8: "asterisk-open", 9: "diamond-cross-open",
579
+ 10: "circle-cross-open", 11: "hexagram-open",
580
+ 12: "square-cross-open", 13: "circle-x-open",
581
+ 14: "square-open", 15: "square", 16: "circle",
582
+ 17: "triangle-up", 18: "diamond", 19: "circle", 20: "circle",
583
+ 21: "circle", 22: "square", 23: "diamond", 24: "triangle-up",
584
+ 25: "triangle-down"}
585
+ _LESSR_SHAPES = {"triup": "triangle-up", "tridown": "triangle-down"}
586
+ # pt_shape="vary": one shape per by group, in lessR's order
587
+ _VARY = (["triup", "tridown", "circle", "square", "diamond",
588
+ 8, 7, 9, 10, 12, 13, 14, 11]
589
+ + list("abcdefghijklmnopqrstuvwxyz")
590
+ + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
591
+ + list("+*#%!=-&$?|><@./;:"))
592
+
593
+
594
+ def shape_at(shape, i):
595
+ """Per-group shape as (plotly symbol, None), or (None, char) for
596
+ a single character, drawn as text, as R's pch="$". A scalar
597
+ shape is used for every group (by= differentiates by color
598
+ only, the default); a list gives one shape per group, recycled;
599
+ "vary" a different shape per group. Takes plotly's names,
600
+ lessR's triup/tridown, and R's pch codes 0-25.
601
+ R analog: .plt.shapes()"""
602
+ if isinstance(shape, str) and shape == "vary":
603
+ shape = _VARY
565
604
  if isinstance(shape, (list, tuple)):
566
- return shape[i % len(shape)]
567
- return shape
605
+ shape = shape[i % len(shape)]
606
+ if isinstance(shape, (int, np.integer)) and not isinstance(
607
+ shape, bool):
608
+ if int(shape) not in _R_PCH:
609
+ raise ValueError(
610
+ f"pt_shape={shape}: R's numbered shapes are 0 to 25")
611
+ return _R_PCH[int(shape)], None
612
+ s = str(shape)
613
+ if len(s) == 1:
614
+ return None, s
615
+ return _LESSR_SHAPES.get(s, s), None
616
+
617
+
618
+ def marks(shape, i, mode, marker):
619
+ """go.Scatter keywords that draw the points of group i: the
620
+ marker with its symbol, or for a character shape the
621
+ character as text in the marker's border color, sized to the
622
+ marker. mode is "markers" or "lines+markers"."""
623
+ sym, ch = shape_at(shape, i)
624
+ if ch is None:
625
+ return dict(mode=mode, marker=dict(marker, symbol=sym))
626
+ color = (marker.get("line") or {}).get("color", marker.get("color"))
627
+ return dict(mode=mode.replace("markers", "text"), text=ch,
628
+ textposition="middle center",
629
+ textfont=dict(color=color,
630
+ size=max(8, round(float(np.median(
631
+ marker.get("size", 8))) * 1.4))))
568
632
 
569
633
 
570
634
  def square_layout(main=False, side=460):
@@ -693,8 +757,8 @@ def finish_facet(fig, levels, ax, x_lab, y_lab, gridT1,
693
757
  anns = list(fig.layout.annotations or [])
694
758
  s_fill = as_plotly_color(get_option("strip_fill",
695
759
  "#7F7F7F37"))
696
- s_line = to_hex(get_option("strip_color", "gray40"))
697
- s_text = to_hex(get_option("strip_text_color", "gray15"))
760
+ s_line = as_plotly_color(get_option("strip_color", "gray40"))
761
+ s_text = as_plotly_color(get_option("strip_text_color", "gray15"))
698
762
  strip_h = max(0.10, min(0.30, 0.05 * n_row))
699
763
  # strip font: shrink so the longest label fits its panel
700
764
  # (the two-facet Var = "level" cell labels run long)
@@ -716,7 +780,7 @@ def finish_facet(fig, levels, ax, x_lab, y_lab, gridT1,
716
780
  if y_cat is None:
717
781
  ay = axis_num(show_ylab, ax["axT2"], ax["axL2"])
718
782
  ay.update(showgrid=True,
719
- gridcolor=to_hex(style_opts["grid_col"]),
783
+ gridcolor=as_plotly_color(style_opts["grid_col"]),
720
784
  gridwidth=1, griddash="dot",
721
785
  showticklabels=c == 1,
722
786
  range=[0, float(ax["axT2"][-1]) * 1.04])
@@ -749,7 +813,7 @@ def finish_facet(fig, levels, ax, x_lab, y_lab, gridT1,
749
813
  else:
750
814
  fig.update_xaxes(
751
815
  row=r, col=c, showline=True, ticks="",
752
- linecolor=to_hex(get_option("axis_color",
816
+ linecolor=as_plotly_color(get_option("axis_color",
753
817
  "black")))
754
818
  for v in (gridT1 or []): # vertical panel grid
755
819
  shapes.append({
@@ -762,7 +826,7 @@ def finish_facet(fig, levels, ax, x_lab, y_lab, gridT1,
762
826
  "type": "rect",
763
827
  "xref": f"x{suf} domain", "yref": f"y{suf} domain",
764
828
  "x0": 0, "x1": 1, "y0": 0, "y1": 1,
765
- "line": {"color": to_hex("gray75"), "width": 1},
829
+ "line": {"color": as_plotly_color("gray75"), "width": 1},
766
830
  "fillcolor": "rgba(0,0,0,0)"})
767
831
  shapes.append({ # strip label band
768
832
  "type": "rect",
@@ -787,8 +851,8 @@ def finish_facet(fig, levels, ax, x_lab, y_lab, gridT1,
787
851
 
788
852
  layout_kw = dict(
789
853
  shapes=shapes, annotations=anns, template=None,
790
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
791
- paper_bgcolor=to_hex(style_opts["window_fill"]),
854
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
855
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
792
856
  height=height if height is not None else 140 + 160 * n_row,
793
857
  )
794
858
  if width is not None:
@@ -883,7 +947,7 @@ def plot_border(top=True, right=True, color=None, width=None):
883
947
  if isinstance(color, str) and re.fullmatch(r"gray\d+", color):
884
948
  color = "gray75" # make a gray a little lighter for plotly
885
949
 
886
- line = {"color": to_hex(color), "width": width}
950
+ line = {"color": as_plotly_color(color), "width": width}
887
951
  out = []
888
952
  if top:
889
953
  out.append({"type": "line", "xref": "paper", "yref": "paper",
lessPy/plt_add.py CHANGED
@@ -13,14 +13,9 @@
13
13
  import numpy as np
14
14
  import plotly.graph_objects as go
15
15
 
16
- from .plotly_utils import as_plotly_color, make_trans, to_hex
16
+ from .plotly_utils import as_plotly_color, make_trans, r_dash, to_hex
17
17
  from .utils import get_option
18
18
 
19
- _DASH = {"dashed": "dash", "dotted": "dot",
20
- "dotdash": "dashdot", "longdash": "longdash",
21
- "twodash": "longdashdot"}
22
-
23
-
24
19
  def _as_list(v):
25
20
  if v is None:
26
21
  return []
@@ -86,7 +81,7 @@ def plt_add(fig, add, x1=None, x2=None, y1=None, y2=None):
86
81
  else as_plotly_color(fll))
87
82
  line = dict(color=col, width=max(0.5, lwd * 2))
88
83
  if lty != "solid":
89
- line["dash"] = _DASH.get(lty, lty)
84
+ line["dash"] = r_dash(lty)
90
85
 
91
86
  for xx1, yy1, xx2, yy2 in locs(obj):
92
87
  if obj == "v_line":
lessPy/plt_contour.py CHANGED
@@ -14,7 +14,7 @@ import plotly.graph_objects as go
14
14
  from scipy import stats as sps
15
15
 
16
16
  from .plotly_utils import (
17
- axis_format, axis_num, get_tick_fmt, plot_border,
17
+ as_plotly_color, axis_format, axis_num, get_tick_fmt, plot_border,
18
18
  plotly_style, to_hex)
19
19
  from .utils import get_option, pretty
20
20
 
@@ -134,7 +134,7 @@ def plt_contour(xv, yv, contour_n, contour_nbins, contour_points,
134
134
  showscale=bool(legend),
135
135
  colorbar=dict(
136
136
  tickfont=dict(
137
- color=to_hex(get_option("axis_color", "black")),
137
+ color=as_plotly_color(get_option("axis_color", "black")),
138
138
  size=16 * get_option("axis_size", 0.9)),
139
139
  outlinewidth=0),
140
140
  hovertemplate=(f"{x_lab}: {xpart}<br>{y_lab}: {ypart}"
@@ -158,7 +158,7 @@ def plt_contour(xv, yv, contour_n, contour_nbins, contour_points,
158
158
  for ex_l, ey_l in ellipses or []:
159
159
  fig.add_trace(go.Scatter(
160
160
  x=ex_l, y=ey_l, mode="lines",
161
- line=dict(color=to_hex(ellipse_color),
161
+ line=dict(color=as_plotly_color(ellipse_color),
162
162
  width=ellipse_lwd),
163
163
  hoverinfo="skip", showlegend=False))
164
164
 
@@ -167,7 +167,7 @@ def plt_contour(xv, yv, contour_n, contour_nbins, contour_points,
167
167
  continue
168
168
  fig.add_trace(go.Scatter(
169
169
  x=fl["x"], y=fl["y"], mode="lines",
170
- line=dict(color=to_hex(fit_color), width=fit_lwd),
170
+ line=dict(color=as_plotly_color(fit_color), width=fit_lwd),
171
171
  hoverinfo="skip", showlegend=False))
172
172
 
173
173
  ax_x = axis_num(x_lab, axT1, axL1)
@@ -178,8 +178,8 @@ def plt_contour(xv, yv, contour_n, contour_nbins, contour_points,
178
178
  xaxis=ax_x, yaxis=ax_y,
179
179
  shapes=plot_border(),
180
180
  template=None,
181
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
182
- paper_bgcolor=to_hex(style_opts["window_fill"]),
181
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
182
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
183
183
  )
184
184
 
185
185
  if main:
@@ -17,7 +17,7 @@ import numpy as np
17
17
  import plotly.graph_objects as go
18
18
 
19
19
  from .plotly_utils import (
20
- axis_format, facet_fig, facet_panels, finish_facet,
20
+ as_plotly_color, axis_format, facet_fig, facet_panels, finish_facet,
21
21
  plotly_style, to_hex)
22
22
  from .plt_contour import _COLORSCALE, _bw_nrd, _kde2d
23
23
  from .plt_smooth import _RAMP as _SMOOTH_RAMP
@@ -159,7 +159,7 @@ def plt_contour_facet(xv, yv, facet_arr, facet_order, facet_name,
159
159
  ex, ey = ellipse_fn(xi, yi, float(lv))
160
160
  fig.add_trace(go.Scatter(
161
161
  x=ex, y=ey, mode="lines",
162
- line=dict(color=to_hex(ellipse_color),
162
+ line=dict(color=as_plotly_color(ellipse_color),
163
163
  width=ellipse_lwd),
164
164
  hoverinfo="skip", showlegend=False,
165
165
  ), row=row, col=col)
@@ -169,7 +169,7 @@ def plt_contour_facet(xv, yv, facet_arr, facet_order, facet_name,
169
169
  if okf.sum() >= 2:
170
170
  fig.add_trace(go.Scatter(
171
171
  x=xs[okf], y=f[okf], mode="lines",
172
- line=dict(color=to_hex(fit_color),
172
+ line=dict(color=as_plotly_color(fit_color),
173
173
  width=fit_lwd),
174
174
  hoverinfo="skip", showlegend=False,
175
175
  ), row=row, col=col)
lessPy/plt_mat_plotly.py CHANGED
@@ -68,12 +68,12 @@ def scatter_matrix(df, fit="lm", digits_d=2, cor_coef=True,
68
68
  bg = window_fill if panel_fill == "transparent" else panel_fill
69
69
  diag_fill = get_option("se_fill", "#1A1A1A19")
70
70
  se_fill = get_option("se_fill", "#1A1A1A19")
71
- border = to_hex(style["panel_border"])
71
+ border = as_plotly_color(style["panel_border"])
72
72
  pt_fill = get_option("pt_fill", get_option("pt_color",
73
73
  "#324E5C"))
74
74
  pt_color = get_option("pt_color", "#324E5C")
75
- lab_color = to_hex(style["lab_color"])
76
- fit_color = to_hex(get_option("fit_color", "#5C4032"))
75
+ lab_color = as_plotly_color(style["lab_color"])
76
+ fit_color = as_plotly_color(get_option("fit_color", "#5C4032"))
77
77
  fit_lwd = get_option("fit_lwd", 2)
78
78
 
79
79
  # sizes shrink with the variable count, as R's cex.adj
@@ -160,8 +160,8 @@ def scatter_matrix(df, fit="lm", digits_d=2, cor_coef=True,
160
160
 
161
161
  fig.update_layout(
162
162
  template=None, showlegend=False,
163
- plot_bgcolor=to_hex(bg),
164
- paper_bgcolor=to_hex(window_fill))
163
+ plot_bgcolor=as_plotly_color(bg),
164
+ paper_bgcolor=as_plotly_color(window_fill))
165
165
  if main:
166
166
  fig.update_layout(title=dict(
167
167
  text=main, x=0.5, xanchor="center",
@@ -207,16 +207,17 @@ def _scatter_cell(fig, r, c, xv, yv, fit, do_fit, px,
207
207
  (f - tq * se_f)[::-1]]))
208
208
  else:
209
209
  poly = _se_band(xs, ys_s, f, 0.95)
210
- fig.add_trace(go.Scatter(
211
- x=poly[0], y=poly[1], mode="none", fill="toself",
212
- fillcolor=as_plotly_color(se_fill),
213
- hoverinfo="skip", showlegend=False), row=r, col=c)
210
+ if poly is not None:
211
+ fig.add_trace(go.Scatter(
212
+ x=poly[0], y=poly[1], mode="none", fill="toself",
213
+ fillcolor=as_plotly_color(se_fill),
214
+ hoverinfo="skip", showlegend=False), row=r, col=c)
214
215
 
215
216
  fig.add_trace(go.Scatter(
216
217
  x=xv, y=yv, mode="markers",
217
218
  marker=dict(symbol="circle", size=px, sizemode="diameter",
218
219
  color=make_trans(pt_fill, 0.9), opacity=1,
219
- line=dict(color=to_hex(pt_color), width=0.5)),
220
+ line=dict(color=as_plotly_color(pt_color), width=0.5)),
220
221
  hoverinfo="x+y", showlegend=False), row=r, col=c)
221
222
 
222
223
  if do_fit and len(xv) >= 2:
lessPy/plt_plotly.py CHANGED
@@ -17,7 +17,7 @@ import plotly.graph_objects as go
17
17
 
18
18
  from .plotly_utils import (
19
19
  as_plotly_color, axis_base, axis_num, get_tick_fmt, make_trans,
20
- plot_border, plotly_style, sym_at, to_hex, x_grid, y_grid,
20
+ plot_border, plotly_style, marks, to_hex, x_grid, y_grid,
21
21
  )
22
22
  from .utils import get_option
23
23
 
@@ -47,6 +47,7 @@ def plt_plotly(groups, by_name=None,
47
47
  se_polys=None, se_fill=None,
48
48
  ellipses=None, ellipse_fill=None,
49
49
  ellipse_color=None, ellipse_lwd=1,
50
+ sizes=None, size_name=None,
50
51
  style_opts=None):
51
52
  """Scatterplot / time-series renderer for XY().
52
53
 
@@ -107,19 +108,34 @@ def plt_plotly(groups, by_name=None,
107
108
  hover = hover.replace(
108
109
  "<extra></extra>", f"<br>{by_name}: {nm}"
109
110
  "<extra></extra>")
111
+ # bubbles: a pixel diameter per point, its value in the hover
112
+ custom = None
113
+ if sizes is not None:
114
+ custom = sizes[i][1]
115
+ hover = hover.replace(
116
+ "<extra></extra>",
117
+ f"<br>{size_name}: %{{customdata:.{digits_d}f}}"
118
+ "<extra></extra>")
110
119
  marker = dict(
111
- symbol=sym_at(shape, i), size=px, sizemode="diameter",
120
+ size=px if sizes is None else sizes[i][0],
121
+ sizemode="diameter",
112
122
  color=make_trans(fills[i], pt_opacity), opacity=1,
113
- line=dict(color=to_hex(borders[i]), width=1),
114
- ) if "markers" in mode_pts else None
123
+ line=dict(color=as_plotly_color(borders[i]), width=1),
124
+ )
125
+ pts_kw = (marks(shape, i, mode_pts, marker)
126
+ if "markers" in mode_pts else dict(mode=mode_pts))
127
+ # a single series' line takes segment_color when it is given;
128
+ # several series keep the color that distinguishes them
129
+ ln_col = (style_opts["segment_color"]
130
+ if not has_groups and get_option("segment_color_set")
131
+ else borders[i])
115
132
  fig.add_trace(go.Scatter(
116
- x=xv, y=yv, mode=mode_pts,
133
+ x=xv, y=yv, **pts_kw,
117
134
  name=None if nm is None else str(nm),
118
135
  legendgroup=None if nm is None else str(nm),
119
- marker=marker,
120
- line=(dict(color=to_hex(borders[i]), width=ln_width)
136
+ line=(dict(color=as_plotly_color(ln_col), width=ln_width)
121
137
  if connect else None),
122
- hovertemplate=hover,
138
+ hovertemplate=hover, customdata=custom,
123
139
  # connected series show a legend-only line sample below
124
140
  showlegend=has_groups and not connect,
125
141
  ))
@@ -127,15 +143,15 @@ def plt_plotly(groups, by_name=None,
127
143
  fig.add_trace(go.Scatter(
128
144
  x=xv[:2], y=yv[:2], mode="lines",
129
145
  name=str(nm), legendgroup=str(nm),
130
- line=dict(color=to_hex(borders[i]), width=ln_width),
146
+ line=dict(color=as_plotly_color(borders[i]), width=ln_width),
131
147
  hoverinfo="skip", showlegend=True,
132
148
  ))
133
149
 
134
150
  # ellipses: border per group when grouped, else ellipse_color
135
151
  if ellipses:
136
152
  for k, (ex, ey) in enumerate(ellipses):
137
- edge = (to_hex(borders[k]) if has_groups
138
- else to_hex(ellipse_color))
153
+ edge = (as_plotly_color(borders[k % n_grp]) if has_groups
154
+ else as_plotly_color(ellipse_color))
139
155
  fig.add_trace(go.Scatter(
140
156
  x=ex, y=ey, mode="lines",
141
157
  line=dict(color=edge, width=ellipse_lwd),
@@ -171,8 +187,8 @@ def plt_plotly(groups, by_name=None,
171
187
  name="Fit" if single else f"Fit: {nm}",
172
188
  legendgroup="fit" if single else str(nm),
173
189
  line=dict(
174
- color=(to_hex(fit_color) if single
175
- else to_hex(fills[i])),
190
+ color=(as_plotly_color(fit_color) if single
191
+ else as_plotly_color(fills[i])),
176
192
  width=fit_lwd),
177
193
  hovertemplate=(
178
194
  f"{x_lab}: {xpart}<br>{lbl}: {ypart}"
@@ -185,7 +201,7 @@ def plt_plotly(groups, by_name=None,
185
201
  ax_x = axis_base()
186
202
  ax_x["title"]["text"] = x_lab
187
203
  ax_x.update(showgrid=True,
188
- gridcolor=to_hex(style_opts["grid_col"]),
204
+ gridcolor=as_plotly_color(style_opts["grid_col"]),
189
205
  gridwidth=1)
190
206
  shapes = y_grid(gridT2) + plot_border()
191
207
  else:
@@ -193,7 +209,7 @@ def plt_plotly(groups, by_name=None,
193
209
  shapes = x_grid(gridT1) + y_grid(gridT2) + plot_border()
194
210
  ax_y = axis_num(y_lab, ax["axT2"], ax["axL2"])
195
211
 
196
- leg_color = to_hex(style_opts["lab_color"])
212
+ leg_color = as_plotly_color(style_opts["lab_color"])
197
213
  fig.update_layout(
198
214
  xaxis=ax_x, yaxis=ax_y,
199
215
  shapes=shapes,
@@ -209,12 +225,12 @@ def plt_plotly(groups, by_name=None,
209
225
  family="Arial", color=leg_color),
210
226
  orientation="v", x=1.05, y=0.5,
211
227
  xanchor="left", yanchor="middle",
212
- bgcolor=to_hex(style_opts["window_fill"]),
228
+ bgcolor=as_plotly_color(style_opts["window_fill"]),
213
229
  bordercolor="#CCCCCC", borderwidth=1,
214
230
  itemsizing="constant",
215
231
  ),
216
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
217
- paper_bgcolor=to_hex(style_opts["window_fill"]),
232
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
233
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
218
234
  )
219
235
 
220
236
  if main:
lessPy/plt_smooth.py CHANGED
@@ -129,7 +129,7 @@ def plt_smooth(xv, yv, smooth_points, smooth_size, smooth_power,
129
129
  for ex, ey in ellipses or []:
130
130
  fig.add_trace(go.Scatter(
131
131
  x=ex, y=ey, mode="lines",
132
- line=dict(color=to_hex(ellipse_color),
132
+ line=dict(color=as_plotly_color(ellipse_color),
133
133
  width=ellipse_lwd),
134
134
  fill="toself",
135
135
  fillcolor=as_plotly_color(ellipse_fill),
@@ -145,7 +145,7 @@ def plt_smooth(xv, yv, smooth_points, smooth_size, smooth_power,
145
145
  fig.add_trace(go.Scatter(
146
146
  x=fl["x"], y=fl["y"], mode="lines", name="Fit",
147
147
  legendgroup="fit",
148
- line=dict(color=to_hex(fit_color), width=fit_lwd),
148
+ line=dict(color=as_plotly_color(fit_color), width=fit_lwd),
149
149
  hoverinfo="skip", showlegend=True))
150
150
 
151
151
  ax_x = axis_num(x_lab, ax["axT1"], ax["axL1"])
@@ -156,8 +156,8 @@ def plt_smooth(xv, yv, smooth_points, smooth_size, smooth_power,
156
156
  xaxis=ax_x, yaxis=ax_y,
157
157
  shapes=x_grid(gridT1) + y_grid(gridT2) + plot_border(),
158
158
  template=None,
159
- plot_bgcolor=to_hex(style_opts["panel_fill"]),
160
- paper_bgcolor=to_hex(style_opts["window_fill"]),
159
+ plot_bgcolor=as_plotly_color(style_opts["panel_fill"]),
160
+ paper_bgcolor=as_plotly_color(style_opts["window_fill"]),
161
161
  )
162
162
 
163
163
  if main:
lessPy/prob_norm.py CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  import numpy as np
10
10
 
11
- from .utils import fmt, show_plots
11
+ from .utils import fmt, file_output, show_plots
12
12
 
13
13
  _FILL_NRM = "#E8E8E8" # grey91
14
14
  _FILL_INT = "#9FB6CD" # slategray3
@@ -109,3 +109,7 @@ def _plot(lo, hi, lo_lbl, hi_lbl, mu, sigma, prob, nrm_color,
109
109
  showarrow=False,
110
110
  font=dict(size=11))
111
111
  return fig
112
+
113
+
114
+ # pdf_file= and png_file= write the figures, as lessR's pdf_file=
115
+ prob_norm = file_output(prob_norm)
lessPy/prob_tcut.py CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  import numpy as np
11
11
 
12
- from .utils import fmt, show_plots
12
+ from .utils import fmt, file_output, show_plots
13
13
 
14
14
  _BG = "#F9F9FC" # rgb(249,249,252)
15
15
  _TAIL = "#8B475D" # palevioletred4
@@ -129,3 +129,7 @@ def _plot(df, alpha, t_lo, t_hi, n_lo, n_hi, digits_d, y_axis,
129
129
  bordercolor="black", borderwidth=0.5,
130
130
  font=dict(size=10)))
131
131
  return fig
132
+
133
+
134
+ # pdf_file= and png_file= write the figures, as lessR's pdf_file=
135
+ prob_tcut = file_output(prob_tcut)
lessPy/prob_znorm.py CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import numpy as np
13
13
 
14
- from .utils import show_plots
14
+ from .utils import file_output, show_plots
15
15
 
16
16
 
17
17
  class ProbZnormResults:
@@ -110,3 +110,7 @@ def _plot(mu, sigma, border, fill, main, y_axis, z):
110
110
  showarrow=False,
111
111
  font=dict(size=11))
112
112
  return fig
113
+
114
+
115
+ # pdf_file= and png_file= write the figures, as lessR's pdf_file=
116
+ prob_znorm = file_output(prob_znorm)