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']
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.
Este capítulo usará el paquete de análisis de datos pandas.
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:
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.
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']
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.categoriesIndex(['b', 'c', 'd'], dtype='object')
df["cat_type"].cat.orderedFalse
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.
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).
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.month0 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.codes0 0
1 1
2 2
3 3
4 4
dtype: int8