Proyecto CC5206: Clustering de datos del Banco Mundial

Integrantes: Constantino Hernández - René Silva

1. Introducción

El estudio de datos de paises es de gran importancia para la humanidad debido a que permite caracterizar los principales problemas de la misma, mostrar las principales tendencias que experimentan los distintos paises y además otorga parametros de referencia que permiten determinar la situación de un país en un momento dado de la historia o a lo largo de esta misma. Uno de los principales organismos internacionales que se encarga de la recopilación y estudio de los paises es el banco mundial, el cual tiene como objetivo declarado la reducción de la pobreza. Mediante el estudio de los datos del banco mundial se pretende encontrar clusters de paises de acuerdo a su desarrollo, relacionarlos con su ubicación geografica y estudiar su comportamiento a travez del tiempo, para así poder caracterizar el posicionamiento de Chile con respecto al resto del mundo y países de la OCDE. Adicionalmente se busca determinar las variables que más afecta en el desarrollo de los paises.

2. Descripción de los datos y exploración inicial

Para la realización de clustering de datos del Banco Mundial se utilizó la base de datos de "Development" de la misma institución, la cual incluye todas las subcategorias que pueden descargarse por separado a desde la misma página web. La base de datos se encuentra disponibe en:

http://databank.worldbank.org/data/reports.aspx?source=world-development-indicators#

2.1 Descripción de los datos

La pagina entregada anteriormente contiene mas de 1500 caracteristicas de mas de 200 paises entre los años 1960 y 2017, es decir que contiene millones de datos, es por esto que la página no permite descargar todos los datos al mismo tiempo y se optó por descargar los datos entre 1980 y 2015 en intervalos de, por lo general, 5 años en formato xlsx usandp la nomenclatura "dev_añoinicial-añofinal.xlsx". Al observar los datos de cada Excel es posible notar que tienen un formato amigable con las librerias de data mining de python (gran redundancia en donde cada pais tiene un numero de filas igual al numero de caracteristicas estudiadas), que tienen una firma al final del documento que no permite leer el documento y los datos faltantes son representados por el string "..". A continuación se presenta un codigo que permite ver el formato de un excel descargado una vez que se ha eliminado "a mano" la firma al final del mismo.

In [1]:
import numpy as np
import pandas as pd

