Create interactive plot of the Continuous Uniform Distribution with sliders for parameter values

350 views Asked by At

How can I create an interactive plot of the pdf and the cdf of the Continuous Uniform Distribution using python?

uniform distribution

I would like to have interactive sliders with which I can adjust the parameters of the distribution.

This is handy for getting a better insight in the behavior of this distribution for different values of its parameters.

2

There are 2 answers

0
Sander van den Oord On

1. The simplest way is to use scipy.stats.uniform() to get the pdf and the cdf of the distribution and then using pn.interact() to get the interactive sliders for the parameters:

# import libraries
from scipy import stats
import pandas as pd
import hvplot.pandas
import panel as pn
pn.extension()
import panel.widgets as pnw
import holoviews as hv
hv.extension('bokeh')


# define pdf and cdf for cont uniform distr and return plots
def get_interactive_plot_cont_uniform(loc=1, scale=5):
    continuous_uniform = stats.uniform(loc=loc, scale=scale)
    
    x_values = np.linspace(0, 10, num=1000)
    fx_values = continuous_uniform.pdf(x_values)
    Fx_values = continuous_uniform.cdf(x_values)
    
    interactive_plot = (
        hv.Curve((x_values, fx_values), label='PDF') 
        + hv.Curve((x_values, Fx_values), label='CDF'))
    return interactive_plot

# use pn.interact() to get interactive sliders 
# and define the sliders yourself for more flexibility
pn.interact(
    get_interactive_plot_cont_uniform, 
    loc=pnw.FloatSlider(
       name='Value for loc', value=1., 
       start=0, step=0.5, end=10),
    scale=pnw.FloatSlider(
       name='Value for scale', value=5., 
       start=0, step=0.5, end=10),
)

Resulting interactive plot with sliders:

interactive plot with sliders of continuous uniform distribution

2. A more extensive and flexible example which allows setting parameter a and b more intuitively:

# create sliders to adjust parameter a and b
param_a = pnw.FloatSlider(name='Value for parameter a', value=1., start=0, step=0.5, end=10)
param_b = pnw.FloatSlider(name='Value for parameter b', value=6., start=0, step=0.5, end=10)

# get interactivity by using following decorator
@pn.depends(param_a, param_b)
def get_interactive_cont_uniform(param_a, param_b):
    
    # define the uniform distribution
    # scale in scipy.stats.uniform is b - a
    loc = param_a
    scale = param_b - param_a
    continuous_uniform = stats.uniform(loc=loc, scale=scale)

    # calculate x and y values for pdf and cdf and put in dataframe
    x_values = np.linspace(0, 10, num=1000)
    fx_values = continuous_uniform.pdf(x_values)
    Fx_values = continuous_uniform.cdf(x_values)

    df = pd.DataFrame({
        'x': x_values, 
        'f(x)': fx_values,
        'F(x)': Fx_values,
    })

    # create pdf and cdf plot
    pdf_plot = df.hvplot.line(
        x='x', 
        y='f(x)', 
        ylim=(0, 1.02), 
        ylabel='f(x) - pdf values', xlabel='',
        title=f'pdf where a={param_a} and b={param_b}',
        height=225,
    ).opts(fontscale=1.25)
    
    cdf_plot = df.hvplot.line(
        x='x', 
        y='F(x)', 
        ylim=(0, 1.02), 
        ylabel='F(x) - cdf values',
        title=f'cdf where a={param_a} and b={param_b}',
        height=225,
    ).opts(fontscale=1.25)
    
    return (pdf_plot + cdf_plot).cols(1)

# use pyviz panel to get a nice view of sliders and the plots
pn.Column(
    pn.pane.Markdown("## Continuous Uniform Distribution"),
    pn.Row(param_a, param_b),
    pn.Row(get_interactive_cont_uniform)
)
2
Cameron Riddell On

To build on the other answer, you can also use the class oriented approach that panel implements. This is my favorite approach since it keeps your code nice and modularized. Additionally it plots directly via bokeh instead of going through holoviews (which I'm personally on the fence about in general). Using bokeh instead of holoviews gives you finer tuned control over your plots, but for these purposes it doesn't really matter which route you choose.

This snippet works from within a notebook, but can be adapted to a script format by changing the call to the .show() method to .servable() and running the script from the command line/terminal with the command: panel serve file.py --show

from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
import panel as pn
import param
from scipy import stats

class UniformViewer(param.Parameterized):
    a = param.Number(1, bounds=(0, 15), step=.5)
    b = param.Number(4, bounds=(0, 15), step=.5, inclusive_bounds=(False, True))
    
    def __init__(self, **params):
        super().__init__(**params)
        
        # Initialize 2 plots with common arguments
        plot_kwargs = {
            "height": 250,
            "width": 250,
            "y_range": (-.1, 1.1)
        }
        self.p_pdf = figure(title="PDF", **plot_kwargs)
        self.p_cdf = figure(title="CDF", **plot_kwargs)
        
        # Calculate range of data based on parameter bounds
        a_min, a_max = self.param.a.bounds
        b_min, b_max = self.param.b.bounds
        
        # Create x-values for pdf/cdf calculations
        x_min, x_max = min(a_min, b_min), max(a_max, b_max)
        self.x = np.linspace(x_min, x_max, 1000)
        
        # store x values in our CDS since they won't change
        self.cds = ColumnDataSource({"x": self.x}) 
        self.update_cds() # Populate cdf/pdf values into the CDS before plotting
        
        # Draw our pdf and cdf on their respective figures
        self.p_pdf.line(x="x", y="pdf", source=self.cds)
        self.p_cdf.line(x="x", y="cdf", source=self.cds)
        
    # Add callback that executes anytime self.a or self.b changes
    @param.depends("a", "b", watch=True)
    def update_cds(self):
        # Calcualte the uniform distribution using
        # self.a as the left endpoint and self.b as the right endpoint
        uniform_dist = stats.uniform(loc=self.a, scale=self.b-self.a)
        
        self.cds.data.update({
            "pdf": uniform_dist.pdf(self.x),
            "cdf": uniform_dist.cdf(self.x)
        })
    
    def view(self):
        return pn.Row(self.p_pdf, self.p_cdf)
    
interactive_plot = UniformViewer()

pn.Column(interactive_plot, interactive_plot.view()).show()

enter image description here