Global Patterns in Wealth-Income Ratios

economy
An overview of the economic situation
Published

Sep 21, 2025

Keywords

wealth-income

Summary

A plot that shows a snapshot of the wealth-income ratio in 2023, offering insight into the current economic landscape.

Code
# Libraries
# ===================================================
import requests
import pandas as pd
import numpy as np
import plotly.graph_objects as go

# Extract Data (Countries)
# ===================================================
# Extract JSON and bring data to a dataframe
url = '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'})

# Extract Data (WID)
# ===================================================
# URL del archivo Parquet en GitHub
url = "https://raw.githubusercontent.com/guillemmaya92/Analytics/master/Data/WID_Values.parquet"
df = pd.read_parquet(url, engine="pyarrow")
df = df[df['year'].isin([1980, 2023])]

# Transform Data
# ===================================================
df['tincome'] = df['tincome2'] / df['xusd'] / 1000
df['twealth'] = df['twealth2'] / df['xusd'] / 1000
df['gdptotal'] = df['gdptotal'] / df['xusd']
df['tincomeVAR'] = (df['tincome'] / df.groupby('country')['tincome'].shift(1) -1) * 100
df['twealthVAR'] = (df['twealth'] / df.groupby('country')['twealth'].shift(1) -1) * 100
df['wiratioVAR'] = (df['wiratio'] - df.groupby('country')['wiratio'].shift(1))
df = df[df['year'] == 2023]
df = df[df['wiratio'].notna() & df['tincome'].notna()]
df = pd.merge(df, df_countries, left_on='country', right_on='ISO2', how='inner')
df = df[['year', 'country', 'Country_Abr', 'gdptotal', 'tincome', 'twealth', 'wiratio', 'tincomeVAR', 'twealthVAR', 'wiratioVAR']]
df = df[(df['tincome'] >= 0) & (df['tincome'] <= 120000)]
df = df.sort_values(by='gdptotal', ascending=True)
df = df.rename(
        columns={
            'year': 'year',
            'Country_Abr': 'country_name',
            'gdptotal': 'total_income',
            'tincome': 'incomeCY',
            'twealth': 'wealthCY', 
            'wiratio': 'betaCY',
            'tincomeVAR': 'incomeVAR',
            'twealthVAR': 'wealthVAR',
            'wiratioVAR': 'betaVAR'
        }
    )
print(df)

# Data Visualization
# ===================================================
# Crea la figura
fig = go.Figure()

# Marker size y line width calculados
marker_size = np.sqrt(df["total_income"] / df["total_income"].max()) * 100 + 3
line_width  = np.sqrt(df["total_income"] / df["total_income"].max()) * 4 + 0.5

# Primero agregamos los puntos del scatter
fig.add_trace(go.Scatter(
    x=df["betaCY"],
    y=df["incomeCY"],
    mode='markers',
    text=df["country_name"],
    customdata=np.vstack((df["incomeCY"], df["wealthCY"], df["incomeVAR"], df["wealthVAR"], df["betaCY"], df["betaVAR"])).T,
    marker=dict(
        size=marker_size,
        color="rgba(0,0,0,0)",
        line=dict(
            width=line_width,
            color='black'
        )
    ),
    hovertemplate="<b>Country:</b> %{text}<br>" +
                  "<b>Income Avg ($):</b> %{y:.0f}k | <b>Var. 1980:</b> %{customdata[2]:.2f}%<br>" + 
                  "<b>Wealth Avg ($):</b> %{customdata[1]:.0f}k | <b>Var. 1980:</b> %{customdata[3]:.2f}%<br>" +
                  "<b>Ratio:</b> %{customdata[4]:.2f} | <b>Var. 1980:</b> %{customdata[5]:.2f}pp<extra></extra>",
    showlegend=False
))