name = 'dev_2006-2010.xlsx'  #formato dev_añoinicial-añofinal.xlsx
anho = '2010'    #año para el cual se pretende extraer información  
d_input = pd.read_excel(name, keep_default_na=False, na_values='..')
d_input.head()
Out[1]:
Country Name Country Code Series Name Series Code 2006 [YR2006] 2007 [YR2007] 2008 [YR2008] 2009 [YR2009] 2010 [YR2010]
0 Afghanistan AFG 2005 PPP conversion factor, GDP (LCU per inter... PA.NUS.PPP.05 NaN NaN NaN NaN NaN
1 Afghanistan AFG 2005 PPP conversion factor, private consumptio... PA.NUS.PRVT.PP.05 NaN NaN NaN NaN NaN
2 Afghanistan AFG Access to clean fuels and technologies for coo... EG.CFT.ACCS.ZS 20.589262 20.182705 19.774789 19.365638 18.955386
3 Afghanistan AFG Access to electricity (% of population) EG.ELC.ACCS.ZS 27.506411 34.290512 42.400000 47.888466 42.700000
4 Afghanistan AFG Access to electricity, rural (% of rural popul... EG.ELC.ACCS.RU.ZS 17.332747 24.826653 32.500000 39.877850 32.400000

Para poder realizar la exploración inicial es necesario adaptar la base de datos a un formato adecuado que permita utilizar las librerias de data mining de python, es decir un formato en donde las filas sean los paises y las columnas sean las caracteristicas. A continuación se entregan dos codigos, uno encargado de eliminar columnas innecesarias para el estudio con el fin de reducir los tiempos de procesamiento y el otro encargado de cambiar el formato de la base de datos al formato adecuado.

Eliminar columnas innecesarias

In [2]:
todel_columns = ['Series Code']
for column in todel_columns:
    d_input.__delitem__(column)
#d_input.head()

Dejar paises como única fila

In [3]:
#El metodo np.unique ordena las listas 
#es necesario devolver la lista a su orden original

#====================================================
paises_d = np.array(d_input['Country Name'])
indexes_d = np.unique(paises_d, return_index=True)[1]
paises_d = [paises_d[index] for index in sorted(indexes_d)]

features_d = np.array(d_input['Series Name'])
indexes_d = np.unique(features_d, return_index=True)[1]
features_d = [features_d[index] for index in sorted(indexes_d)]

codes_d = np.array(d_input['Country Code'])
indexes_d = np.unique(codes_d, return_index=True)[1]
codes_d = [codes_d[index] for index in sorted(indexes_d)]

#====================================================

columns_d = [('Country Name',paises_d),('Country Code',codes_d)]

empty = 0
notempty = 0
for c_feature in features_d:
    vals = d_input[d_input['Series Name'] == c_feature][anho+' ['+'YR'+anho+']']   #Valores de una caracteristica para todos los paises
    xvals = vals.tolist()
    if (~np.isnan(np.array(xvals))).any():      #No considerar columnas con puros NaN
        columns_d.append((c_feature,xvals))
        notempty+=1
    else:
        empty+=1

new_input = pd.DataFrame.from_items(columns_d)
new_input.head()
Out[3]:
Country Name Country Code Access to clean fuels and technologies for cooking (% of population) Access to electricity (% of population) Access to electricity, rural (% of rural population) Access to electricity, urban (% of urban population) Adequacy of social insurance programs (% of total welfare of beneficiary households) Adequacy of social protection and labor programs (% of total welfare of beneficiary households) Adequacy of social safety net programs (% of total welfare of beneficiary households) Adequacy of unemployment benefits and ALMP (% of total welfare of beneficiary households) ... Wholesale price index (2010 = 100) Women participating in the three decisions (own health care, major household purchases, and visiting family) (% of women age 15-49) Women who believe a husband is justified in beating his wife (any of five reasons) (%) Women who believe a husband is justified in beating his wife when she argues with him (%) Women who believe a husband is justified in beating his wife when she burns the food (%) Women who believe a husband is justified in beating his wife when she goes out without telling him (%) Women who believe a husband is justified in beating his wife when she neglects the children (%) Women who believe a husband is justified in beating his wife when she refuses sex with him (%) Women who were first married by age 18 (% of women ages 20-24) Women's share of population ages 15+ living with HIV (%)
0 Afghanistan AFG 18.955386 42.700000 32.400000 82.8 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 28.642105
1 Albania ALB 61.763644 100.000000 100.000000 100.0 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 29.373536
2 Algeria DZA 99.969707 99.711174 98.133492 100.0 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 44.078491
3 American Samoa ASM NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 Andorra AND 100.000000 100.000000 100.000000 100.0 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 1466 columns

Haciendo un analisis visual de los datos es posible notar que existe una cantidad significativa de caracteristicas con valores faltantes para un gran numero de paises y paises con un gran numero de caracteristicas sin datos. Se determinó que la inclusión de estas filas y columnas con medidas como rellenar datos faltantes con el promedio de la fila perjudica considerablemente la obtención de resultados, es por esto que se optó por eliminar caracteristicas y paises que tuvieran una cantidad considerable de datos faltantes. A continuación se encuentra el script que realiza esta operación.

In [4]:
coef = 0.2   # nans/total debe dar superior a este numero
deleted = 0
output = new_input
todel = []
itodel = []

for k in range(2,len(new_input.keys())):    
    llave = new_input.keys()[k]
    valores = new_input[llave] 
    total = valores.shape[0]
    nans = np.count_nonzero(np.isnan(valores))
    xnans = 0    
    
    if coef < float(nans)/float(total):
        todel.append(llave)
        itodel.append(k)
        deleted += 1

for column in todel:
    output.__delitem__(column)


print("Se eliminaron "+str(deleted)+" columnas")


deleted = 0
new_output = output.T
todel = []
itodel = []

for k in range(len(new_output.keys())):    
    llave = new_output.keys()[k]
    valores = new_output[llave] #
    xvalores = np.array(valores)[2:-1]
    xvalores = xvalores.astype(float)
    total = xvalores.shape[0]
    #pdb.set_trace()
    nans = np.count_nonzero(np.isnan(xvalores))  
    
    if coef < float(nans)/float(total):
        todel.append(llave)
        itodel.append(k)
        deleted += 1

for column in todel:
    new_output.__delitem__(column)


print("Se eliminaron "+str(deleted)+" filas")

out = new_output.T
out.head()
Se eliminaron 1055 columnas
Se eliminaron 40 filas
Out[4]:
Country Name Country Code Access to clean fuels and technologies for cooking (% of population) Access to electricity (% of population) Access to electricity, rural (% of rural population) Access to electricity, urban (% of urban population) Adjusted net national income (constant 2010 US$) Adjusted net national income (current US$) Adjusted net national income per capita (constant 2010 US$) Adjusted net national income per capita (current US$) ... Unemployment, youth female (% of female labor force ages 15-24) (modeled ILO estimate) Unemployment, youth male (% of male labor force ages 15-24) (modeled ILO estimate) Unemployment, youth total (% of total labor force ages 15-24) (modeled ILO estimate) Urban land area (sq. km) Urban land area where elevation is below 5 meters (% of total land area) Urban land area where elevation is below 5 meters (sq. km) Urban population Urban population (% of total) Urban population growth (annual %) Urban population living in areas where elevation is below 5 meters (% of total population)
0 Afghanistan AFG 18.9554 42.7 32.4 82.8 1.42562e+10 1.42562e+10 494.954 494.954 ... 23.178 16.568 17.709 NaN NaN NaN 7.11121e+06 24.689 4.34728 NaN
1 Albania ALB 61.7636 100 100 100 1.02835e+10 1.02835e+10 3530.19 3530.19 ... 23.369 26.868 25.493 1689.4 0.299001 84.845 1.51952e+06 52.163 1.60937 2.38281
2 Algeria DZA 99.9697 99.7112 98.1335 100 1.31599e+11 1.31599e+11 3643.61 3643.61 ... 38.022 18.905 21.97 30195.8 0.00865101 200.346 2.43888e+07 67.526 2.86939 0.603106
5 Angola AGO 39.3729 35.132 6.23472 78.3032 2.55988e+10 2.55988e+10 1095.41 1095.41 ... 12.357 10.822 11.526 1427.03 0.00433175 54.1307 9.37032e+06 40.097 5.58034 1.19494
6 Antigua and Barbuda ATG 99.7978 93.9616 99.9527 100 NaN NaN NaN NaN ... NaN NaN NaN 266.278 5.75251 24.9738 24838 26.239 -1.03736 8.63931

5 rows × 411 columns

Una vez que se preprocesaron los datos se exporta la tabla del año a estudiar en otro documento con la nomenclatura "dev_año_proc.xlsx"

Exportar

In [5]:
writer = pd.ExcelWriter('dev_'+anho+'_proc.xlsx')
out.to_excel(writer,'Data')
writer.save()

2.2 Exploración inicial

Impotar librerias y dataset

In [6]:
import numpy as np
import pandas as pd
import pdb

d_2016 = pd.read_excel('dev_'+anho+'_proc.xlsx', keep_default_na=False, 
                       na_values='') 
#el nombre de esta variable NO significa que se esté estudiando el año 2016, lo que determina el año será el
#archivo que lea la variable
d_2016.head()
Out[6]:
Country Name Country Code Access to clean fuels and technologies for cooking (% of population) Access to electricity (% of population) Access to electricity, rural (% of rural population) Access to electricity, urban (% of urban population) Adjusted net national income (constant 2010 US$) Adjusted net national income (current US$) Adjusted net national income per capita (constant 2010 US$) Adjusted net national income per capita (current US$) ... Unemployment, youth female (% of female labor force ages 15-24) (modeled ILO estimate) Unemployment, youth male (% of male labor force ages 15-24) (modeled ILO estimate) Unemployment, youth total (% of total labor force ages 15-24) (modeled ILO estimate) Urban land area (sq. km) Urban land area where elevation is below 5 meters (% of total land area) Urban land area where elevation is below 5 meters (sq. km) Urban population Urban population (% of total) Urban population growth (annual %) Urban population living in areas where elevation is below 5 meters (% of total population)
0 Afghanistan AFG 18.955386 42.700000 32.400000 82.800000 1.425623e+10 1.425623e+10 494.953612 494.953612 ... 23.177999 16.568001 17.709000 NaN NaN NaN 7111214 24.689 4.347280 NaN
1 Albania ALB 61.763644 100.000000 100.000000 100.000000 1.028352e+10 1.028352e+10 3530.191992 3530.191992 ... 23.368999 26.868000 25.493000 1689.403809 0.299001 84.844978 1519519 52.163 1.609373 2.382807
2 Algeria DZA 99.969707 99.711174 98.133492 100.000000 1.315986e+11 1.315986e+11 3643.609805 3643.609805 ... 38.021999 18.905001 21.969999 30195.796880 0.008651 200.346344 24388796 67.526 2.869395 0.603106
5 Angola AGO 39.372867 35.132019 6.234723 78.303197 2.559877e+10 2.559877e+10 1095.409432 1095.409432 ... 12.357000 10.822000 11.526000 1427.029541 0.004332 54.130676 9370320 40.097 5.580344 1.194939
6 Antigua and Barbuda ATG 99.797843 93.961617 99.952728 100.000000 NaN NaN NaN NaN ... NaN NaN NaN 266.277557 5.752507 24.973799 24838 26.239 -1.037358 8.639313

5 rows × 411 columns

Tras realizar el calculo de medidas de tendencia central y distribución se descubrió que en la base de datos aparecen como paises agrupaciones como "OCDE Members", "South America" entre otros, por lo que para realizar un correcto estudio de la base de datos es necesario eliminar estos datos redundantes. También se determinó que la diferencia de magnitud entre las caracteristicas estudiadas alterará los resultados obtenidos (Numero de hijos en el orden de las unidades vs poblacion total en el orden de las decenas-centenas de millones, en general), por lo que se optó por efectuar una normalización min-max de los datos.

Eliminar datos redundantes

In [7]:
redun1 = [u'High income', u'High income: OECD', u'OECD members', u'World']
redun2 = [u'East Asia & Pacific',u'East Asia & Pacific (excluding high income)',
             u'Euro area',u'Europe & Central Asia',u'European Union',u'Low & middle income',
             u'Middle income',u'North America',u'Upper middle income']

redun3 = [u'Arab World', u'Sub-Saharan Africa',
             u'Sub-Saharan Africa (excluding high income)', u'Middle East & North Africa',
             u'Middle East & North Africa (excluding high income)', 
             u'Latin America & Caribbean',u'Latin America & Caribbean (excluding high income)',
             u'Least developed countries: UN classification', 
             u'Heavily indebted poor countries (HIPC)',u'High income',u'High income: nonOECD',
             u'High income: OECD', u'Euro area',u'Europe & Central Asia',
             u'Europe & Central Asia (excluding high income)',u'European Union',
             u'East Asia & Pacific',u'East Asia & Pacific (excluding high income)',
             u'Lower middle income',u'South Asia']

redun4 = [u'IDA & IBRD total', u'IDA blend', u'IDA only', u'IDA total',
            u'East Asia & Pacific (IDA & IBRD countries)',u'Europe & Central Asia (IDA & IBRD countries)',
            u'Fragile and conflict affected situations',u'IBRD only',u'Late-demographic dividend',
            u'Latin America & the Caribbean (IDA & IBRD countries)',
            u'Middle East & North Africa (IDA & IBRD countries)',u'Not classified',
            u'Other small states',u'Post-demographic dividend',u'Pre-demographic dividend']


redun5 = [u'Early-demographic dividend',u'Early-demographic dividend',u'Central Europe and the Baltics',
            u'South Asia (IDA & IBRD)',u'Sub-Saharan Africa (IDA & IBRD countries)',
            u'Sub-Saharan Africa (IDA & IBRD countries)', u'South Asia (IDA & IBRD)',u'Low income']

for out in redun1:
    d_2016 = d_2016[d_2016['Country Name'] != out]
for out in redun2:
    d_2016 = d_2016[d_2016['Country Name'] != out]
for out in redun3:
    d_2016 = d_2016[d_2016['Country Name'] != out]
for out in redun4:
    d_2016 = d_2016[d_2016['Country Name'] != out]
for out in redun5:
    d_2016 = d_2016[d_2016['Country Name'] != out]

paises = d_2016['Country Name'].values.tolist()

Normalización

In [8]:
d_2016_n = d_2016
for k in range(2,len(d_2016.keys())):
    
    llave = d_2016.keys()[k]
    valores = d_2016[llave] 
    xvals = valores.tolist()
    #Normalización
    xmax = np.nanmax(valores)
    xmin = np.nanmin(valores)
    norm = xmax - xmin
    #pdb.set_trace()
    if (not np.isnan(xmax)) and norm!=0:
            d_2016_n[llave] = (d_2016[llave]-xmin)/norm
    

d_2016_n.head()
Out[8]:
Country Name Country Code Access to clean fuels and technologies for cooking (% of population) Access to electricity (% of population) Access to electricity, rural (% of rural population) Access to electricity, urban (% of urban population) Adjusted net national income (constant 2010 US$) Adjusted net national income (current US$) Adjusted net national income per capita (constant 2010 US$) Adjusted net national income per capita (current US$) ... Unemployment, youth female (% of female labor force ages 15-24) (modeled ILO estimate) Unemployment, youth male (% of male labor force ages 15-24) (modeled ILO estimate) Unemployment, youth total (% of total labor force ages 15-24) (modeled ILO estimate) Urban land area (sq. km) Urban land area where elevation is below 5 meters (% of total land area) Urban land area where elevation is below 5 meters (sq. km) Urban population Urban population (% of total) Urban population growth (annual %) Urban population living in areas where elevation is below 5 meters (% of total population)
0 Afghanistan AFG 0.173014 0.398411 0.318663 0.811118 0.001112 0.001112 0.005043 0.005043 ... 0.319229 0.290923 0.295624 NaN NaN NaN 1.076254e-02 0.171569 0.514782 NaN
1 Albania ALB 0.609833 1.000000 1.000000 1.000000 0.000799 0.000799 0.049015 0.049015 ... 0.321904 0.476545 0.429716 0.002106 0.013201 0.003546 2.270646e-03 0.473787 0.326641 0.046184
2 Algeria DZA 0.999691 0.996968 0.981188 1.000000 0.010366 0.010366 0.050658 0.050658 ... 0.527145 0.333039 0.369027 0.037648 0.000382 0.008373 3.700135e-02 0.642782 0.413226 0.011690
5 Angola AGO 0.381356 0.318955 0.054945 0.761736 0.002007 0.002007 0.013742 0.013742 ... 0.167661 0.187370 0.189113 0.001779 0.000191 0.002262 1.419336e-02 0.341059 0.599515 0.023161
6 Antigua and Barbuda ATG 0.997937 0.936603 0.999524 1.000000 NaN NaN NaN NaN ... NaN NaN NaN 0.000332 0.253983 0.001044 7.304765e-07 0.188619 0.144766 0.167449

5 rows × 411 columns

Una vez que se tiene la base de datos en un formato adecuado se realiza un análisis estadistico de los datos.

Analisis estadistico de los datos

In [9]:
varianzas = []
promedios = []
maximos = []
minimos = []
desv = []
for k in range(2,len(d_2016_n.keys())):
    
    llave = d_2016_n.keys()[k]
    valores = d_2016_n[llave] 
    count = 0
    
    xmax = np.nanmax(valores)
    xmean = np.nanmean(valores)
    xmin = np.nanmin(valores)
    xstd = np.nanstd(valores)
    xvar = np.nanvar(valores)
    
    promedios.append(xmean)
    minimos.append(xmin)
    maximos.append(xmax)
    desv.append(xstd)
    varianzas.append(xvar)
    
sort = np.sort(varianzas)

most = -1   

print "Estadisticas con mayor varianza:"
#Se entrega como output las estadisticas con mayor varianza ya que son las que entregan mayor información
#Sin embargo es posible estudiar las otras medidas mediantes las variables promedios, minimos, maximos, etc.
#Recordar que se está rabajando con datos normalizados, por lo que sería necesario revertir la normalización para
#obtener los valores reales en caso de max, min y mean
for i in range(10): #numero de variables deseadas como output
    value = sort[most]
    j = np.argwhere(varianzas == sort[most])+2
    features = d_2016_n.keys()
    print str(i+1)+' '+str(features[j])+' ('+str(sort[most]) +')'
    most-=1
Estadisticas con mayor varianza:
1 Index([[u'Access to clean fuels and technologies for cooking  (% of population)']], dtype='object') (0.152074178745)
2 Index([[u'Access to electricity, rural (% of rural population)']], dtype='object') (0.140942670588)
3 Index([[u'People using basic sanitation services, rural (% of rural population)']], dtype='object') (0.119733601855)
4 Index([[u'Improved sanitation facilities, rural (% of rural population with access)']], dtype='object') (0.1178195693)
5 Index([[u'Private credit bureau coverage (% of adults)']], dtype='object') (0.113808472979)
6 Index([[u'Renewable electricity output (% of total electricity output)']], dtype='object') (0.108298575296)
7 Index([[u'Improved sanitation facilities (% of population with access)']], dtype='object') (0.106951938846)
8 Index([[u'Access to electricity (% of population)']], dtype='object') (0.105956627278)
9 Index([[u'Cause of death, by communicable diseases and maternal, prenatal and nutrition conditions (% of total)']], dtype='object') (0.101614489061)
10 Index([[u'People using basic sanitation services (% of population)']], dtype='object') (0.100694847478)

Otra parte importante de la exploración de los datos consiste el la geración de gráficos, es por esto que se programó un codigo capaz de generar histogramas para una caracteristica determinada

Histogramas

In [10]:
%matplotlib inline
import matplotlib.pyplot as plt

for k in range(2,len(d_2016.keys())):
    fig = plt.figure()
    ax1 = fig.add_subplot(1, 1, 1)
    llave = d_2016.keys()[k]
    x = []
    for nonan in d_2016[llave]:
        if not np.isnan(nonan):
            x.append(nonan)

    ax1.hist(x, 5, facecolor='blue', alpha=0.75)

    ax1.set_xlabel(llave)
    ax1.set_ylabel('Total')

    plt.show()
    plt.close

3. Problematica inicial y abordaje de la misma

Tal como se indicó anteriormente, el objetivo del proyecto es realizar un clustering de la base de datos trabajada para estudiar el posicionamiento de Chile en base a su desarrollo y compararlo con los paises de la OCDE, además de la validación de la agrupación de paises según su desarrollo realizando un estudio geográfico y temporal. Las principales problematicas encontradas para esto son la alta dimensionalidad de los datos, lo cual dificulta la visualización de los mismos y el desconocimiento de la posible existencia de una "métrica" que permita comparar datos de años distintos.

Para la realización del estudio mencionado se realizará un clustering con el algoritmo k-means. Inicialmente se desconoce el número de clusters en la base de datos, sin embargo se espera que los clusters serán del tipo desarrollo-ubicación geográfica (Sudamerica desarrollados, Sudamerica subdesarrollados, etc). En base a los resultados entregados por el clustering de los datos se verá a que cluster pertenecen Chile y los paises de la OCDE, asi como los valores de las caracteristicas de mayor varianza a lo largo de los años.

Para el desarrollo del estudio temporal de los clusters será necesario validar o descartar la existencia de una "métrica" que permita agrupar los paises para cualquier año. Para esto se verá la evolución de las caracteristicas de mayor varianza de los clusters a traves del tiempo para determinar si existen patrones en común para la evolución de los paises y de esta forma determinar si corresponde hacer la agrupación según un estandar de desarrollo para cualquier año o si corresponde realizarla aisladamente para cada año.

Finalmente, para el estudio geográfico se hará una visualizacion de los paises clusterizados en forma de mapa, lo cual permitirá comprobar/refutar el supuesto de que el desarrollo de un país está fuertemente relacionado con la ubicación geografica del mismo.

3.1 Clustering utilizando K-Means

Debido a que se utilizará K-Means para el clustering de los datos de cada año, se debe ejecutar el algoritmo del método del codo para determinar la cantidad óptima de clusters correspondiente a cada año. De la ejecución de este algoritmo para los años entre 1980 y 2016 se determina que un número adecuado de clusters es K = 3.

In [11]:
np_df = d_2016_n.as_matrix()
test = np_df[:,2:]
for row in range(test.shape[0]):
    for column in range(test.shape[1]):
        val = test[row][column]
        #print val
        if np.isnan(val):
            test[row][column]= promedios[column] 
        else:
            #test[row][column]= val/maximos[column] 
            pass

xdf = pd.DataFrame(test)

vectors = []
tot = test#.T
for i in range(len(tot[:,0])):
    vectors.append(tot[i])
    

import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn import datasets
from sklearn import metrics
from scipy.spatial.distance import cdist

X = tot

# k means determine k
distortions = []
K = range(1,10)
for k in K:
    kmeanModel = KMeans(n_clusters=k).fit(X)
    kmeanModel.fit(X)
    distortions.append(sum(np.min(cdist(X, kmeanModel.cluster_centers_, 'euclidean'), axis=1)) / X.shape[0])
 
# Plot the elbow
plt.plot(K, distortions, 'bx-')
plt.xlabel('k')
plt.ylabel('Distortion')
plt.title('The Elbow Method showing the optimal k')
plt.show()

Visualización preliminar

Una vez que se determina el número de clusters que se usarán en el método de K-Means se utilizan tSNE y PCA para la reducción de la dimensionalidad del dataset y su visualización mediante Scatter Plots.

tSNE

In [12]:
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
X = vectors
X_embedded = TSNE(n_components=2,n_iter=100000000).fit_transform(X)
X_embedded.shape
plt.scatter(X_embedded[:, 0], X_embedded[:, 1], c="r", cmap=plt.cm.Spectral)
plt.show()

PCA

In [13]:
from mpl_toolkits.mplot3d import Axes3D
from sklearn import decomposition
from sklearn import datasets

np.random.seed(5)

centers = [[1, 1], [-1, -1], [1, -1]]

X = tot
pca = decomposition.PCA(n_components=2)
pca.fit(X)
X = pca.transform(X)


plt.scatter(X[:, 0], X[:, 1], c="r", cmap=plt.cm.spectral,
           edgecolor='k')


plt.show()

Aplicar K-Means al dataset y visualizar resultados mediante tSNE y PCA

Habiendo visualizado los datos solo resta la ejecución del método K-Means, y su posterior visualización mediante tSNE y PCA.

In [14]:
from sklearn.cluster import KMeans
from sklearn import datasets

X = tot

kmeans = KMeans(n_clusters=3)
kmeans.fit(X)
labels = kmeans.labels_

centroids = kmeans.cluster_centers_

tSNE

In [15]:
%matplotlib inline

r = []
r_l = []
g = []
g_l = []
b = [] 
b_l = []
y = []
y_l = []
c=[]
c_l=[]
m=[]
m_l=[]

plt.gcf().clear()
r_embedded = TSNE(n_components=2,n_iter=100000000).fit_transform(X)
r_embedded.shape
for k in range(len(labels)):
    if labels[k]==0:
        r.append(X_embedded[k])
        r_l.append(paises[k])
    elif labels[k]==1:
        g.append(X_embedded[k])
        g_l.append(paises[k])
    elif labels[k]==2:
        b.append(X_embedded[k])
        b_l.append(paises[k])
    elif labels[k]==3:
        y.append(X_embedded[k])
        y_l.append(paises[k])
    elif labels[k]==4:
        c.append(X_embedded[k])
        c_l.append(paises[k])
    elif labels[k]==5:
        m.append(X_embedded[k])
        m_l.append(paises[k])
plt.scatter([item[0] for item in r], [item[1] for item in r], c="r", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in g], [item[1] for item in g], c="g", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in b], [item[1] for item in b], c="b", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in y], [item[1] for item in y], c="y", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in c], [item[1] for item in c], c="c", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in m], [item[1] for item in m], c="m", cmap=plt.cm.Spectral)
plt.show()
# LAS LINEAS A CONTINUACION SE UTILIZAN PARA GUARDAR LOS RESULTADOS EN UNA IMAGEN #
#tsnename='k3_dev_'+str(yr)+'_tsne'
#plt.savefig(tsnename)
#plt.gcf().clear()

