Este es un post largo, con un enfoque pensando para los que recién se inician en el área de Data Science. Contiene descripciones detalladas de cada paso realizado y ha sido dividido en 2. Siendo esta la primera parte.

Introducción

Todas los proyectos que he realizado tienen, sin excepción, 2 cosas en común. El preprocesamiento y entendimiento de datos. Para poder entender la importancia de estas etapas, primero es necesario mostrarles que entiendo por cada una.

  • Preprocesamiento: Son todos los procesos de transformación de la data, desde su estado crudo (tal como se recibió), hasta que sea posible analizarla/modelarla, con los modelos que se han propuesta para resolver el problema.
  • Entendimiento: Es la etapa de depuración y exploración de la data:
    • Para la fase de depuración buscamos resolver todos los datos omitidos/erróneos, además de outliers que puedan estar presentes en los datos. Es vital este paso, si esto no se realiza a conciencia, es posible que lleguemos a conclusiones erróneas de nuestra.
    • En la fase de exploración se proponen y evalúan hipótesis que tenemos en torno al problema en base a los datos. Aquí, se ocupan intensivamente distintas técnicas de visualización. El rol que ellas cumplen es de lograr un panorama general de la data. En nuestro caso, tratamos siempre evaluar la mayor cantidad de hipótesis razonables posibles. Es muy frecuente vernos sorprendidos por hipótesis que pensábamos se cumplían, en la realidad no lo hacían y viceversa. Mi “futuro yo”, siempre me lo ha agradecido.

Nota 1 : El código que verán a continuación es Python, quizás el lenguaje de programación más utilizado en Data Science, análisis de datos, etc. Próximamente, te mostraré como instalar y utilizar esta herramienta en tu propio computador y algunas razones de porqué es tan utilizada.

Nota 2 : Los datos fueron obtenidos del excelente repositorio de la Universidad de California - Irvine. Se trata de un caracterización de un pequeño listado de modelos de autos, junto con el precio de venta. Este dataset es bastante sencillo y se eligió para ilustrar con mayor claridad las dos etapas en las que se enfoca este post.

Plan:

  • Carga Librerías
  • Cargar Datos
  • Limpieza
  • Visualización
import numpy as np # para operaciones numéricas/matrices
import pandas as pd # para tablas de datos

#import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import os #para funcionalidades relacionadas al sistema operativo (carpetas)

Preprocesamiento

Carga

ruta_datos = os.getcwd() + "\\datos\\"

cols = ['symboling', 'norm_losses', 'make', 'fuel_type', 'aspiration', 
        'num_of_doors', 'body_style', 'drive_wheels',
       'engine_location','wheel_base', 'length', 'width', 'height', 
        'curb-weight', 'engine_type', 'num_of_cylinders', 'engine_size',
       'fuel_system', 'bore', 'stroke', 'compression_ratio', 'horsepower', 
        'peak_rpm', 'city_mpg', 'highway_mpg', 'price']

autos_df_orig = pd.read_csv(ruta_datos + 'auto.txt', names = cols)
len(autos_df_orig)
205

Revisemos el código anterior: La primera linea simplemente, recupera la ruta de la carpeta donde actualmente está el script. A esta ruta le agregamos el string \\datos\\ para obtener la carpeta donde están ubicados los datos. La segunda columna es necesaria por que Las columnas no vienen incluidas en el dataset por lo que hay que agregarlas de forma separada. La tercera, decimos que queremos leer los datos y almacenarlos en memoria (en este caso, en un variable de tipo dataframe a la cual denominaremos auto_df_orig). Las última linea nos indica cuantas filas tiene el dataframe.

autos_df_orig.head(5).iloc[:,:5] 
symboling norm_losses make fuel_type aspiration
0 3 ? alfa-romero gas std
1 3 ? alfa-romero gas std
2 1 ? alfa-romero gas std
3 2 164 audi gas std
4 2 164 audi gas std

Una vista rápida de las 5 primeras filas y columnas del dataframe.

Limpieza

cols_rl = [ 'price', 'horsepower', 'num_of_doors', 'body_style', 'length', 'width', 
           'num_of_cylinders', 'engine_size', 'peak_rpm']
autos_df_orig[['horsepower', 'num_of_doors', 'price', 'peak_rpm']].head(5)
horsepower num_of_doors price peak_rpm
0 111 two 13495 5000
1 111 two 16500 5000
2 154 two 16500 5000
3 102 four 13950 5500
4 115 four 17450 5500

En la primera linea creamos un lista de las variables qué, a nuestro juicio, podrían ser más interesantes de analizar. En tanto que en la segunda mostramos las 5 primeras filas de las columnas especificadas.

El siguiente comando nos permite, con la ayuda de la función anónima lambda y concat, obtener un resumen de columnas que tienen (o no) el carácter ? y a que tipo de datos corresponde cada una (en pandas se le denomina object a las columnas de tipo texto).

pd.concat([autos_df_orig[cols_rl].apply(lambda x: x.str.contains('\?').any()),
           autos_df_orig[cols_rl].dtypes],
          axis = 1, keys = ['contiene_?', 'tipo_dato'])
