Data Science en Python: cargar archivos brutos desde una URL y exportarlos a cualquier formato (xls, csv, json)

A ver, según la wikipedia, el data cleaning es “el acto de descubrimiento y corrección o eliminación de registros de datos erróneos de una tabla o base de datos”. Por este significado propiamente dicho, lo que voy a explicar a continuación no estaría dentro del data cleaning. Sin embargo, siendo realmente estrictos, lo que vamos a hacer va a ser la primera limpieza o formateo de los datos. No es tanto corregir datos, sino adecuarlos a un formato que podamos utilizar en nuestro ecosistema (python con pandas en este caso usando dataframes). Utilizaremos dos librerías: pandas y urllib3. La tarea se compone de tres sencillos pasos: la conexión y descarga de los datos en bruto, el formateo en filas y columnas para crear el diccionario y por último la creación del dataframe para exportarlo directamente al formato que queramos.

Conexión y descarga de los datos

Para probar vamos a probar con una web que guarda las medallas olímpicas ganadas en los juegos de invierno. Se trata de winterolympicsmedals.com y si accedemos a http://winterolympicsmedals.com/medals.csv podemos descargarnos el csv de su base de datos. Lo que haremos será conectarnos a dicha dirección con el siguiente código:

import urllib3

url = "http://winterolympicsmedals.com/medals.csv"
http = urllib3.PoolManager()
resp = http.request('GET', url)

Probablemente existan otros métodos, pero yo he elegido este. No hace falta expilcarlo mucho. Simplemente con usar un poco el buscador puedes conocer más métodos o profundizar en este mismo método. Lo importante es conocer que ahora ya tenemos nuestra respuesta del servidor almacenada en una cadena de string, pero no es un String común: es binario. Debemos decodificarlo a UTF-8 para poder procesarlo adecuadamente.

#Codificamos a UTF8
datos = resp.data.decode('UTF-8')

Si queremos ver qué tenemos cargado en la variable “datos” (que ya es un string en UTF-8) simplemente tecelamos “datos” y ejecutamos, y nos mostrará su contenido. Algo así:

respuesta de carga url csv sin procesar

Creación del diccionario de datos

Antes de poder convertir todo a un data frame para que nuestra librería pueda procesarlo adecuadamente, debemos crear un diccionario. Recordemos rápidamente: un diccionario en python es un conjunto de claves con sus valores correspondientes. (De ahí el nombre de diccionario) Por ejemplo, si queremos guardar el significado de ciertas palabras, creamos un diccionario tal que así:

diccionario = {
    'palabra1' : "significado1",
    'palabra2' : "significado2",
    'palabraX' : "significadoX"
}

Y si queremos imprimirlo nos lo mostraría así:

ejemplo diccionario python

¿Porqué es necesario saber esto? Porque, ahora que sabemos cómo funciona un diccionario (clave, valores) sabemos que es posible guardar, en lugar de una cadena String como valor, una lista de valores. (Imagina una palabra que tiene varios significados) ¿Y para qué nos sirve esto? Muy sencillo: el nombre de cada columna serán las claves de nuestro diccionario, y cada valor que contenga en sus filas, será nuestra lista de valores asociada a dicha clave. Después de tener nuestro diccionario, directamente la libredía pandas se lo tragará y lo hará todo facilísimo.

Para poder hacer nuestro diccionario completo tenemos que extraer los datos de la cabecera, es decir, la primera fila de datos que corresponde con el nombre de las columnas. En otros casos es común que nos den en un archivo aparte el nombre el nombre de las columnas, pero vamos a extraerlas como viene en este ejemplo: en el propio archivo de datos la primera fila corresponde con la cabecera.

Pero claro, tenemos guardados nuestros datos como una gran cadena de texto separada por comas. Antes debemos dividirla en filas para poder extraer la primera:

#dividimos en líneas (filas)
lineas = datos.split("\n")

print(lineas[0]) #cabecera
print(lineas[1]) #primera fila de datos