PCA

In [16]:
from mpl_toolkits.mplot3d import Axes3D
from sklearn import decomposition
from sklearn import datasets


X = tot
pca = decomposition.PCA(n_components=2)
pca.fit(X)
X = pca.transform(X)

r = []
r_l = []
g = []
g_l = []
b = [] 
b_l = []
y = []
y_l = []
c = []
c_l = []

for k in range(len(labels)):
    if labels[k]==0:
        r.append(X[k])
        r_l.append(paises[k])
    elif labels[k]==1:
        g.append(X[k])
        g_l.append(paises[k])
    elif labels[k]==2:
        b.append(X[k])
        b_l.append(paises[k])
    elif labels[k]==3:
        y.append(X[k])
        y_l.append(paises[k])
    elif labels[k]==4:
        c.append(X[k])
        c_l.append(paises[k])
        
plt.scatter([item[0] for item in r], [item[1] for item in r], c="r", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in g], [item[1] for item in g], c="g", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in b], [item[1] for item in b], c="b", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in y], [item[1] for item in y], c="y", cmap=plt.cm.Spectral)
plt.scatter([item[0] for item in c], [item[1] for item in c], c="c", cmap=plt.cm.Spectral)
plt.show()
# LAS LINEAS A CONTINUACION SE UTILIZAN PARA GUARDAR LOS RESULTADOS EN UNA IMAGEN #
#pcaname='k3_dev_'+str(yr)+'_pca'
#plt.savefig(pcaname)
#plt.gcf().clear()

