Técnicas para codificar las variables categóricas (II): codificación binaria y hashing
Como ya explicamos, la mayoría de los modelos de machine learning en Python, entre ellos las redes neuronales, solo pueden leer valores numéricos y por ello es importante convertir este tipo de variables categóricas en numéricas para que los modelos puedan utilizarlas.
En la anterior entrega presentamos los métodos más populares: la codificación ordinal y one-hot. En esta ocasión nos centraremos en otros dos métodos probablemente no tan conocidos, pero bastante útiles en caso de que tengamos variables categóricas de alta cardinalidad: la codificación binaria y hashing.
El conjunto de datos Adult
En los casos prácticos seguiremos usando el conjunto de datos Adult, perteneciente al repositorio UCI Machine Learning. Como ya explicamos, este dataset contiene 48.842 instancias que representan datos personales (edad, género, raza, etc.) de ciudadanos anónimos extraídos de la base de datos del Censo de 1994 de los Estados Unidos.
En primer lugar, cargamos el dataset usando para ello la función read_csv
de pandas y lo almacenamos en un un objeto DataFrame
. Posteriormente le asignamos el nombre de cada columna como se muestra en el siguiente código:
import pandas as pd
# Cargamos el dataset
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-'
'databases/adult/adult.data', encoding = 'utf-8',
header = None)
# Añadimos el nombre de cada variable
df.columns = ["age", "workclass", "fnlwgt", "education",
"education-num", "marital-status", "occupation",
"relationship", "race", "sex", "capital-gain",
"capital-loss", "hours-per-week", "native-country",
"class"]
Codificación binaria
Este tipo de codificación binaria es parecido a one-hot, pero en este caso las categorías se almacenan primero como códigos binarios. Los pasos que sigue este método para codificar son los siguientes:
- Primeramente, cada categoría única de nuestra variable categórica se convierte en un número ordinal sin tener en cuenta ningún orden. Así, este número irá desde el 1 hasta el número total de categorías únicas que tengamos.
- Después, estos valores ordinales se transforman a su código binario. De esta manera, la categoría que le corresponda un valor de 10 se convertiría al código binario 1010.
- Finalmente, cada dígito binario se separa en distintas columnas, una por cada dígito, por lo que para el código binario 1010 necesitaríamos 4 columnas.
Para calcular el número total de columnas que se crearán tras este procedimiento, podemos aplicar la siguiente formula, cuyo resultado deberemos redondear hacia arriba:
donde n es el número total de categorías.
Entonces, si queremos saber cuántas columnas se necesitan para codificar una variable con 10 categorías, aplicamos la anterior fórmula y tenemos que necesitamos 4 columnas.
Esta claro que esta técnica nos proporciona una solución con muchas menos dimensiones que en el caso de one-hot, por lo que este método es recomendable cuando tenemos un gran número de categorías únicas y estamos buscando reducir la cardinalidad. ¡Mediante este proceso podrás representar 10.000 categorías con tan solo 14 columnas!
Para poder aplicar este tipo de codificación en Python, la librería category_encoders
nos proporciona el objeto BinaryEncoder
. Veamos un ejemplo de cómo podemos utilizarlo en nuestro dataset. Aplicaremos esta codificación a la variable native-country
, que contiene 42 categorías únicas correspondientes a nombres de países.
A partir de la anterior fórmula, sabemos que vamos a necesitar 6 columnas. Ahora sí, creamos el codificador indicándole en nombre de nuestra columna, lo ajustamos con esta y finalmente la transformamos.
from category_encoders import BinaryEncoder
# Creamos el codificador indicandole la columna
encoder = BinaryEncoder(cols=["native-country"])
# Ajustamos el codificador con native-country y la transformamos
encoder.fit(df["native-country"])
df_binario = encoder.transform(df["native-country"])
df = pd.concat([df, df_binario], axis=1)
native-country
junto con las nuevas variables creadas para comparar. df.loc[:, df.columns.str.startswith('native-country')].head()
BinaryEncoder
nos creará la mayor parte de las veces una variable de más que no utiliza. Si investigamos un poco más veremos que se trata de la variable native-country_0
, ya que solo contiene ceros, por lo que sería buena idea eliminarla del conjunto de datos.
Codificación hashing
La principal característica de la codificación hashing es que utiliza una función hash para asignar un número de longitud fija a cada categoría única. Para conocer cómo funciona de manera detallada podéis consultar el paper original.
Para emplear este método en Python, usaremos de nuevo la librería category_encoders
que nos proporciona el objeto HashingEncoder
. Pero antes debemos tener en cuanto los siguientes factores.
Por defecto, el codificador hashing implementado en category_encoders
utiliza el algoritmo hash denominado MD5 (Message Digest Algorithm 5), pero podemos elegir cualquier otro algoritmo siempre que se encuentre dentro del módulo hashlib en Python. Para ello debemos indicar el algoritmo de nuestra elección con el argumento hash_method
.
Además, debemos elegir el numero de bits que vamos a usar para representar la variable, es decir, el número de dimensiones que obtendremos tras la codificación. Para ello simplemente le indicamos el número que queramos mediante el argumento n_components
.
Probablemente os estaréis preguntado cuántas dimensiones necesitáis para representar vuestra variable categórica. No hay una respuesta clara sobre esto, pero si miramos en la documentación oficial, la función utiliza por defecto 8 bits para representar la variable y nos recomiendan usar hasta 32 en caso de que se trate de una variable de alta cardinalidad.
Asimismo, debemos tener en cuenta que si tenemos un gran número de categorías y elegimos un número de columnas demasiado bajo, corremos el riesgo de que se produzca una colisión de hash, es decir, que dos entradas distintas tengan el mismo valor hash.
Dicho esto, estamos listos para ver un ejemplo de cómo podemos utilizarlo con la variable native-country
de nuestro dataset. Lo primero de todo y como recordaréis, necesitamos fijar el número de dimensiones. En este caso usaré 6 dimensiones para representar las 42 categorías de nuestra variable native-country
.
from category_encoders import HashingEncoder
# Creamos el codificador indicandole el número de columnas
encoder = HashingEncoder(n_components=6)
# Ajustamos el codificador con native-country y la transformamos
encoder.fit(df["native-country"])
df_hash = encoder.transform(df["native-country"])
df = pd.concat([df, df_hash], axis=1)
native-country
junto con las nuevas variable creadas. df.loc[:, df.columns.str.startswith('col_') |
df.columns.str.startswith("native")].head(8)
Además, con esta técnica podemos elegir nosotros mismos el número de dimensiones a utilizar y nos aseguramos de esta manera que nunca aumente.
Eso sí, un inconveniente que tenemos que tener en cuenta al usar este método es el hecho de que no se puede deshacer un hash MD5 o cualquier otra función hash unidireccional.
En esta ocasión hemos aprendido otras dos aproximaciones no tan conocidas para codificar variables categóricas. Si te ha parecido útil o interesante este contenido, por favor, ¡compártelo!
Como siempre recordar que el código usado en este artículo se puede obtener desde mi GitHub.