Datos categóricos

Introducción

En este capítulo, veremos cómo trabajar con variables categóricas, es decir, variables que tienen un conjunto fijo y conocido de valores posibles. Este capítulo le debe muchísimo a la documentación de pandas.

Requisitos previos

Este capítulo usará el paquete de análisis de datos pandas.

El tipo de dato category

Todo en Python tiene un tipo, incluso los datos de las columnas de un dataframe de pandas. Aunque quizá estés más familiarizado con los números e incluso con los strings, también existe un tipo de dato especial para datos categóricos llamado Categorical. Usar variables categóricas (cuando corresponde) tiene algunas ventajas:

  • permiten llevar la cuenta incluso cuando los elementos de una categoría no están presentes, lo que a veces puede ser tan interesante como cuando sí lo están (imagina que descubres que nadie de una escuela en particular va a la universidad)
  • pueden usar muchísima menos memoria de tu computadora que codificar la misma información de otras maneras
  • pueden usarse de forma eficiente con paquetes de modelado, donde se reconocerán como posibles ‘variables dummy’, o con paquetes de gráficos, que las tratarán como valores discretos
  • puedes ordenarlas (por ejemplo, “neutral”, “de acuerdo”, “muy de acuerdo”)

Todos los valores de datos categóricos de una columna de pandas están en las categorías dadas o toman el valor np.nan.

Crear datos categóricos

Creemos una columna de datos categórica:

import numpy as np
import pandas as pd

df = pd.DataFrame({"A": ["a", "b", "c", "a"]})

df["A"] = df["A"].astype("category")
df["A"]
0    a
1    b
2    c
3    a
Name: A, dtype: category
Categories (3, object): ['a', 'b', 'c']

Observa que obtenemos información adicional al final de la serie mostrada: se nos indica no solo que se trata de una columna de tipo categórico, sino también que tiene tres valores: ‘a’, ‘b’ y ‘c’.

También puedes usar funciones especiales, como pd.cut(), para agrupar datos en intervalos discretos. Aquí tienes un ejemplo en el que especificamos directamente las etiquetas de las categorías:

df = pd.DataFrame({"value": np.random.randint(0, 100, 20)})
labels = [f"{i} - {i+9}" for i in range(0, 100, 10)]
df["group"] = pd.cut(df.value, range(0, 105, 10), right=False, labels=labels)
df.head()
value group
0 86 80 - 89
1 42 40 - 49
2 36 30 - 39
3 73 70 - 79
4 25 20 - 29

En el ejemplo anterior, la columna group es de tipo categórico.

Otra forma de crear una variable categórica es usar directamente la función pd.Categorical():

raw_cat = pd.Categorical(
    ["a", "b", "c", "a", "d", "a", "c"], categories=["b", "c", "d"]
)
raw_cat
[NaN, 'b', 'c', NaN, 'd', NaN, 'c']
Categories (3, object): ['b', 'c', 'd']

Luego podemos introducirla en un dataframe:

df = pd.DataFrame(raw_cat, columns=["cat_type"])
df["cat_type"]
0    NaN
1      b
2      c
3    NaN
4      d
5    NaN
6      c
Name: cat_type, dtype: category
Categories (3, object): ['b', 'c', 'd']

Observa que aparecen NaN para cualquier valor que no esté en las categorías que especificamos; puedes encontrar más sobre esto en Valores faltantes.

También puedes crear categorías ordenadas:

ordered_cat = pd.Categorical(
    ["a", "b", "c", "a", "d", "a", "c"],
    categories=["a", "b", "c", "d"],
    ordered=True,
)
ordered_cat
['a', 'b', 'c', 'a', 'd', 'a', 'c']
Categories (4, object): ['a' < 'b' < 'c' < 'd']

Trabajar con categorías

Los datos categóricos tienen las propiedades categories y ordered, que indican, respectivamente, los valores posibles y si el orden importa o no. Estas propiedades se exponen como .cat.categories y .cat.ordered. Si no especificas manualmente las categorías y el orden, se infieren a partir de los argumentos pasados.

Veamos algunos ejemplos:

df["cat_type"].cat.categories
Index(['b', 'c', 'd'], dtype='object')
df["cat_type"].cat.ordered
False

Si los datos categóricos están ordenados (es decir, .cat.ordered == True), el orden de las categorías tiene significado y se pueden realizar ciertas operaciones: puedes ordenar valores (con .sort_values) y aplicar .min y .max.

Renombrar categorías

Las categorías se renombran mediante el método rename_categories() (que funciona con una lista o un diccionario).

df["cat_type"] = df["cat_type"].cat.rename_categories(["alpha", "beta", "gamma"])

Con bastante frecuencia te encontrarás en una situación en la que quieras añadir una categoría. Puedes hacerlo con .add_categories():

df["cat_type"] = df["cat_type"].cat.add_categories(["delta"])
df["cat_type"]
0      NaN
1    alpha
2     beta
3      NaN
4    gamma
5      NaN
6     beta
Name: cat_type, dtype: category
Categories (4, object): ['alpha', 'beta', 'gamma', 'delta']

De forma similar, existen las funciones .remove_categories() y .remove_unused_categories(). .set_categories añade y elimina categorías de una sola vez. Una de las propiedades útiles de set categories es que Recuerda, eso sí, que necesitas usar df["columnname"].cat antes de llamar a cualquier función cat(egory).

Operaciones con categorías

Como ya mencionamos, las categorías ordenadas admiten de por sí algunas operaciones. Pero hay otras que funcionan con cualquier conjunto de categorías. Quizá la más útil sea value_counts()

df["cat_type"].value_counts()
cat_type
beta     2
alpha    1
gamma    1
delta    0
Name: count, dtype: int64

Observa que, aunque ‘delta’ no aparece en absoluto, obtiene un conteo (de cero). Este seguimiento de los valores ausentes puede ser muy práctico.

mode() es otra:

df["cat_type"].mode()
0    beta
Name: cat_type, dtype: category
Categories (4, object): ['alpha', 'beta', 'gamma', 'delta']

Y si tu columna categórica resulta estar formada por elementos que admiten operaciones, esas mismas operaciones seguirán funcionando. Por ejemplo,

time_df = pd.DataFrame(
    pd.Series(pd.date_range("2015/05/01", periods=5, freq="M"), dtype="category"),
    columns=["datetime"],
)
time_df
/var/folders/8n/n8x6tb9n5tz72hhnwdf7l04w0000gn/T/ipykernel_75806/379284818.py:2: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.
  pd.Series(pd.date_range("2015/05/01", periods=5, freq="M"), dtype="category"),
datetime
0 2015-05-31
1 2015-06-30
2 2015-07-31
3 2015-08-31
4 2015-09-30
time_df["datetime"].dt.month
0    5
1    6
2    7
3    8
4    9
Name: datetime, dtype: int32

Por último, si alguna vez necesitas convertir los valores reales de tu columna categórica en un código, puedes usar .cat.codes para obtener códigos únicos para cada valor.

time_df["datetime"].cat.codes
0    0
1    1
2    2
3    3
4    4
dtype: int8