Los datos categóricos son extremadamente comunes en la mayoría de aplicaciones de machine learning en el mundo real. El problema principal es que la mayoría de algoritmos no saben realmente cómo manejar datos categóricos: son muy buenos trabajando con números, pero no entienden muy bien el concepto de categoría. Por esto, es importante tener una manera de convertir datos categóricos a un formato con el que la mayoría de algoritmos puedan trabajar.
One-hot encoding es una forma muy simple y popular de manejar categorías. En términos simples, codifica atributos categóricos como 1s o 0s en vectores que representan cada clase.
Hoy en día la mayoría de herramientas de ciencia de datos te permiten realizar one-hot encoding de maneras muy simples. Veremos el concepto y luego crearemos nuestra propia implementación para codificar atributos de clase de una manera comprensible para nuestros algoritmos de ML.
Cuándo deberías usar one-hot encoding
Comúnmente, algunos atributos de los datos con los que trabajas son categóricos por naturaleza.
Imagina que tienes una tabla con datos sobre diferentes tipos de pastelería. Como atributos, tienes cosas como el nombre del pastel, los precios en euros, la cantidad de azúcar en gramos y el tamaño.
Este último atributo es categórico, y puede tomar los valores de pequeño, mediano y grande. La siguiente tabla ilustra una muestra hipotética:
Nombre | Precio(euros) | Azúcar(g) | Tamaño |
---|---|---|---|
Pastel_1 | 4.2 | 30 | Grande |
Pastel_2 | 3.8 | 17 | Med |
Pastel_3 | 1.2 | 22 | Grande |
Pastel_4 | 3 | 16 | Med |
Pastel_5 | 0.7 | 8 | Pequeño |
Pastel_6 | 4 | 28 | Med |
Pastel_7 | 1.8 | 15 | Grande |
Pastel_8 | 2.2 | 15 | Grande |
Pastel_9 | 0.5 | 7 | Pequeño |
Pastel_10 | 3 | 21 | Pequeño |
Como está ahora, tendrías problemas pasando estos datos a la mayoría de algoritmos de ML. No tendrían problema manejando valores como el precio y contenido de azúcar, pero no sabrían qué hacer con el tamaño.
A veces, los datos categóricos tienen una progresión lógica que puedes usar para resolver este problema. El tamaño, por ejemplo, puede transformarse fácilmente a valores numéricos: puedes decir que un pastel mediano es 4 veces más grande que uno pequeño, y uno grande 12 veces más grande. Si eliges 1 como el valor base para pequeño, puedes transformar fácilmente la tabla en la siguiente:
Nombre | Precio(euros) | Azúcar(g) | Tamaño |
---|---|---|---|
Pastel_1 | 4.2 | 30 | 12 |
Pastel_2 | 3.8 | 17 | 4 |
Pastel_3 | 1.2 | 22 | 12 |
Pastel_4 | 3 | 16 | 4 |
Pastel_5 | 0.7 | 8 | 1 |
Pastel_6 | 4 | 28 | 4 |
Pastel_7 | 1.8 | 15 | 12 |
Pastel_8 | 2.2 | 15 | 12 |
Pastel_9 | 0.5 | 7 | 1 |
Pastel_10 | 3 | 21 | 1 |
Estos números son solo para ilustración, sus valores dependen del problema del dominio.
Ahora, ¿qué pasa si los datos no tienen una relación numérica clara entre categorías? En nuestro ejemplo anterior, era muy fácil porque grande > mediano > pequeño, así que es razonable reemplazar los valores con enteros siempre que tengan sentido. Pero ¿qué pasa si la variable es algo como colores? No puedes decir, por ejemplo, que rojo equivale a tres veces azul.
Aquí es cuando one-hot encoding se vuelve útil. Te permite representar este tipo de datos categóricos de una manera que la mayoría de algoritmos pueden manejar. Para explicar cómo funciona, veamos la siguiente tabla con información básica sobre 10 pokemon (sí, estamos trabajando con pokedatos).
Puedes notar que atributos como HP, Ataque,…, Velocidad son fáciles de manejar, todos son enteros. Por otro lado, el atributo Tipo necesita ser manejado para poder ser usado.
Tipo es obviamente categórico: un pokemon puede pertenecer a diferentes categorías de tipo. También es obvio que no puedes hacer un reemplazo como en el caso de los tamaños de pastelería. Si dices que Veneno = 2x Eléctrico = 5.5x Agua, el algoritmo aprenderá esta jerarquía aunque no tenga ningún sentido en la vida real.
Este es un caso donde podemos usar one-hot encoding para transformar el atributo Tipo en un formato amigable para algoritmos.
One-hot encoding funciona así:
- Crearás una nueva columna para cada categoría que los datos puedan tomar. En nuestro caso, necesitaremos 6 nuevas columnas: Veneno, Eléctrico, Agua, Tierra, Bicho, y Fantasma.
- En cada fila (pokemon) pondrás un 1 si pertenece a la categoría y un 0 si no. Para el primer ejemplo (Koffing), pondrás un 1 en Veneno y 0s en el resto de los tipos.
Puedes ver los resultados de aplicar one-hot encoding al atributo tipo en la siguiente tabla. Puse en gris las celdas con un 1 para hacerla más fácil de leer.
Tipo ahora está codificado como vectores de 1s y 0s (¡que los algoritmos como las redes neuronales aman!).
Ahora que entiendes one-hot encoding desde un ángulo conceptual, escribamos nuestra propia implementación usando Python para obtener algo de experiencia práctica.
One-hot encoding en Python
Todo el código en esta sección está disponible como un notebook de Jupyter. Puedes encontrar un enlace al repo en la sección Qué hacer después al final del artículo.
Transformaremos la columna Tipo en una representación one-hot-encoded usando dos enfoques. El primero será un enfoque paso a paso que te ayudará a entender el proceso de codificación, mientras que el segundo hace uso de una función incorporada que codifica una columna categórica en una sola línea de código.
Lo primero que necesitamos es cargar nuestros datos en un formato con el que podamos trabajar fácilmente. Como primer paso, crearemos un DataFrame de Pandas con los datos de Pokemon:
import pandas as pd
poke_data = pd.read_csv('./poke_data.csv')
poke_data.head(10)
Podemos acceder fácilmente a la información de Tipo y ponerla en una lista con esto:
type_data = poke_data["Type"].to_list()
type_data
>[' "Poison"',
' "Electric"',
' "Water"',
' "Water"',
' "Electric"',
' "Ground"',
' "Water"',
' "Bug"',
' "Ghost"',
' "Ground"']
Ahora, escribiremos una pequeña función de python que toma una lista de categorías y crea un dataframe de Pandas con la versión one-hot encoded de la misma.
Usaremos una matriz de NumPy como estructura de datos intermedia. El código está comentado y explicado de manera sección por sección, así que debería ser fácil de entender:
import numpy as np
def strings_to_onehot(categories_column):
# Primero, construiremos una lista que contiene las categorías
# para eso, creamos un array con los elementos únicos
unique_categories = list(set(categories_column))
# Crearemos una matriz one-hot, el primer paso es crear una matriz de ceros
# de dimensiones número de puntos de datos X número de categorías
one_hot_matrix = np.zeros( (len(categories_column), len(unique_categories)), dtype=int )
# Este bucle pone en 1 el slot correcto en cada fila para cada ejemplo en nuestros datos
for row, category in zip(one_hot_matrix, categories_column):
category_index = unique_categories.index(category)
row[category_index] = 1
# Ahora, construyamos y retornemos un DataFrame con los valores
return pd.DataFrame(columns = unique_categories, data = one_hot_matrix)
# onehot_types es el DataFrame con las categorías en one-hot encoding
onehot_types = strings_to_onehot(type_data)
onehot_types
Puedes ver que el dataframe sigue el orden original: la primera línea es para Koffing (por tanto, el tipo es Veneno), la segunda es para Pikachu (Eléctrico) y así sucesivamente.
Ahora, podemos unir este DataFrame con los datos originales para obtener uno nuevo con el tipo codificado como una matriz one-hot. Esto puede lograrse fácilmente con las siguientes líneas:
# Ahora concatenaremos ambos datos
final_pokedata = pd.concat([poke_data, onehot_types],axis=1)
final_pokedata
Normalmente, la columna Tipo original se elimina, pero en este caso, la dejamos ahí por completitud.
Genial, ¿pero no hay una manera más fácil de codificar atributos categóricos? Sí, podrías usar la función get_dummies de Pandas y obtener el mismo resultado:
poke_data = pd.read_csv('./poke_data.csv')
onehot_types = pd.get_dummies(poke_data['Type'])
poke_data = poke_data.join( onehot_types )
poke_data
Hay muchas otras maneras de transformar una columna con datos categóricos en una representación one-hot. Como probablemente ya estés usando Pandas, manejar datos categóricos será muy fácil de ahora en adelante.
Desventajas de one-hot encoding
La desventaja más obvia de one-hot encoding es que hace el dataframe mucho más grande de lo que necesita ser.
En nuestro ejemplo, trabajamos con solo 6 nuevas categorías, lo que significa que cada fila crecerá en 6 atributos, no es mucho problema. Los datos reales, sin embargo, pueden tener muchas más categorías, y si tienes varios millones de filas, el impacto en almacenamiento puede ser significativo.
Esto es especialmente problemático en NLP. En un vocabulario que contiene solo 10.000 palabras, representarías cada palabra como un vector con un tamaño de 10.000 con un 1 en el índice correcto. Esto significa que incluso un dataset de tamaño mediano que consiste en unos pocos cientos de libros puede terminar consumiendo terabytes de almacenamiento si se representa usando one-hot encoding.
A pesar de los problemas, one hot encoding es una solución práctica en un vasto número de problemas, y probablemente es lo primero que intentarás cuando trabajas con datos categóricos.
Ahora conoces lo básico de esta técnica útil, ¡así que ve y construye algo interesante usando datos con atributos categóricos!
Gracias por leer.
Qué hacer después
- Comparte este artículo con amigos y colegas. Gracias por ayudarme a llegar a personas que podrían encontrar útil esta información.
- Aquí encontrarás un repo con el código para este artículo.
- Este artículo está basado en el libro: Data Science for Business: What You Need to Know about Data Mining and Data-Analytic Thinking.
- Envíame un email con preguntas, comentarios o sugerencias (está en la página Autor)