Herramientas Computacionales

para la Investigación Interdisciplinaria Reproducible



  • Profesor: Dr. José Manuel Magallanes, PhD (jmagallanes@pucp.edu.pe)
    Profesor del Departamento de Ciencias Sociales, Pontificia Universidad Católica del Peru.
    Senior Data Scientist del eScience Institute and Visiting Professor at Evans School of Public Policy and Governance, University of Washington.
    Fellow Catalyst, Berkeley Initiative for Transparency in Social Sciences, UC Berkeley.

Sesión 3: Pre Procesamiento de Datos

Parte A: Data Cleaning en Python

El pre procesamiento de datos es la parte más tediosa del proceso de investigación.

Esta primera parte delata diversos problemas que se tienen con los datos reales que están en la web, como la que vemos a continuación:

In [ ]:
import IPython
wikiLink="https://en.wikipedia.org/wiki/List_of_freedom_indices" 
iframe = '<iframe src=' + wikiLink + ' width=700 height=350></iframe>'
IPython.display.HTML(iframe)

Recuerda inspeccionar la tabla para encontrar algun atributo que sirva para su descarga. De ahí, continua.

In [ ]:
# antes instala'beautifulsoup4'
# es posible que necesites salir y volver a cargar notebook

import pandas as pd

wikiTables=pd.read_html(wikiLink,header=0,flavor='bs4',attrs={'class': 'wikitable sortable',})
In [ ]:
# cuantas tenemos?
len(wikiTables)

Hasta aquí todo parece bien. Como solo hay uno, lo traigo y comienzo a verificar 'suciedades'.

In [ ]:
DF=wikiTables[0]

#primera mirada
DF

La limpieza requiere estrategia. Lo primero que salta a la vista, son los footnotes que están en los títulos:

In [ ]:
DF.columns
In [ ]:
# aqui ves que pasa cuando divido cada celda usando el caracter '['
[element.split('[') for element in DF.columns]
In [ ]:
# Te das cuenta que te puedes quedar con el primer elemento cada vez que partes:
[element.split('[')[0] for element in DF.columns]

También hay que evitar espacios en blanco:

In [ ]:
outSymbol=' ' 
inSymbol=''
[element.split('[')[0].replace(outSymbol,inSymbol) for element in DF.columns]

Los números también molestan, pero están en diferentes sitios. Mejor intentemos expresiones regulares:

In [ ]:
import re  # debe estar instalado.

# espacios: \\s+
# uno o mas numeros \\d+
# bracket que abre \\[
# bracket que cierra \\]

pattern='\\s+|\\d+|\\[|\\]'
nothing=''

#substituyendo 'pattern' por 'nothing':
[re.sub(pattern,nothing,element) for element in DF.columns]

Ya tengo nuevos titulos de columna (headers)!!

In [ ]:
newHeaders=[re.sub(pattern,nothing,element) for element in DF.columns]

Preparemos los cambios:

In [ ]:
# veamos los cambios:
{old:new for old,new in zip(DF.columns,newHeaders)}

Uso un dict por si hubieses querido cambiar solo algunas columnas:

In [ ]:
changes={old:new for old,new in zip(DF.columns,newHeaders)}
DF.rename(columns=changes,inplace=True)
In [ ]:
# ahora tenemos:
DF

Las columnas son categorías, veamos si todas se han escrito de la manera correcta:

In [ ]:
DF.FreedomintheWorld.value_counts()
In [ ]:
DF.IndexofEconomicFreedom.value_counts()
In [ ]:
DF.PressFreedomIndex.value_counts()
In [ ]:
DF.DemocracyIndex.value_counts()

Pues hasta aquí está conforme. Veamos otro caso.


In [ ]:
idhCol="https://www.datosmacro.com/idh/colombia" 
iframe = '<iframe src=' + idhCol + ' width=700 height=350></iframe>'
IPython.display.HTML(iframe)

Luego de inspeccionar la tabla, podemos traerla:

In [ ]:
import pandas as pd

webTable=pd.read_html(idhCol,header=0,flavor='bs4',attrs={'id': 'tb0',})
In [ ]:
len(webTable)
In [ ]:
idhColT=webTable[0]
idhColT

El problema es que se borraron los decimales. Como se ve en la web, estos tenían una coma en vez de un punto. A esta altura podemos eliminarlo, o buscar si durante el proceso de colección se puede mejorar esto; dale una mirada a la función:

In [ ]:
?pd.read_html

Siguiendo las instrucciones escibimos:

In [ ]:
idhColT=pd.read_html(idhCol,header=0,flavor='bs4',attrs={'id': 'tb0',},
                      thousands=None, decimal=',')[0]
idhColT

El ranking no es un numero, pues el símbolo lo evita, eliminemoslo (revisar):

In [ ]:
idhColT.loc[:,'Ranking IDH']=idhColT.loc[:,'Ranking IDH'].str.replace(chr(186),"")
idhColT

Traigamos una nueva tabla:

In [ ]:
idhCol2='https://es.wikipedia.org/wiki/Anexo:Departamentos_de_Colombia_por_IDH'
iframe = '<iframe src=' + idhCol2 + ' width=700 height=350></iframe>'
IPython.display.HTML(iframe)

Aparentemente sabemos qué hacer:

