Spaces:
Runtime error
Runtime error
| 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() | |