Mirror Chart
Urban vs Rural Income Distribution
Mirror density comparing household incomes with poverty line and median annotations
Output
Python
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
np.random.seed(555)
BG_COLOR = '#ffffff'
TEXT_COLOR = '#1f2937'
urban = np.random.lognormal(4.3, 0.55, 1000)
rural = np.random.lognormal(3.9, 0.65, 1000)
urban = urban[urban < 250]
rural = rural[rural < 250]
fig, ax = plt.subplots(figsize=(12, 7), facecolor=BG_COLOR)
ax.set_facecolor(BG_COLOR)
x = np.linspace(0, 250, 500)
kde_u = gaussian_kde(urban)
y_u = kde_u(x)
ax.fill_between(x, y_u, alpha=0.25, color='#4927F5')
ax.plot(x, y_u, color='#4927F5', linewidth=3, label='Urban (median=$%.0fK)' % np.median(urban))
kde_r = gaussian_kde(rural)
y_r = kde_r(x) * -1
ax.fill_between(x, y_r, alpha=0.25, color='#6CF527')
ax.plot(x, y_r, color='#6CF527', linewidth=3, label='Rural (median=$%.0fK)' % np.median(rural))
ax.axhline(0, color=TEXT_COLOR, linewidth=1.5)
poverty_line = 30
ax.axvline(poverty_line, color='#ef4444', linestyle='--', linewidth=2, alpha=0.7)
ax.fill_betweenx([min(y_r), max(y_u)], 0, poverty_line, alpha=0.08, color='#ef4444')
ax.text(poverty_line + 3, max(y_u)*0.85, 'Poverty Line', color='#ef4444', fontsize=10, fontweight='bold')
urban_poverty = (urban < poverty_line).mean() * 100
rural_poverty = (rural < poverty_line).mean() * 100
ax.scatter([np.median(urban)], [kde_u(np.median(urban))], color='#4927F5', s=120, zorder=5, edgecolors='white', linewidths=2)
ax.scatter([np.median(rural)], [-kde_r(np.median(rural))], color='#6CF527', s=120, zorder=5, edgecolors='white', linewidths=2)
gap = np.median(urban) - np.median(rural)
ax.annotate('', xy=(np.median(urban), max(y_u)*0.5), xytext=(np.median(rural), max(y_u)*0.5),
arrowprops=dict(arrowstyle='<->', color='#F5276C', lw=2))
ax.text((np.median(urban)+np.median(rural))/2, max(y_u)*0.55, 'Gap: $%.0fK' % gap,
ha='center', color='#F5276C', fontsize=11, fontweight='bold')
stats = 'Below Poverty: Urban %.1f%% | Rural %.1f%%' % (urban_poverty, rural_poverty)
ax.text(0.5, 0.98, stats, transform=ax.transAxes, ha='center', va='top',
fontsize=10, color='#6b7280', fontfamily='monospace',
bbox=dict(boxstyle='round,pad=0.4', facecolor=BG_COLOR, edgecolor='#e5e7eb', lw=1.5))
ax.set_xlabel('Annual Household Income ($K)', fontsize=12, color=TEXT_COLOR, fontweight='500')
ax.set_title('Income Inequality: Urban vs Rural', fontsize=16, color=TEXT_COLOR, fontweight='bold', pad=25)
ax.tick_params(colors=TEXT_COLOR, labelsize=10)
ax.set_yticks([])
for spine in ['top', 'right']:
ax.spines[spine].set_visible(False)
for spine in ['bottom', 'left']:
ax.spines[spine].set_color('#e5e7eb')
ax.legend(loc='upper right', facecolor=BG_COLOR, edgecolor='#e5e7eb', labelcolor=TEXT_COLOR, fontsize=10)
plt.tight_layout()
plt.show()
Library
Matplotlib
Category
Statistical
☕