A continuación se muestran los países correspondientes a cada una de las etiquetas:

Cluster Rojo

In [17]:
rojo = ''
for i in r_l:
    rojo += i
    if i != r_l[-1]:
        rojo+= ', '
        
print rojo
Australia, Austria, Barbados, Belarus, Belgium, Bosnia and Herzegovina, Bulgaria, Canada, China, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hong Kong SAR, China, Hungary, Iceland, Ireland, Israel, Italy, Japan, Korea, Rep., Latvia, Lithuania, Luxembourg, Macedonia, FYR, Malta, Montenegro, Netherlands, New Zealand, Norway, Poland, Portugal, Romania, Russian Federation, Serbia, Singapore, Slovak Republic, Slovenia, Spain, Sweden, Switzerland, Ukraine, United Kingdom, United States

Cluster Azul

In [18]:
azul = ''
for i in b_l:
    azul += i
    if i != b_l[-1]:
        azul+= ', '
        
print azul
Albania, Algeria, Antigua and Barbuda, Argentina, Armenia, Azerbaijan, Bahamas, The, Bahrain, Bangladesh, Belize, Bhutan, Bolivia, Botswana, Brazil, Brunei Darussalam, Cabo Verde, Caribbean small states, Chile, Colombia, Costa Rica, Dominican Republic, Ecuador, Egypt, Arab Rep., El Salvador, Fiji, Georgia, Grenada, Guatemala, Guyana, Honduras, India, Indonesia, Iran, Islamic Rep., Iraq, Jamaica, Jordan, Kazakhstan, Kuwait, Kyrgyz Republic, Lebanon, Libya, Malaysia, Maldives, Mauritius, Mexico, Moldova, Mongolia, Morocco, Myanmar, Nicaragua, Oman, Pacific island small states, Panama, Paraguay, Peru, Philippines, Qatar, Samoa, Saudi Arabia, Seychelles, Small states, South Africa, Sri Lanka, St. Lucia, St. Vincent and the Grenadines, Suriname, Tajikistan, Thailand, Tonga, Trinidad and Tobago, Tunisia, Turkey, United Arab Emirates, Uruguay, Uzbekistan, Vanuatu, Venezuela, RB, Vietnam, West Bank and Gaza

