Preprocesamiento

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 15

Contenido

1. Preprocesado de datos ............................................................................................................................... 2


1.1 ¿Que es el preprocesado de datos? .................................................................................................... 2
1.2 Creación de conjunto de entrenamiento y pruebas ............................................................................ 3
1.3 Manejo de datos ausentes .................................................................................................................. 4
1.4 Manejo de datos categóricos .............................................................................................................. 7
1.5 Escalamiento de caracteristicas ........................................................................................................ 12

ING. CARLOS MARIO PINEDA PERTUZ


1. PREPROCESADO DE DATOS

1.1 ¿QUE ES EL PREPROCESADO DE DATOS?

En muchos casos los conjuntos de datos que recopilamos por ejemplo a través de una
encuesta en línea, presentan ausencia o inconsistencia de algunos datos siendo las causas de
esto muy diversas: Pueden ir desde incompletitud al momento de suministrar la información,
en el caso de una encuesta, o hasta por un error al momento de la captura de los datos. Este
hecho si no se atiende debidamente puede ocasionar errores o resultados no deseados en los
modelos de aprendizaje automático, de allí la importancia de esta etapa en los procesos de
aprendizaje automático.

En este apartado estudiaremos justamente algunos aspectos y técnicas relevantes


relacionadas con el preprocesamiento de datos. Pero antes de hacerlo, haremos mención de
las funciones fit() y transform() que comparten muchas clases que implementan algoritmos
de Machine Learning dentro de la biblioteca Scikit-learn.

En todos los algoritmos que vamos a comenzar a estudiar desde ahora, el modo de operar
después de efectivamente haber cargado el dataset, es el siguiente: 1) instanciamos un objeto
de alguna clase de interés con los argumentos respectivos; 2) con el objeto instanciado se
invoca al método fit(), el cual se ajusta con los datos del subconjunto de entrenamiento y
estima valores para los parámetros internos del modelo; 3) luego se llama al método
transform() con el cual se llevan a cabo las transformaciones requeridas sobre los datos de
entrada. Estas transformaciones pueden ser por ejemplo escalado de características,
imputación de valores ausentes, etc. Scikit-learn nos ofrece un método fit_transform() para
efectuar estas dos tareas de ajustar y transformar el objeto en un solo paso.

Cabe anotar, que el método fit() además de usarse con algoritmos de preprocesamiento
también se puede emplear con los algoritmos típicos de machine learning.

Para algoritmos supervisados a fit() le pasamos como parámetros los conjuntos X y Y de


entrenamiento, mientras que para algoritmos no supervisados solo le pasamos X. Siendo X
una matriz con los atributos descriptivos y Y un arreglo con las etiquetas de clase.
1.2 CREACIÓN DE CONJUNTO DE ENTRENAMIENTO Y PRUEBAS

Cuando un conjunto de datos es lo suficientemente grande se puede separar en un conjunto


de entrenamiento usado para entrenar el modelo de aprendizaje, y un conjunto de prueba
para validar la calidad de dicho modelo. La separación de las instancias generalmente es
realizada de manera aleatoria, considerando un 70% u 80% del conjunto original para
conformar el conjunto de entrenamiento y el resto para el conjunto de pruebas.

Con la biblioteca Scikit-learn esto puede ser logrado a través de la función train_test_split()
del submódulo model_selection.

import pandas as pd

from sklearn.model_selection import train_test_split

df = pd.read_csv("precios_casas.csv")

X = df.iloc[:,1:].values

y = df.iloc[:,0].values

print(X)

entX, pruX, enty, pruy = train_test_split(X, y, test_size=0.2, random_state=100)

En este código básicamente se recuperan los valores de los atributos descriptivos y se


almacenan en X. Mientras que los valores de la etiqueta de clase se almacenan en y. Ambos
tanto X como y son arreglos de numpy. El primero es una matriz de dos dimensiones con 4.600
filas y 16 columnas y el segundo es un vector de 4.600 elementos.

La función train_test_split() recibe como parámetros los conjuntos X e y junto con el tamaño
del conjunto de prueba (test_size=0.2), es decir 20%, devolviendo los siguientes 4 arreglos de
numpy:

entX: Conjunto de entrenamiento con atributos descriptivos: 3.680 filas y 16 columnas.


enty: Conjunto de entrenamiento con la etiqueta de clase: 3.680 etiquetas.

pruX: Conjunto de prueba con los atributos descriptivos: 920 filas y 16 columnas.

pruY: Conjunto de prueba con la etiqueta de clase: 920 etiquetas.

