statistics_playground / visualizing_gaussian.py
chrisblodgett's picture
made delay longer
ca42357
from matplotlib import pyplot as plt
import numpy as np
from collections import Counter,deque
import time
import panel as pn
import holoviews as hv
import random
class CustomDeque(deque):
def __init__(self, maxlen=None):
super().__init__(maxlen=maxlen)
def appendleft(self, x):
# Close the oldest figure before removing it
if len(self) == self.maxlen:
plt.close(self[-1])
super().appendleft(x)
def append(self, x):
# Close the oldest figure before removing it
if len(self) == self.maxlen:
plt.close(self[0])
super().append(x)
global_prob = []
chart_history = CustomDeque(maxlen=4)
def get_dice_prob_bargraph(events_num,equal,color='#7a5af5'):
global global_prob
probabilities = create_distribution(events_num = events_num, equal=equal)
global_prob = probabilities
if not np.isclose(sum(probabilities), 1):
raise ValueError("Probabilities must sum to 1.")
outcomes = np.arange(1,len(probabilities)+1,dtype=int)# the number of probabilities will tell us how many outcomes or sides of dice we have
np.arange(1,len(probabilities)+1,dtype=int)
fig, ax = setup_darkmode_axes(1.20,1.2)
ax.bar(outcomes, probabilities, tick_label=outcomes, color=color, alpha=0.7)
ax.set_xticks(np.linspace(0, len(probabilities), len(probabilities)+1))
ax.set_yticks(np.linspace(0, 1, 11))
ax.tick_params(axis='x', colors='white',labelsize=4,pad=1)
ax.tick_params(axis='y', colors='white',labelsize=4,pad=1)
ax.set_title("Distribution of Sides",color="w",fontsize=5)
ax.set_xlabel("Number of Sides",color="w",fontsize=6,labelpad=2)
ax.set_ylabel("Probability",color="w",fontsize=6,labelpad=2)
return fig
def roll_weighted_die(probabilities):
if not np.isclose(sum(probabilities), 1):
raise ValueError("Probabilities must sum to 1.")
# Define the possible outcomes of the die roll
outcomes = np.arange(1,len(probabilities)+1,dtype=int)# the number of probabilities will tell us how many outcomes or sides of dice we have
# Sample from the distribution
roll = np.random.choice(outcomes,p=probabilities)
return roll
def make_dice_hist(bins,dice_num=50,events_num=6, fig=None, ax=None,color='y'):
min_val = min(bins)
max_val = max(bins)
first_time = False
if fig is None:
fig, ax = setup_darkmode_axes(9, 2)
ax.set_xlabel("Dice Sum",color="w",fontsize=6)
ax.set_ylabel("Occured How Many Times",color="w",fontsize=6)
ax.clear()
ax.set_title(f"Dice Sum",color="w")
#bin_edges = np.arange(min_val, max_val + 1.5) - 0.5 # Create bin edges the 1.5 will get the last bin in (it needs both left and right edges, and the -.5 will shift them all so they are
#bin_edges = np.arange(min_val, max_val + 2) - 0.5
#centered around each bin starting
#ax.clear()
#counts, bins, patches = ax.hist(bins,bins=bin_edges,density=False, alpha=0.8,rwidth=1, color='y', histtype='bar', orientation = 'vertical', edgecolor='w') # counts are the y or height of each bin,
counts, bins, patches = ax.hist(bins,density=False, alpha=0.6,rwidth=1, color=color, histtype='bar', orientation = 'vertical', edgecolor='w') # counts are the y or height of each bin,
# Set x-ticks to match the bin edges
#bin_midpoints = bins[:-1] + np.diff(bins)/2 # for each bin left side except the last one (because that is the right side) add to it the difference of each pair of bins /2 (midpoint) thus storing
# the midpoints of the bins
# ax.tick_params(axis='x', labelsize=6, colors="white") # Adjust the font size here
#ax.tick_params(axis='y', labelsize=6, colors="white") # Adjust the font size here
#ax.set_xticks(bin_midpoints[::2]) # put a tick at each mid point of each 10th' bin
#ax.set_yticks(np.linspace(0, max(counts)*1.25, 3))
return fig,ax,counts,bins
def create_distribution(events_num = 6, equal=True):
# Generate random numbers
if not equal:
random_numbers = np.random.random(events_num) # random numbers between 0-1 of length num_events to make a weighted distribution (not equal for all)
# Normalize to get probabilities that add up to 1
probabilities = random_numbers / np.sum(random_numbers)
else:
probabilities = [1/events_num] * events_num #
return probabilities
def create_sum(dice_num: int = 20,probabilities=[1/6]*6) -> list :
return sum(roll_weighted_die(probabilities) for _ in range(dice_num)) # roll the dice the number of times needed and sum it them up
def run_trials(trials_num = 10, dice_num: int =20,probabilities=[1/6]*6):
sum_list= []
fig = None
ax = None
for i in range(trials_num):
sum_list.append(create_sum(dice_num,probabilities))
# create/update our histogram
(fig,ax,counts,bins) = make_dice_hist(sum_list,i+1,dice_num,len(probabilities),fig,ax)
plt.show()
time.sleep(.25)
return sum_list
def setup_darkmode_axes(fig_width = 6, fig_height= 2, grid_on = True):
fig, ax = plt.subplots(nrows = 1,figsize=(fig_width,fig_height),dpi=400,layout='compressed')
fig.set_facecolor("#212529")
ax.set_facecolor("black")
if grid_on:
ax.grid(color='white', linewidth=.6, alpha=0.5, zorder=0)
else:
ax.set_axis_off()
return fig,ax
def update_hex_value(event):
print('updating color picker hex value')
color_picker_hex.value = event.new
def show_chart_history(running,local_chart_history = None):
print(f'show_chart_history called chart history {len(chart_history)}\n')
if not running:
return
col_layout = pn.Column()
# Add each figure as a separate panel to the row layout
for fig in local_chart_history:
panel = pn.pane.Matplotlib(fig, dpi=400, format="svg",sizing_mode="stretch_width")
col_layout.append(panel)
return col_layout
def run_simulation(running,num_sums=50,num_dice=30,color='yellow'):
run_input.disabled = True # Disable the button
global global_prob,chart_history
fig = None
ax = None
try:
num_sides = len(global_prob)
if not running:
return # if the panel isn't active just return
sum_list= []
start_time = time.time()
for i in range(num_sums):
loop_start = time.time()
start_time = time.time()
sum_list.append(create_sum(num_dice,global_prob))
end_time = time.time()
elapsed_time = end_time - start_time
# print(f"sum list took {elapsed_time:.4f}")
start_time = time.time()
if not (i+1) % 50: # only update every 10th sum
start_time = time.time() # Record the start time
(fig,ax,counts,bins) = make_dice_hist(sum_list,20,len(global_prob),fig,ax,color)
ax.set_title(f"Dice Sum #{i+1}/{num_sums} using {num_dice} {num_sides}-sided dice",color="w")
ax.set_xlabel("Dice Sum",color="w",fontsize=6)
ax.set_ylabel("Occured How Many Times",color="w")
ax.tick_params(axis='x', labelsize=5, colors="w",pad=1) # Adjust the font size here
ax.tick_params(axis='y', labelsize=5, colors="w",pad=1) # Adjust the font size here
ax.grid(color='white', linewidth=.6, alpha=0.2, zorder=0)
end_time = time.time()
elapsed_time = end_time - start_time
# print(f"Make dice hist took {elapsed_time:.4f}")
start_time = time.time() # Record the start time
layout=pn.pane.Matplotlib(fig, dpi=400, format="svg",sizing_mode="stretch_width")
end_time = time.time()
elapsed_time = end_time - start_time
# print(f"Setting the layout to fig took {elapsed_time:.4f}")
yield layout
time.sleep(.2)
#time.sleep(.15)
end_time = time.time()
elapsed_time = end_time - start_time
# print(f"Yield took {elapsed_time:.4f}")
loop_end = time.time()
elapsed_time = loop_end - loop_start
yield layout
# print(f"Loop took {elapsed_time:.4f}\n\n")
finally:
run_input.disabled = False # Re-enable the button after the simulation
if fig:
chart_history.appendleft(fig)
color_picker = pn.widgets.ColorPicker(name='Bar Color', value='#5e43f3')
color_picker_hex = pn.widgets.StaticText(name='Hex Value', value=color_picker.value)
str_pane1 = pn.pane.Str('Sides Probability Equal?', width=110)
random_switch = pn.widgets.Switch(name='Switch1', value=True, width=25)
num_events = pn.widgets.IntSlider(name='Number of Sides',start=2, end=12,step=1,value=6,width=150)
num_sums = pn.widgets.IntSlider(name='Run For How Long',start=50, end=20000,step=10,value=50,width=150)
num_dice = pn.widgets.IntSlider(name='Number of Dice (size of sum)',start=1, end=100,step=1,value=2,width=150)
#bind a plot to different params
bplot = pn.bind(get_dice_prob_bargraph, events_num=num_events, equal=random_switch,color=color_picker)
layout = pn.pane
# Attach the callback function to the color_picker's value parameter
color_picker.param.watch(update_hex_value, 'value') # Define callback function to update plots
pn.extension(design='material',theme='dark',sizing_mode="stretch_width")
run_input = pn.widgets.Button(name="Run Simulation",icon="hand-click")
pn.template.MaterialTemplate(
title=f'Visualizing The Central Limit Theorm',
site="Statistics Playground",
header_background="#1a1625",
sidebar=['dice.png',num_events,str_pane1,random_switch,bplot,num_dice,num_sums,color_picker,run_input,
pn.Row('gaussian_dist.png')],
main=[pn.Card(pn.bind(run_simulation,run_input,num_sums=num_sums,num_dice=num_dice,color=color_picker),
title="Current Sim", sizing_mode='stretch_width',styles={'background': '#5e5a66'}),
pn.Card(pn.bind(show_chart_history,run_input,local_chart_history=chart_history),title="History (newest first)",sizing_mode='stretch_width',styles={'background': '#5e5a66'})
],
sidebar_width=210,
).servable()