Where demographic weight does not match economic influence
Published
Nov 25, 2026
Keywords
divergence
Summary
The gap between a country’s share of the world’s population and its share of the global economy highlights a profound divergence. Some nations host large populations yet hold only a small fraction of global economic power, while others exert outsized influence relative to their demographic weight.
Code
# Libraries# =====================================================================import osfrom io import BytesIOimport requestsimport pandas as pdimport numpy as npimport seaborn as snsimport matplotlib.pyplot as pltimport matplotlib.image as mpimgfrom matplotlib.offsetbox import OffsetImage, AnnotationBboximport matplotlib.ticker as mtickimport matplotlib.patches as mpatchesfrom matplotlib.legend_handler import HandlerPatch# Data Extraction (Countries)# =====================================================================# Extract JSON and bring data to a dataframeurl ='https://raw.githubusercontent.com/guillemmaya92/world_map/main/Dim_Country.json'response = requests.get(url)data = response.json()df = pd.DataFrame(data)df = pd.DataFrame.from_dict(data, orient='index').reset_index()df_countries = df.rename(columns={'index': 'ISO3'})# Data Extraction - WBD (2024)# ========================================================#Parametroparameters = ['LP', 'NGDPD']# Create an empty listrecords = []# Iterar sobre cada parámetrofor parameter in parameters:# Request URL url =f"https://www.imf.org/external/datamapper/api/v1/{parameter}" response = requests.get(url) data = response.json() values = data.get('values', {})# Iterate over each country and yearfor country, years in values.get(parameter, {}).items():for year, value in years.items(): records.append({'parameter': parameter,'iso3': country,'year': int(year),'value': float(value) })# Create dataframedf = pd.DataFrame(records)# Pivot Parameter to columns and filter nullsdf = df.pivot(index=['iso3', 'year'], columns='parameter', values='value').reset_index()# Add URSS 1980urss = {'iso3': 'SUN', 'year': 1980, 'LP': 264, 'NGDPD': 354}df = pd.concat([df, pd.DataFrame([urss])], ignore_index=True)# Filter after 2024df = df[df['year'].isin([1992, 2024])]df = df.dropna(subset=['LP', 'NGDPD'])# Data Manipulation# =====================================================================# Merge queriesdf = df.merge(df_countries, how='left', left_on='iso3', right_on='ISO3')df = df[(df['Country_Abr'].notna()) | (df['iso3'] =='SUN')]df = df[['iso3', 'Country_Abr', 'year', 'LP', 'NGDPD']]df.columns = df.columns.str.lower()# Add calculated fieldsdf['lp_percent'] = df['lp'] / df.groupby('year')['lp'].transform('sum')df['ngdpd_percent'] = df['ngdpd'] / df.groupby('year')['ngdpd'].transform('sum')df['gap'] = df['ngdpd_percent'] - df['lp_percent']# Ordenar por gapdf = df.sort_values(by='gap', ascending=True)# Filter countriesdf = df[(df['iso3'].isin(['IND', 'NGA', 'PAK', 'IDN', 'BGD', 'CHN', 'USA', 'DEU', 'GBR', 'JPN', 'FRA']))]# Assign colorsdef get_color(row):if row['year'] ==2024:return'#C00000'if row['gap'] <0else'#153D64'else: # 1992return'#FFCCCC'if row['gap'] <0else'#C0E6F5'df['color'] = df.apply(get_color, axis=1)# Data Visualization# ==========================================# Font and styleplt.rcParams.update({'font.family': 'sans-serif', 'font.sans-serif': ['Franklin Gothic'], 'font.size': 9})sns.set(style="white", palette="muted")# Create figurefig, ax = plt.subplots(figsize=(8, 6))# Configuración de posicionescountries = df[df['year'] ==1992].sort_values('gap', ascending=True)['country_abr'].tolist()y_pos = np.arange(len(countries))bar_width =0.4# Plot bars for each yearfor i, year inenumerate(sorted(df['year'].unique())): year_data = df[df['year']==year].set_index('country_abr').reindex(countries) offsets = y_pos - bar_width/2+ i*bar_width plt.barh(offsets, year_data['gap'], height=bar_width, color=year_data['color'], label=str(year))# Labels for each columnyears =sorted(df['year'].unique())min_year, max_year = years[0], years[-1]for year in years: year_data = df[df['year']==year].set_index('country_abr').reindex(countries) offsets = y_pos - bar_width/2+ (0if year==min_year else bar_width)# Different color for min and max year color ='#595959'if year == min_year else'#0D0D0D'for y, val inzip(offsets, year_data['gap']): plt.text(val + (0.002if val >0else-0.002), y,f'{val:.1%}', va='center', ha='left'if val >0else'right', fontsize=7, color=color)# Define flagsflag_urls = {'CHN': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/CN.png','IND': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/IN.png','IDN': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/ID.png','NGA': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/NG.png','PAK': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/PK.png','BGD': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/BD.png','USA': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/US.png','GBR': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/GB.png','JPN': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/JP.png','FRA': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/FR.png','DEU': 'https://raw.githubusercontent.com/matahombres/CSS-Country-Flags-Rounded/master/flags/DE.png',}# Load flags onceflags = {country: mpimg.imread(BytesIO(requests.get(url).content)) for country, url in flag_urls.items()}# Add flagsax = plt.gca()for y, ctry inzip(y_pos, countries): iso = df.loc[df['country_abr'] == ctry, 'iso3'].iloc[0]if iso in flags: im = OffsetImage(flags[iso], zoom=0.025) ab = AnnotationBbox(im, (0, y), frameon=False, xycoords=('axes fraction','data'), box_alignment=(0,0.5)) ax.add_artist(ab)# Remove spinesfor position, spine in ax.spines.items():if position !='bottom': spine.set_visible(False)else: spine.set_color('#808080') spine.set_linewidth(1)# Add title and subtitlefig.add_artist(plt.Line2D([0.135, 0.135], [0.87, 0.97], linewidth=6, color='#203764', solid_capstyle='butt'))plt.text(0.02, 1.12, f'A Huge Democratic Divergence', fontsize=16, fontweight='bold', ha='left', transform=plt.gca().transAxes)plt.text(0.02, 1.08, f'Where demographic weight does not match economic influence', fontsize=11, color='#262626', ha='left', transform=plt.gca().transAxes)plt.text(0.02, 1.045, f'(difference between economic share and population share)', fontsize=9, color='#262626', ha='left', transform=plt.gca().transAxes)# Adjust axisplt.xlim(-0.25, 0.25)ax.xaxis.set_major_formatter(mtick.PercentFormatter(1.0))plt.xticks(fontsize=8)plt.yticks(y_pos, countries, fontsize=10)plt.axvline(0, color='#808080', linewidth=0.5)# Add label "Underrepresented" and "Overrepresented"plt.text(0, -0.12, 'Under-represented', transform=ax.transAxes, fontsize=8, fontweight='bold', color='darkred', ha='left', va='center')plt.text(0.85, -0.12, 'Over-represented', transform=ax.transAxes, fontsize=8, fontweight='bold', color='darkblue', va='center')# Add Data Sourceplt.text(0, -0.17, 'Data Source:', transform=plt.gca().transAxes, fontsize=8, fontweight='bold', color='gray')space =" "*23plt.text(0, -0.17, space +'IMF World Economic Outlook Database, 2024', transform=plt.gca().transAxes, fontsize=8, color='gray')# Legend configuration# Handler que divide el cuadrado en dos coloresclass HandlerSplitSquare(HandlerPatch):def__init__(self, color_left, color_right, **kwargs):super().__init__(**kwargs)self.color_left = color_leftself.color_right = color_rightdef create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): p1 = mpatches.Rectangle([xdescent, ydescent], width/2, height, facecolor=self.color_left, transform=trans) p2 = mpatches.Rectangle([xdescent+width/2, ydescent], width/2, height, facecolor=self.color_right, transform=trans)return [p1, p2]# Handles ficticiospatch_min = mpatches.Rectangle((0,0),1,1, facecolor="none")patch_max = mpatches.Rectangle((0,0),1,1, facecolor="none")plt.legend( handles=[patch_min, patch_max], labels=[f"{min_year}", f"{max_year}"], handler_map={ patch_min: HandlerSplitSquare("#C0E6F5", "#FFCCCC"), patch_max: HandlerSplitSquare("#153D64", "#C00000"), }, loc='lower center', bbox_to_anchor=(0.5, -0.12), ncol=2, fontsize=8, frameon=False)# Adjust layoutplt.tight_layout()# Save it...download_folder = os.path.join(os.path.expanduser("~"), "Downloads")filename = os.path.join(download_folder, f"FIG_IMG_GAP_GDP_Population.png")plt.savefig(filename, dpi=300, bbox_inches='tight')# Show :)plt.show()
Economic-Population Gap
Difference between economic share and population share.