China's Financial Flows to Uzbekistan¶
Data Visualisation Project/p>
Audience¶
Our client is the NGO n-ost, an international media network focused on cross-border investigations. Their current project “Spheres of Influence Uncovered” is focused on "economic cooperation and geopolitical competition across borders".
They seek for data-driven insights into China’s development financing in Uzbekistan.
To further provide journalists with figures and trends to enrich and enable investigative articles.
Dataset¶
We are working with the AidData GDCF_3.0 (Global Chinese Development Finance Dataset), provided by the client, which includes a comprehensive record of China’s global development financing activities, capturing 20,985 projects.
The dataset covers China’s international development loans, grants and other form of assistance in the period from 2000 to 2021.
It includes 165 recipient low- and middle-income countries across Global South, but fro this particular project, we focus on all records pertaining to Uzbekistan.
AidData. 2023. Global Chinese Development Finance Dataset, Version 3.0. Retrieved from https://www.aiddata.org/data/aiddatas-global-chinese-development-finance-dataset-version-3-0
from google.colab import drive
drive.mount('/content/drive')
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
import pandas as pd
file_path = '/content/drive/MyDrive/AidData.xlsx'
df = pd.read_excel(file_path)
xls = pd.ExcelFile(file_path)
print(xls.sheet_names)
['Contents', 'Guide', 'Definitions', 'CountryList', 'GCDF_3.0', 'License']
df = pd.read_excel(file_path, sheet_name='GCDF_3.0')
Data Filtering and Preparation¶
After we load the AidData spreadsheet into a pandas DataFrame we subset our data to keep only the entries that are “Recommended For Aggregates,” which ensures we’re working with confirmed records.
Next, we select a subset of columns that seem most relevant to our analysis (Recipient, Year, Amount, Sector, etc.) and for further exploration and visualization.
We also define 2 functions, which will help us later with readability of hover and label annotations.
import pandas as pd
# Keep only confirmed flows
df_filtered = df[df['Recommended For Aggregates'] == 'Yes']
# Most interesting columns
columns_to_keep = [
'Recipient',
'Recipient Region',
'Commitment Year',
'Implementation Start Year',
'Completion Year',
'Status',
'Flow Type',
'Sector Name',
'Funding Agencies Type',
'Amount (Constant USD 2021)'
]
df_final = df_filtered[columns_to_keep].copy()
# Define functions for amount formatting
def format_hover(usd_value):
if pd.isnull(usd_value):
return ""
if usd_value < 1e9:
return f"{usd_value / 1e6:.2f} million"
else:
return f"{usd_value / 1e9:.2f} billion"
def format_label(usd_value):
if pd.isnull(usd_value):
return ""
if usd_value < 1e9:
return f"{usd_value / 1e6:.2f}M"
else:
return f"{usd_value / 1e9:.2f}B"
print(df_final.head())
Recipient Recipient Region Commitment Year Implementation Start Year \ 0 Afghanistan Asia 2021 2021.0 1 Afghanistan Asia 2021 2021.0 2 Afghanistan Asia 2021 2021.0 3 Afghanistan Asia 2021 2021.0 4 Afghanistan Asia 2021 NaN Completion Year Status Flow Type Sector Name \ 0 2021.0 Completion Grant EMERGENCY RESPONSE 1 2021.0 Completion Grant HEALTH 2 2021.0 Completion Grant HEALTH 3 2022.0 Completion Grant EMERGENCY RESPONSE 4 2021.0 Completion Grant EMERGENCY RESPONSE Funding Agencies Type Amount (Constant USD 2021) 0 Government Agency 7.111456e+06 1 Government Agency 1.260000e+07 2 Government Agency 1.440000e+07 3 Government Agency 1.300000e+07 4 Government Agency 7.500000e+06
Total Amount of Chinese Development Finance¶
In this section, we want to check which countries receive the greatest amount of development finance from China (based on commitments). By grouping the data by recipient country and summing the finance amounts, we can identify top recipients and see where Uzbekistan stands globally.
All the following amounts of finance are referred to a column 'Amount (Constant USD 2021)', which in turn refers to particular projects and their cost. Most of projects as wee will see later are implemented on China's loans, but some of them are grants and other types.
The is a preparation of the data frame for the following visualization.
# Group by country and sum the amounts
country_totals = (
df_final.groupby("Recipient", as_index=False)["Amount (Constant USD 2021)"]
.sum()
.rename(columns={"Amount (Constant USD 2021)": "Total Amount"}))
# Sort by total amount
country_totals = country_totals.sort_values(
by="Total Amount", ascending=False).reset_index(drop=True)
# Create a ranking column
country_totals["Rank"] = (
country_totals["Total Amount"]
.rank(method="min", ascending=False)
.astype(int))
print(country_totals.head(10))
# Display Uzbekistan's data
uzbekistan_data = country_totals[country_totals["Recipient"] == "Uzbekistan"]
print("...")
print(uzbekistan_data)
Recipient Total Amount Rank
0 Russia 1.695684e+11 1
1 Argentina 1.387510e+11 2
2 Venezuela 1.128844e+11 3
3 Pakistan 1.028524e+11 4
4 Angola 6.510495e+10 5
5 Kazakhstan 6.414616e+10 6
6 Indonesia 5.518729e+10 7
7 Brazil 5.434253e+10 8
8 Viet Nam 2.894673e+10 9
9 Turkey 2.833342e+10 10
...
Recipient Total Amount Rank
20 Uzbekistan 1.820903e+10 21
Visualization #1: Global distribution and Top Recipients¶
We create a composite figure that displays two interconnected visualizations:
Global Overview (Geo Map):
A geographic scatter plot illustrates China's development finance distributed across different recipient countries from 2000 to 2021.Top 10 Recipients (Bar Chart):
A horizontal bar chart shows the top 10 recipient countries by total investment. This chart quickly highlights which countries receive the most financing.
Additionally, we include a text that emphasizes key insights about Uzbekistan’s financing.
The composite visualization is designed to provide both a global context and a focused insight into the top recipients to compare it with Uzbekistan. It is more of an exploratory visualization.
from IPython.display import HTML, display
import plotly.io as pio #THIS CELL IS FOR HTML EXPORT
def export_fig(fig, filename):
html = pio.to_html(fig, full_html=True, include_plotlyjs='cdn')
with open(filename, 'w') as f:
f.write(html)
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
# Formatting for hover and labels
country_totals["Hover_Amount"] = country_totals["Total Amount"].apply(format_hover)
country_totals["Label_Amount"] = country_totals["Total Amount"].apply(format_label)
# a figure with 2 subplots
fig = make_subplots(
rows=1, cols=2,
specs=[[{"type": "geo"}, {"type": "xy"}]],
subplot_titles=("Global Chinese Development Finance (2000-2021)", "Top 10 Recipient Countries"))
for ann in fig.layout.annotations: # title settings
ann.y += 0.05
ann.font.size = 18
# Geo Map
map_fig = px.scatter_geo(
country_totals,
locations="Recipient",
locationmode="country names",
size="Total Amount",
hover_name="Recipient",
custom_data=["Hover_Amount"],
projection="equirectangular",
size_max=30)
map_trace = map_fig.data[0]
map_trace.marker.color = "red"
map_trace.hovertemplate = "<b>%{hovertext}</b><br>%{customdata[0]}<extra></extra>"
fig.add_trace(map_trace, row=1, col=1)
fig.update_geos(
projection_type="equirectangular",
fitbounds="locations",
showocean=False,
domain=dict(x=[0.0, 0.45], y=[0, 1]),
row=1, col=1)
# Bar Chart
top10 = country_totals.head(10).sort_values(by="Total Amount", ascending=True).copy()
bar_fig = px.bar(
top10,
x="Total Amount",
y="Recipient",
orientation="h",
text="Label_Amount",
custom_data=["Hover_Amount"])
bar_trace = bar_fig.data[0]
bar_trace.marker.color = "rgba(255,0,0,0.8)"
bar_trace.texttemplate = "%{text}"
bar_trace.textposition = "outside"
bar_trace.hovertemplate = "<b>%{y}</b><br>%{customdata[0]}<extra></extra>"
fig.add_trace(bar_trace, row=1, col=2)
fig.update_xaxes(domain=[0.51, 1.0], row=1, col=2) # barchart settings
fig.update_yaxes(domain=[0.27, 1.0], row=1, col=2)
fig.update_xaxes(visible=False, row=1, col=2)
fig.update_yaxes(visible=True, row=1, col=2)
fig.update_layout(plot_bgcolor="white")
# text annotation
long_text = (
"Over the past 10 years, <b>Uzbekistan</b> has received approximately "
"<b>$18.2 billion</b> in development finance from China.<br>"
"This places Uzbekistan <b>21st</b> among all recipient countries globally and "
"<b>2nd</b> among Central Asian countries.")
fig.add_annotation(
text=long_text,
xref="paper", yref="paper",
x=0.75, y=0.2,
showarrow=False,
align="left",
xanchor="center",
yanchor="top",
font=dict(size=14))
# Show the final figure
export_fig(fig, 'figure1.html')
Visualization #2.1: China’s Development Finance to Uzbekistan Yearly¶
To explore how amount of inflows have changed over time, we plot a simple line chart.
We highlight major spikes with annotations, indicating 2 largest loans for key infrastructure projects — particularly in the oil and gas sector.
Later, we will log scale them as outliers in separate visualization to check if there are any other patterns in trend.
# Filter for Uzbekistan
df_uz = df_final[df_final['Recipient'] == 'Uzbekistan']
# Group by Commitment Year for total investments
df_invest = df_uz.groupby('Commitment Year')['Amount (Constant USD 2021)'].sum().reset_index()
# Apply format_hover
df_invest['Hover_Amount'] = df_invest.apply(
lambda row: f"{int(row['Commitment Year'])}<br>{format_hover(row['Amount (Constant USD 2021)'])}", axis=1)
# Convert amount to billions for better Y-axis scaling
df_invest['Amount_Billions'] = df_invest['Amount (Constant USD 2021)'] / 1e9
fig = go.Figure()
# Line chart
fig.add_trace(
go.Scatter(
x=df_invest['Commitment Year'],
y=df_invest['Amount_Billions'],
mode='lines+markers',
name='Investments',
line=dict(color='red'),
text=df_invest['Hover_Amount'],
hovertemplate='%{text}<extra></extra>' ))
# Peak annotations
peak_notes = {
2008: "5.33B syndicated loan for gas pipeline",
2013: "1.35B for same project",
2019: "1.6B syndicated loan for gas-to-liquids plant"}
for year, note_text in peak_notes.items():
val = df_invest.loc[df_invest['Commitment Year'] == year, 'Amount_Billions']
if not val.empty:
y_val = val.iloc[0]
fig.add_annotation(
x=year,
y=y_val,
xref="x",
yref="y",
text=note_text,
showarrow=True,
arrowhead=2,
arrowcolor='grey',
ax=0,
ay=-40,
font=dict(color='grey', size=13),
bordercolor='grey',
borderwidth=1,
borderpad=4 )
# Layout updates
fig.update_layout(
title_text="China's Development Finance to Uzbekistan Yearly",
plot_bgcolor="white",
paper_bgcolor="white",
legend=dict(x=0.02, y=0.98))
fig.update_yaxes(
title_text="billion", # x label upd
tickformat=".0f", )
export_fig(fig, 'figure2.html')
Visualization #2.2: Linechart (Log Scale)¶
In this visualization, we apply a logarithmic scale to better capture trends in China’s development finance to Uzbekistan, that were overshadowed by a few large outliers.
We noticed and highlighted two key moments with annotations:
- 2007: Chinese Premier Wen Jiabao’s official visit to Uzbekistan marked a beginning of billion-size projects for years ahead.
- 2016: The transition to new President's leadership, leading to policy changes and new increase in chinese inflows. Furthermore, 2016 is one of the lowest year in amounts received. It is also the date of Uzbekistan's first President Islam Karimov's death.
import numpy as np
# Create a log-transformed column: log10(investment/100M)
df_invest['Amount_Log'] = np.log10(df_invest['Amount (Constant USD 2021)'] / 1e8)
# Apply format_hover function
df_invest['Hover_Amount'] = df_invest['Amount (Constant USD 2021)'].apply(format_hover)
df_invest['Hover_Amount'] = df_invest.apply(
lambda row: f"{int(row['Commitment Year'])}<br>{row['Hover_Amount']}", axis=1)
# Define tick values manually for better readability
tick_values = [0, 0.301, 0.477, 0.602, 0.699, 0.778, 1, 1.301, 1.477, 1.602, 1.699]
tick_text = ["100M", "200M", "300M", "400M", "500M", "600M", "1B", "2B", "3B", "4B", "5B"]
fig = go.Figure()
# Add trace using log-transformed values
fig.add_trace(
go.Scatter(
x=df_invest['Commitment Year'],
y=df_invest['Amount_Log'],
mode='lines+markers',
name='Investments',
line=dict(color='red'),
text=df_invest['Hover_Amount'],
hovertemplate='%{text}<extra></extra>' ))
# Find y-values for the grey dot placement
y_2007 = df_invest.loc[df_invest['Commitment Year'] == 2007, 'Amount_Log'].values[0]
y_2016 = df_invest.loc[df_invest['Commitment Year'] == 2016, 'Amount_Log'].values[0]
# Add vertical lines
for year in [2007, 2016]:
fig.add_shape(
go.layout.Shape(
type="line",
x0=year, x1=year,
y0=0, y1=1,
xref="x", yref="paper",
line=dict(color="grey", width=1, dash="dot") ))
# Add grey dots
fig.add_trace(go.Scatter(
x=[2007, 2016],
y=[y_2007, y_2016],
mode="markers",
marker=dict(size=6, color="grey"),
hoverinfo="skip", #remove hover interference
showlegend=False ))
# Add annotations
fig.add_annotation(
x=2007,
y=y_2007, # Align with grey dot
xref="x",
yref="y",
text="China's Premier Wen Jiabao paid an official visit to Uzbekistan",
showarrow=False,
align="right",
font=dict(color="grey", size=11.5),
xanchor="right" )
fig.add_annotation(
x=2016,
y=y_2016,
xref="x",
yref="y",
text="Uzbekistan entered an era under new President Shavkat Mirziyoyev",
showarrow=False,
align="left",
font=dict(color="grey", size=11.5),
xanchor="left")
# Layout updates
fig.update_layout(
title_text="China's Development Finance to Uzbekistan Yearly (Log Scale)",
plot_bgcolor="white",
paper_bgcolor="white",
showlegend=False)
fig.update_yaxes(
title_text="",
tickvals=tick_values,
ticktext=tick_text)
export_fig(fig, 'figure3.html')
Visualization #3: Funding Share by Flow Type¶
This visualization help us to see how China’s financial flows to Uzbekistan are structured, distinguishing between Loans, Grants, and Other forms of assistance.
We grouped up smaller types of inflows into Other, since it was too small for the scale. We also included the number of projects to show a few projects contain most of the finance.
Insights:
Loans dominate by far, accounting for ~96% of total financing, yet they represent only 26.6% of projects. This means that most of the capital is concentrated in a few large-scale loan agreements. I tried a risky move - big white text on the bar.
Grants contain only ~3% of total finance but a higher share of projects.
Other financial flows, including debt forgiveness, scholarships, rescheduling, and technical assistance, make up a marginal fraction of both funding and project count.
# Define Flow Type categories
loan_types = ["Loan"]
grant_types = ["Grant"]
other_types = df_final["Flow Type"].unique()
other_types = [t for t in other_types if t not in loan_types + grant_types]
df_final["Flow Category"] = df_final["Flow Type"].apply(
lambda x: "Loan" if x in loan_types else ("Grant" if x in grant_types else "Other"))
# group by and sum up
flow_summary = df_final.groupby("Flow Category").agg(
Total_Amount=("Amount (Constant USD 2021)", "sum"),
Project_Count=("Flow Type", "count")).reset_index()
# compute percentages
total_finance = flow_summary["Total_Amount"].sum()
total_projects = flow_summary["Project_Count"].sum()
flow_summary["Percentage_Amount"] = (flow_summary["Total_Amount"] / total_finance * 100).round(1)
flow_summary["Percentage_Projects"] = (flow_summary["Project_Count"] / total_projects * 100).round(1)
# apply formatting
flow_summary["Label_Amount"] = flow_summary["Total_Amount"].apply(format_label)
# create white label
flow_summary["Bar_Label"] = flow_summary.apply(
lambda row: f"{row['Percentage_Amount']}% of total funding<br>are located in {row['Percentage_Projects']}% of projects"
if row["Flow Category"] == "Loan" else "",
axis=1)
# hover text
flow_summary["Hover_Text"] = flow_summary.apply(
lambda row: f"{row['Label_Amount']}, {row['Project_Count']} projects",
axis=1)
fig = go.Figure()
# Bar Chart
fig.add_trace(go.Bar(
x=flow_summary["Flow Category"],
y=flow_summary["Total_Amount"],
text=flow_summary["Bar_Label"],
textposition="inside",
insidetextanchor="middle",
marker_color="red",
hovertemplate=flow_summary["Hover_Text"] + "<extra></extra>",
textfont=dict(size=20, color="white")
))
# Update layout
fig.update_layout(
title_text="Funding Share by Flow Type",
plot_bgcolor="white",
xaxis_title="",
yaxis_title="",
yaxis_showgrid=False,
xaxis_tickangle=0,
bargap=0.2,
showlegend=False )
export_fig(fig, 'figure4.html')
Visualization #4: Funding by Sector¶
The goal of this vis was to find out how Chinese development finance is distributed across different sectors in Uzbekistan.
We use two treemaps to illustrate this distribution:
- The first treemap highlights the top 7 sectors receiving the largest share of funding, with all remaining sectors grouped under “Other” in grey, since they are too small compared to top 7.
- The second treemap zooms into these smaller sectors.
Insights:
- Industry, Mining, and Construction dominates with 27% of total financing, followed by Energy (18%) and Banking & Financial Services (15%).
- Transport, infrastructure, and communications sectors also receive significant investment.
- The “Other” category accounts for 11% of total financing, covering a diverse set of smaller investments in areas such as education, health, governance, and trade policies.
# Shorter display names
sector_name_mapping = {
"DISASTER PREVENTION AND PREPAREDNESS": "DISASTER PREVENTION",
"DEVELOPMENTAL FOOD AID/FOOD": "FOOD",
"SECURITY ASSISTANCE": "SECURITY",
"ACTION RELATING TO DEBT": "DEBT RELATED",
"OTHER SOCIAL INFRASTRUCTURE AND SERVICES": "OTHER SOCIAL INFRASTRUCTURE",
"BUSINESS AND OTHER SERVICES": "BUSINESS",
"AGRICULTURE, FORESTRY, FISHING": "AGRICULTURE",
"OTHER COMMODITY ASSISTANCE": "OTHER COMMODITY",
"TRADE POLICIES AND REGULATIONS": "TRADE POLICIES",
"UNALLOCATED/UNSPECIFIED": "UNSPECIFIED",
"RECONSTRUCTION RELIEF AND REHABILITATION": "REHABILITATION",
"GENERAL ENVIRONMENTAL PROTECTION": "ENVIRONMENT",
"WATER SUPPLY AND SANITATION": "WATER",
"GENERAL BUDGET SUPPORT": "GENERAL BUDGET",
"POPULATION POLICIES/PROGRAMMES AND REPRODUCTIVE HEALTH": "POPULATION"
}
# Aggregate data by Sector
sector_summary = df_final.groupby("Sector Name", as_index=False).agg(
Total_Amount=("Amount (Constant USD 2021)", "sum"))
# Apply mapping for display names and ensure missing names remain visible
sector_summary["Sector Short Name"] = sector_summary["Sector Name"].apply(lambda x: sector_name_mapping.get(x, x))
# Apply hover formatting with full names
sector_summary["Hover_Amount"] = sector_summary.apply(
lambda row: f"{row['Sector Name']}<br>{format_hover(row['Total_Amount'])}", axis=1)
# Sort sectors by total amount
sector_summary = sector_summary.sort_values(by="Total_Amount", ascending=False)
# Define top 7 sectors
top_sectors = sector_summary.head(7).copy()
other_sectors = sector_summary.iloc[7:].copy()
# Calculate total for "Other" category
other_total = other_sectors["Total_Amount"].sum()
# Append "Other"
other_row = pd.DataFrame({
"Sector Name": ["Other"],
"Sector Short Name": ["Other"],
"Total_Amount": [other_total],
"Hover_Amount": [format_hover(other_total)]})
top_sectors = pd.concat([top_sectors, other_row], ignore_index=True)
# subplot layout
fig = make_subplots(
rows=1, cols=2,
subplot_titles=("Top 7 Sectors + Other", "Other Sectors"),
specs=[[{"type": "domain"}, {"type": "domain"}]])
# First treemap
fig.add_trace(
go.Treemap(
labels=top_sectors["Sector Short Name"],
parents=[""] * len(top_sectors),
values=top_sectors["Total_Amount"],
marker=dict(colors=["grey" if name == "Other" else "red" for name in top_sectors["Sector Short Name"]]),
textinfo="label+percent entry",
hovertext=top_sectors["Hover_Amount"],
hoverinfo="text",
insidetextfont=dict(color="white", size=14)),
row=1, col=1)
# Second treemap
fig.add_trace(
go.Treemap(
labels=other_sectors["Sector Short Name"],
parents=[""] * len(other_sectors),
values=other_sectors["Total_Amount"],
marker=dict(colors=["grey"] * len(other_sectors)),
textinfo="label+percent entry",
hovertext=other_sectors["Hover_Amount"],
hoverinfo="text",
insidetextfont=dict(color="white", size=14)),row=1, col=2)
# Update layout
fig.update_layout(
title_text="Funding by Sector",
plot_bgcolor="white",
showlegend=False,
uniformtext=dict(minsize=10, mode='show'))
export_fig(fig, 'figure5.html')
Visualization #5: Project Status Distribution¶
This stacked bar chart illustrates how many Chinese-financed projects in Uzbekistan are completed, and what status has the rest.
The majority (~78.5%) of all projects have reached completion. Meanwhile, a smaller proportion of projects are still in implementation, while a marginal share remains in the pipeline/commitment phase.
I have removed all axis legends, since everything seems clear.
# Group by status and count the number of projects
df_status = df_final.groupby('Status').size().reset_index(name='Project_Count')
# Sort by descending count
df_status = df_status.sort_values('Project_Count', ascending=False)
# Calculate percentage
total_projects = df_status['Project_Count'].sum()
df_status['Percentage'] = (df_status['Project_Count'] / total_projects) * 100
colors = ['red', 'grey', 'grey']
fig = go.Figure()
# Add each status as a stacked segment
for i, row in df_status.iterrows():
fig.add_trace(
go.Bar(
x=['Projects'],
y=[row['Project_Count']], # actual projects count
name=row['Status'],
text=[row['Status']], # show status name inside the bar
textposition='inside',
marker_color=colors[i],
textfont=dict(color='white'),
hoverinfo="text",
hovertext=f"{row['Status']}<br>{row['Project_Count']} projects<br>{row['Percentage']:.1f}% of total"))
#layout settings
fig.update_layout(
barmode='stack',
showlegend=False,
title="Project Status Distribution",
xaxis=dict(showticklabels=False, title=""),
yaxis=dict(showticklabels=False, title=""),
plot_bgcolor="white",
paper_bgcolor="white")
export_fig(fig, 'figure6.html')
Conclusion¶
China’s development finance to Uzbekistan has grown substantially over the past two decades, making Uzbekistan top 2 country-recipient in Central Asia.
A few exceptionally large loans drive the overall trend. Particularly in the industrial, mining, and construction sectors, as well as in energy.
Most of funding are alocated in loans. Smaller flow types such as grants and technical assistance exist but are overshadowed.
The majority of all projects have already reached completion.
Understanding the distribution and structure of these financial flows can hopefully help journalists to further investigate the nature of China’s role in Uzbekistan’s development.
!jupyter nbconvert --to html --execute /content/AidData_Uzbekistan-7.ipynb
[NbConvertApp] Converting notebook /content/AidData_Uzbekistan-7.ipynb to html 0.00s - Debugger warning: It seems that frozen modules are being used, which may 0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off 0.00s - to python to disable frozen modules. 0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation. 0.00s - Debugger warning: It seems that frozen modules are being used, which may 0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off 0.00s - to python to disable frozen modules. 0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation. [NbConvertApp] ERROR | unhandled iopub msg: colab_request [NbConvertApp] ERROR | unhandled iopub msg: colab_request [NbConvertApp] ERROR | unhandled iopub msg: colab_request [NbConvertApp] Writing 455784 bytes to /content/AidData_Uzbekistan-7.html