Con la operación split(“separador) dividimos un conjunto de datos (que bien pueden ser extraídos de un csv) según el carácter que le indiquemos (más conocido tambíen como token). En nuestro caso, elegimos “\n” porque es lo que representa un salto de línea (vamos, la tecla enter) y como queremos serparar por filas, significa que cada salto de línea de nuestro documento es una fila. Sencillo, ¿verdad?. Como puedes ver en el código imprime dos líneas de datos. Debería salir algo así:

ejemplo primeras filas csv separado

Como puedes observar, la primera línea impresa corresponde al nombre de las columnas, y la segunda es la primera fila que contiene datos como tal. Por lo tanto, para extraer las cabeceras, debemos extraer la línea cero de nuestro set de datos y volver a dividirla, pero esta vez utilizando la coma como carácter separador:

#extraemos la cabecera
nombre_columnas = lineas[0].split(",")
num_col = len(nombre_columnas)

print(nombre_columnas)
print(num_col)

Como puedes ver ahora en la operación split() hemos utilizado la coma como carácter de escape, y nos ha guardado en “nombre_columnas” una lista con el contenido separado por comas de la línea cero de nuestro set. Lo que nos devuelve es lo siguiente:

Como se ve en la imagen, hemos incluido una variable que guarda el número de columnas. Siempre viene bien ir guardando este tipo de datos para tener estadísticas básicas a mano, ya que nunca sabes cuándo te van a venir bien.

Ahora viene la parte más dificil del código. Debemo hacer dos bucles: el primer bucle leerá nuestra lista de claves “nombre_columnas” para generar el diccionario con sus claves, pero con el contenido vacío. Después, en el segundo bucle insertaremos a cada clave su conjunto de valores.

#generamos el diccionario
#recuerda: clave (nombre columna) con conjunto de datos
#incluimos contador RECOMENDABLE SIEMPRE
contador = 0
diccionario = {}
for columna in nombre_columnas:
    diccionario[columna] = [] #ya tenemos nuestras claves con su contenido vacio
    #IMPORTANTE
    #el diccionario se define con llaves {}
    #las listas se defines con corchetes []
    #el diccionario en cada clave guarda una lista de datos (porque lo hemos definido asi)
    #si definimos la lista de dentro como {} será diccionario y no podremos llamar a dict.append
diccionario

#rellenamos el diccionario fila a fila. OJO: la cabecera ya esta procesada
for linea in lineas:
    if(contador > 0): #con esto nos saltamos la fila 0 (cabecera), porque ya está procesada en el bucle anterior
        valores = linea.strip().split(",") 
        #recuerda: strip retira caracteres de espacio en blanco por defecto
        #ahora añadimos cada valor a su columna
        for i in range(len(nombre_columnas)):
            diccionario[nombre_columnas[i]].append(valores[i])
    contador += 1 #vamos contando cuántos valores tiene cada columna, así sabemos cuántas filas tiene
    
print("El data set tiene %d filas y %d columnas" % (contador, num_col))

Y si ejecutamos el código y todo sale bien, la salida nos dice “El data set tiene 2312 filas y 8 columnas” y ya tenemos nuestro diccionario listo para ser procesado.

Lo siguiente necesario es importar el paquete pandas (debes tenerlo instalado, recomiendo como todos Anaconda). Aunque te lo estoy contando ahora, lo normal es que juntes todas las importaciones al principio de tu código. Lo comúnes poner “import pandas as pd” y cada vez que queramos llamar al paquete escribiremos “pd.operacionDeseada(“parametros”)” y punto. Si has entendido asta aquí te felicito, porque lo último que queda es lo más fácil de todo: la librería va a hacer todo totalmente automático.

Conversión de diccionario a data frame y exportación a formatos de archivo deseados

No hay nada que decir, solo mira el código:

#ahora viene lo facil: convertir a dataframe para hacer todo automatico
medallas_df = pd.DataFrame(diccionario)
medallas_df.head()

Ahora “medallas_df” tiene los datos guardados, pero en data frame. Ya no se trata de una cadena de texto o de un diccionario de valores, sino de un formato que la propia librería comprende y utiliza de forma automática para procesar los datos como nos dé absolutamente la gana.