Cluster Verde

In [19]:
verde = ''
for i in g_l:
    verde += i
    if i != g_l[-1]:
        verde+= ', '
        
print verde
Afghanistan, Angola, Benin, Burkina Faso, Burundi, Cambodia, Cameroon, Central African Republic, Chad, Comoros, Congo, Dem. Rep., Congo, Rep., Cote d'Ivoire, Djibouti, Equatorial Guinea, Eritrea, Ethiopia, Gabon, Gambia, The, Ghana, Guinea, Guinea-Bissau, Haiti, Kenya, Kiribati, Lao PDR, Lesotho, Liberia, Madagascar, Malawi, Mali, Mauritania, Mozambique, Namibia, Nepal, Niger, Nigeria, Pakistan, Papua New Guinea, Rwanda, Sao Tome and Principe, Senegal, Sierra Leone, Solomon Islands, Sudan, Swaziland, Tanzania, Timor-Leste, Togo, Uganda, Yemen, Rep., Zambia, Zimbabwe

De esta clusterización se identifican tres clusters que identificamos que corresponden a la medida subjetiva de desarrollo: el cluster Rojo es identificado como "Países desarrollados", el Azul como "Países en vías de desarrollo" y el cluster Verde como "Países sub-desarrollados".

Sin embargo, no basta solamente con la visualización de estos clusters ni interpretar su significado. Es necesario aplicar algoritmos que nos permitan visualizar la evolución de estos clusters en el tiempo, así como poder representarlos en un mapa que entregue información más clara y nos permita observar patrones que vinculen estos clusters con su ubicación geográfica.

