Loading...

Panel

Python library for creating interactive dashboards and applications from Python objects

Interactive Visualization & Exploration Intermediate Recommended Tool
Quick Info
  • Category: Interactive Visualization & Exploration
  • Level: Intermediate
  • Type: Recommended Tool
  • Requires:
    • bokeh
    • param

Why We Recommend Panel

Panel makes it easy to turn your Jupyter notebooks into interactive dashboards without learning web development. You can create widgets, layouts, and deploy applications directly from Python, making your analyses interactive and shareable with collaborators.

Common Use Cases

  • Creating interactive data exploration dashboards
  • Building parameter exploration tools
  • Sharing analyses with non-programmers
  • Rapid prototyping of data applications

Getting Started

Panel is a Python library for creating interactive dashboards, apps, and data exploration tools. It works with many plotting libraries (Matplotlib, Bokeh, Plotly) and can turn Jupyter notebooks into standalone applications.

Why Panel?

  • Framework Agnostic: Works with any Python plotting library
  • Notebook Integration: Build dashboards directly in Jupyter
  • Reactive: Automatically updates when inputs change
  • Deploy Anywhere: Jupyter, standalone server, or static HTML
  • Rich Widgets: Extensive widget library for interactivity

Basic Concepts

Simple Dashboard

import panel as pn
pn.extension()

# Create a simple dashboard
text = pn.pane.Markdown('# My Dashboard')
plot = df.hvplot.line(x='time', y='value')

dashboard = pn.Column(text, plot)
dashboard.servable()

Widgets

# Create interactive widgets
slider = pn.widgets.FloatSlider(name='Threshold', start=0, end=10, value=5)
select = pn.widgets.Select(name='Channel', options=['Ch1', 'Ch2', 'Ch3'])
button = pn.widgets.Button(name='Update', button_type='primary')

# Display widgets
pn.Column(slider, select, button)

Building Interactive Dashboards

Reactive Functions

import panel as pn
import hvplot.pandas

pn.extension()

# Create widgets
channel_select = pn.widgets.Select(
    name='Channel',
    options=['Ch1', 'Ch2', 'Ch3', 'Ch4']
)

# Reactive function that updates when widget changes
@pn.depends(channel_select.param.value)
def plot_channel(channel):
    filtered_df = df[df['channel'] == channel]
    return filtered_df.hvplot.line(x='time', y='voltage', title=f'{channel} Voltage')

# Create dashboard
dashboard = pn.Column(
    '# LFP Explorer',
    channel_select,
    plot_channel
)

dashboard.servable()

With Parameters

import param

class DataExplorer(param.Parameterized):
    channel = param.Selector(default='Ch1', objects=['Ch1', 'Ch2', 'Ch3', 'Ch4'])
    threshold = param.Number(default=5.0, bounds=(0, 10))
    smooth = param.Boolean(default=False)

    @param.depends('channel', 'threshold', 'smooth')
    def view(self):
        # Filter data
        data = df[df['channel'] == self.channel]
        data = data[data['value'] > self.threshold]

        if self.smooth:
            data['value'] = data['value'].rolling(10).mean()

        return data.hvplot.line(x='time', y='value')

explorer = DataExplorer()
pn.Row(explorer.param, explorer.view).servable()

Layout Options

Columns and Rows

# Vertical layout
pn.Column(widget1, plot1, plot2)

# Horizontal layout
pn.Row(widget1, plot1)

# Nested layouts
pn.Column(
    '# Dashboard',
    pn.Row(widget1, widget2),
    pn.Row(plot1, plot2)
)

Tabs

tabs = pn.Tabs(
    ('Overview', overview_plot),
    ('Detailed Analysis', detail_plot),
    ('Statistics', stats_table)
)

Templates

# Use built-in templates
template = pn.template.FastListTemplate(
    title='Neural Data Explorer',
    sidebar=[channel_select, threshold_slider],
    main=[plot1, plot2]
)

template.servable()

Practical Examples

Calcium Imaging ROI Explorer

import panel as pn
import hvplot.pandas

pn.extension()

# Widgets
roi_selector = pn.widgets.IntSlider(name='ROI', start=1, end=100, value=1)
trial_selector = pn.widgets.IntSlider(name='Trial', start=1, end=50, value=1)

@pn.depends(roi_selector.param.value, trial_selector.param.value)
def plot_trace(roi, trial):
    # Filter data
    trace = traces_df[
        (traces_df['roi'] == roi) &
        (traces_df['trial'] == trial)
    ]

    return trace.hvplot.line(
        x='time', y='dF_F',
        xlabel='Time (s)',
        ylabel='ΔF/F',
        title=f'ROI {roi}, Trial {trial}'
    )

