eaglelandsonce commited on
Commit
aa85c6d
·
verified ·
1 Parent(s): 9cc8e2d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -0
app.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # skew_normal_explorer_app.py
2
+ # Run locally:
3
+ # pip install -r requirements.txt
4
+ # python skew_normal_explorer_app.py
5
+
6
+ import numpy as np
7
+ import gradio as gr
8
+ import matplotlib.pyplot as plt
9
+ from math import sqrt, pi
10
+ from scipy.stats import skewnorm
11
+
12
+ def theoretical_stats_skewnorm(alpha, mu, sigma):
13
+ """
14
+ Compute theoretical moments for the Skew-Normal(alpha, loc=mu, scale=sigma).
15
+ Returns mean, median, mode (numeric), variance, std dev, IQR, skewness,
16
+ kurtosis (Pearson) & excess kurtosis.
17
+ """
18
+ m, v, s, exk = skewnorm.stats(alpha, loc=mu, scale=sigma, moments="mvsk")
19
+ m = float(m); v = float(v); s = float(s); exk = float(exk)
20
+ std = float(np.sqrt(v))
21
+
22
+ # Quartiles
23
+ q1 = float(skewnorm.ppf(0.25, alpha, loc=mu, scale=sigma))
24
+ q3 = float(skewnorm.ppf(0.75, alpha, loc=mu, scale=sigma))
25
+ iqr = q3 - q1
26
+
27
+ # Median
28
+ median = float(skewnorm.ppf(0.5, alpha, loc=mu, scale=sigma))
29
+
30
+ # Mode via grid search
31
+ xs = np.linspace(m - 6*std, m + 6*std, 4001)
32
+ pdf_vals = skewnorm.pdf(xs, alpha, loc=mu, scale=sigma)
33
+ mode = float(xs[int(np.argmax(pdf_vals))])
34
+
35
+ return {
36
+ "mean": m,
37
+ "median": median,
38
+ "mode": mode,
39
+ "variance": v,
40
+ "std_dev": std,
41
+ "IQR": iqr,
42
+ "range": float("inf"),
43
+ "skewness": s,
44
+ "kurtosis": exk + 3.0,
45
+ "excess_kurtosis": exk
46
+ }
47
+
48
+ def sample_stats(sample):
49
+ n = len(sample)
50
+ if n < 2:
51
+ s_mean = float(sample[0]) if n == 1 else float("nan")
52
+ return {
53
+ "mean": s_mean, "median": s_mean, "mode": s_mean,
54
+ "variance": 0.0, "std_dev": 0.0, "IQR": 0.0, "range": 0.0,
55
+ "skewness": 0.0, "kurtosis": 3.0, "excess_kurtosis": 0.0
56
+ }
57
+
58
+ s = np.asarray(sample, dtype=float)
59
+ s_mean = float(np.mean(s))
60
+ s_median = float(np.median(s))
61
+
62
+ # Mode estimate via histogram
63
+ counts, bin_edges = np.histogram(s, bins=min(50, max(5, int(np.sqrt(n)))))
64
+ max_bin_idx = int(np.argmax(counts))
65
+ mode_est = float((bin_edges[max_bin_idx] + bin_edges[max_bin_idx+1]) / 2.0)
66
+
67
+ s_var = float(np.var(s, ddof=1))
68
+ s_std = float(np.sqrt(s_var))
69
+
70
+ q1, q3 = np.percentile(s, [25, 75])
71
+ iqr = q3 - q1
72
+ s_range = float(np.max(s) - np.min(s))
73
+
74
+ m2 = np.mean((s - s_mean)**2)
75
+ m3 = np.mean((s - s_mean)**3)
76
+ m4 = np.mean((s - s_mean)**4)
77
+ if m2 <= 0:
78
+ skew, kurt = 0.0, 3.0
79
+ else:
80
+ skew = m3 / (m2 ** 1.5)
81
+ kurt = m4 / (m2 ** 2)
82
+ ex_kurt = kurt - 3.0
83
+
84
+ return {
85
+ "mean": s_mean,
86
+ "median": s_median,
87
+ "mode": mode_est,
88
+ "variance": s_var,
89
+ "std_dev": s_std,
90
+ "IQR": iqr,
91
+ "range": s_range,
92
+ "skewness": skew,
93
+ "kurtosis": kurt,
94
+ "excess_kurtosis": ex_kurt
95
+ }
96
+
97
+ def format_stats_block(title, d):
98
+ range_str = "∞" if d["range"] == float("inf") else f"{d['range']:.6g}"
99
+ lines = [
100
+ f"**{title}**",
101
+ f"- Mean: {d['mean']:.6g}",
102
+ f"- Median: {d['median']:.6g}",
103
+ f"- Mode: {d['mode']:.6g}",
104
+ f"- Variance: {d['variance']:.6g}",
105
+ f"- Std Dev: {d['std_dev']:.6g}",
106
+ f"- IQR: {d['IQR']:.6g}",
107
+ f"- Range: {range_str}",
108
+ f"- Skewness: {d['skewness']:.6g}",
109
+ f"- Kurtosis: {d['kurtosis']:.6g}",
110
+ f"- Excess Kurtosis: {d['excess_kurtosis']:.6g}",
111
+ ]
112
+ return "\n".join(lines)
113
+
114
+ def render(alpha, mu, sigma, n, seed, x_min, x_max, bins, show_hist, overlay_empirical_pdf):
115
+ sigma = max(1e-6, sigma)
116
+
117
+ if x_min >= x_max:
118
+ theo_tmp = theoretical_stats_skewnorm(alpha, mu, sigma)
119
+ m, std = theo_tmp["mean"], max(1e-9, theo_tmp["std_dev"])
120
+ x_min, x_max = m - 4*std, m + 4*std
121
+
122
+ x = np.linspace(x_min, x_max, 800)
123
+ y = skewnorm.pdf(x, alpha, loc=mu, scale=sigma)
124
+
125
+ rng = np.random.default_rng(int(seed))
126
+ sample = skewnorm.rvs(alpha, loc=mu, scale=sigma, size=int(n), random_state=rng)
127
+
128
+ theo = theoretical_stats_skewnorm(alpha, mu, sigma)
129
+ samp = sample_stats(sample)
130
+
131
+ fig, ax = plt.subplots(figsize=(8, 4.5), dpi=120)
132
+ ax.plot(x, y, label="Theoretical PDF (Skew-Normal)")
133
+ if show_hist:
134
+ ax.hist(sample, bins=int(bins), density=True, alpha=0.5, label="Sample histogram")
135
+
136
+ if overlay_empirical_pdf:
137
+ bw = 1.06 * max(1e-8, samp["std_dev"]) * (len(sample) ** (-1/5))
138
+ bw = max(bw, 1e-6)
139
+ diffs = (x.reshape(-1, 1) - sample.reshape(1, -1)) / bw
140
+ kernel_vals = np.exp(-0.5 * diffs**2) / (sqrt(2*pi) * bw)
141
+ kde = np.mean(kernel_vals, axis=1)
142
+ ax.plot(x, kde, linestyle="--", label="Empirical density (KDE-like)")
143
+
144
+ ax.set_title("Skew-Normal & Normal Explorer (α=0 gives Normal)")
145
+ ax.set_xlabel("x")
146
+ ax.set_ylabel("density")
147
+ ax.legend(loc="best")
148
+ ax.grid(True, linestyle="--")
149
+
150
+ left = format_stats_block("Theoretical (Skew-Normal)", theo)
151
+ right = format_stats_block("Sample (from sliders)", samp)
152
+ stats_md = left + "\n\n" + right
153
+
154
+ return fig, stats_md
155
+
156
+ with gr.Blocks(title="Skew-Normal & Normal Explorer") as demo:
157
+ gr.Markdown("# Skew-Normal & Normal Explorer")
158
+ gr.Markdown(
159
+ "Slide **α (skewness)** to skew left/right. **α=0 → Normal(μ, σ²)**. "
160
+ "Adjust μ, σ, n, and window. Compare theoretical vs sample stats."
161
+ )
162
+
163
+ with gr.Row():
164
+ with gr.Column(scale=1):
165
+ alpha = gr.Slider(-15.0, 15.0, value=0.0, step=0.1, label="Skewness (α)")
166
+ mu = gr.Slider(-10.0, 10.0, value=0.0, step=0.1, label="Location (μ)")
167
+ sigma = gr.Slider(0.1, 10.0, value=1.0, step=0.1, label="Scale (σ)")
168
+ n = gr.Slider(10, 200000, value=2000, step=10, label="Sample size (n)")
169
+ seed = gr.Slider(0, 99999, value=42, step=1, label="Random seed")
170
+
171
+ with gr.Accordion("Plot window & layers", open=False):
172
+ x_min = gr.Number(value=-5.0, label="x min")
173
+ x_max = gr.Number(value=5.0, label="x max")
174
+ bins = gr.Slider(5, 200, value=40, step=1, label="Histogram bins")
175
+ show_hist = gr.Checkbox(value=True, label="Show sample histogram")
176
+ overlay_empirical_pdf = gr.Checkbox(value=False, label="Overlay empirical density (KDE-like)")
177
+
178
+ with gr.Column(scale=2):
179
+ plot = gr.Plot(label="Curve / Histogram")
180
+ stats = gr.Markdown(label="Descriptive Statistics")
181
+
182
+ inputs = [alpha, mu, sigma, n, seed, x_min, x_max, bins, show_hist, overlay_empirical_pdf]
183
+ demo.load(render, inputs=inputs, outputs=[plot, stats])
184
+ for w in inputs:
185
+ w.change(render, inputs=inputs, outputs=[plot, stats])
186
+
187
+ if __name__ == "__main__":
188
+ demo.launch()
189
+