El parámetro random_state=100, se usa como semilla para inicializar un objeto generador de


números aleatorios interno, utilizado por numpy para realizar la separación, además con esta
configuración garantizamos que aún cuando se ejecute el programa varias veces se producirán
los mismos conjuntos de puntos para train y test. Esto es útil en ciertos casos, por ejemplo,
cundo se tiene un error y se quiere hacer una depuración (debug), entonces se necesita
reproducir varias veces el problema con los mismos datos generados en un principio, con el
fin de verificar si la solución propuesta es la adecuada.

Finalmente después de llamar a train_test_split(), ya tendríamos nuestros datos debidamente


separados (80% para entrenamiento y 20% para pruebas) y así poder aplicar las diferentes
técnicas de preprocesamiento.

Puede usar un valor diferente para hacer la separación como 90%-10%, pero las más usadas
son 80%-20% y 70%-30%, en todo caso, se debe tener en cuenta siempre la cantidad de datos
disponibles, dado que por ejemplo en un dataset de millones de registros quizas un 99%-1%
podría ser una buena medida de separación.

1.3 MANEJO DE DATOS AUSENTES

Es muy común que después de recopilar datos para realizar una actividad de aprendizaje
automático nos demos cuenta que faltan algunos de ellos. Esta es una situación a la cual hay
que prestarle mucha atención debido a que la ausencia de datos puede provocar problemas
a los algoritmos utilizados, dado que estos últimos son muy susceptibles a los faltantes a tal
punto que les hacen producir resultados inesperados. La ausencia de un valor en Python se
representa con un NaN (Not a Number).

Para observar esta situación vamos a crear un conjunto de datos nuevo, ya que el de los
precios de las casas viene por defecto debidamente preprocesado y no tiene valores faltantes.
Esto lo puede comprobar escribiendo la siguiente instrucción, esta nos devuelve la suma de
valores ausentes por cada columna:

df.isnull().sum()

Siguiendo con el ejemplo, el dataframe que usaremos será el que sigue:

