Py.Cafe

nespinoza/

gaussian-data-simulation-analysis

Interactive Gaussian Data Simulation and Analysis

DocsPricing
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import numpy as np
import panel as pn
import holoviews as hv
from scipy.stats import chisquare

pn.extension()
hv.extension('bokeh')

# Widgets
amplitude = pn.widgets.FloatSlider(name='Amplitude', start=0, end=10, step=0.1, value=1)
center = pn.widgets.FloatSlider(name='Center', start=-5, end=5, step=0.1, value=0)
width = pn.widgets.FloatSlider(name='Width', start=0.1, end=5, step=0.1, value=1)
noise_std = pn.widgets.FloatSlider(name='Noise Std Dev', start=0.01, end=2, step=0.01, value=0.2)
binning_factor = pn.widgets.IntSlider(name='Binning Factor', start=1, end=100, step=1, value=1)
n_points = pn.widgets.IntSlider(name='Number of Points', start=200, end=20000, step=100, value=200)
generate_button = pn.widgets.Button(name='Generate New Dataset', button_type='primary')

# NEW: toggle to show/hide the model curve
show_model = pn.widgets.Toggle(name='Show Model', value=True, button_type='success')

# State
x_vals = None
data_y = None
binned_x = None
binned_y = None
binned_err = None
original_noise_std = None

# Functions
def gaussian(x, amp, cen, wid):
    return amp * np.exp(-0.5 * ((x - cen) / wid)**2)

def simulate(event=None):
    global data_y, original_noise_std, x_vals
    x_vals = np.linspace(-5, 5, n_points.value)
    y_model = gaussian(x_vals, amplitude.value, center.value, width.value)
    noise = np.random.normal(0, noise_std.value, size=len(x_vals))
    data_y = y_model + noise
    original_noise_std = noise_std.value
    update_plot()

def compute_binned_data():
    global binned_x, binned_y, binned_err
    factor = binning_factor.value
    if factor == 1 or data_y is None:
        binned_x, binned_y, binned_err = x_vals, data_y, np.full_like(data_y, original_noise_std)
        return

    n_bins = len(x_vals) // factor
    x_reshape = x_vals[:n_bins * factor].reshape(n_bins, factor)
    y_reshape = data_y[:n_bins * factor].reshape(n_bins, factor)
    binned_x = x_reshape.mean(axis=1)
    binned_y = y_reshape.mean(axis=1)
    binned_err = y_reshape.std(axis=1) / np.sqrt(factor)

def calculate_pvalue():
    mean = np.mean(binned_y)
    residuals = binned_y - mean
    chi2_stat = np.sum((residuals / binned_err)**2)
    dof = len(binned_y) - 1
    pval = 1 - chisquare(f_obs=binned_y, f_exp=np.full_like(binned_y, mean))[1]
    return pval

def update_plot(event=None):
    compute_binned_data()
    pval = calculate_pvalue()
    pval_text = f"P-value (null = noise around mean): {pval:.3g}"

    elements = []

    # Optional: original unbinned data if binning > 1
    if binning_factor.value > 1 and x_vals is not None and data_y is not None:
        orig = hv.Scatter((x_vals, data_y)).opts(alpha=0.1, size=4, color='gray')
        elements.append(orig)

    # Binned data + error bars
    scatter = hv.Scatter((binned_x, binned_y), label='Binned Data').opts(size=6)
    errorbars = hv.ErrorBars((binned_x, binned_y, binned_err)).opts(line_width=1.5)
    elements.extend([scatter, errorbars])

    # NEW: only add model curve if toggle is on
    if show_model.value:
        curve = hv.Curve(
            (binned_x, gaussian(binned_x, amplitude.value, center.value, width.value)),
            label='Model'
        )
        elements.append(curve)

    plot = hv.Overlay(elements).opts(
        width=700,
        height=400,
        show_legend=True,
        xlabel='x',
        ylabel='y'
    )

    plot_pane.object = plot
    pval_pane.object = f"**{pval_text}**"

# UI
plot_pane = pn.pane.HoloViews()
pval_pane = pn.pane.Markdown("")

generate_button.on_click(simulate)
binning_factor.param.watch(update_plot, 'value')
show_model.param.watch(update_plot, 'value')  # NEW: re-render when toggled

controls = pn.Column(
    amplitude, center, width, noise_std,
    binning_factor, n_points,
    generate_button,
    show_model,          # NEW: add toggle to the controls
    pval_pane
)

layout = pn.Row(controls, plot_pane)
simulate()  # Initialize with one dataset
layout.servable()