Para ello se guardan las etiquetas obtenidas de cada cluster, las que serán usadas posteriormente para poder representar a cada uno de los países en el cluster que le corresponda en un mapa. Los algoritmos que fueron presentados se ejecutan para cada uno de los años entre 1980 y 2016 y se guardan sus resultados, con los que se trabajará en la sección siguiente para mostrar la evolución temporal de los clusters.

In [20]:
clusters = [r,g,b,y,c,m]
clusters_l = [r_l,g_l,b_l,y_l,c_l,m_l]
ncluster = 1
pre_out = []#('Country','Label')]
for cluster_l in clusters_l:
    for country in cluster_l:
        pre_out.append((country,ncluster))
    ncluster +=1

out = pd.DataFrame(pre_out,columns=['Country', 'Label'])
out.head()
labelname='Labels'+str(anho)+'.xlsx'
writer = pd.ExcelWriter(labelname)
out.to_excel(writer,'Data')
writer.save()

3.2 Evolución temporal de Clusters

Habiendo ejecutado los algoritmos y guardado sus resultados, es necesario poder representar los clusters y su evolución temporal en el tiempo. Lamentablemente utilizar la visualización por medio de PCA o tSNE no es de mucha utilidad debido a la reducción de dimensionalidad que no nos entrega una representación que entregue mayor información. Sin embargo, es posible seleccionar ciertas variables de cada país y evaluar su comportamiento a medida que transcurren los años. Esto es lo que definimos como una "métrica" para mostrar la evolución de los clusters en el tiempo.

