Durante los últimos diez años, los países desarrollados han experimentado una variedad de problemas relacionados con la migración. La Organización Internacional para las Migraciones (OIM) declaró en la Agenda 2030 para el Desarrollo Sostenible una serie de lineamientos que contribuyen directamente a muchos Objetivos de Desarrollo Sostenible (ODS). Por ejemplo:
Investigadores de todo el mundo buscan respuestas y soluciones para colaborar con estos lineamientos. En esta caso el papel de los científicos de datos, consiste en aplicar diferentes algoritmos para identificar causas raíz y hacer predicciones. Por ello, en este caso particular el presente estudio intenta colaborar en responder la siguiente pregunta:
"Cuáles son las posibles causas de la migración y cómo la migración evolucionará con el tiempo".
Para encontrar respuestas, se realizará una exploración de la migración a nivel global y un caso particular de la migración en América del Sur. En general, el estudio actual se puede tratar como análisis de datos exploratorios. Es decir, se explorarán los conjuntos de datos sobre la correlación y se construirán algunos modelos de regresión. Para el desarrollo de las predicciones se consideran los problemas y limitaciones de dicho análisis (falsa correlación, multicolinealidad, etc.) sin embargo, los resultados obtenidos se compararán con los resultados del modelo System Dynamics realizado por Wigman (2018).
Desde el punto de vista práctico, este Jupyter Notebook consta de los siguientes pasos. Primero, temas relacionados con los conjuntos de datos descargados y preprocesados. En segundo lugar, los resultados se trazan para obtener algunas ideas. En tercer lugar, se aplicaran un par de algoritmos para identificar relaciones y predecir flujos de migración futuros. Y finalmente, una comparación con los resultados del modelo System Dynamics realizado.
Para responder a la pregunta de investigación establecida se utilizaron 4 conjuntos de datos, a saber,
El primero servirá principalmente para fines de visualización. Los valores del segundo servirán como variables predictivas (y), mientras que el Índice de estado frágil como predictores (x). El cuarto a efectos comparativos.
Finalmente, se utilizó Natural Earth para unsar los shapefiles y trazar los datos en el mapa mundial.
Este conjunto de datos no es más que datos de migración por país de origen y destino. Se descargó de las Naciones Unidas (2015) como un archivo Excel en formato xlsx. Los datos sobre diferentes años se dividen en hojas de Excel separadas. Las filas representan región, subregión o país de destino, mientras que las columnas son países de origen. El archivo original se modificó ligeramente, por ejemplo, los encabezados se dividieron para evitar problemas con el índice múltiple.
Desafortunadamente, este conjunto de datos tiene un número bastante limitado de puntos de datos y, por lo tanto, difícilmente puede usarse para algoritmos. Sin embargo, se puede utilizar para visualizar dinámicas de migración. Igualmente se descartó utilizar los datos preliminares para el 2017, ya que para el caso práctico de América del Sur es evidente el ruido en los datos para Venezuela debido al propio ajuste que debe realizarse.
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot')
%matplotlib inline
import seaborn as sns
import pandas as pd
import geopandas as gpd
from sklearn import preprocessing
import time
# Almacenaremos todas las hojas para varios años como un dictado de pandas para un fácil acceso por año
sheet_name = "Table " # Nombre original de la hoja
sheet_numbers = [1, 4, 7, 10, 13, 16] # Número de hojas
years = [1990, 1995, 2000, 2005, 2010, 2015] # Años disponibles
n = len(sheet_numbers) # Número de datos disponibles
my_dict = {} # estructura de los datos
# {'1990' : df_1990, '1995' : df_1995 ...}
path = "data/UN_MigrantStockByOriginAndDestination_2015.xlsx"
start_time = time.time()
for i in range(n):
sheet_name = "Table " + str(sheet_numbers[i])
my_dict[years[i]] = pd.read_excel(path, sheet_name=sheet_name, header=[15])
print(str(years[i]) + " datos cargados")
elapsed_time = time.time() - start_time
print(str(round(elapsed_time, 2)) + " tiempo")
# Datos
my_dict[2010].head()
for key, value in my_dict.items():
# Se ajustan nombres de las columnas
s = my_dict[key].columns.values
s[1] = 'Destination'
my_dict[key].columns = s
# Se eliminan datos irrelevantes
my_dict[key] = my_dict[key].drop(
['Sort\norder', 'Notes', 'Country code', 'Type of data (a)'], axis=1)
my_dict[key] = my_dict[key].drop([0, 1, 2, 3, 4, 5]).reset_index(drop=True)
# Datos Nulos
my_dict[key] = my_dict[key].fillna(0)
# Data
my_dict[2010].head()
from copy import deepcopy
# Se estructuran los datos agrupandolos y ordenandolos para una mejor manipulación
# Para evitar problemas de columnas dejaremos el nombre de los datos tal cual como se presentan en idioma Inglés
df = deepcopy(my_dict[2010])
regions = ['Africa', 'Asia', 'Europe',
'Latin America and the Caribbean', 'Northern America', 'Oceania']
subregions = {regions[0]: ['Eastern Africa', 'Middle Africa', 'Northern Africa', 'Southern Africa', 'Western Africa'],
regions[1]: ['Central Asia', 'Eastern Asia', 'South-Eastern Asia', 'Southern Asia', 'Western Asia'],
regions[2]: ['Eastern Europe', 'Northern Europe', 'Southern Europe', 'Western Europe'],
regions[3]: ['Caribbean', 'Central America', 'South America'],
regions[4]: [],
regions[5]: ['Australia and New Zealand',
'Melanesia', 'Micronesia', 'Polynesia']
}
countries = {} # Clave para las regiones y subregiones
for key in subregions:
for i in range(0, len(subregions[key])):
if (i == len(subregions[key]) - 1):
first_country = df[df.Destination ==
subregions[key][i]].index.values[0] + 1
if (key != 'Oceania'):
next_region = regions.index(key) + 1
last_country = df[df.Destination ==
regions[next_region]].index.values[0]
else:
last_country = len(df)
else:
# primer pais
first_country = df[df.Destination ==
subregions[key][i]].index.values[0] + 1
last_country = df[df.Destination == subregions[key]
[i + 1]].index.values[0] # último país de la región
l = [] # lista de paises dentro de la region
for k in range(first_country, last_country):
l.append(df.Destination[k])
countries[subregions[key][i]] = l
# Modificamos el nombre d elas filas
for key, value in my_dict.items() :
my_dict[key].index = my_dict[key].Destination
my_dict[key] = my_dict[key].drop(columns='Destination')
# Datos
my_dict[2010].head()
from pprint import pprint
def get_countries_from_region(region, subregions, countries):
"""List all countries within a given region
Keyword arguments:
region -- region of interest
subregions -- dict with regions and subregions
countries -- dict with subregions and countries
"""
res = []
for subregion in subregions[region]:
res.append(countries[subregion])
res = [item for sublist in res for item in sublist]
return res
# Si todo sale bien deberia imprimir la lista de paises según región y subregión
America = get_countries_from_region("Latin America and the Caribbean", subregions, countries)
pprint(America[:5])
# Extraemos los datos de migraciones para los años de estudio para Colombia
def extract_temporal_data(country, my_dict, years=[1990, 1995, 2000, 2005, 2010, 2015]):
"""Extract total migration values for a given time frame
Keyword arguments
country -- country of interest
years -- years under consideration
my_dict -- data set that contains all migration data
"""
res = []
for year in years:
res.append(my_dict[year].loc[country, "Total"])
return res
extract_temporal_data("Colombia", my_dict)
# Extraemos los datos de migraciones para los años de estudio para Ecuador
def extract_temporal_data(country, my_dict, years=[1990, 1995, 2000, 2005, 2010, 2015]):
"""Extract total migration values for a given time frame
Keyword arguments
country -- country of interest
years -- years under consideration
my_dict -- data set that contains all migration data
"""
res = []
for year in years:
res.append(my_dict[year].loc[country, "Total"])
return res
extract_temporal_data("Ecuador", my_dict)
# Graficamos los valores de migración
def bar_plot(origin, destination, year, my_dict, countries, figsize=(8, 6)):
"""Plot migration as a bar plot where bars are contries of origin
and y axis is values for specified country of destination
Keyword arguments:
origin -- country or list of countries of origin
destination -- country of destination
year -- year under consideration
my_dict -- data set that contains all migration data
countries -- dict with subregions and countries
figsize -- size of the figure if should be adjusted
"""
# Select data to plot
X = my_dict[year].loc[destination, countries[origin]]
# Set up plot parameters
fig, ax = plt.subplots(figsize=figsize)
plt.style.use('ggplot')
# Set tick labels
ax.set_xticks(list(range(1, len(X) + 1)))
ax.set_xticklabels(countries[origin])
ax.xaxis.set_tick_params(rotation=45)
# Set up title and labels
title = "Migración desde " + origin + \
" a " + destination + " en " + str(year)
ax.set_title(title)
ax.set_ylabel("Número de personas")
ax.set_xlabel("Paises de Origen")
ax.bar(list(range(1, len(X) + 1)), X)
bar_plot("South America", 'Colombia', 2015, my_dict, countries)
#
time_frame = [1990, 1995, 2000, 2005, 2010, 2015]
plt.title("Migración Total a Colombia")
plt.xlabel("Año")
plt.ylabel("Personas")
plt.bar(time_frame, extract_temporal_data("Colombia", my_dict))
#
time_frame = [1990, 1995, 2000, 2005, 2010, 2015]
plt.title("Migración Total a Ecuador")
plt.xlabel("Año")
plt.ylabel("Personas")
plt.bar(time_frame, extract_temporal_data("Ecuador", my_dict))
#
time_frame = [1990, 1995, 2000, 2005, 2010, 2015]
plt.title("Migración Total a Venezuela")
plt.xlabel("Año")
plt.ylabel("Personas")
plt.bar(time_frame, extract_temporal_data("Venezuela (Bolivarian Republic of)", my_dict))
def line_chart(origin, destination, my_dict,
years=[1990, 1995, 2000, 2005, 2010, 2015], figsize=(8, 6)):
"""Plot migration as a line chart within a given time frame
Keyword arguments:
origin -- country or list of countries of origin
destination -- country of destination
years -- years under consideration
my_dict -- data set that contains all migration data
figsize -- size of the figure if should be adjusted
"""
X = []
# Extraemos los datos a graficar
for year in years:
X.append(year)
X.append(my_dict[year].loc[destination, origin])
X = np.array(X)
sh = (len(years), 2)
X = X.reshape(sh)
# Configuramos parámetros
fig, ax = plt.subplots(figsize=figsize)
plt.style.use('ggplot')
# Configuramos titulos
title = "Migración desde " + origin + " a " + destination + \
" Desde " + str(years[0]) + " a " + str(years[-1])
ax.set_title(title)
ax.set_ylabel("Número de personas migradas")
ax.set_xlabel("Año")
ax.plot(X[:, 0], X[:, 1])
line_chart("Venezuela (Bolivarian Republic of)", "Colombia", my_dict)
line_chart("Colombia", "Venezuela (Bolivarian Republic of)", my_dict)
line_chart("Venezuela (Bolivarian Republic of)", "Ecuador", my_dict)
line_chart("Ecuador", "Venezuela (Bolivarian Republic of)", my_dict)
# Graficamos la migración total para la región desde un determinado país
def lines_chart(origins, destination, my_dict,
years=[1990, 1995, 2000, 2005, 2010, 2015], figsize=(8, 6)):
"""Plot single or multiple line cahrts representing migration
from origin(-s) to destination within a given time frame
Keyword arguments:
origins -- country or countries of origin
destination -- country of destination
years -- years under consideration
my_dict -- data set that contains all migration data
figsize -- size of the figure if should be adjusted
"""
x = []
# Extraemos los datos a graficar
for origin in origins:
X = []
for year in years:
X.append(year)
X.append(my_dict[year].loc[destination, origin])
x.append(X)
for i in range(len(x)):
x[i] = np.array(x[i])
sh = (len(years), 2)
x[i] = x[i].reshape(sh)
# Configuramos los parámetros
fig, ax = plt.subplots(figsize=figsize)
plt.style.use('ggplot')
# Configuramos los titulos
title = "Migración a " + destination + " Desde " + \
str(years[0]) + " to " + str(years[-1])
ax.set_title(title)
ax.set_ylabel("Número de personas Migradas")
ax.set_xlabel("Año")
for i in range(len(x)):
ax.plot(x[i][:, 0], x[i][:, 1])
ax.legend(origins)
lines_chart(countries["South America"], "Venezuela (Bolivarian Republic of)", my_dict)
Para obtener una mejor visión general de alto nivel, se pueden utilizar dos formas de visualización de datos: Heatmap y mapa de Clúster Map. Para identificar los posibles grupos de países (según sus valores de migración). Una vez identificados los es posible identificar los responsables de la formulación de políticas a manera de establecer coaliciones o asociaciones.
# Hacemos un Heatmap por regiones
def migration_heatmap(origin, destination, year, my_dict, subregions, countries, scale=True):
"""Plot migration values as a heatmap
Keyword arguemnts:
origin -- region of origin (Asia, for example)
destination -- region of destination (Europe, for example)
year -- year of interest
my_dict -- data set that contains all migration data
subregions -- subregions dict created earlier
countries -- countires dict created earlier
scale -- make scaling or not
"""
a = get_countries_from_region(origin, subregions, countries)
b = get_countries_from_region(destination, subregions, countries)
# Ajustamos el tamaño
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 12
fig_size[1] = 9
plt.rcParams["figure.figsize"] = fig_size
X = my_dict[year].loc[b, a]
if (scale == True):
# Escalamos para una mejor representación
min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
X_train_minmax = min_max_scaler.fit_transform(X)
xticks = X.columns.values
yticks = X.index.values
sns.heatmap(X_train_minmax, xticklabels=xticks,
yticklabels=yticks)
else:
sns.heatmap(X)
migration_heatmap("Latin America and the Caribbean", "Europe", 2015, my_dict, subregions, countries)
Un patrón interesante se puede observar aquí. Hay un par de países que están tomando un número significativo de inmigrantes de América del Sur: España principalmente para los paises americanos de habla castellana y para los paises de habla inglesa las naciones receptoras principalmente figuran Reino Unido y Holanda.
# Hacemos un Clustermap por regiones en este caso continuamos comparando la emigración desde América Latina a Europa
def migration_clustermap(origin, destination, year, my_dict, subregions, countries, scale = True) :
"""Plot migration values as a clustermap
Keyword arguemnts:
origin -- region of origin (Asia, for example)
destination -- region of destination (Europe, for example)
year -- year of interest
my_dict -- data set that contains all migration data
subregions -- subregions dict created earlier
countries -- countires dict created earlier
scale -- make scaling or not
"""
a = get_countries_from_region(origin, subregions, countries)
b = get_countries_from_region(destination, subregions, countries)
# Ajustamos el tamaño
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 12
fig_size[1] = 9
plt.rcParams["figure.figsize"] = fig_size
X = my_dict[year].loc[b, a]
if (scale == True) :
# Escalamos para una mejor representación
min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
X_train_minmax = min_max_scaler.fit_transform(X)
xticks = X.columns.values
yticks = X.index.values
sns.clustermap(X_train_minmax, xticklabels=xticks,
yticklabels=yticks)
else :
sns.clustermap(X)
migration_clustermap("Latin America and the Caribbean", "Europe", 2015, my_dict, subregions, countries)
Ahora es posible observar cuáles son los grupos de países que experimentan los mayores flujos migratorios. Sin embargo, todos los países de origen estan asociados a su historia colonial. La migración a España, por ejemplo, es mayoritariamente de América Latina, mientras que la migración a Reino Unido e Irlanda proviene de sus extintas colonias, así como tambien sucede entre Brasil y Portugal.
El Banco Mundial es bien conocido por sus conjuntos de datos abiertos. El conjunto de datos de migración neta refleja la cantidad de personas que abandonan o ingresan al país en un año determinado. El conjunto de datos se descargó del Banco Mundial (2018) como archivo Excel en xls fromat. Los datos de todos los años y todos los países se encuentran almacenados en una sola hoja.
# Origen de los datos
netm_df = pd.read_excel(
"data/API_SM.POP.NETM_DS2_en_excel_v2_10182252.xls", sheet_name="Data")
# Datos
netm_df.head(5)
# Preprocesamiento del DataSet
netm_df.index = netm_df["Country Name"]
netm_df = netm_df.drop(columns=["Indicator Name", "Indicator Code"])
# Se eliminan datos irrelevantes
netm_df = netm_df.dropna(axis=1, how='all')
# Observamos el DataSet
netm_df.head()
Choropleth
Un mapa coroplético nos brindará una visión espacial del conjunto de datos
# Cargamos los datos
g_df = gpd.read_file(
"data/geo/ne_10m_admin_0_countries.shp")[['ADM0_A3', 'geometry']].to_crs('+proj=robin')
# Utilizamos un elemento clave para unir el codigo de nombre del país con el shapefile utilizando Geopandas
netm_df = pd.read_excel(
"data/API_SM.POP.NETM_DS2_en_excel_v2_10182252.xls", sheet_name="Data")
netm_df = netm_df.drop(columns=["Indicator Name", "Indicator Code"])
netm_df = netm_df.dropna(axis=1, how='all')
def plot_map(year):
"""Plot choropleth representing net migration by country.
Keyword arguments:
year -- year of interest
netm_df -- World Bank net migration data set
g_df -- shapefiles in geopandas data frame
"""
year = str(year)
if year not in list(netm_df.columns):
raise ValueError("This year is unavailable. Please, try another one.")
clusters = 9 # número de agrupaciones
cmap = "OrRd" # Color
figsize = (16, 10) # Medidas
cols = ["Country Name", "Country Code", year]
title = "Migración Neta en " + year # Titulo del mapa
# Conectamos al shapefile
merged_df = g_df.merge(
netm_df[["Country Code", year]], left_on="ADM0_A3", right_on="Country Code")
# Graficamos
ax = merged_df.dropna().plot(column=year, cmap=cmap,
scheme='equal_interval', figsize=figsize, k=clusters, legend=True)
# Eliminamos datos irrelevantes
merged_df[merged_df.isna().any(axis=1)].plot(
ax=ax, color='#fafafa', hatch='...')
# Configuramos ejes
ax.set_title(title, fontdict={'fontsize': 20}, loc='left')
ax.set_axis_off()
ax.set_xlim([-1.5e7, 1.7e7])
ax.get_legend().set_bbox_to_anchor((.12, .4))
ax.get_figure()
plot_map("2017")
Modificando un poco el código es posible crear una visualización interactiva bajo el esquema de serie de tiempo para ver la evolución de los valores de Migración Neta anuales
from ipywidgets import interactive
years = netm_df.columns[2:]
step = 5
start = int(years[0])
end = int(years[-1])
interactive_plot = interactive(plot_map, year=(start, end, step))
output = interactive_plot.children[-1]
output.layout.height = '550px'
interactive_plot
# Agregamos los datos para diseñar el Heatmap y el Clústermap
netm_df = pd.read_excel(
"data/API_SM.POP.NETM_DS2_en_excel_v2_10182252.xls", sheet_name="Data")
netm_df.index = netm_df["Country Name"]
netm_df = netm_df.drop(
columns=["Country Name", "Country Code", "Indicator Name", "Indicator Code"])
netm_df.columns = [int(i) for i in list(netm_df.columns)]
netm_df = netm_df.dropna(axis=1, how='all')
netm_df = netm_df.dropna(axis=0, how='all')
def netmigration_chmap(countries, netm_df, flag):
"""Plot net migration values as a heatmap or clustermap
Keyword arguemnts:
countries -- list of countries to show
netm_df -- data set with net migration values
flag -- 1 if heatmap and 2 if clustermap
"""
# Ajustamos medidas
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 12
fig_size[1] = 9
plt.rcParams["figure.figsize"] = fig_size
X = netm_df.loc[countries, :].fillna(0)
min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
X_train_minmax = min_max_scaler.fit_transform(X)
xticks = X.columns.values
yticks = X.index.values
if flag == 1:
sns.heatmap(X_train_minmax, xticklabels=xticks,
yticklabels=yticks)
elif flag == 2:
sns.clustermap(X_train_minmax, xticklabels=xticks,
yticklabels=yticks)
netmigration_chmap(America, netm_df, 1)
Parece que Chile ha dominado la recepcción del flujo de inmigración
El índice de estado frágil (FSI) es un informe anual realizado por el Fund for Peace del Fondo para la Paz. Este informe se basa en un conjunto de 12 indicadores que se utilizan para caracterizar la vulnerabilidad de los países al conflicto o colapso. Los indicadores se dividen en 4 categorías:
Todos los indicadores se nombran de manera que sean intuitivamente comprensibles. Los valores de los indicadores varían en la escala de 0 a 10, donde 0 es absolutamente estable y 10 es altamente vulnerable. Todos los indicadores se suman al puntaje FSI total que se utiliza para la clasificación de los países. Dependiendo de la puntuación los países se dividen en 4 categorías:
Hay un proceso complejo detrás del cálculo de estos indicadores. Para más detalles y críticas ver el Fondo para la Paz (2018).
Nuestra hipótesis aquí es que estos indicadores pueden explicar la migración hacia o desde un determinado país, y por lo tanto, pueden usarse para nuestros modelos estadísticos. El conjunto de datos es un conjunto de países e indicadores correspondientes.
fsi_years = list(range(2006, 2018)) # Años disponibles
file_name = "fsi-" # archivos
extension = ".xlsx"
fsi_dict = {} # estructura de los DataSet
# Leemos los archivos en Pandas
for i in range(len(fsi_years)):
path = "data/" + file_name + str(fsi_years[i]) + extension
fsi_dict[fsi_years[i]] = pd.read_excel(path)
# Ajustamos los datos por país y año
for key, values in fsi_dict.items():
fsi_dict[key].index = fsi_dict[key].Country
fsi_dict[key] = fsi_dict[key].drop(columns=['Country', 'Year'])
# Resultado
fsi_dict[2010].head(5)
def extract_temporal_fsi_data(country, fsi_dict, years=list(range(2006, 2018))):
"""Extract FSI values for a given time frame
Keyword arguments
country -- country of interest
fsi_dict -- data set that contains all FSI data
years -- years under consideration
"""
d = {}
for year in years:
if country in fsi_dict[year].index:
d[year] = fsi_dict[year].loc[country, "Total":].values
return d
pprint(extract_temporal_fsi_data("Colombia", fsi_dict))
def extract_total_fsi_country(country, fsi_dict, years=list(range(2006, 2018))):
"""Extract total FSI score for a given time frame
Keyword arguments
country -- country of interest
fsi_dict -- data set that contains all FSI data
years -- years under consideration
"""
l = []
for year in years:
if country in fsi_dict[year].index:
l.append(fsi_dict[year].loc[country, "Total"])
return l
extract_total_fsi_country("Ecuador", fsi_dict)
def extract_total_fsi_countries(countries, fsi_dict, years=list(range(2006, 2018))):
"""Extract total FSI scores for specified countries in a given time frame
Keyword arguments
countries -- countries of interest
fsi_dict -- data set that contains all FSI data
years -- years under consideration
"""
d = {}
for country in countries:
l = []
for year in years:
if country in fsi_dict[year].index:
l.append(fsi_dict[year].loc[country, "Total"])
d[country] = l
# Check for the countries with no values
res = {}
for country, value in d.items():
if len(value) != 0 :
res[country] = value
return res
pprint(extract_total_fsi_countries(["Colombia", "Ecuador"], fsi_dict))
def fsi_lines_chart(countries, fsi_dict, years=list(range(2006, 2018)), figsize=(10, 8)):
"""
Keyword arguments:
"""
data = extract_total_fsi_countries(countries, fsi_dict)
fig, ax = plt.subplots(figsize=figsize)
title = "Total FSI Valor"
ax.set_title(title)
ax.set_ylabel("Valores FSI")
ax.set_xticks(np.arange(len(years)))
ax.set_xticklabels(years)
ax.set_xlabel("Año")
for country in data:
ax.plot(data[country])
loc = "upper right"
ax.legend(data.keys(), loc = loc)
fsi_lines_chart(countries["South America"] + countries["Caribbean"], fsi_dict)