SimPy with matplotlib: Visualising Simulation Results
Numbers tell the story. Visualisations make people believe it.
Essential Plots
Every simulation needs: 1. Queue length over time 2. Wait time distribution 3. Utilisation chart 4. Throughput curve
Setup
import simpy
import random
import matplotlib.pyplot as plt
import numpy as np
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 12
Queue Length Over Time
def plot_queue_length(time_series):
"""Step plot of queue length over simulation time."""
times = [r['time'] for r in time_series]
queues = [r['queue'] for r in time_series]
fig, ax = plt.subplots()
ax.step(times, queues, where='post', linewidth=1, color='steelblue')
ax.fill_between(times, queues, step='post', alpha=0.3)
ax.set_xlabel('Time')
ax.set_ylabel('Queue Length')
ax.set_title('Queue Length Over Time')
plt.tight_layout()
plt.savefig('queue_length.png', dpi=150)
plt.show()
Wait Time Distribution
def plot_wait_histogram(wait_times):
"""Histogram with key statistics marked."""
fig, ax = plt.subplots()
ax.hist(wait_times, bins=30, edgecolor='white', alpha=0.7, color='steelblue')
# Mark statistics
mean_wait = np.mean(wait_times)
median_wait = np.median(wait_times)
p95 = np.percentile(wait_times, 95)
ax.axvline(mean_wait, color='red', linestyle='--', linewidth=2,
label=f'Mean: {mean_wait:.1f}')
ax.axvline(median_wait, color='green', linestyle='--', linewidth=2,
label=f'Median: {median_wait:.1f}')
ax.axvline(p95, color='orange', linestyle='--', linewidth=2,
label=f'95th pct: {p95:.1f}')
ax.set_xlabel('Wait Time')
ax.set_ylabel('Frequency')
ax.set_title('Distribution of Wait Times')
ax.legend()
plt.tight_layout()
plt.savefig('wait_distribution.png', dpi=150)
plt.show()
CDF Plot
def plot_cdf(data, label='Wait Time'):
"""Cumulative distribution function."""
sorted_data = np.sort(data)
cdf = np.arange(1, len(sorted_data) + 1) / len(sorted_data)
fig, ax = plt.subplots()
ax.plot(sorted_data, cdf, linewidth=2, color='steelblue')
# Mark percentiles
for p in [50, 90, 95, 99]:
val = np.percentile(data, p)
ax.axhline(p/100, color='gray', linestyle=':', alpha=0.5)
ax.axvline(val, color='gray', linestyle=':', alpha=0.5)
ax.annotate(f'{p}%: {val:.1f}', xy=(val, p/100),
xytext=(val + 0.5, p/100 - 0.05), fontsize=10)
ax.set_xlabel(label)
ax.set_ylabel('Cumulative Probability')
ax.set_title(f'CDF of {label}')
ax.set_ylim(0, 1.05)
plt.tight_layout()
plt.savefig('cdf.png', dpi=150)
plt.show()
Utilisation Over Time
def plot_utilisation(time_series, capacity):
"""Resource utilisation over time."""
times = [r['time'] for r in time_series]
busy = [r['busy'] for r in time_series]
utilisation = [b / capacity for b in busy]
fig, ax = plt.subplots()
ax.plot(times, utilisation, linewidth=1, color='steelblue')
ax.fill_between(times, utilisation, alpha=0.3)
# Average line
avg_util = np.mean(utilisation)
ax.axhline(avg_util, color='red', linestyle='--',
label=f'Average: {avg_util:.1%}')
ax.set_xlabel('Time')
ax.set_ylabel('Utilisation')
ax.set_ylim(0, 1.1)
ax.set_title('Resource Utilisation Over Time')
ax.legend()
plt.tight_layout()
plt.savefig('utilisation.png', dpi=150)
plt.show()
Scenario Comparison Box Plot
def plot_scenario_comparison(results_dict):
"""Box plot comparing scenarios."""
fig, ax = plt.subplots()
labels = list(results_dict.keys())
data = list(results_dict.values())
bp = ax.boxplot(data, labels=labels, patch_artist=True)
# Color boxes
for patch in bp['boxes']:
patch.set_facecolor('steelblue')
patch.set_alpha(0.7)
ax.set_ylabel('Wait Time')
ax.set_title('Wait Time Comparison Across Scenarios')
plt.tight_layout()
plt.savefig('comparison.png', dpi=150)
plt.show()
Multi-Panel Dashboard
def simulation_dashboard(entity_data, time_series, capacity):
"""Create a multi-panel dashboard."""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Panel 1: Queue length
ax1 = axes[0, 0]
times = [r['time'] for r in time_series]
queues = [r['queue'] for r in time_series]
ax1.step(times, queues, where='post', color='steelblue')
ax1.fill_between(times, queues, step='post', alpha=0.3)
ax1.set_xlabel('Time')
ax1.set_ylabel('Queue Length')
ax1.set_title('Queue Length Over Time')
# Panel 2: Wait distribution
ax2 = axes[0, 1]
waits = [r['wait'] for r in entity_data]
ax2.hist(waits, bins=30, edgecolor='white', color='steelblue', alpha=0.7)
ax2.axvline(np.mean(waits), color='red', linestyle='--',
label=f'Mean: {np.mean(waits):.1f}')
ax2.set_xlabel('Wait Time')
ax2.set_ylabel('Frequency')
ax2.set_title('Wait Time Distribution')
ax2.legend()
# Panel 3: Utilisation
ax3 = axes[1, 0]
util = [r['busy'] / capacity for r in time_series]
ax3.plot(times, util, color='steelblue')
ax3.fill_between(times, util, alpha=0.3)
ax3.axhline(np.mean(util), color='red', linestyle='--')
ax3.set_xlabel('Time')
ax3.set_ylabel('Utilisation')
ax3.set_ylim(0, 1.1)
ax3.set_title('Resource Utilisation')
# Panel 4: Throughput
ax4 = axes[1, 1]
departures = sorted([r['departure'] for r in entity_data])
cumulative = range(1, len(departures) + 1)
ax4.plot(departures, cumulative, color='steelblue')
ax4.set_xlabel('Time')
ax4.set_ylabel('Cumulative Departures')
ax4.set_title('Throughput')
plt.tight_layout()
plt.savefig('dashboard.png', dpi=150)
plt.show()
Confidence Interval Plot
def plot_confidence_intervals(scenarios_results):
"""Bar chart with confidence intervals."""
from scipy import stats
fig, ax = plt.subplots()
names = list(scenarios_results.keys())
means = []
errors = []
for name, data in scenarios_results.items():
mean = np.mean(data)
se = stats.sem(data)
ci = 1.96 * se
means.append(mean)
errors.append(ci)
x = range(len(names))
ax.bar(x, means, yerr=errors, capsize=10, color='steelblue', alpha=0.7)
ax.set_xticks(x)
ax.set_xticklabels(names)
ax.set_ylabel('Mean Wait Time')
ax.set_title('Scenario Comparison with 95% Confidence Intervals')
plt.tight_layout()
plt.savefig('confidence.png', dpi=150)
plt.show()
Heatmap
def plot_heatmap(df, x_col, y_col, value_col):
"""Heatmap of values by two dimensions."""
pivot = df.pivot_table(values=value_col, index=y_col, columns=x_col, aggfunc='mean')
fig, ax = plt.subplots(figsize=(12, 8))
im = ax.imshow(pivot.values, aspect='auto', cmap='YlOrRd')
ax.set_xticks(range(len(pivot.columns)))
ax.set_xticklabels(pivot.columns)
ax.set_yticks(range(len(pivot.index)))
ax.set_yticklabels(pivot.index)
plt.colorbar(im, label=value_col)
ax.set_xlabel(x_col)
ax.set_ylabel(y_col)
ax.set_title(f'{value_col} by {x_col} and {y_col}')
plt.tight_layout()
plt.savefig('heatmap.png', dpi=150)
plt.show()
Animation (Basic)
from matplotlib.animation import FuncAnimation
def animate_queue(time_series, interval=100):
"""Animate queue length over time."""
fig, ax = plt.subplots()
ax.set_xlim(0, time_series[-1]['time'])
ax.set_ylim(0, max(r['queue'] for r in time_series) + 1)
ax.set_xlabel('Time')
ax.set_ylabel('Queue Length')
line, = ax.plot([], [], lw=2)
def init():
line.set_data([], [])
return line,
def update(frame):
data = time_series[:frame+1]
times = [r['time'] for r in data]
queues = [r['queue'] for r in data]
line.set_data(times, queues)
return line,
ani = FuncAnimation(fig, update, frames=len(time_series),
init_func=init, blit=True, interval=interval)
ani.save('queue_animation.gif', writer='pillow')
plt.show()
Complete Visualisation Suite
class SimulationVisualiser:
def __init__(self, entity_data, time_series, config):
self.entity_data = entity_data
self.time_series = time_series
self.config = config
def all_plots(self, save_dir='.'):
"""Generate all standard plots."""
import os
# Ensure directory exists
os.makedirs(save_dir, exist_ok=True)
# Queue length
self._plot_queue(f'{save_dir}/queue.png')
# Wait distribution
self._plot_wait_dist(f'{save_dir}/wait_dist.png')
# CDF
self._plot_cdf(f'{save_dir}/wait_cdf.png')
# Utilisation
self._plot_util(f'{save_dir}/utilisation.png')
# Dashboard
self._plot_dashboard(f'{save_dir}/dashboard.png')
print(f"All plots saved to {save_dir}/")
def _plot_queue(self, filename):
# Implementation as above
pass
def _plot_wait_dist(self, filename):
# Implementation as above
pass
# ... etc
Summary
Visualisation essentials: - Queue length over time (step plot) - Wait time distribution (histogram + CDF) - Utilisation (line + fill) - Scenario comparison (box plots) - Confidence intervals (error bars)
Show the data. Make it clear. Win the argument.
Next Steps
Discover the Power of Simulation
Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.
Explore the Bootcamp