9. Variables categóricas y numéricas: cómo tratarlas

9.1 El problema: no todas las columnas se procesan igual

En un dataset real, no todas las variables tienen el mismo tipo. Algunas son numéricas, como la edad, el salario o la cantidad de productos comprados. Otras son categóricas, como la ciudad, el tipo de plan o el canal de venta.

Para un ser humano, leer una columna como plan = premium es trivial. Pero un algoritmo de Machine Learning no entiende palabras: necesita números. Por eso, antes de entrenar, debemos decidir cómo transformar cada tipo de variable.

9.2 Diferencia entre variable numérica y categórica

Una variable numérica expresa una cantidad o magnitud. Por ejemplo:

  • edad = 35
  • ingresos = 1200
  • visitas = 8

Una variable categórica expresa una categoría o etiqueta. Por ejemplo:

  • ciudad = Rosario
  • plan = premium
  • dispositivo = móvil

El error clásico de principiante es intentar pasar columnas de texto directamente al modelo. La mayoría de los algoritmos de Scikit-learn no aceptan cadenas como entrada.

9.3 Por qué no conviene reemplazar texto por números arbitrarios

Supongamos que tenemos la variable plan con estos valores:

  • básico
  • estándar
  • premium

Una mala idea sería convertirlos así:

básico = 1
estándar = 2
premium = 3

Eso hace creer al modelo que existe una distancia numérica natural entre las categorías. En muchos casos, esa relación no existe. Por eso suele preferirse One-Hot Encoding, que crea una columna por categoría.

9.4 Qué hace OneHotEncoder

OneHotEncoder transforma una categoría en varias columnas binarias. Por ejemplo, si una columna canal tiene los valores web, móvil y tienda, el resultado puede quedar así:

canal_web     canal_móvil     canal_tienda
    1              0                0
    0              1                0
    0              0                1

De este modo, el modelo recibe números sin interpretar que una categoría es “más grande” que otra.

9.5 Ejemplo completo: predecir si un cliente compra

Vamos a trabajar con un dataset pequeño donde combinamos variables numéricas y categóricas:

  • edad e ingresos: variables numéricas.
  • canal y plan: variables categóricas.
  • compra: variable objetivo binaria.

Usaremos ColumnTransformer para aplicar transformaciones diferentes según el tipo de columna.

import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

clientes = pd.DataFrame({
    "edad": [22, 25, 27, 30, 35, 40, 45, 50, 28, 33],
    "ingresos": [300, 350, 400, 500, 650, 800, 900, 1100, 420, 560],
    "canal": ["web", "web", "móvil", "tienda", "web", "móvil", "tienda", "web", "móvil", "tienda"],
    "plan": ["básico", "básico", "básico", "estándar", "estándar", "premium", "premium", "premium", "estándar", "premium"],
    "compra": [0, 0, 0, 1, 1, 1, 1, 1, 0, 1]
})

X = clientes[["edad", "ingresos", "canal", "plan"]]
y = clientes["compra"]

columnas_numericas = ["edad", "ingresos"]
columnas_categoricas = ["canal", "plan"]

preprocesador = ColumnTransformer(
    transformers=[
        ("num", "passthrough", columnas_numericas),
        ("cat", OneHotEncoder(handle_unknown="ignore"), columnas_categoricas)
    ]
)

modelo = Pipeline([
    ("preprocesamiento", preprocesador),
    ("clasificador", LogisticRegression(max_iter=1000))
])

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

modelo.fit(X_train, y_train)
y_pred = modelo.predict(X_test)

print("Predicciones:", y_pred)
print("Valores reales:", y_test.values)
print("Exactitud:", accuracy_score(y_test, y_pred))

nuevo_cliente = pd.DataFrame({
    "edad": [36],
    "ingresos": [700],
    "canal": ["web"],
    "plan": ["premium"]
})

prediccion = modelo.predict(nuevo_cliente)[0]
probabilidad = modelo.predict_proba(nuevo_cliente)[0, 1]

print("Predicción para el nuevo cliente:", prediccion)
print(f"Probabilidad de compra: {probabilidad:.3f}")

Salida resumida esperada:

Predicciones: [1 0 1]
Valores reales: [1 0 1]
Exactitud: 1.0
Predicción para el nuevo cliente: 1
Probabilidad de compra: 0.7...

9.6 Explicación detallada del programa

  • columnas_numericas: identifica qué variables pasan al modelo como números.
  • columnas_categoricas: identifica qué variables necesitan codificación.
  • OneHotEncoder(handle_unknown="ignore"): transforma categorías en columnas binarias y evita errores si aparece una categoría nueva en predicción.
  • ColumnTransformer(...): permite aplicar una transformación a las columnas numéricas y otra distinta a las categóricas.
  • Pipeline([...]): une el preprocesamiento y el modelo en un solo flujo ordenado.
  • modelo.fit(...): primero transforma los datos y luego entrena la regresión logística.
  • modelo.predict(...): cuando llega un nuevo caso, aplica automáticamente la misma preparación y luego predice.

9.7 Por qué ColumnTransformer es tan útil

Cuando un dataset mezcla tipos de columnas, intentar preparar todo manualmente se vuelve incómodo y propenso a errores. ColumnTransformer resuelve esto de forma limpia:

  • mantiene separado qué hacer con cada tipo de variable;
  • evita olvidos al momento de predecir con nuevos datos;
  • se integra naturalmente con Pipeline;
  • hace que el código sea más claro y escalable.

En proyectos reales, esta forma de trabajar es mucho más segura que transformar columnas “a mano” en distintos momentos del flujo.

9.8 Error frecuente: mezclar mal las categorías

Un error común consiste en transformar una columna categórica con reglas improvisadas o distintas entre entrenamiento y predicción. Por ejemplo, si hoy codificamos web = 0 y mañana cambiamos el criterio, el modelo recibirá información inconsistente.

La solución práctica es clara:

  • definir la transformación una sola vez;
  • entrenarla junto con el modelo;
  • reutilizar siempre exactamente el mismo flujo.

Por eso Pipeline y ColumnTransformer suelen usarse juntos.

9.9 Qué deberías retener

  • Las variables numéricas y las categóricas no deben tratarse igual.
  • Las columnas de texto normalmente deben transformarse antes del entrenamiento.
  • OneHotEncoder convierte categorías en columnas binarias sin imponer un orden numérico artificial.
  • ColumnTransformer permite combinar transformaciones diferentes en un mismo dataset.
  • Pipeline asegura que el mismo preprocesamiento se repita al entrenar y al predecir.