Como extra, la operación .head() nos muestra las primeras 5 filas de nuestro set de datos. Si en lugar de no poner nada indicamos en el paréntesis un número nos imprimirá esa cantidad de filas. Si en lugar de head usamos la operación .tail() hará lo mismo pero con las últimas filas en lugar de las primeras.

Ahora que ya tenemos nuestros datos en formato de data frame, para exportarlo al archivo que queramos solo debemos utilizar una operación de la librería.

filename = "/Users/Urbano/Desktop/EjemplosData/medallas"

#podemos ahora exportarlo al formato que queramos
medallas_df.to_csv(filename + ".csv")
medallas_df.to_json(filename + ".json")
medallas_df.to_excel(filename + ".xls")

Y ya está. Ahora puedes ver en la carpeta que hayas elegido tus archivos recién generados:

muestra de los archivos generados en python desde pandasY por si no os sirve como prueba suficiente, aquí podeis ver cómo se ve el archivo xls abierto en excel. Mucho mejor que un montón de datos separados por comas, ¿verdad?

Conclusión y receta de cocina completa

Con este código podemos conectarnos a una URL que nos devuelva un set de datos en formato csv en este caso. Además de poder hacerlo sobre un archivo online, podemos hacerlo exactamente igual con un archivo que tengamos guardado en nuestro equipo. Solamente tendríamos que suprimir la parte de conexión a una web y cambiar el directorio por un directorio local. Hemos dado el primer paso del data cleaning: hemos limpiado el formato de nuestros datos para poder tratarlos como dataframe, el estándar de nuestra librería para poder continuar de forma eficiente, y además hemos generado archivos como xls que visualmente ayudan mucho a ver organizados los datos y tenerlos guardados en un estándar visible común para poder compartirlos de una forma mucho más ordenada y visual, al alcance de entendimiento de cualquiera.

Finalmente, os dejo la receta (el código fuente) completa aquí:

import urllib3
import pandas as pd

url = "http://winterolympicsmedals.com/medals.csv"
http = urllib3.PoolManager()
resp = http.request('GET', url)

#para saber si la conexion es correcta:
#resp.status devuelve código de estado de html. 200 es código bueno
#resp.data

#Codificamos a UTF8
datos = resp.data.decode('UTF-8')

#dividimos en líneas (filas)
lineas = datos.split("\n")

print(lineas[0]) #cabecera
print(lineas[1]) #primera fila de datos

#extraemos la cabecera
nombre_columnas = lineas[0].split(",")
num_col = len(nombre_columnas)

print(nombre_columnas)
print(num_col)

#generamos el diccionario
#recuerda: clave (nombre columna) con conjunto de datos
#incluimos contador RECOMENDABLE SIEMPRE
contador = 0
diccionario = {}
for columna in nombre_columnas:
    diccionario[columna] = [] #ya tenemos nuestras claves con su contenido vacio
    #IMPORTANTE
    #el diccionario se define con llaves {}
    #las listas se defines con corchetes []
    #el diccionario en cada clave guarda una lista de datos (porque lo hemos definido asi)
    #si definimos la lista de dentro como {} será diccionario y no podremos llamar a dict.append
diccionario

#rellenamos el diccionario fila a fila. OJO: la cabecera ya esta procesada
for linea in lineas:
    if(contador > 0): #con esto nos saltamos la fila 0 (cabecera)
        valores = linea.strip().split(",")
        #recuerda: strip retira caracteres de espacio en blanco por defecto
        #ahora añadimos cada valor a su columna
        for i in range(len(nombre_columnas)):
            diccionario[nombre_columnas[i]].append(valores[i])
    contador += 1
    
print("El data set tiene %d filas y %d columnas" % (contador, num_col))

medallas_df = pd.DataFrame(diccionario)
medallas_df.head()

#para guardar el archivo
filename = "/Users/Urbano/Desktop/EjemplosData/medallas"

#podemos ahora exportarlo al formato que queramos
medallas_df.to_csv(filename + ".csv")
medallas_df.to_json(filename + ".json")
medallas_df.to_excel(filename + ".xls")

2 Comentarios

  1. excelente tutorial, practico, dinamico y muy claro

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*