En particular, se eligen de manera arbitraria las variables de Tasa de Natalidad y la Esperanza de Vida, bajo la hipótesis de que países más desarrollados tienden a tener una esperanza de vida mayor, acompañado de una menor tasa de natalidad, inspirados en el vídeo de Hans Rosling (https://www.ted.com/talks/hans_rosling_shows_the_best_stats_you_ve_ever_seen).

El código presentado a continuación es ejecutado para obtener el Scatter Plot Esperanza de Vida vs Tasa de Natalidad, se ejecuta para cada año sin "limpiar" la figura del gráfico, de manera que se van sobreponiendo los gráficos generados permitiendo visualizar una evolución y la direccionalidad del movimiento de los clusters. En este caso particular se hace para el año 2010:

In [25]:
hijos = d_2016['Fertility rate, total (births per woman)']
expectativa = d_2016['Life expectancy at birth, total (years)']

import matplotlib.pyplot as plt

plt.gcf().clear()

sizes = np.linspace(1,100,100)
#pdb.set_trace()


size = 1
for pais in g_l:
    hijos = d_2016[d_2016['Country Name']==pais]['Fertility rate, total (births per woman)']
    expectativa = d_2016[d_2016['Country Name']==pais]['Life expectancy at birth, total (years)']
    plt.scatter(hijos, expectativa, c="r", cmap=plt.cm.Spectral,s=size)
    size = size*1.12
    
size = 1
for pais in b_l:
    hijos = d_2016[d_2016['Country Name']==pais]['Fertility rate, total (births per woman)']
    expectativa = d_2016[d_2016['Country Name']==pais]['Life expectancy at birth, total (years)']
    plt.scatter(hijos, expectativa, c="g", cmap=plt.cm.Spectral,s=size)
    size = size*1.09
    
size = 1
for pais in r_l:
    hijos = d_2016[d_2016['Country Name']==pais]['Fertility rate, total (births per woman)']
    expectativa = d_2016[d_2016['Country Name']==pais]['Life expectancy at birth, total (years)']
    plt.scatter(hijos, expectativa, c="b", cmap=plt.cm.Spectral,s=size)
    size = size*1.1

'''
c_hijos = d_2016[d_2016['Country Name']=='Chile']['Fertility rate, total (births per woman)']
c_expectativa = d_2016[d_2016['Country Name']=='Chile']['Life expectancy at birth, total (years)']
plt.scatter(c_hijos, c_expectativa, c="r", cmap=plt.cm.Spectral,s=300,label='Chile[2015]')
'''
'''
f_hijos = d_2016[d_2016['Country Name']=='Finland']['Fertility rate, total (births per woman)']
f_expectativa = d_2016[d_2016['Country Name']=='Finland']['Life expectancy at birth, total (years)']
plt.scatter(f_hijos, f_expectativa, c="b", cmap=plt.cm.Spectral,s=500,label='Finland[2005]')
'''

plt.ylabel('Life expectancy at birth, total (years)')
plt.xlabel('Fertility rate, total (births per woman)')
plt.title(str(anho))
#plt.title('Chile[2015] vs Finland[2005]')
plt.xlim([0,10])
plt.ylim([20, 90])
#plt.legend(loc='best')#, borderaxespad=0.)
plt.show()
#plt.savefig(str(year)+'.png')

A continuación se muestra el estado inicial en 1980, y el estado final de estos gráficos en 2015. 1980 2015 2015_clear

Se observa una tendencia de todos los países a la esquina superior izquierda, es decir, a un aumento de la expectativa de vida correlacionado con una menor tasa de natalidad.

Se puede verificar además que en general, a medida que transcurren los años la distancia entre los clusters es cada vez menor.

Resulta interesante también, aislar a Chile y otro de los países que se identifican como países desarrollados. De manera arbitraria se decide realizar esta comparación con Finlandia, para determinar en qué año Chile logra alcanzar el nivel de "desarrollo" dado por esta métrica.

chifinT

Otras métricas podrían mostrar de manera más fidedigna la evolución temporal del nivel de desarrollo de los distintos países y clusters obtenidos, sin embargo, esto implicaba un trabajo de exploración de datos muy acabado, y dada la arbitrariedad con que se realiza esta tarea parecía agregar poco valor al proyecto.

Sin embargo, se proponen en iteraciones futuras implementar métricas de desarrollo establecidas, y un análisis estadístico más profundo para lograr determinar la correlación entre el nivel de desarrollo y las distintas variables que componen nuestra base de datos.

3.3 Representación geográfica de clusters

Por último, se busca representar geográficamente en un mapa estos clusters, y poder mostrar su evolución temporal a lo largo de los años. Para ello se ejecuta el código siguiente que los coloca en sus respectivas posiciones en el mapa, y guarda la imagen resultante. Luego se pueden observar las diferencias en el mapa para cada año.

In [24]:
# encoding=utf8  
import sys  

reload(sys)  
sys.setdefaultencoding('utf8')
import numpy as np
import pandas as pd
import pdb
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.io.shapereader as shpreader
import copy

plt.gcf().clear()
for yr in range(2010,2011): # El rango que se debe ejecutar es 1980 a 2015, pero en este caso se muestra solo el año 2010
    labelname = 'Labels'+str(yr)+'.xlsx'
    labels = pd.read_excel(labelname, keep_default_na=False, na_values='')
    #labels.head()
    

    Cluster1 = labels[labels['Label'] == 1]['Country']
    Cluster2 = labels[labels['Label'] == 2]['Country']
    Cluster3 = labels[labels['Label'] == 3]['Country']

    shapename = 'admin_0_countries'
    countries_shp = shpreader.natural_earth(resolution='110m', category='cultural', name=shapename)

    plt.figure(figsize=(20,15))
    ax = plt.axes(projection=ccrs.PlateCarree())
    #color = "r"
    for country in shpreader.Reader(countries_shp).records():
        if any(country.attributes['NAME_LONG'] in x for x in Cluster1):
            if any(Cluster1.str.contains("United States")):
                color = 'b'
            elif any(Cluster1.str.contains("Brazil")):
                color = 'g'
            else:
                color = 'r'
        elif any(country.attributes['NAME_LONG'] in x for x in Cluster2):
            if any(Cluster2.str.contains("United States")):
                color = 'b'
            elif any(Cluster2.str.contains("Brazil")):
                color = 'g'
            else:
                color = 'r'
        elif any(country.attributes['NAME_LONG'] in x for x in Cluster3):
            if any(Cluster3.str.contains("United States")):
                color = 'b'
            elif any(Cluster3.str.contains("Brazil")):
                color = 'g'
            else:
                color = 'r'
        '''
        elif any(country.attributes['NAME_LONG'] in x for x in y_l):
            color = 'y'
        '''
        ax.add_geometries(country.geometry, ccrs.PlateCarree(),facecolor=color,label=country.attributes['NAME_LONG'])

    fig_size = plt.rcParams["figure.figsize"]

    # Set figure width to 12 and height to 9
    fig_size[0] = 240
    fig_size[1] = 180
    plt.rcParams["figure.figsize"] = fig_size
    plt.show()
    #mapname='k3_dev_'+str(yr)+'_map'
    #plt.savefig(mapname)
    #plt.gcf().clear()
<matplotlib.figure.Figure at 0xf8bae10>

A continuación se muestra el estado inicial de los clusters geográficamente, en 1980:

1980

mapa_1980

2015

mapa_2015

En estos mapas se observa que en un comienzo existía un mayor número de países pertenecientes al cluster de países subdesarrollados, concentrados principalmente en África, Asia y algunos países de Sudamérica. Al año 2015, sin embargo, Sudamérica se encuentra en un estado de vías de desarrollo tendiendo al desarrollo, con Chile formando parte del cluster de países con más alto desarrollo. Así mismo, se observa un aumento del número de países africanos que se integran al cluster verde de países en vías de desarrollo y una disminución de los países sub-desarrollados, situación muy similar en Asia. Así mismo, cabe destacar fenómenos como la inclusión de China en el cluster azul, debido a su predominancia económica luego del comienzo del siglo XXI.

4. Conclusiones

El trabajo realizado y presentado en este reporte puede ser una importante herramienta de visualización y comunicación de la importancia de ciertas variables en el nivel de desarrollo de un país. Sin embargo, no hay que caer en el vicio de vincular correlación con causalidad, la información que entregan los gráficos presentados puede resultar engañosa.

De lo que no hay duda alguna es que los resultados muestran una tendencia a una mayor igualdad entre los países del mundo y de los clusters que se obtuvieron, potenciados por el fortalecimiento de las economías y de una mayor estabilidad política.

Se considera el trabajo realizado insuficiente, sin embargo, lamentablemente por los tiempos de los que se dispone no fue posible realizar un análisis más acabado que ayudara a identificar las variables que tienen incidencia en estas clasificaciones. Parte del problema nace debido a que la concepción de este proyecto se dio sin un objetivo claro, ajustándose sus objetivos con cada entrega para cumplir con los requerimientos del curso. Sin lugar a dudas resulta un aprendizaje valioso la importancia de tener una hipótesis clara en mente antes de comenzar a desarrollar un proyecto de Data Mining.