Tipos y valores faltantes
En resumen: Cada columna en pandas tiene un tipo (dtype) que decide qué puedes hacer con ella, y los valores faltantes (NaN) se encuentran con isna y se rellenan con fillna o se eliminan con dropna.
Problema
«¿Qué día se pide más?»
El gerente quiere saber cuándo poner más gente en el almacén. El archivo year_orders.csv tiene 11 116 pedidos de un año, cada uno con fecha y hora. Parece un solo comando: tomar el día de la fecha y contar.
Un error. dtypes dice por qué: ordered_at es str, texto. Para nosotros «2025-11-01 07:20» es una fecha, pero read_csv ve dígitos, guiones y dos puntos, y no adivina.
El tipo de una columna decide qué puedes hacer con ella. En esta lección verás cómo cambiarlo, qué tipos ahorran memoria y dan un orden, y qué pasa cuando falta un valor.
La idea
Cada columna tiene un tipo: astype
Cada columna de un DataFrame tiene un tipo (dtype). Has visto int64 y float64 para números y str para texto. También existen bool para True y False, datetime64 para fechas y category para unos pocos valores que se repiten.
read_csv adivina el tipo a partir del texto del archivo. Cuando adivina mal, o cuando necesitas otro, astype hace una columna de un tipo nuevo. En products la categoría es float64, porque la tarjeta de regalo no tiene categoría, y NaN es un número decimal. Los enteros con huecos los guarda el tipo Int64 (con I mayúscula):
Los números vuelven a ser enteros, y el hueco es <NA>: el signo para «nada» en los tipos más nuevos de pandas. Con astype(int) obtendrías el error IntCastingNaNError, porque un int normal no tiene sitio para un hueco.
Míralo
Pocos valores, muchas filas: category
La columna status tiene 11 116 filas pero solo cinco palabras distintas. Como texto, cada palabra se guarda en cada fila. El tipo category guarda las cinco palabras una vez, y para cada fila solo el número de su palabra:
377 057 bytes pasan a 11 345, unas 33 veces menos. En el año de la tienda es poco, pero en una tabla con millones de filas es la diferencia entre «cabe en la memoria» y «no cabe».
Una categoría tiene también un segundo uso: un orden. Los estados no son un alfabeto sino un camino: new, paid, shipped, delivered. CategoricalDtype con ordered=True fija ese orden:
value_counts(sort=False) cuenta en el orden del camino, no por cantidad. La comparación < 'delivered' significa «antes de la entrega»: 205 pedidos siguen en camino. Los cancelados se ponen al final, fuera del camino.
Pruébalo
Los estados a lo largo del camino
Convierte la columna status en una categoría con el orden new, paid, shipped, delivered, cancelled, y guarda en status_counts cuántos pedidos hay en cada paso, en ese orden.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv')
steps = pd.CategoricalDtype(['new', 'paid', 'shipped', 'delivered', 'cancelled'], ordered=True)
orders['status'] = orders['status'].astype(steps)
status_counts = orders['status'].value_counts(sort=False)
status_countspd.CategoricalDtype(['new', 'paid', 'shipped', 'delivered', 'cancelled'], ordered=True) fija el orden, astype(steps) se lo pone a la columna, y value_counts(sort=False) cuenta a lo largo de él: 90 nuevos, 63 pagados, 52 enviados, 10 254 entregados y 657 cancelados. Con un value_counts() normal el orden es por cantidad, con delivered arriba. Con astype('category') y sin lista, el orden es alfabético: cancelled, delivered, new, paid, shipped.
La idea
Fechas: pd.to_datetime y .dt
pd.to_datetime convierte el texto en fechas. La columna recibe el tipo datetime64 y ahora conoce el calendario. Las partes de una fecha salen de .dt: .dt.year, .dt.month, .dt.day_name() y muchas más:
El año va del 1 de noviembre de 2025, 7:20, al 31 de octubre de 2026, 23:01. El primer pedido es un sábado (Saturday: day_name() da los nombres de los días en inglés). .dt funciona solo con fechas: con el texto del principio dio el error.
Más sobre fechas (por semana, por mes, una media móvil) en la lección sobre el tiempo.
La idea
Texto: .str
Lo que .dt es para las fechas, .str lo es para el texto: aplica un método de cadena a cada fila de una vez. Viste str.contains con los filtros. También existen str.lower() y str.upper(), str.strip() (quita los espacios de los extremos), str.len(), str.split() y el corte como en una cadena: str[:18].
Las etiquetas de los estantes admiten 18 caracteres. ¿Qué nombres no caben, y cómo quedarán cortados?
4 nombres son más largos que 18 caracteres. str[:18] los corta, y str.upper() los pone en mayúsculas para la etiqueta. El resultado vuelve a ser una columna, así que assign la pone junto al nombre.
La idea
Valores faltantes: isna y notna
Casi toda tabla real tiene algo que falta: un cliente no dio su ciudad, la caja no mandó un informe. pandas escribe el hueco como NaN (NaT para fechas, <NA> para los tipos más nuevos). isna() da una máscara que es True donde falta un valor, y .sum() sobre ella cuenta los huecos de cada columna:
Un cliente no tiene ciudad. notna() es la máscara contraria: True donde hay un valor. Filtras con las dos máscaras como con cualquier condición.
Míralo
Por qué 0 no es «faltante»
La banitsa y los pepinos tienen stock 0. ¿Es un hueco?
isna() no encuentra nada en stock: los ceros son valores reales. 0 significa «sabemos que no hay», y NaN significa «no lo sabemos».
Ahora los ingresos por día de la lección de resúmenes, pero como Serie de pandas. Una diferencia importante: NumPy daba nan para la media, y pandas se salta los huecos:
count() cuenta 6 días conocidos de 7. Su media es 494,70 €, como con np.nanmean. La suma, 2968,20 €, deja fuera en silencio el miércoles, eso sí: pandas no te avisa. Con fillna(0) la media baja a 424,03 €, porque el cero dice «el miércoles no vendimos nada», y eso no es verdad.
Más precisamente, para curiosos
El salto lo controla el parámetro skipna: revenue.mean(skipna=False) da nan, como NumPy. Es útil cuando prefieres que el resultado señale un hueco en lugar de dar en silencio un número más pequeño.
La idea
fillna, dropna o nada
Hay tres maneras de tratar un hueco, y la elección depende de la pregunta, no del código:
fillna(valor), cuando sabes lo que significa el hueco. En un informe de «clientes por ciudad», una ciudad que falta es «desconocida»: es una respuesta, no una invención.dropna(), cuando la fila no puede responder a la pregunta. En un informe por categoría, la tarjeta de regalo no tiene adónde ir.subset=[...]mira solo las columnas indicadas.- Nada, cuando no lo sabes. Los ingresos del miércoles no son 0, y la media de los días conocidos es más honesta.
dropna quitó la tarjeta de regalo: de 25 productos quedan 24, tantos como cuenta notna() en la categoría. En un informe de «cuántos productos vendemos» eso sería un error. fillna('desconocida') convierte la ciudad que falta en una fila propia en value_counts.
Pruébalo
Clientes por ciudad
Marketing quiere saber de dónde son los clientes. Guarda en missing_city cuántos clientes no tienen ciudad, y en by_city cuántos clientes hay en cada ciudad, contando la ciudad que falta como «desconocida».
Solución
import pandas as pd
customers = pd.read_csv('customers.csv')
missing_city = customers['city'].isna().sum()
by_city = customers['city'].fillna('desconocida').value_counts()
print(missing_city)
by_citycustomers['city'].isna().sum() da 1. customers['city'].fillna('desconocida').value_counts() cuenta los 20 clientes: 6 en Sofía, 4 en Plovdiv y así sucesivamente, y 1 «desconocida». Sin fillna, value_counts se salta los NaN y cuenta 19, y count() da 19, el número de clientes con ciudad, no sin ella.
Ejercicio
El día con más pedidos
Responde al gerente. Convierte ordered_at en una fecha, pon el nombre del día de la semana en una columna nueva day, guarda en by_day cuántos pedidos hay en cada día, y en best_day el día con más pedidos.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv')
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
orders['day'] = orders['ordered_at'].dt.day_name()
by_day = orders['day'].value_counts()
best_day = by_day.index[0]
print(best_day)
by_daypd.to_datetime hace las fechas, y .dt.day_name() da el día. value_counts() ordena de más a menos, así que by_day.index[0] es Friday (viernes): 1890 pedidos. El más flojo es el domingo (Sunday) con 1280. Se cuentan todos los pedidos, también los cancelados: la pregunta es cuándo llegan, no cuánto valen. Con .dt.day obtendrías 31 filas, una por cada día del mes.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Series y DataFrame · Seleccionar: [], loc e iloc · Filtrar y ordenar · Columnas nuevas
- Continúa en: Grupos: groupby · Unir tablas: merge · Ancho y largo: pivot_table y melt · Fechas y series temporales
- Ejercicios sobre el tema: Tablas con pandas