Violin Plot
Global Air Quality Index
Annual AQI distribution with health impact zones
Output
Python
import matplotlib.pyplot as plt
import numpy as np
# AQI data by city
np.random.seed(42)
cities = ['Stockholm', 'Vancouver', 'Tokyo', 'Los Angeles', 'Beijing', 'Delhi']
aqi_data = [
np.random.gamma(3, 5, 365), # Stockholm - excellent
np.random.gamma(4, 6, 365), # Vancouver - good
np.random.gamma(6, 7, 365), # Tokyo - moderate
np.random.gamma(8, 8, 365), # LA - moderate-unhealthy
np.random.gamma(15, 10, 365), # Beijing - unhealthy
np.random.gamma(20, 12, 365), # Delhi - hazardous
]
# AQI color scale
def get_aqi_color(val):
if val < 50: return '#22C55E' # Good
elif val < 100: return '#EAB308' # Moderate
elif val < 150: return '#F97316' # Unhealthy sensitive
elif val < 200: return '#EF4444' # Unhealthy
elif val < 300: return '#9333EA' # Very unhealthy
else: return '#7F1D1D' # Hazardous
colors = [get_aqi_color(np.median(d)) for d in aqi_data]
# Create figure
fig, ax = plt.subplots(figsize=(12, 7), facecolor='#F0FDF4')
ax.set_facecolor('#F0FDF4')
vp = ax.violinplot(aqi_data, positions=range(len(cities)), widths=0.75,
showmeans=False, showmedians=False, showextrema=False)
for i, body in enumerate(vp['bodies']):
body.set_facecolor(colors[i])
body.set_edgecolor('white')
body.set_linewidth(2)
body.set_alpha(0.75)
# AQI bands
bands = [(0, 50, '#22C55E', 'Good'), (50, 100, '#EAB308', 'Moderate'),
(100, 150, '#F97316', 'USG'), (150, 200, '#EF4444', 'Unhealthy'),
(200, 300, '#9333EA', 'Very Unhealthy'), (300, 400, '#7F1D1D', 'Hazardous')]
for y1, y2, color, label in bands:
ax.axhspan(y1, y2, color=color, alpha=0.1, zorder=0)
if y2 <= 250:
ax.text(5.55, (y1 + y2) / 2, label, fontsize=8, color=color,
va='center', fontweight='500')
# Statistics
for i, aqi in enumerate(aqi_data):
median = np.median(aqi)
p95 = np.percentile(aqi, 95)
ax.scatter(i, median, c='white', s=100, zorder=10,
edgecolor=colors[i], linewidth=2.5)
# 95th percentile marker
ax.scatter(i, p95, c=colors[i], s=40, marker='^', zorder=10,
edgecolor='white', linewidth=1)
# Healthy days percentage
healthy_pct = (aqi < 100).mean() * 100
ax.text(i, -25, f'{healthy_pct:.0f}%', ha='center', fontsize=10,
color='#22C55E' if healthy_pct > 80 else '#F97316',
fontweight='bold')
# Legend
ax.scatter([], [], c='white', s=80, edgecolor='#6366F1', linewidth=2, label='Median')
ax.scatter([], [], c='#6366F1', s=40, marker='^', edgecolor='white', label='95th percentile')
ax.legend(loc='upper left', frameon=True, facecolor='white',
edgecolor='#E5E7EB', fontsize=9)
# Label
ax.text(-0.5, -20, 'Healthy Days', fontsize=9, color='#6B7280')
# Styling
ax.set_xticks(range(len(cities)))
ax.set_xticklabels(cities, fontsize=11, fontweight='600')
ax.set_ylabel('Air Quality Index (AQI)', fontsize=12, fontweight='500', color='#374151')
ax.set_ylim(-35, 400)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color('#D1D5DB')
ax.spines['bottom'].set_color('#D1D5DB')
ax.tick_params(colors='#6B7280', labelsize=10)
plt.tight_layout()
plt.show()
Library
Matplotlib
Category
Statistical
More Violin Plot examples
☕