< Home
A. Research ideas¶
The purpose of this notebook is:
- investigate in which countries fablabs can have most impact in specific SDGs
- countries where some SDGs have code RED,
- where there is a fair presence of fablabs and fablabs committed to SDGs,
- where fablabs sdg prevalence matches the SDGs with code RED;
- investigate which fablabs are best candidates to offer help from abroad
- countries where some SDGs have code GREEN,
- where here is a fair presence of fablabs and fablabs committed to the SDGs,
- where fablabs sdg prevalence matches the SDGs with code GREEN.
B. Research planning and design¶
- make a heatmap of the state of the SDGs in all countries
- make a heatmap of the state of the fablab sdg prevalences in all countries
- focus on relevant countries
- look for potential impact map by local fablabs
- look for potential support by foreign fablabs
# import python modules
import fabmodules as fm
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib.colors import ListedColormap
from IPython.display import Image, display
# set parameters
datafile = "fabnetdata.xlsx"
# directories
data_path = "data/" # include in repository
output_path = "outputs/" # exclude from repository
# sdg names
sdg_names = {
'01': '01: No poverty',
'02': '02: Zero hunger',
'03': '03: Good health',
'04': '04: Quality education',
'05': '05: Gender equality',
'06': '06: Clean water',
'07': '07: Clean energy',
'08': '08: Decent work',
'09': '09: Industry, innovation',
'10': '10: Reduced inequalities',
'11': '11: Sustainable cities',
'12': '12: Responsible cons. & prod.',
'13': '13: Climate action',
'14': '14: Life below water',
'15': '15: Life on land',
'16': '16: Peace, justice',
'17': '17: Partnerships'
}
sdg_nums = [f"{i:02d}" for i in range(1,18)] # list of strings 01-17
sdg_titles = [sdg_names[n] for n in sdg_nums] # list of "01: No poverty"...
# colors etc
traffic_strong="grey,red,orange,yellow,green".split(",")
traffic_soft=np.array([(231,231,242),(247,149,148),(247,180,149),(247,220,149),(144,240,156)]) / 255 # grey, red, orange, yellow, green
blue_scale=np.array([(231,231,242),(230,245,255),(189,227,251),(148,209,247),(81,172,230),(16,106,166)]) / 255 # grey, blue 20, 40, 60, 80, 100
green_scale=np.array([(231,231,242),(228,252,231),(196,245,202),(144,240,156),(84,209,95),(18,179,47)]) /255 # grey, green 20, 40, 60, 80, 100
red_scale=np.array([(231,231,242),(255,230,230),(251,188,195),(247,149,148),(230,82,81),(174,18,17)]) /255 # grey, red 20, 40, 60, 80, 100
# read fabnetdata
fablabs, fabcities, countries, continents, retrieved = fm.read_fabnetdata(datafile)
url https://gitlab.fabcloud.org/fl-management/fablab-network-data/-/raw/main/public/fabnetdata.xlsx fablabs (2597, 29) fabcities (56, 7) countries (250, 44) continents (7, 35) data retrieved 2025-12-01
Read Sustainable Development Report Data (newest annual update, 2025)¶
# read sustainable development report
sdr=fm.read_sdreport()
2025-12-18T18:33Z SDSN 2025 https://dashboards.sdgindex.org/static/downloads/files/SDR2025-data.xlsx (208, 39)
# state of the sdgs
# select columns from countries dataframe
columnlist = "country,cc2,cc3,ct2,continent,fablabs_c,profiles_c,fablabs_cpop,fablabs_carea,fablabs_density".split(",")
c0 = countries[columnlist].copy()
# select columns from sdr dataframe
columnlist = "cc3,indexScore,s01,s02,s03,s04,s05,s06,s07,s08,s09,s10,s11,s12,s13,s14,s15,s16,s17".split(",")
s0 = sdr[columnlist].copy()
# state_of_sdgs
state_sdgs = pd.merge(c0,s0, how="left", on="cc3")
state_sdgs.shape
(250, 28)
The state of the Fablabs¶
# state of the fablabs
# copy countries dataframe
df = countries.copy()
# convert prevalences to percentages
# column names
sdg_cols = [f"{i:02d}" for i in range(1, 18)]
df[sdg_cols] = (
df[sdg_cols].div(df["profiles_c"], axis=0) * 100
)
# convert percentages to categories
# cutoff points
b0 = 0
b1 = 25
b2 = 50
b3 = 75
b4 = 100
bins = [b0 - 0.0001, b0 + 0.0001, b1, b2, b3, b4]
labels = [0, 1, 2, 3, 4]
# apply classification
for col in sdg_cols:
df[col] = df[col].fillna(0) # handle NaN
df[col] = pd.cut(
df[col],
bins=bins,
labels=labels,
include_lowest=True
).astype(int)
# state of the fablabs
state_fablabs = df
state_fablabs.shape
(250, 44)
Reduce the number of countries¶
We focus on countries:
- that have fablabs, even more than .5 fablab per M inhabitants
- that have fablab SDG profiles, even at least half of the global average of 22%, so more than 11%
- which have been included into the SDG Index Report 2025
# parameters
min_fablabs_cpop = .5 # only countries with fablabs per M people more than this number
min_profiles_cperc = 11.0 # only countries where percentage of fablabs with sdg profile is higher than this
# reduce the number of countries
c0 = state_sdgs # all countries
fm.log("c0","all countries",c0)
c1 = c0[c0["fablabs_c"]>0] # countries with fablabs
fm.log("c1","countries with fablabs",c1)
c2 = c1[c1["fablabs_cpop"]> min_fablabs_cpop] # countries with fablabs per M people > min_fablabs_cpop
fm.log("c2","countries with fablabs per M people > " + str(min_fablabs_cpop),c2)
c3 = c2[c2["profiles_c"]>0] # countries with fablab sdg profiles
fm.log("c3","countries with fablab sdg profiles",c3)
c4 = c3[(c3["profiles_c"] / c3["fablabs_c"] * 100) > min_profiles_cperc]
fm.log("c4","countries with fablab sdg profiles % >"+str(min_profiles_cperc),c4)
c5 = c4[c4["indexScore"].notna()]
fm.log("c5","countries with sdr data present",c5)
# select the same countries for the state of the fablabs
d0 = state_fablabs
d5 = d0[d0["cc2"].isin(c5["cc2"])]
fm.log("d5","countries state of fablabs",d5)
2025-12-18T18:33Z c0 all countries (250, 28) 2025-12-18T18:33Z c1 countries with fablabs (142, 28) 2025-12-18T18:33Z c2 countries with fablabs per M people > 0.5 (78, 28) 2025-12-18T18:33Z c3 countries with fablab sdg profiles (56, 28) 2025-12-18T18:33Z c4 countries with fablab sdg profiles % >11.0 (52, 28) 2025-12-18T18:33Z c5 countries with sdr data present (44, 28) 2025-12-18T18:33Z d5 countries state of fablabs (44, 44)
Conclusion¶
The output of the previous cell shows how we downgraded the number of "eligible" counties due to the application of various criteria. The sequence of the criteria does not matter. The resulting number of countries is relatively low (44, where there are 142 countries with fablabs). The main reason is that the fab network data are based on countries (250), while the SDR data are based on sovereign states, i.c. UN member states (193).
E. Data Study and Analysis¶
Heatmap 1 - SDG Status by country¶
We want to investigate in which countries fablabs can have most impact in specific SDG.
For above list of countries we created a heat map "SDG Status by Country". The colors grey, red, orange, yellow, green indicate how far a country has come towards the goals for 2030. We focus on the worst cases: RED goals.
# prepare for state-of-sdgs heatmap
sdg_cols = [f"s{i:02d}" for i in range(1,18)] # s01...s17
mapping = {"grey": "0", "red": "1", "orange": "2", "yellow": "3", "green": "4"}
df = c5.copy()
df[sdg_cols] = df[sdg_cols].replace(mapping) # replace text "red" by value "1", etc
df[sdg_cols] = df[sdg_cols].astype(int) # replace "1" by integer 1 etc
data_s = df[sdg_cols].values # nd.array
def heatmap(title,df,data):
# software prepared by chatGPT
plt.figure(figsize=(18, 12))
plt.imshow(data, cmap=cmap, aspect='auto', vmin=0, vmax=4)
plt.title(title, fontsize=18, pad=20)
# Y-axis: countries
plt.yticks(np.arange(len(df)), df["country"], fontsize=10)
# Bottom: 01–17
plt.xticks(np.arange(17), sdg_nums, rotation=45, fontsize=10)
# Top: short SDG names
ax = plt.gca()
ax.tick_params(top=True, labeltop=True)
ax.set_xticks(np.arange(17), minor=False)
ax.set_xticklabels(sdg_titles, rotation=60, fontsize=9)
# Add grid lines
ax.set_xticks(np.arange(-0.5, 17, 1), minor=True)
ax.set_yticks(np.arange(-0.5, len(df), 1), minor=True)
ax.grid(which="minor", color="black", linestyle='-', linewidth=0.7)
ax.grid(which="major", visible=False)
#plt.tight_layout()
figname = title.lower().replace(" ","_")
plt.savefig(output_path+figname+".png")
plt.show()
cmap = ListedColormap(traffic_soft)
heatmap("SDG Status by Country",df,data_s)
print("Legend")
display(Image(filename="images/Legend sdr.png"))
Legend
Conclusion¶
The overall picture of the state of the SDGs shows that the SDGs worldwide are far from completed. We will focus especially on the worst cases (RED) that need support and the best cases (GREEN) that could have insights and good practices that could be "exported" to less favourite countries.
Example: Austria (line 1) has code GREEN for SDG-01 and SDG-07; it has code RED for SDG-12 and SDG-13.
Note that for space reasons we have shortened the titles of the 17 SDGs.
Heatmap 2 - Fablab status by country¶
We now create a heatmap for the same countries as before. Now we indicate for each SDG the prevalence in fablab sdg profiles; we convert the percentages in 5 categories: 0%, 0-25%, 25-50%, 50-75%, 75-100%, indicated by shades of blue.
# prepare for state-of-fabs heatmap
sdg_cols = [f"{i:02d}" for i in range(1,18)]
df = d5.copy()
df[sdg_cols] = df[sdg_cols].astype(int)
data_f = df[sdg_cols].values
cmap = ListedColormap(blue_scale)
heatmap("Fablab Status by Country",df,data_f)
print("Legend")
display(Image(filename="images/Legend blue.png"))
Legend
Conclusion¶
The "blue" heatmap shows the prevalence of fablabs by SDG by country. We will focus on the three darker shades of blue.
Example: Austria (line 1) has one of the three darker shades of blue for SDG-04, SDG-05, SDG-09, SDG-11 ans SDG-12. Note this matches only partially with the Red and Green SDGs in heatmap 1.
Heatmap 3 - Fablab status where SDG has code RED, by country¶
import numpy as np
# Keep only 1’s in data_s
data_s_clean = np.where(data_s == 1, 1, np.nan)
# Keep only 2/3/4 in data_f
data_f_valid = np.where(np.isin(data_f, [2, 3, 4]), data_f, np.nan)
# Result array
data_r = data_s_clean.copy()
# Replace 1’s in data_s with 2/3/4 from data_f
mask = (data_s_clean == 1) & ~np.isnan(data_f_valid)
data_r[mask] = data_f_valid[mask]
cmap = ListedColormap(red_scale)
heatmap("Fablab Status where SDG has code RED by Country",df,data_r)
print("Legend")
display(Image(filename="images/Legend red.png"))
Legend
Conclusion¶
Now, we have merged the first heatmap with the blue one as far as it concerns SDGs with code RED.
As an example: In Austria only SDG 12 and SDG 13 have RED (heatmap 1); the darker shades of red now are used to show a possible match with these two SDGs. Only SDG-12 has a match.
We in the case of Austria we need to inform the fablabs with SDG-12 in their profile about the state of SDG-12 in their country and provide them with additional information.
Heatmap 4 - Fablab status where SDG has code GREEN, by country¶
import numpy as np
# Keep only 1’s in data_s
data_s_clean = np.where(data_s == 4, 1, np.nan)
# Keep only 2/3/4 in data_f
data_f_valid = np.where(np.isin(data_f, [2, 3, 4]), data_f, np.nan)
# Result array
data_g = data_s_clean.copy()
# Replace 1’s in data_s with 2/3/4 from data_f
mask = (data_s_clean == 1) & ~np.isnan(data_f_valid)
data_g[mask] = data_f_valid[mask]
cmap = ListedColormap(green_scale)
heatmap("Fablab Status where SDG has code GREEN by Country",df,data_g)
print("Legend")
display(Image(filename="images/Legend green.png"))
Legend
Conclusion¶
Here, we have merged the first heatmap with the blue one as far as it concerns SDGs with code GREEN.
As an example: In Austria only SDG-01 and SDG-07 have code GREEN (heatmap 1); the darker shades of green now are used to show a possible match with these two SDGs. Unfortunately, there is no match.
F. Data Publishing and Access¶
Publication will require some improvements in the graphics first (see Evaluation and Follow-up, below) and are further depenandt of the intended audience:
- global information for a general public
- information by continent (or other country groupings) as input for scientific papers
- information by country to address all fablabs in a given country;
- information by sdg by country for the fablabs involved.
G. Data Preservation¶
- The input data is updated monthly (fablabs) or annually (sustainable development report). The notebook as is can be re-run at any moment.
- Once, our data publishing policy is clear, software runs can be added to the existing monthly processes.
H. Data Re-use¶
Evaluation and Follow-up¶
IMHO this approach for try to match SDG performance to fablab SDG profiles, either in the role of fablabs addressing the needs in their own country or fablabs being prepared to help fablabs abroad.
We should further think how to make the maps better to understand and/or how to split them up into a little graphic per country.
Follow-up¶
- try to improve the visual power of the heatmaps;
- consider reducing the fablab state to one shade of blue (red, green) instead of four;
- try to integrate the colour legends with the graphics;
- try to improve the x-axis labels; the shortened titles (as now) could be confusing;
- crosscheck for two countries that all the data are OK
- consider extending the active help to red plus orange and the passive help to green and yellow
- consider to increment the number of countries involved by playing with the filter parameters used;
- tell the story and add a few extra pictures
- sdg picture with only red
- sdg picture with only green
- generate a list of active help and passive help projects
- write mailing to involved fablabs
- how about making a heatmap for each country wit two lines only: the sdg scores and the fablab scores?