Mirror Chart

Urban vs Rural Income Distribution

Mirror density comparing household incomes with poverty line and median annotations

Output
Urban vs Rural Income Distribution
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

Did this help you?

Support PyLucid to keep it free & growing

Support