Interactive Widgets Example¶
This notebook demonstrates the use of interactive widgets in Jupyter notebooks for creating dynamic, engaging content.
Objectives¶
- Learn about different types of Jupyter widgets
- Create interactive visualizations
- Build interactive data exploration tools
- Understand widget event handling
- Demonstrate real-world widget applications
Table of Contents¶
Setup¶
First, let's import the necessary libraries and enable widget display.
# Import essential libraries
import warnings
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from IPython.display import HTML, display
from ipywidgets import fixed, interact, interact_manual, interactive
warnings.filterwarnings("ignore")
# Set up matplotlib for inline plotting
%matplotlib inline
plt.style.use("default")
print("Widgets and plotting libraries imported successfully!")
print(f"ipywidgets version: {widgets.__version__}")
# Enable widget display
widgets.Widget.close_all()
1. Basic Widgets¶
Let's start with simple widget examples to understand the basic concepts.
# Simple function to demonstrate interaction
def greet(name, age, excited=False):
"""A simple greeting function"""
greeting = f"Hello {name}, you are {age} years old!"
if excited:
greeting += " 🎉"
print(greeting)
# Create interactive widget
print("Interactive Greeting Widget:")
interact(
greet,
name=widgets.Text(value="World", description="Name:"),
age=widgets.IntSlider(value=25, min=0, max=100, description="Age:"),
excited=widgets.Checkbox(value=False, description="Excited?"),
);
# Different widget types demonstration
def show_widget_value(dropdown_value, slider_value, text_value, checkbox_value):
"""Display values from different widgets"""
print(f"Dropdown selection: {dropdown_value}")
print(f"Slider value: {slider_value}")
print(f"Text input: {text_value}")
print(f"Checkbox state: {checkbox_value}")
print("-" * 30)
print("Multiple Widget Types:")
interact(
show_widget_value,
dropdown_value=widgets.Dropdown(
options=["Option A", "Option B", "Option C"],
value="Option A",
description="Choice:",
),
slider_value=widgets.FloatSlider(
value=5.0, min=0.0, max=10.0, step=0.1, description="Value:"
),
text_value=widgets.Text(value="Sample text", description="Text:"),
checkbox_value=widgets.Checkbox(value=True, description="Enable:"),
);
2. Interactive Plots¶
Widgets are particularly powerful when combined with matplotlib for creating interactive visualizations.
# Interactive sine wave plot
def plot_sine_wave(frequency, amplitude, phase, num_points):
"""Plot an interactive sine wave"""
x = np.linspace(0, 2 * np.pi, num_points)
y = amplitude * np.sin(frequency * x + phase)
plt.figure(figsize=(10, 6))
plt.plot(
x,
y,
"b-",
linewidth=2,
label=f"y = {amplitude}·sin({frequency}x + {phase:.2f})",
)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Interactive Sine Wave")
plt.grid(True, alpha=0.3)
plt.legend()
plt.ylim(-5, 5)
plt.show()
print("Interactive Sine Wave Plot:")
interact(
plot_sine_wave,
frequency=widgets.FloatSlider(
value=1.0, min=0.1, max=5.0, step=0.1, description="Frequency:"
),
amplitude=widgets.FloatSlider(
value=1.0, min=0.1, max=3.0, step=0.1, description="Amplitude:"
),
phase=widgets.FloatSlider(
value=0.0, min=0, max=2 * np.pi, step=0.1, description="Phase:"
),
num_points=widgets.IntSlider(
value=100, min=10, max=500, step=10, description="Points:"
),
);
# Interactive scatter plot with different distributions
def plot_distribution(distribution, n_samples, param1, param2):
"""Plot different probability distributions"""
np.random.seed(42) # For reproducibility
if distribution == "Normal":
data = np.random.normal(param1, param2, n_samples)
title = f"Normal Distribution (μ={param1}, σ={param2})"
elif distribution == "Exponential":
data = np.random.exponential(param1, n_samples)
title = f"Exponential Distribution (λ={1/param1:.2f})"
elif distribution == "Uniform":
data = np.random.uniform(param1, param2, n_samples)
title = f"Uniform Distribution ({param1}, {param2})"
else: # Beta
data = np.random.beta(param1, param2, n_samples)
title = f"Beta Distribution (α={param1}, β={param2})"
plt.figure(figsize=(12, 5))
# Histogram
plt.subplot(1, 2, 1)
plt.hist(data, bins=30, alpha=0.7, density=True, color="skyblue", edgecolor="black")
plt.title(f"Histogram - {title}")
plt.xlabel("Value")
plt.ylabel("Density")
plt.grid(True, alpha=0.3)
# Box plot
plt.subplot(1, 2, 2)
plt.boxplot(data, vert=True)
plt.title(f"Box Plot - {title}")
plt.ylabel("Value")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Print statistics
print(f"Statistics for {title}:")
print(f"Mean: {np.mean(data):.3f}")
print(f"Std: {np.std(data):.3f}")
print(f"Min: {np.min(data):.3f}")
print(f"Max: {np.max(data):.3f}")
print("Interactive Distribution Plot:")
interact(
plot_distribution,
distribution=widgets.Dropdown(
options=["Normal", "Exponential", "Uniform", "Beta"],
value="Normal",
description="Distribution:",
),
n_samples=widgets.IntSlider(
value=1000, min=100, max=5000, step=100, description="Samples:"
),
param1=widgets.FloatSlider(
value=0.0, min=-5.0, max=5.0, step=0.1, description="Parameter 1:"
),
param2=widgets.FloatSlider(
value=1.0, min=0.1, max=5.0, step=0.1, description="Parameter 2:"
),
);
3. Data Exploration¶
Let's create interactive tools for exploring datasets.
# Create a sample dataset for exploration
np.random.seed(42)
n_rows = 1000
data = {
"category": np.random.choice(["A", "B", "C", "D"], n_rows),
"value1": np.random.normal(50, 15, n_rows),
"value2": np.random.normal(100, 25, n_rows),
"value3": np.random.exponential(20, n_rows),
"date": pd.date_range("2023-01-01", periods=n_rows, freq="H"),
"boolean": np.random.choice([True, False], n_rows),
}
df = pd.DataFrame(data)
df["value2"] = df["value2"] + df["value1"] * 0.3 # Add some correlation
print(f"Dataset created with {len(df)} rows and {len(df.columns)} columns")
print("\nDataset preview:")
display(df.head())
# Interactive data exploration tool
def explore_data(x_column, y_column, category_filter, plot_type, show_trend):
"""Interactive data exploration function"""
# Filter data
if category_filter != "All":
filtered_df = df[df["category"] == category_filter]
else:
filtered_df = df
plt.figure(figsize=(12, 6))
if plot_type == "Scatter":
# Scatter plot with categories
for cat in df["category"].unique():
cat_data = (
filtered_df[filtered_df["category"] == cat]
if category_filter == "All"
else filtered_df
)
if len(cat_data) > 0:
plt.scatter(
cat_data[x_column],
cat_data[y_column],
label=cat if category_filter == "All" else category_filter,
alpha=0.6,
s=30,
)
if show_trend and len(filtered_df) > 1:
# Add trend line
z = np.polyfit(filtered_df[x_column], filtered_df[y_column], 1)
p = np.poly1d(z)
plt.plot(
filtered_df[x_column].sort_values(),
p(filtered_df[x_column].sort_values()),
"r--",
alpha=0.8,
linewidth=2,
label="Trend",
)
plt.xlabel(x_column)
plt.ylabel(y_column)
plt.legend()
elif plot_type == "Histogram":
# Histogram
if category_filter == "All":
for cat in df["category"].unique():
cat_data = filtered_df[filtered_df["category"] == cat]
plt.hist(
cat_data[x_column], bins=30, alpha=0.6, label=cat, density=True
)
else:
plt.hist(
filtered_df[x_column], bins=30, alpha=0.7, color="skyblue", density=True
)
plt.xlabel(x_column)
plt.ylabel("Density")
if category_filter == "All":
plt.legend()
elif plot_type == "Box Plot":
# Box plot by category
if category_filter == "All":
filtered_df.boxplot(column=x_column, by="category", ax=plt.gca())
else:
plt.boxplot(filtered_df[x_column])
plt.xticks([1], [category_filter])
plt.ylabel(x_column)
plt.title(
f'{plot_type}: {x_column} vs {y_column if plot_type == "Scatter" else ""} ({category_filter})'
)
plt.grid(True, alpha=0.3)
plt.show()
# Show summary statistics
print(f"\nSummary Statistics ({len(filtered_df)} rows):")
if plot_type == "Scatter":
correlation = filtered_df[x_column].corr(filtered_df[y_column])
print(f"Correlation between {x_column} and {y_column}: {correlation:.3f}")
print(f"\n{x_column} statistics:")
print(f"Mean: {filtered_df[x_column].mean():.2f}")
print(f"Std: {filtered_df[x_column].std():.2f}")
print(f"Min: {filtered_df[x_column].min():.2f}")
print(f"Max: {filtered_df[x_column].max():.2f}")
# Get numeric columns
numeric_columns = df.select_dtypes(include=[np.number]).columns.tolist()
print("Interactive Data Exploration Tool:")
interact(
explore_data,
x_column=widgets.Dropdown(
options=numeric_columns, value="value1", description="X Column:"
),
y_column=widgets.Dropdown(
options=numeric_columns, value="value2", description="Y Column:"
),
category_filter=widgets.Dropdown(
options=["All"] + list(df["category"].unique()),
value="All",
description="Category:",
),
plot_type=widgets.Dropdown(
options=["Scatter", "Histogram", "Box Plot"],
value="Scatter",
description="Plot Type:",
),
show_trend=widgets.Checkbox(value=False, description="Show Trend"),
);
4. Mathematical Functions¶
Explore mathematical concepts with interactive visualizations.
# Interactive polynomial function explorer
def plot_polynomial(a, b, c, d, x_range):
"""Plot polynomial function: f(x) = ax³ + bx² + cx + d"""
x = np.linspace(-x_range, x_range, 1000)
y = a * x**3 + b * x**2 + c * x + d
plt.figure(figsize=(10, 6))
plt.plot(x, y, "b-", linewidth=2, label=f"f(x) = {a}x³ + {b}x² + {c}x + {d}")
plt.axhline(y=0, color="k", linestyle="-", alpha=0.3)
plt.axvline(x=0, color="k", linestyle="-", alpha=0.3)
plt.grid(True, alpha=0.3)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.title("Interactive Polynomial Function")
plt.legend()
# Calculate and show roots (approximately)
if len(x) > 0:
# Find approximate zeros
sign_changes = np.where(np.diff(np.sign(y)))[0]
if len(sign_changes) > 0:
roots = x[sign_changes]
plt.scatter(
roots,
np.zeros_like(roots),
color="red",
s=50,
zorder=5,
label="Approximate roots",
)
plt.legend()
plt.ylim(-50, 50)
plt.show()
# Show function properties
print(f"Function: f(x) = {a}x³ + {b}x² + {c}x + {d}")
print(f"y-intercept: f(0) = {d}")
if a != 0:
print(f"Leading coefficient: {a} (cubic function)")
elif b != 0:
print(f"Leading coefficient: {b} (quadratic function)")
elif c != 0:
print(f"Leading coefficient: {c} (linear function)")
else:
print(f"Constant function: f(x) = {d}")
print("Interactive Polynomial Explorer:")
interact(
plot_polynomial,
a=widgets.FloatSlider(
value=1.0, min=-2.0, max=2.0, step=0.1, description="a (x³):"
),
b=widgets.FloatSlider(
value=0.0, min=-5.0, max=5.0, step=0.1, description="b (x²):"
),
c=widgets.FloatSlider(value=0.0, min=-5.0, max=5.0, step=0.1, description="c (x):"),
d=widgets.FloatSlider(
value=0.0, min=-5.0, max=5.0, step=0.1, description="d (const):"
),
x_range=widgets.FloatSlider(
value=5.0, min=1.0, max=10.0, step=0.5, description="X Range:"
),
);
# Interactive trigonometric function explorer
def plot_trig_functions(function, amplitude, frequency, phase, show_components):
"""Plot trigonometric functions with customizable parameters"""
x = np.linspace(0, 4 * np.pi, 1000)
if function == "sin":
y = amplitude * np.sin(frequency * x + phase)
func_name = "sin"
elif function == "cos":
y = amplitude * np.cos(frequency * x + phase)
func_name = "cos"
elif function == "tan":
y = amplitude * np.tan(frequency * x + phase)
# Limit y range for tan to avoid extreme values
y = np.clip(y, -10, 10)
func_name = "tan"
else: # sin + cos
y = amplitude * (np.sin(frequency * x + phase) + np.cos(frequency * x + phase))
func_name = "sin + cos"
plt.figure(figsize=(12, 6))
plt.plot(
x,
y,
"b-",
linewidth=2,
label=f"f(x) = {amplitude}·{func_name}({frequency}x + {phase:.2f})",
)
if show_components and function == "sin + cos":
# Show individual components
y_sin = amplitude * np.sin(frequency * x + phase)
y_cos = amplitude * np.cos(frequency * x + phase)
plt.plot(
x,
y_sin,
"r--",
alpha=0.7,
label=f"{amplitude}·sin({frequency}x + {phase:.2f})",
)
plt.plot(
x,
y_cos,
"g--",
alpha=0.7,
label=f"{amplitude}·cos({frequency}x + {phase:.2f})",
)
plt.axhline(y=0, color="k", linestyle="-", alpha=0.3)
plt.grid(True, alpha=0.3)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.title("Interactive Trigonometric Functions")
plt.legend()
# Set y-limits based on function type
if function == "tan":
plt.ylim(-10, 10)
else:
max_y = amplitude * (2 if function == "sin + cos" else 1)
plt.ylim(-max_y * 1.2, max_y * 1.2)
# Add x-axis labels for π multiples
pi_ticks = np.arange(0, 4 * np.pi + 0.1, np.pi)
pi_labels = ["0", "π", "2π", "3π", "4π"]
plt.xticks(pi_ticks, pi_labels)
plt.show()
# Show function properties
print(f"Function: f(x) = {amplitude}·{func_name}({frequency}x + {phase:.2f})")
if function != "tan":
period = 2 * np.pi / frequency
print(f"Period: {period:.2f} (2π/{frequency})")
print(f"Amplitude: {amplitude}")
print(f"Phase shift: {phase:.2f} radians")
print("Interactive Trigonometric Function Explorer:")
interact(
plot_trig_functions,
function=widgets.Dropdown(
options=["sin", "cos", "tan", "sin + cos"], value="sin", description="Function:"
),
amplitude=widgets.FloatSlider(
value=1.0, min=0.1, max=3.0, step=0.1, description="Amplitude:"
),
frequency=widgets.FloatSlider(
value=1.0, min=0.1, max=3.0, step=0.1, description="Frequency:"
),
phase=widgets.FloatSlider(
value=0.0, min=0, max=2 * np.pi, step=0.1, description="Phase:"
),
show_components=widgets.Checkbox(value=False, description="Show Components"),
);
5. Advanced Examples¶
More sophisticated widget applications.
# Interactive linear regression demonstration
def interactive_regression(n_points, noise_level, slope, intercept, show_residuals):
"""Interactive linear regression with adjustable parameters"""
np.random.seed(42)
# Generate data
x = np.linspace(0, 10, n_points)
y_true = slope * x + intercept
noise = np.random.normal(0, noise_level, n_points)
y_observed = y_true + noise
# Calculate best fit line
coefficients = np.polyfit(x, y_observed, 1)
best_fit_slope, best_fit_intercept = coefficients
y_fitted = best_fit_slope * x + best_fit_intercept
plt.figure(figsize=(12, 8))
# Main plot
plt.subplot(2, 1, 1)
plt.scatter(x, y_observed, alpha=0.7, color="blue", label="Observed data")
plt.plot(
x, y_true, "g-", linewidth=2, label=f"True line (y = {slope}x + {intercept})"
)
plt.plot(
x,
y_fitted,
"r--",
linewidth=2,
label=f"Best fit (y = {best_fit_slope:.2f}x + {best_fit_intercept:.2f})",
)
if show_residuals:
# Show residual lines
for i in range(len(x)):
plt.plot(
[x[i], x[i]],
[y_observed[i], y_fitted[i]],
"k--",
alpha=0.5,
linewidth=1,
)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Interactive Linear Regression")
plt.legend()
plt.grid(True, alpha=0.3)
# Residuals plot
plt.subplot(2, 1, 2)
residuals = y_observed - y_fitted
plt.scatter(x, residuals, alpha=0.7, color="red")
plt.axhline(y=0, color="k", linestyle="-", alpha=0.5)
plt.xlabel("x")
plt.ylabel("Residuals")
plt.title("Residuals Plot")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Calculate and display statistics
mse = np.mean(residuals**2)
r_squared = 1 - (
np.sum(residuals**2) / np.sum((y_observed - np.mean(y_observed)) ** 2)
)
print(f"Regression Statistics:")
print(f"True parameters: slope = {slope}, intercept = {intercept}")
print(
f"Fitted parameters: slope = {best_fit_slope:.3f}, intercept = {best_fit_intercept:.3f}"
)
print(f"Mean Squared Error: {mse:.3f}")
print(f"R-squared: {r_squared:.3f}")
print(f"Sample size: {n_points} points")
print(f"Noise level: {noise_level}")
print("Interactive Linear Regression:")
interact(
interactive_regression,
n_points=widgets.IntSlider(
value=50, min=10, max=200, step=10, description="Points:"
),
noise_level=widgets.FloatSlider(
value=1.0, min=0.1, max=3.0, step=0.1, description="Noise:"
),
slope=widgets.FloatSlider(
value=2.0, min=-5.0, max=5.0, step=0.1, description="True Slope:"
),
intercept=widgets.FloatSlider(
value=1.0, min=-5.0, max=5.0, step=0.1, description="True Intercept:"
),
show_residuals=widgets.Checkbox(value=False, description="Show Residuals"),
);
6. Widget Layouts¶
Demonstrating more complex widget layouts and custom interfaces.
# Create a custom dashboard with multiple widgets
print("Custom Dashboard Example:")
# Create widgets
output = widgets.Output()
dataset_dropdown = widgets.Dropdown(
options=["Sine Wave", "Random Walk", "Normal Distribution"],
value="Sine Wave",
description="Dataset:",
)
param1_slider = widgets.FloatSlider(
value=1.0, min=0.1, max=5.0, step=0.1, description="Parameter 1:"
)
param2_slider = widgets.FloatSlider(
value=1.0, min=0.1, max=5.0, step=0.1, description="Parameter 2:"
)
n_points_slider = widgets.IntSlider(
value=100, min=50, max=500, step=25, description="N Points:"
)
update_button = widgets.Button(description="Update Plot", button_style="primary")
def update_plot(button):
with output:
output.clear_output(wait=True)
dataset = dataset_dropdown.value
param1 = param1_slider.value
param2 = param2_slider.value
n_points = n_points_slider.value
x = np.linspace(0, 10, n_points)
if dataset == "Sine Wave":
y = param1 * np.sin(param2 * x)
title = f"Sine Wave: {param1:.1f} * sin({param2:.1f} * x)"
elif dataset == "Random Walk":
np.random.seed(42)
steps = np.random.normal(0, param1, n_points)
y = np.cumsum(steps) * param2
title = f"Random Walk: std={param1:.1f}, scale={param2:.1f}"
else: # Normal Distribution
np.random.seed(42)
y = np.random.normal(param1, param2, n_points)
title = f"Normal Distribution: μ={param1:.1f}, σ={param2:.1f}"
plt.figure(figsize=(10, 6))
if dataset == "Normal Distribution":
plt.hist(y, bins=30, alpha=0.7, density=True, color="skyblue")
plt.ylabel("Density")
else:
plt.plot(x, y, "b-", linewidth=2)
plt.ylabel("y")
plt.xlabel("x" if dataset != "Normal Distribution" else "Value")
plt.title(title)
plt.grid(True, alpha=0.3)
plt.show()
# Show statistics
print(f"Statistics for {dataset}:")
print(f"Mean: {np.mean(y):.3f}")
print(f"Std: {np.std(y):.3f}")
print(f"Min: {np.min(y):.3f}")
print(f"Max: {np.max(y):.3f}")
# Bind the button click event
update_button.on_click(update_plot)
# Create layout
controls = widgets.VBox(
[
widgets.HTML("<h3>Dashboard Controls</h3>"),
dataset_dropdown,
param1_slider,
param2_slider,
n_points_slider,
update_button,
]
)
dashboard = widgets.HBox([controls, output])
# Display the dashboard
display(dashboard)
# Initial plot
update_plot(None)
Dashboard Controls
"), dataset_dropdown, param1_slider, param2_slider, n_points_slider, update_button, ] ) dashboard = widgets.HBox([controls, output]) # Display the dashboard display(dashboard) # Initial plot update_plot(None)Conclusion¶
This notebook has demonstrated various aspects of Jupyter widgets:
Key Concepts Covered:¶
- Basic Widgets: Text inputs, sliders, dropdowns, checkboxes
- Interactive Plots: Real-time visualization updates
- Data Exploration: Interactive tools for dataset analysis
- Mathematical Functions: Exploring equations and their properties
- Advanced Applications: Complex interactive demonstrations
- Custom Layouts: Building dashboard-like interfaces
Benefits of Jupyter Widgets:¶
- Enhanced Learning: Interactive exploration improves understanding
- User Engagement: Dynamic content keeps users interested
- Parameter Exploration: Easy testing of different scenarios
- Presentation: Professional-looking interactive demonstrations
- Prototyping: Quick development of interactive tools
Best Practices:¶
- Clear Labels: Use descriptive widget labels and titles
- Reasonable Ranges: Set appropriate min/max values for sliders
- Performance: Be mindful of computation time for complex operations
- Error Handling: Include validation for edge cases
- Documentation: Explain what each widget does
Next Steps:¶
- Explore
ipywidgets
documentation for more widget types - Learn about
bqplot
for more advanced interactive plotting - Investigate
voila
for converting notebooks to standalone web apps - Consider
panel
orstreamlit
for more complex applications
Interactive widgets make Jupyter notebooks powerful tools for education, exploration, and presentation. Experiment with different combinations to create engaging content for your specific use cases.