contiene_? tipo_dato
price True object
horsepower True object
num_of_doors True object
body_style False object
length False float64
width False float64
num_of_cylinders False object
engine_size False int64
peak_rpm True object

A partir de lo anterior, vemos que las columnas horsepower, num_of_doors, price y peak_rpm, contienen este carácter. Dado ello, Python no le queda más que establecer que todas aquellas columnas sean de tipo texto. Esto es problemático, dado que por lo general, las columnas de tipo texto son más limitadas y difíciles de analizar.

Por lo que es conveniente convertir estas columnas, que en realidad tienen naturaleza numérica, a este tipo datos. Pero si es así, ¿Cómo es que debemos convertir tales caracteres?

Ante el caso de datos erróneos u omitidos (como es en este ejemplo) podemos hacer dos cosas, eliminarlas u imputarlas. La decisión de cual elegir y de qué manera hacerlo, no es sencilla^ y esta fuera del alcance de este post. Por lo que para esta ocasión, optaremos por la estrategia más sencilla (pero probablemente errada) de solamente considerar las filas con datos válidos de esas 4 columnas, almacenándola en un nuevo dataframe auto_df. Esto lo realizamos con el siguiente comando:

autos_df = autos_df_orig[(autos_df_orig.horsepower != '?') & 
                         (autos_df_orig.num_of_doors != '?') & 
                         (autos_df_orig.price != '?') & 
                         (autos_df_orig.peak_rpm != '?')].copy()

^ Para mayor detención sugiero el libro Machine Learning de K. Murhpy (más técnico) o Data Mining de Ian Witten.

Transformación

El último paso restante es prestar atención a las columnas que están codificadas como texto, pero debiesen estar como número: num_of_doors y num_of_cylinders.

Para las dos primeras se sigue una metodología similar. En el lado izquierdo estamos creando una nueva columna num_puertas y num_cilindros, las cuales corresponderán respectivamente a las columnas num_of_doors y num_of_cylinders, pero donde estamos reemplazando cada cadena de texto por el número correspondiente. Para esto último utilizamos la función replace, donde le entregamos un diccionario donde las llaves del diccionario son el dato a reemplazar, y los valores, con qué lo vamos a reemplazar.

La tercera línea es simplemente un listado de todas las columnas de naturaleza numérica presentes en la data. La cual alimenta a la función del último código, donde aplicamos la función de conversión to_numeric para quede del tipo de datos adecuado. Acá utilizamos el parámetro ignore, que nos devuelve el mismo input si este no se puede convertir.

autos_df['num_puertas'] = autos_df['num_of_doors'].replace(
    {"two": 2, "four": 4})

autos_df['num_cilindros'] = autos_df['num_of_cylinders'].replace(
    {"two": 2, "three": 3 , "four": 4, "five": 5,'six': 6, 'eight': 8, 
     'twelve': 12})

cols_num = [ 'price', 'horsepower', 'num_puertas', 'length', 'width', 
            'num_cilindros', 'engine_size', 'peak_rpm']

autos_df[cols_num] = autos_df[cols_num].apply(pd.to_numeric, errors='ignore')

Revisando el siguiente comando, vemos que para la columna body_style (o estilo de chasis), necesitamos un tratamiento especial. Al ser una variable categórica, necesitamos codificarla de alguna forma, para que las librerías de modelamiento lo puedan entender (a ti te hablamos sklearn!). Aquí, utilizaremos la metodología más común y simple, one hot encoding. Esto es que para cada una de esas categorías, crearemos una columna y en donde codificaremos con un 1 cada vez que en la columna body_style aparezca esa categoría, y un 0 en caso contrario. Esto pandas lo realiza de forma simple con el comando get_dummies como se muestra en la última linea, devolviéndonos un dataframe, la cual la guardamos en la variable chasis_codif.

autos_df['body_style'].value_counts()
sedan          92
hatchback      67
wagon          24
hardtop         8
convertible     6
Name: body_style, dtype: int64
chasis_codif = pd.get_dummies(autos_df['body_style'])
autos_df = pd.concat([autos_df, chasis_codif], axis = 1)
%store autos_df
%store cols_num
Stored 'autos_df' (DataFrame)
Stored 'cols_num' (list)

Finalmente, para unir ambos dataframe utilizamos la función concat. Esta función toma un listado de series o dataframes y la concatena hacia al lado o hacia abajo, coincidiendo ya sea las etiquetas de filas o columnas respectivamente. Como en este caso, queremos concatenarlas hacia el lado utilizamos axis = 1, si hubiese sido en caso contrario sería axis = 0. Para almacenar este dataframe concatenado, por conveniencia simplemente reutilizamos la variable autos_df. Luego le indicamos a jupyter explícitamente que queremos guardar el deataframe autos_df' y la lista cols_num, para así utilizarla en el siguiente notebook.

Esto culmina la primera parte de este Post. En la segunda parte entraremos de lleno a Entender la data.