# Ahora agregamos las imágenes de las banderas
for i, row in df.iterrows():
    country_iso = row["country"]
    
    # Calcular tamaño de la imagen
    image_size = marker_size[i] * 0.205

    # Añadir la imagen de la bandera, asegurándose de que el orden es correcto
    fig.add_layout_image(
        dict(
            source=f"https://raw.githubusercontent.com/guillemmaya92/world_flags_round/refs/heads/master/flags/{country_iso}.png",
            xref="x",
            yref="y",
            xanchor="center",
            yanchor="middle",
            x=row["betaCY"],
            y=row["incomeCY"],
            sizex=image_size,
            sizey=image_size,
            sizing="contain",
            opacity=0.8
        )
    )

# Add red and green shapes
fig.add_shape(
    type="rect",
    xref="x", yref="paper",
    x0=0, x1=6,
    y0=0, y1=1,
    fillcolor="green",
    opacity=0.04,
    layer="below",
    line_width=0
)
fig.add_shape(
    type="rect",
    xref="x", yref="paper",
    x0=6, x1=12,
    y0=0, y1=1,
    fillcolor="red",
    opacity=0.04,
    layer="below",
    line_width=0
)

# Configuration plot
fig.update_layout(
    title="<b>Wealth-Income Ratio</b>",
    title_x=0.11,
    title_y=0.93,
    title_font=dict(size=16),
    annotations=[
        dict(
            text="Global Patterns in Wealth-Income Ratios and Average Income per Capita",
            xref="paper",
            yref="paper",
            x=0,
            y=1.06,
            showarrow=False,
            font=dict(size=11)
        ),
        dict(
            text="<b>Data Source:</b> World Inequality Database (WID)",
            xref="paper",
            yref="paper",
            x=0,
            y=-0.12,
            showarrow=False,
            font=dict(size=10),
            align="left"
        ),
        dict(
            text=f"<b>Currency:</b> Official exchange rate {df["year"].max()} of the local currency to USD.",
            xref="paper",
            yref="paper",
            x=0,
            y=-0.14,
            showarrow=False,
            font=dict(size=10),
            align="left"
        ),
        dict(
            text=f"<i>@guillemmaya</i>",
            xref="paper",
            yref="paper",
            x=1,
            y=-0.14,
            showarrow=False,
            font=dict(size=11),
            align="right"
        ),
        dict(
            text=str(df["year"].max()),
            xref="paper", 
            yref="paper",
            x=1, 
            y=1.08,
            showarrow=False,
            font=dict(size=22, color='lightgray', weight='bold'),
            align="right"
        )
    ],
    xaxis=dict(
        title="<b>Wealth-Income Ratio</b>",
        range=[0, 12],
        tickvals=[i *  4 / 2 for i in range(7)],
        ticktext=[f"{int(i * 4 / 2)}" for i in range(7)],
        showline=True,
        linewidth=1,
        linecolor="black",
        gridcolor="#ebebeb"
    ),
    yaxis=dict(
        title="<b>Average Income per Capita ($US)</b>",
        range=[0, 120],
        tickvals=[i * 120 / 6 for i in range(7)],
        ticktext=[f"{int(i * 120 / 6)}k" for i in range(7)],
        showline=True,
        linewidth=1,
        linecolor="black",
        gridcolor="#ebebeb"
    ),
    height=750,
    width=750,
    plot_bgcolor="white",   
    paper_bgcolor="white"
)

# Add a custom legend
size_legend = ['Smaller', 'Middle', 'Bigger']
size_values = [5, 10, 20]

for label, size in zip(size_legend, size_values):
    fig.add_trace(go.Scatter(
        x=[None],
        y=[None],
        mode='markers',
        marker=dict(
            size=size,
            color="rgba(0,0,0,0)",
            line=dict(
                width=1,
                color='black'
            )
        ),
        legendgroup='size',
        showlegend=True,
        name=f'{label}'
    ))

fig.update_layout(
    legend=dict(
        title=dict(text='<b>  Total Income</b>'), 
        font=dict(size=11),
        x=0.025,
        y=0.95,
        xanchor='left',
        bgcolor='white',
        bordercolor='black',
        borderwidth=1
    )
)

# Save as HTML file!
fig.write_html("C:/Users/guill/Desktop/FIG_WID_CapitalisBack_Flag.html")
fig.write_image("C:/Users/guill/Desktop/FIG_WID_CapitalisBack_Flag.png")

# Show the plot!
fig.show()
Back to top