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/rename.py ADDED
@@ -0,0 +1,33 @@
1
+ # rename.py — analog of rename.R.
2
+ #
3
+ # rename(): rename one or more columns of a data frame, reporting
4
+ # each old --> new change, and return the renamed data frame.
5
+ # R's from/to are `old` and `new` here (`from` is a Python
6
+ # keyword); each is a column name or a list of names.
7
+
8
+ import pandas as pd
9
+
10
+
11
+ def rename(data, old, new):
12
+ """Rename columns of data: old (a name or list of names) to
13
+ new (the same length). Prints each change and returns the
14
+ renamed DataFrame (a copy). R analog: rename() (from/to)."""
15
+ if not isinstance(data, pd.DataFrame):
16
+ raise TypeError("data must be a pandas DataFrame")
17
+ old = [old] if isinstance(old, str) else list(old)
18
+ new = [new] if isinstance(new, str) else list(new)
19
+ if len(old) != len(new):
20
+ raise ValueError(
21
+ f"old has {len(old)} name(s) but new has {len(new)}; "
22
+ "they must match")
23
+ missing = [c for c in old if c not in data.columns]
24
+ if missing:
25
+ raise ValueError(
26
+ f"not columns of data: {', '.join(map(str, missing))}")
27
+
28
+ out = data.rename(columns=dict(zip(old, new)))
29
+ print("Change the following variable names:\n")
30
+ for o, n in zip(old, new):
31
+ print(f"{o} --> {n}")
32
+ print()
33
+ return out
lessPy/reshape.py ADDED
@@ -0,0 +1,95 @@
1
+ # reshape.py — analog of reshape_long.R and reshape_wide.R.
2
+ #
3
+ # reshape_long(): wide -> long, either the "rect" melt of a set of
4
+ # columns into a group/response pair, or the "square" conversion
5
+ # of a matrix (rows x cols) into a Row/Col/value long form for a
6
+ # heat map. reshape_wide(): the inverse long -> wide pivot.
7
+ # Thin wrappers over pandas melt / pivot that reproduce lessR's
8
+ # column naming, ID handling, and (square) factor-level order.
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+ _DEFAULT = object() # prefix sentinel: default to the ID
14
+
15
+
16
+ def reshape_long(data, transform=None, group=None,
17
+ response="Response", ID="ID", prefix=_DEFAULT,
18
+ sep="", shape="rect", reverse_y=None):
19
+ """Reshape wide data to long. shape="rect" stacks the
20
+ transform columns into a group column (their names) and a
21
+ response column (their values), adding an ID for the original
22
+ row. shape="square" turns a matrix (a correlation or
23
+ frequency table) into Row/Col/value long form for a heat map.
24
+ R analog: reshape_long()"""
25
+ if shape not in ("rect", "square"):
26
+ raise ValueError('shape: "rect" or "square"')
27
+ if shape == "square":
28
+ return _reshape_square(data, group, response, reverse_y)
29
+
30
+ if transform is None:
31
+ raise ValueError("reshape_long needs transform=, the "
32
+ "columns to stack into long form")
33
+ transform = [transform] if isinstance(transform, str) \
34
+ else list(transform)
35
+ grp = "Group" if group is None else group
36
+ id_vars = [c for c in data.columns if c not in transform]
37
+ long = data.melt(id_vars=id_vars, value_vars=transform,
38
+ var_name=grp, value_name=response)
39
+
40
+ if ID is not None:
41
+ if prefix is _DEFAULT:
42
+ prefix = ID
43
+ if ID not in data.columns: # synthetic row id 1..n
44
+ long.insert(0, ID, np.tile(
45
+ np.arange(1, len(data) + 1), len(transform)))
46
+ if prefix is not None:
47
+ long[ID] = [f"{prefix}{sep}{v}" for v in long[ID]]
48
+ long = long[[ID] + [c for c in long.columns
49
+ if c != ID]]
50
+ return long.reset_index(drop=True)
51
+
52
+
53
+ def _reshape_square(data, group, response, reverse_y):
54
+ """Matrix -> long, column-major (Row varies fastest within
55
+ Col), as R's as.data.frame(as.table()). ~ reshape_long square"""
56
+ var1, var2 = (("Row", "Col") if group is None
57
+ else (f"{group}1", f"{group}2"))
58
+ df = pd.DataFrame(data)
59
+ rows = [str(r) for r in df.index]
60
+ cols = [str(c) for c in df.columns]
61
+ tmp = df.copy()
62
+ tmp.index, tmp.columns = rows, cols
63
+ tmp.index.name = var1
64
+ long = tmp.reset_index().melt(id_vars=var1, var_name=var2,
65
+ value_name=response)
66
+ if reverse_y is None:
67
+ reverse_y = True
68
+ print('reshape_long: for shape="square", row levels are '
69
+ "reversed by default so the first row appears at "
70
+ 'the top of a heat map y-axis.\n Set '
71
+ "reverse_y=False to keep the original row order.")
72
+ long[var1] = pd.Categorical(
73
+ long[var1], categories=rows[::-1] if reverse_y else rows)
74
+ long[var2] = pd.Categorical(long[var2], categories=cols)
75
+ return long
76
+
77
+
78
+ def reshape_wide(data, widen, response, ID, prefix=None,
79
+ sep="_"):
80
+ """Reshape long data to wide: each value of widen becomes a
81
+ column, filled from response, one row per ID. Extra columns
82
+ are dropped. By default the response prefix on the new column
83
+ names is kept only when widen is numeric. R analog:
84
+ reshape_wide()"""
85
+ wide = data[[ID, widen, response]].pivot(
86
+ index=ID, columns=widen, values=response)
87
+ if prefix is None:
88
+ prefix = pd.api.types.is_numeric_dtype(data[widen])
89
+ if prefix:
90
+ wide.columns = [f"{response}{sep}{c}" for c in wide.columns]
91
+ else:
92
+ wide.columns = [str(c) for c in wide.columns]
93
+ wide = wide.reset_index()
94
+ wide.columns.name = None
95
+ return wide
lessPy/showColors.py ADDED
@@ -0,0 +1,130 @@
1
+ # showColors.py — analog of showColors.R.
2
+ #
3
+ # showColors(): display the named colors as a labeled grid of
4
+ # swatches, each with its name and RGB, optionally filtered to
5
+ # names containing a substring. R shows its colors() database and
6
+ # writes a PDF; here the standard CSS named colors (which plotly
7
+ # accepts) are shown, returning a plotly figure.
8
+
9
+ from .plotly_utils import hex_to_rgb3
10
+
11
+ # the 148 standard CSS (Color Module Level 4) named colors
12
+ _NAMED = {
13
+ "aliceblue": "#F0F8FF", "antiquewhite": "#FAEBD7",
14
+ "aqua": "#00FFFF", "aquamarine": "#7FFFD4",
15
+ "azure": "#F0FFFF", "beige": "#F5F5DC",
16
+ "bisque": "#FFE4C4", "black": "#000000",
17
+ "blanchedalmond": "#FFEBCD", "blue": "#0000FF",
18
+ "blueviolet": "#8A2BE2", "brown": "#A52A2A",
19
+ "burlywood": "#DEB887", "cadetblue": "#5F9EA0",
20
+ "chartreuse": "#7FFF00", "chocolate": "#D2691E",
21
+ "coral": "#FF7F50", "cornflowerblue": "#6495ED",
22
+ "cornsilk": "#FFF8DC", "crimson": "#DC143C",
23
+ "cyan": "#00FFFF", "darkblue": "#00008B",
24
+ "darkcyan": "#008B8B", "darkgoldenrod": "#B8860B",
25
+ "darkgray": "#A9A9A9", "darkgreen": "#006400",
26
+ "darkgrey": "#A9A9A9", "darkkhaki": "#BDB76B",
27
+ "darkmagenta": "#8B008B", "darkolivegreen": "#556B2F",
28
+ "darkorange": "#FF8C00", "darkorchid": "#9932CC",
29
+ "darkred": "#8B0000", "darksalmon": "#E9967A",
30
+ "darkseagreen": "#8FBC8F", "darkslateblue": "#483D8B",
31
+ "darkslategray": "#2F4F4F", "darkslategrey": "#2F4F4F",
32
+ "darkturquoise": "#00CED1", "darkviolet": "#9400D3",
33
+ "deeppink": "#FF1493", "deepskyblue": "#00BFFF",
34
+ "dimgray": "#696969", "dimgrey": "#696969",
35
+ "dodgerblue": "#1E90FF", "firebrick": "#B22222",
36
+ "floralwhite": "#FFFAF0", "forestgreen": "#228B22",
37
+ "fuchsia": "#FF00FF", "gainsboro": "#DCDCDC",
38
+ "ghostwhite": "#F8F8FF", "gold": "#FFD700",
39
+ "goldenrod": "#DAA520", "gray": "#808080",
40
+ "green": "#008000", "greenyellow": "#ADFF2F",
41
+ "grey": "#808080", "honeydew": "#F0FFF0",
42
+ "hotpink": "#FF69B4", "indianred": "#CD5C5C",
43
+ "indigo": "#4B0082", "ivory": "#FFFFF0",
44
+ "khaki": "#F0E68C", "lavender": "#E6E6FA",
45
+ "lavenderblush": "#FFF0F5", "lawngreen": "#7CFC00",
46
+ "lemonchiffon": "#FFFACD", "lightblue": "#ADD8E6",
47
+ "lightcoral": "#F08080", "lightcyan": "#E0FFFF",
48
+ "lightgoldenrodyellow": "#FAFAD2", "lightgray": "#D3D3D3",
49
+ "lightgreen": "#90EE90", "lightgrey": "#D3D3D3",
50
+ "lightpink": "#FFB6C1", "lightsalmon": "#FFA07A",
51
+ "lightseagreen": "#20B2AA", "lightskyblue": "#87CEFA",
52
+ "lightslategray": "#778899", "lightslategrey": "#778899",
53
+ "lightsteelblue": "#B0C4DE", "lightyellow": "#FFFFE0",
54
+ "lime": "#00FF00", "limegreen": "#32CD32",
55
+ "linen": "#FAF0E6", "magenta": "#FF00FF",
56
+ "maroon": "#800000", "mediumaquamarine": "#66CDAA",
57
+ "mediumblue": "#0000CD", "mediumorchid": "#BA55D3",
58
+ "mediumpurple": "#9370DB", "mediumseagreen": "#3CB371",
59
+ "mediumslateblue": "#7B68EE", "mediumspringgreen": "#00FA9A",
60
+ "mediumturquoise": "#48D1CC", "mediumvioletred": "#C71585",
61
+ "midnightblue": "#191970", "mintcream": "#F5FFFA",
62
+ "mistyrose": "#FFE4E1", "moccasin": "#FFE4B5",
63
+ "navajowhite": "#FFDEAD", "navy": "#000080",
64
+ "oldlace": "#FDF5E6", "olive": "#808000",
65
+ "olivedrab": "#6B8E23", "orange": "#FFA500",
66
+ "orangered": "#FF4500", "orchid": "#DA70D6",
67
+ "palegoldenrod": "#EEE8AA", "palegreen": "#98FB98",
68
+ "paleturquoise": "#AFEEEE", "palevioletred": "#DB7093",
69
+ "papayawhip": "#FFEFD5", "peachpuff": "#FFDAB9",
70
+ "peru": "#CD853F", "pink": "#FFC0CB",
71
+ "plum": "#DDA0DD", "powderblue": "#B0E0E6",
72
+ "purple": "#800080", "rebeccapurple": "#663399",
73
+ "red": "#FF0000", "rosybrown": "#BC8F8F",
74
+ "royalblue": "#4169E1", "saddlebrown": "#8B4513",
75
+ "salmon": "#FA8072", "sandybrown": "#F4A460",
76
+ "seagreen": "#2E8B57", "seashell": "#FFF5EE",
77
+ "sienna": "#A0522D", "silver": "#C0C0C0",
78
+ "skyblue": "#87CEEB", "slateblue": "#6A5ACD",
79
+ "slategray": "#708090", "slategrey": "#708090",
80
+ "snow": "#FFFAFA", "springgreen": "#00FF7F",
81
+ "steelblue": "#4682B4", "tan": "#D2B48C",
82
+ "teal": "#008080", "thistle": "#D8BFD8",
83
+ "tomato": "#FF6347", "turquoise": "#40E0D0",
84
+ "violet": "#EE82EE", "wheat": "#F5DEB3",
85
+ "white": "#FFFFFF", "whitesmoke": "#F5F5F5",
86
+ "yellow": "#FFFF00", "yellowgreen": "#9ACD32",
87
+ }
88
+
89
+
90
+ def showColors(color=None, n_col=6):
91
+ """Show the named colors as a labeled grid of swatches, one
92
+ per color with its name and RGB. color= keeps only the names
93
+ that contain that substring. Returns a plotly figure.
94
+ R analog: showColors()"""
95
+ names = sorted(_NAMED)
96
+ if color is not None:
97
+ names = [n for n in names if color.lower() in n.lower()]
98
+ if not names:
99
+ raise ValueError(f"no named color contains '{color}'")
100
+
101
+ import plotly.graph_objects as go
102
+ n = len(names)
103
+ n_row = (n + n_col - 1) // n_col
104
+ xs, ys, cols, texts, hovers = [], [], [], [], []
105
+ for i, nm in enumerate(names):
106
+ r, c = i // n_col, i % n_col
107
+ xs.append(c)
108
+ ys.append(n_row - 1 - r) # first color at top
109
+ hx = _NAMED[nm]
110
+ cols.append(hx)
111
+ texts.append(nm)
112
+ rr, gg, bb = hex_to_rgb3(hx)
113
+ hovers.append(f"{nm}<br>{hx}<br>rgb({rr},{gg},{bb})")
114
+
115
+ fig = go.Figure(go.Scatter(
116
+ x=xs, y=ys, mode="markers+text",
117
+ marker=dict(symbol="square", size=34, color=cols,
118
+ line=dict(color="#999999", width=1)),
119
+ text=texts, textposition="bottom center",
120
+ textfont=dict(size=9),
121
+ hovertext=hovers, hoverinfo="text"))
122
+ fig.update_xaxes(visible=False, range=[-0.5, n_col - 0.5])
123
+ fig.update_yaxes(visible=False, range=[-0.7, n_row - 0.3])
124
+ fig.update_layout(
125
+ template=None, plot_bgcolor="white", paper_bgcolor="white",
126
+ margin=dict(t=30, r=20, b=20, l=20),
127
+ height=max(200, 60 * n_row),
128
+ title=dict(text="Named Colors", x=0.5,
129
+ xanchor="center"))
130
+ return fig
lessPy/simCImean.py ADDED
@@ -0,0 +1,165 @@
1
+ # simCImean.py — analog of simCImean.R.
2
+ #
3
+ # simCImean(): draw ns samples of size n from a normal population
4
+ # and form a confidence interval for the mean from each. Plot the
5
+ # intervals as vertical bars against a centerline at the true mean
6
+ # mu, colored by whether they capture mu, and report the miss
7
+ # rate — a demonstration that a cl-level interval misses mu about
8
+ # (1 - cl) of the time. Prints the per-sample table and the
9
+ # structure/performance summaries, and returns a results object
10
+ # with the plotly figure in .plots.
11
+ #
12
+ # The draws use numpy's RNG (seed=), so they do not reproduce R's
13
+ # stream value-for-value (a simulation, like the other sim*).
14
+ # R's interactive "pause" mode is not ported.
15
+
16
+ import numpy as np
17
+
18
+ from .utils import fmt
19
+
20
+
21
+ class SimCImeanResults:
22
+ """Numeric results of simCImean(): the per-sample means, SDs,
23
+ interval bounds (lb/ub) and hit/miss flags, the miss count and
24
+ rate, the t cutoff, and the plotly figure in .plots ("ci")."""
25
+
26
+ def __init__(self, **kw):
27
+ self.__dict__.update(kw)
28
+
29
+ def __repr__(self):
30
+ return (f"<lessPy simCImean: ns={self.n_samples}, "
31
+ f"n={self.n}, misses={self.n_miss}>")
32
+
33
+
34
+ def simCImean(ns=None, n=None, mu=0, sigma=1, cl=0.95, seed=None,
35
+ show_data=False, show_title=True, miss_only=False,
36
+ color_hit="gray", color_miss="red", grid="#E5E5E5",
37
+ ylim_bound=None, digits_d=3):
38
+ """Simulate ns confidence intervals for a mean: ns samples of
39
+ size n from a normal population (mean mu, sd sigma), each
40
+ giving a cl-level t interval. Plots the intervals about a
41
+ centerline at mu, colored by hit/miss, and reports the miss
42
+ rate. miss_only lists only the intervals that miss mu;
43
+ show_data overlays the raw values. Prints the tables and
44
+ returns a SimCImeanResults with .plots. R analog: simCImean()"""
45
+ from scipy.stats import t as t_dist
46
+ if ns is None:
47
+ raise ValueError("specify the number of samples: ns")
48
+ if n is None:
49
+ raise ValueError("specify the size of each sample: n")
50
+ if sigma < 0:
51
+ raise ValueError("sigma cannot be negative")
52
+ if not 0 < cl < 1:
53
+ raise ValueError("cl must be between 0 and 1")
54
+
55
+ alpha = 1 - cl
56
+ tcut = t_dist.ppf(1 - alpha / 2, df=n - 1)
57
+
58
+ rng = np.random.default_rng(seed)
59
+ data = rng.normal(mu, sigma, ns * n).reshape(ns, n)
60
+ ymean = data.mean(axis=1)
61
+ ysd = data.std(axis=1, ddof=1)
62
+
63
+ se = ysd / np.sqrt(n)
64
+ E = tcut * se
65
+ lb, ub = ymean - E, ymean + E
66
+ hit = (lb < mu) & (ub > mu)
67
+ n_miss = int((~hit).sum())
68
+ miss_rate = round(n_miss / ns * 100, 2)
69
+
70
+ if ylim_bound is None:
71
+ if show_data:
72
+ lo0, hi0 = data.min(), data.max()
73
+ else:
74
+ lo0, hi0 = lb.min(), ub.max()
75
+ dev = max(abs(mu - lo0), abs(hi0 - mu))
76
+ lo, hi = mu - dev, mu + dev
77
+ else:
78
+ lo, hi = mu - ylim_bound, mu + ylim_bound
79
+
80
+ fig = _plot(ns, n, ymean, E, lb, ub, hit, mu, sigma, cl, data,
81
+ lo, hi, grid, color_hit, color_miss, show_data,
82
+ show_title)
83
+
84
+ _report(ns, n, ymean, ysd, se, E, lb, ub, hit, mu, sigma, cl,
85
+ n_miss, miss_rate, miss_only, digits_d)
86
+
87
+ return SimCImeanResults(
88
+ mu=mu, sigma=sigma, cl=cl, n_samples=ns, n=n, tcut=tcut,
89
+ ymean=ymean, ysd=ysd, lb=lb, ub=ub, hit=hit,
90
+ n_miss=n_miss, miss_rate=miss_rate,
91
+ mean_of_means=ymean.mean(), sd_of_means=ymean.std(ddof=1),
92
+ plots={"ci": fig})
93
+
94
+
95
+ def _plot(ns, n, ymean, E, lb, ub, hit, mu, sigma, cl, data, lo,
96
+ hi, grid, color_hit, color_miss, show_data, show_title):
97
+ import plotly.graph_objects as go
98
+ x = np.arange(1, ns + 1)
99
+ fig = go.Figure()
100
+ if show_data:
101
+ fig.add_trace(go.Scatter(
102
+ x=np.repeat(x, n), y=data.ravel(), mode="markers",
103
+ marker=dict(size=2.5, color="gray"), opacity=0.5,
104
+ showlegend=False, hoverinfo="skip"))
105
+ fig.add_hline(y=mu, line=dict(color="darkslateblue",
106
+ width=1.5))
107
+ for mask, col, nm in ((hit, color_hit, "capture μ"),
108
+ (~hit, color_miss, "miss μ")):
109
+ if not mask.any():
110
+ continue
111
+ fig.add_trace(go.Scatter(
112
+ x=x[mask], y=ymean[mask], mode="markers",
113
+ marker=dict(size=3, color=col),
114
+ error_y=dict(type="data", array=E[mask], color=col,
115
+ thickness=1.5, width=3),
116
+ name=nm, showlegend=False,
117
+ hovertemplate="sample %{x}<br>mean %{y:.3f}"
118
+ "<extra></extra>"))
119
+ title = (f"μ={mu} σ={sigma} cl={round(cl * 100, 2)}% "
120
+ f"n={n}") if show_title else ""
121
+ fig.update_layout(
122
+ plot_bgcolor="#FAFAFA", paper_bgcolor="white",
123
+ margin=dict(t=36, r=30, b=30, l=50),
124
+ xaxis=dict(title="", range=[0, ns + 1], showgrid=False,
125
+ linecolor="black", mirror=True),
126
+ yaxis=dict(title="", range=[lo, hi], showgrid=True,
127
+ gridcolor=grid, gridwidth=0.5,
128
+ linecolor="black", mirror=True),
129
+ title=dict(text=title, x=0.5, xanchor="center",
130
+ font=dict(size=12)))
131
+ return fig
132
+
133
+
134
+ def _report(ns, n, ymean, ysd, se, E, lb, ub, hit, mu, sigma, cl,
135
+ n_miss, miss_rate, miss_only, digits_d):
136
+ w = 8
137
+ def c(v):
138
+ return f"{fmt(v, digits_d):>{w}}"
139
+ print(f"\n{'Sample':>6}{'Mean':>{w}}{'StdDev':>{w}}"
140
+ f"{'StdErr':>{w}}{'Error':>{w}}{'LB':>{w}}{'UB':>{w}}")
141
+ for i in range(ns):
142
+ if miss_only and hit[i]:
143
+ continue
144
+ row = (f"{i + 1:>6}{c(ymean[i])}{c(ysd[i])}{c(se[i])}"
145
+ f"{c(E[i])}{c(lb[i])}{c(ub[i])}")
146
+ if not hit[i]:
147
+ row += " *** MISS ***"
148
+ print(row)
149
+
150
+ print("\nStructure")
151
+ print(f"Population mean, mu : {mu}")
152
+ print(f"Pop std dev, sigma : {sigma}")
153
+ print(f"Number of samples : {ns}")
154
+ print(f"Size of each sample : {n}")
155
+ print(f"Confidence level : {cl}")
156
+
157
+ print("\nPerformance")
158
+ print(f"Number of Misses: {n_miss}")
159
+ print(f"Percent Misses : {miss_rate:.2f}")
160
+
161
+ d2 = digits_d + 1
162
+ print("\nAnalysis of Sample Means")
163
+ print(f" Mean: {fmt(ymean.mean(), d2):>{w}}")
164
+ print(f"Std Dev: {fmt(ymean.std(ddof=1), d2):>{w}}")
165
+ print()