df = pd.DataFrame([

['1', 1, 30],

['2', 1, 32],

['3', 0]],

df.columns = ['codigo','credito','edad']

Como se puede apreciar falta un valor en la columna edad de la última fila. Para paliar esta
situación existen dos maneras posibles: La primera es eliminar del conjunto aquellas filas o
columnas donde se encuentren los valores ausentes, mediante la función dropna(), que en su
forma básica permite:

➢ Eliminar las filas donde hay valores ausentes, así:

df.dropna(axis=0), esta es la opción por defecto.

➢ Eliminar las columnas donde hay valores ausentes en alguna fila, así:

df.dropna(axis=1)
Aquí, axis=0 se refiere a filas y axis=1 a columnas.

la función dropna() maneja otros parámetros interesantes que permiten establecer cierto
control sobre el borrado de filas o columnas: how = {any, all}, con any borra si hay al menos
un valor NaN, en cambio con all hace el borrado si todos los valores son NaN; thresh=valor,
donde valor es un número que funciona como umbral con el cual se establece el número
máximo de NaN que debe superarse para realizar el borrado;
subset=[lista_nombres_columnas] que permite borrar las filas siempre y cuando las columnas
cuyos nombres se indican en la lista tengan NaN.

Sin embargo, este enfoque no se recomienda demasiado puesto que, se pueden perder datos
en demasía o datos que podrían resultar útiles para el entrenamiento del algoritmo de
aprendizaje automático. Por lo tanto, el mejor método en este sentido es el conocido como
imputación de valores ausentes, encontrándose accesible su implementación en Scikit-learn
a través de la clase SimpleImputer del submódulo sklearn.imputer. Al instanciar un objeto, en
el constructor se puede especificar una estrategia, como la de imputación por medias
(strategy=’mean’), la cual consiste en reemplazar el valor ausente con la media aritmética de
toda la columna. Otras opciones para strategy son ‘median’ para sustituir el valor que falta
por la mediana, ‘most-frequent’ para reemplazar por el valor más frecuente (la moda de los
datos) y ‘constant’ para cambiar por un valor fijo o constante.

Otro punto importante a considerar, es que hay filas que tienen columnas con algunos
caracteres especiales como por ejemplo símbolos de interrogación (?), entonces en estos
casos antes de realizar la tarea de imputación se recomienda primero reemplazar el simbolo
por un NaN mediante la función replace().

df.replace('?', np.nan, inplace=True)

Una vez se haya realizado un reemplazo como el anterior se realiza la imputación utilizando
el siguiente fragmento de código:
from sklearn.impute import SimpleImputer

imp = SimpleImputer(missing_values=np.nan, strategy='mean')

imp = imp.fit(df.values)

imp_datos = imp.transform(df.values)

Si imprimimos la variable imp_datos veremos como el valor faltante se ha reemplazado por el


número 31, que es la media aritmética de los datos de la tercera columna: (30 + 32) / 2 = 31.

1.4 MANEJO DE DATOS CATEGÓRICOS

Los algoritmos de aprendizaje automático están diseñados para operar con datos numéricos,
por lo cual se recomienda convertir a números todos aquellos datos categóricos. En este
sentido, algo estrictamente necesario es codificar la etiqueta de clase del conjunto de datos a
un valor numérico. Esto se consigue con la clase LabelEncoder. Miremos como hacer esto
tomando en consideración el siguiente dataframe:

df = pd.DataFrame([

['M', 30, 'Amarillo','Clase 1'],

['P', 28 , 'Azul','Clase 2'],

['J', 21, 'Rojo', 'Clase 1']],

)
df.columns = ['nombre','edad','color', 'etiqueta']

Convertiremos ahora los valores de la columna etiqueta a su representación numérica:

from sklearn.preprocessing import LabelEncoder

le_clase = LabelEncoder()

y = le_clase.fit_transform(df.etiqueta)

print(y)

Donde y sería un vector con los valores [0 1 0], que significa que “Clase 1” se codificó como 0
mientras que “Clase 2” como 1 resultado del ajuste y la transformación realizada con el
método fit_transform(). Desde luego, es posible obtener la representación original ['Clase 1'
'Clase 2' 'Clase 1'], mediante el método inverse_transform():

clase_inv = le_clase.inverse_transform(y)

print(clase_inv)

No obstante, la técnica anterior no es la más adecuada, ya que, si por ejemplo tenemos una
variable llamada profesión con los valores ingeniero, arquitecto y contador, codificada como
1,2 y 3 respectivamente, un algoritmo de aprendizaje podría inferir que contador es mayor a
arquitecto o que arquitecto es mayor que ingeniero, lo cual no es correcto puesto que no se
trata de una variable categórica ordinal.
Para mejorar esta situación hay otra técnica muy utilizada denominada one hot encoding (Una
codificación en caliente), con ella un atributo categórico nominal de n valores posibles
generará n columnas binarias, donde se coloca un 1 en la columna coincidente con el valor
que tiene la instancia para ese atributo y el resto de columnas referentes a ese mismo atributo
se colocan en 0. Por ejemplo, supongamos un conjunto de datos de vehículos con un atributo
color, con tres posibles valores: amarillo, azul y rojo. Entonces el atributo color=azul de una
instancia cualquiera se representaría como: 0,1,0.

Para que la codificación en caliente funcione la entrada recibida debe ser numérica, por lo que
usaremos la clase LabelEncoder vista hace un momento para realizar esta transformación.
Creamos un objeto de la mencionada clase y codificamos la columna color a un consecutivo
comenzando desde 0, cuyos valores los guardaremos en una nueva columna del dataframe
llamada color_cod.

from sklearn.preprocessing import OneHotEncoder

le_color = LabelEncoder()

ohe_color = OneHotEncoder(categories='auto')

df['color_cod'] = le_color.fit_transform(df.color)

print(df)

Posteriormente transformamos la característica color mediante el objeto ohe_color, cuyo


método fit_transform() espera un arreglo 2D (bidimensional), por lo que debemos
redimensionarlo de 1D a 2D. Además este método devuelve una matriz dispersa, entonces
usamos el método toArray() para convertirla a una matriz de Numpy. Seguidamente
generamos otro dataframe con las nuevas columnas generadas y las concatenamos con
nuestro dataframe inicial mediante la función concat().
datos_ohe = ohe_color.fit_transform(df.color_cod.values.reshape(-1,1)).toarray()

dfOneHot = pd.DataFrame(datos_ohe, columns = ["Color_"+str(int(i)) for i in range(len(df.color))])

df = pd.concat([df, dfOneHot], axis=1)

print(df)

Finalmente podemos eliminar las columnas color y color_cod del dataframe porque
sencillamente ya no las necesitamos, mediante la siguiente sentencia:

df= df.drop(['color', 'color_cod'], axis=1)

Existe otra función llamada get_dummies() de la librería Pandas que también convierte
variables categóricas a variables indicadoras (o dummies) y es mucho más apropiada cuando
tienen una gran cantidad de valores.

En la siguiente sentencia invocamos a get_dummies() con los parámetros: data, columns y


drop_first, indicando en ese mismo orden: el dataframe, el nombre de las columnas a ser
codificadas y la opción de borrar la(s) columna(s) objetivo de la codificación (color) y la primera
columna (color_amarillo). Esta operación de ninguna manera significa perdida de información
ya que, si tenemos para una muestra cualquiera, por ejemplo, en la columna color_azul un 0
y en la columna color_rojo otro 0, significa que el color para esa muestra es el amarillo. Por
otra parte esta característica de la función get_dummies() hace que los dataframes no crezcan
tanto en situaciones donde hay características que tienen muchos valores, hecho muy común
cuando se usa one hot encoding, siendo esta sin duda su principal desventaja.
pd.get_dummies(data=dfAux, columns=["color"], drop_first=True)

También hacer un mapeo de un valor categórico a un valor numérico mediante la función


map, como en el siguiente ejemplo donde se efectúa esta acción con los valores ‘M’ y ‘F’ de
una variable género.

d = {'M': 1, 'F':0}
df['genero'] = df['genero'].map(d)

df
1.5 ESCALAMIENTO DE CARACTERISTICAS

El escalamiento es otra de las tareas importantes dentro del preprocesado de datos, debido a
que la gran mayoría de los algoritmos de aprendizaje automático funcionan mejor si se tienen
las características de un conjunto de datos bajo la misma escala. Adicionalmente, es necesario
usar la misma escala en el conjunto de entrenamiento y de prueba a fin de no terminar
provocando un sesgo aleatorio en los datos.

Como ejemplo escalaremos el atributo precio del dataframe de los precios de las casas en un
rango entre 0 y 1, lo haremos usando la técnica de la normalización que le aplicaremos a la
variable por medio de la siguiente función:
𝑥𝑖 − 𝑥𝑚𝑖𝑛
𝑥𝑛𝑜𝑟𝑚𝑎𝑙𝑖𝑧𝑎𝑑𝑎 =
x𝑚𝑎𝑥 − x𝑚𝑖𝑛

Donde, 𝑥𝑖 corresponde a los precios de la columna price, 𝑥𝑚𝑖𝑛 es el valor más pequeño y x𝑚𝑎𝑥
es el valor más grande de la misma. La anterior ecuación la podemos programar en Python de
forma sencilla aprovechando la vectorización, característica para mejorar el rendimiento
computacional evitando en la medida de lo posible el uso de ciclos en la realización de
operaciones con vectores:
def normalizar(columna):

x_norma = (columna - columna.min()) / (columna.max() - columna.min())

return x_norma

Sin embargo, no tendremos que preocuparnos por implementar la función anterior puesto
que ya está implementada en la clase MinMaxScaler de Scikit-learn. Considere el fragmento
de código siguiente:

import pandas as pd

from sklearn.preprocessing import MinMaxScaler

df = pd.read_csv("precios_casas.csv")

esc = MinMaxScaler()

X_ent = esc.fit_transform(df.price.values.reshape(-1,1))

print(X_ent)

El resultado es un array de numpy con los precios en una escala entre 0 y 1.


Otra técnica usada también para el escalado es la estandarización. La ecuación para aplicar
este procedimiento es:
𝑥𝑖 − µ
𝑥𝑒𝑠𝑡𝑎𝑛𝑑𝑎𝑟𝑖𝑧𝑎𝑑𝑎 =
σ
Siendo, µ la media de los valores de la columna y σ la desviación estándar de la misma.
Podríamos expresar la ecuación de la estandarización en Python así:

def estandarizar(columna):

x_estandar = ( columna - columna.mean()) / columna.std()

return x_estandar

Pero, de igual forma como sucede con la normalización, Scikit-learn dispone de una clase
específica para estandarizar características, y es la clase StandardScaler. Retomemos el
ejemplo anterior para estandarizar la variable precio.

import pandas as pd

from sklearn.preprocessing import StandardScaler

df = pd.read_csv("precios_casas.csv")

est = StandardScaler()

X_ent = est.fit_transform(df.price.values.reshape(-1,1))

print(X_ent)
Cabe mencionar que las técnicas de procesamiento explicadas anteriormente, tal y como se
expresó al principio necesitan recibir como entrada datos numéricos para operar.

Lo explicado en esta sección pretende servir de introducción a la temática en cuestión, por


tanto, se recomienda profundizar más sobre la fase de preprocesamiento de datos, debido a
que es una de etapas que más importancia reviste en cualquier actividad de aprendizaje
automático.

También podría gustarte