# Summary statistics
@pn.depends(roi_selector.param.value)
def show_stats(roi):
    roi_data = traces_df[traces_df['roi'] == roi]
    stats = {
        'Mean Response': roi_data['dF_F'].mean(),
        'Peak Response': roi_data['dF_F'].max(),
        'Baseline': roi_data['dF_F'].iloc[:100].mean()
    }
    return pn.pane.DataFrame(pd.DataFrame([stats]))

dashboard = pn.Column(
    '# Calcium Imaging Explorer',
    pn.Row(roi_selector, trial_selector),
    plot_trace,
    '## ROI Statistics',
    show_stats
)

dashboard.servable()

LFP Analysis Dashboard

# Widgets for filtering
freq_range = pn.widgets.RangeSlider(
    name='Frequency Range (Hz)',
    start=0, end=100, value=(4, 8), step=1
)

time_range = pn.widgets.RangeSlider(
    name='Time Window (s)',
    start=0, end=10, value=(0, 10), step=0.1
)

@pn.depends(freq_range.param.value, time_range.param.value)
def filtered_spectrogram(freq, time):
    # Filter spectrogram data
    filtered = spectrogram_df[
        (spectrogram_df['frequency'] >= freq[0]) &
        (spectrogram_df['frequency'] <= freq[1]) &
        (spectrogram_df['time'] >= time[0]) &
        (spectrogram_df['time'] <= time[1])
    ]

    return filtered.hvplot.heatmap(
        x='time', y='frequency', C='power',
        cmap='viridis',
        xlabel='Time (s)',
        ylabel='Frequency (Hz)'
    )

pn.Column(
    '# LFP Spectrogram',
    freq_range,
    time_range,
    filtered_spectrogram
).servable()

Statistical Comparison Tool

# Group comparison dashboard
group1_select = pn.widgets.MultiSelect(
    name='Group 1 Subjects',
    options=subject_list,
    size=10
)

group2_select = pn.widgets.MultiSelect(
    name='Group 2 Subjects',
    options=subject_list,
    size=10
)

metric_select = pn.widgets.Select(
    name='Metric',
    options=['reaction_time', 'accuracy', 'response_variability']
)

run_button = pn.widgets.Button(name='Run Comparison', button_type='primary')

def run_comparison(event):
    from scipy import stats
    import pingouin as pg

    # Get data for selected groups
    g1_data = df[df['subject'].isin(group1_select.value)][metric_select.value]
    g2_data = df[df['subject'].isin(group2_select.value)][metric_select.value]

    # Run t-test
    result = pg.ttest(g1_data, g2_data)

    # Create comparison plot
    comparison_df = pd.DataFrame({
        'Group 1': g1_data,
        'Group 2': g2_data
    })

    plot = comparison_df.hvplot.violin(
        ylabel=metric_select.value,
        title=f'p = {result["p-val"].values[0]:.4f}'
    )

    results_pane.object = result
    plot_pane.object = plot

run_button.on_click(run_comparison)

results_pane = pn.pane.DataFrame()
plot_pane = pn.pane.HoloViews()

dashboard = pn.Column(
    '# Statistical Comparison',
    pn.Row(group1_select, group2_select),
    metric_select,
    run_button,
    plot_pane,
    results_pane
)

dashboard.servable()

Deployment

Jupyter Notebook

# Display inline in notebook
dashboard.show()

# Or use .servable() at the end of the notebook
dashboard.servable()

Standalone Server

# Save dashboard to file: dashboard.py
# Run server
panel serve dashboard.py --show

Static HTML Export

dashboard.save('dashboard.html', embed=True)

Advanced Features

Caching for Performance

@pn.cache
def expensive_computation(param):
    # Expensive operation
    result = process_large_dataset(param)
    return result

@pn.depends(slider.param.value)
def plot(value):
    data = expensive_computation(value)  # Cached
    return data.hvplot()

Custom JavaScript

# Add custom interactivity
js_code = """
console.log('Value changed to:', value);
"""

widget.jscallback(args={'value': widget}, value=js_code)

Authentication

# Add basic auth
pn.serve(
    dashboard,
    port=5006,
    basic_auth={'username': 'password'}
)

Installation

pixi add panel
# or
conda install -c conda-forge panel
# or
pip install panel

For Jupyter notebook support:

jupyter labextension install @pyviz/jupyterlab_pyviz

Best Practices

  • Use .servable() at the end of notebooks for deployment
  • Cache expensive computations with @pn.cache
  • Organize complex dashboards with tabs and layouts
  • Use templates for professional-looking apps
  • Test responsiveness with different screen sizes
  • Document widget purposes for users
  • Consider loading indicators for slow operations
  • Use param.Parameterized for complex state management

Prerequisites

  • bokeh
  • param
Top