In [ ]:
idhColT2=pd.read_html(idhCol2,header=0,flavor='bs4',attrs={'class': 'sortable',},
                       thousands=' ', decimal=',')[0]
idhColT2

Aparentemente, sólo Boyacá tenía espacios en blanco.

En este caso, el primer problema es que los miles marcados con espacios no desaparecieron. Eso se debe a que en el html están señalados como & nbsp;. De ahi que:

In [ ]:
idhColT2=pd.read_html(idhCol2,header=0,flavor='bs4',attrs={'class': 'sortable',},
                       thousands=chr(160), decimal=',')[0]
idhColT2

Pues, Boyacá es ahora el problema. Eso lo resolveremos fuera de la llamada:

In [ ]:
idhColT2.iloc[:,2]=idhColT2.iloc[:,2].str.replace("\s","")
idhColT2

Los nombres de columnas necesitan tratamiento, podríamos usar lo que ya vimos:

In [ ]:
[re.sub(pattern,nothing,element) for element in idhColT2.columns]

O mejorar el patrón:

In [ ]:
pattern2='\\s+|\\d+|\\[|\\]|\\u200b'
[re.sub(pattern2,nothing,element) for element in idhColT2.columns]

Pero esta vez, hay footnotes con texto, cuando antes sólo tenía números, de ahi que jueguemos simple:

In [ ]:
[element.split('[')[0].replace(" ","") for element in idhColT2.columns]

Cambiemos con esto los headers:

In [ ]:
idhColT2.columns=[element.split('[')[0].replace(" ","") for element in idhColT2.columns]
idhColT2

Sucede algo similar con los contenidos de las columnas (vease 'Región Amazónica'). De ahí que:

In [ ]:
idhColT2.Entidad=[element.split('[')[0] for element in idhColT2.Entidad]
idhColT2

Ten en cuenta que la nota que acabamos de eliminar decía: 'Se refiere los departamentos de Amazonas, Guainia, Guaviare, Vaupés y Vichada.' Una alternativa, que haremos aquí es crear esas filas:

In [ ]:
# nombres nuevos
newRows=['Amazonas', 'Guainia', 'Guaviare', 'Vaupés', 'Vichada']
In [ ]:
#Valor de Region Amazonica:
idhColT2[idhColT2.Entidad=='Región Amazónica']
In [ ]:
#Valor de Region Amazonica como lista:
idhColT2[idhColT2.Entidad=='Región Amazónica'].values.tolist()[0]
In [ ]:
#Valor de Region Amazonica como lista, sin elemento 1:
idhColT2[idhColT2.Entidad=='Región Amazónica'].values.tolist()[0][1:]
In [ ]:
info=idhColT2[idhColT2.Entidad=='Región Amazónica'].values.tolist()[0][1:]
In [ ]:
# creando filas nuevas como listas:

[[row] + info for row in newRows]
In [ ]:
newData = pd.DataFrame([[row] + info for row in newRows], columns=idhColT2.columns)
idhColT2.append(newData,ignore_index=True)
In [ ]:
# Ya que estamos satisfechos:
idhColT2=idhColT2.append(newData,ignore_index=True)

En el DF sobran ya tres filas, la que hemos desagregado, 'Bogota' y 'Colombia':

In [ ]:
idhColT2[idhColT2.Entidad.isin (['Región Amazónica','Colombia','Bogotá'])]
In [ ]:
#eliminando
idhColT2.drop([0,24,29],inplace=True)
idhColT2.reset_index(drop=True,inplace=True)

Es importante darnos cuenta que hay símbolos no apropiados para representar valores faltantes (i.e.'--'). Eso es preocupante, en particular para los numéricos. Juntemos ambas columnas para buscar inapropiados:

In [ ]:
numericos=list(idhColT2.IDH)
numericos.extend(list(idhColT2.Población))
In [ ]:
numericos

Vemos que varios numeros está como texto, lo que por ahora no es problema. La idea es encontrar aquello que están entre los números y no lo son:

In [ ]:
for n in numericos:
    float(n)

Hemos encontrado un simbolo que no se puede convertir a float. Podría haber otros, pero ya sabemos qué error arroja.

De ahí que:

In [ ]:
inapropiados=[]
for n in numericos:
    try:
        float(n)
    except ValueError:
        if not n in inapropiados: # evitar duplicados
            inapropiados.append(n)
In [ ]:
# aqui están
inapropiados

Sólo había un símbolo (ya lo sabíamos), pero el código sirve para más de uno.

Lo que nos queda es reemplazar ese valor por 'None':

In [ ]:
idhColT2.replace(inapropiados,value=[None]*len(inapropiados)) # se necesita la misma cantidad de 'None'

Sabiendo como queda, hagásmoslo:

In [ ]:
idhColT2.replace(inapropiados,value=[None]*len(inapropiados),inplace=True)

AUSPICIO:

El desarrollo de estos contenidos ha sido posible gracias al grant del Berkeley Initiative for Transparency in the Social Sciences (BITSS) at the Center for Effective Global Action (CEGA) at the University of California, Berkeley

RECONOCIMIENTO

El autor reconoce el apoyo que el eScience Institute de la Universidad de Washington le ha brindado desde el 2015 para desarrollar su investigación en Ciencia de Datos.