Saltar al contenido

lección 3

Nulos: por qué falta un dato, y qué cambia según el motivo

Un hueco no es un cero: los tres motivos por los que falta un dato, y por qué rellenar con la media puede ser peor que no hacer nada.

⏱ 55 min

En 1986, el transbordador Challenger explotó 73 segundos después de despegar. La noche anterior hubo una reunión para decidir si se lanzaba con la temperatura más baja de la historia del programa. Los ingenieros llevaron los datos de los lanzamientos en los que las juntas de goma habían fallado, y en esos datos no se veía ninguna relación clara con la temperatura. Faltaba la mitad de la información: los lanzamientos en los que no había pasado nada. Al mirar solo los siete vuelos con incidencias, la relación desaparecía; al mirar los veintitrés que habían dejado datos de las juntas, era evidente.

No fue un error de cálculo. Los números que estaban sobre la mesa eran correctos. El problema era lo que no estaba en la mesa, y nadie preguntó por qué faltaba. Ese es el tema de esta lección, y es la razón de que los huecos de una tabla merezcan una lección entera en lugar de una línea de código.

Ya sabes contar cuántos huecos hay: df.isna().sum() te da la cifra por columna. Lo que esa cifra no te dice, y es lo único que importa para decidir qué hacer, es por qué falta. Dos columnas con el mismo 12% de huecos pueden necesitar tratamientos opuestos, y elegir mal no te da un error: te da un resultado limpio y equivocado.

## Un hueco no es un cero, y no es un error

Antes de los motivos, una distinción que cuesta dinero cuando se confunde. Un hueco significa «no lo sé». Un cero significa «lo sé, y es cero». No son lo mismo y no se pueden mezclar.

Si en la columna de ventas de una tienda hay un hueco, puede ser que la tienda estuviera cerrada, que el sistema no enviara el dato, o que nadie lo registrara. Si hay un cero, la tienda abrió y no vendió nada. Rellenar los huecos con cero convierte «no sé si vendió» en «no vendió», y eso hunde la media de esa tienda sin que ningún error salte. Es el fallo de limpieza más común que existe, y el más difícil de detectar después, porque el resultado parece perfectamente sano.

En pandas el hueco se llama NaN y tiene una propiedad que sorprende la primera vez: no es igual a sí mismo. Si compruebas NaN == NaN la respuesta es False, así que df[df["ventas"] == None] no encuentra nada y no avisa de nada. Para encontrar huecos hay que usar .isna() o .notna(), siempre. Es la primera trampa de las tres que trae este tema.

## Los tres motivos por los que falta un dato

La estadística lleva medio siglo con esta clasificación y tiene nombres técnicos horribles en inglés. Los vamos a ver con palabras, porque lo que hay que aprender es a distinguirlos, no a recitarlos.

Motivo 1: falta por azar. El sensor se quedó sin batería un martes. Se cayó la red durante dos horas. Alguien se saltó una fila al teclear. El hueco no tiene nada que ver con lo que habría valido el dato ni con ninguna otra columna: es ruido puro. Es el caso amable, y es el menos frecuente.

Motivo 2: falta por algo que sí tienes en la tabla. Los clientes que se registraron este mes no tienen antigüedad porque acaban de llegar. Los pedidos sin fecha de entrega son exactamente los que aún no se han entregado. El hueco se explica con otra columna que sí conoces, y eso lo hace manejable: si sabes que todos los huecos de antigüedad son clientes nuevos, el hueco en realidad es información.

Motivo 3: falta por su propio valor. Este es el peligroso. Los que ganan mucho son los que no contestan la pregunta del sueldo. Los clientes más descontentos son los que no rellenan la encuesta. Los pacientes que empeoraron son los que dejaron de acudir a la revisión. El dato falta precisamente porque su valor era extremo, así que lo que te queda no es una muestra de lo que había: es una muestra sesgada, y ninguna cuenta la arregla.

Los tres casos tienen el mismo número de huecos. Mira DÓNDE caen: en el tercero se van todos los sueldos altos, así que la media de lo que queda no es la media de nada real.

Ese dibujo es el argumento entero de la lección. Las tres barras tienen seis huecos de veintiséis, un 23% en los tres casos. Si solo miras la cifra, los tres problemas son el mismo. Si miras dónde caen, el tercero te va a mentir y los otros dos no.

## Cómo se averigua el motivo, en la práctica

No hay una función de pandas que te lo diga. Lo que hay es un procedimiento de tres pasos que se hace en cinco minutos y evita meses de conclusiones falsas.

  1. 01.Pregunta a quien genera el dato. Es el paso que más veces se salta y el único que da la respuesta de verdad. «¿Por qué hay clientes sin fecha de nacimiento?» tiene una respuesta concreta en el departamento que rellena el formulario, y no es la que tú imaginarías.
  2. 02.Compara las filas con hueco contra las filas sin hueco, en las OTRAS columnas. Si los clientes sin antigüedad son todos del plan gratuito y todos de este trimestre, ya sabes que estás en el motivo 2. Es un groupby de una línea y descarta o confirma la hipótesis.
  3. 03.Busca el patrón temporal. Agrupa los huecos por mes. Si aparecen todos de golpe en marzo y luego nunca más, hubo un cambio de sistema y no un fenómeno de negocio. Esto explica un porcentaje sorprendente de los huecos de cualquier tabla de empresa.

El segundo paso tiene una versión rápida que conviene tener en el dedo: crea una columna booleana falta = df["sueldo"].isna() y haz df.groupby("falta").mean(numeric_only=True). Si las dos filas del resultado se parecen, el hueco probablemente sea azar; si son muy distintas, el hueco depende de algo, y acabas de descubrir de qué.

## El patrón temporal, que explica más huecos de lo que parece

El tercer paso merece su propio tramo porque resuelve un porcentaje sorprendente de los casos y casi nadie lo prueba. Agrupa los huecos por mes y mira la serie. Hay tres formas que salen una y otra vez, y cada una apunta a una causa distinta.

Si los huecos están repartidos de forma parecida todos los meses, es un problema estructural: el formulario tiene ese campo como opcional, o el sistema no siempre lo recibe. Vive con él y decide qué hacer.

Si aparecen de golpe a partir de un mes concreto y siguen, hubo un cambio de sistema: una migración, un campo nuevo, una integración que se rompió. Y aquí la consecuencia práctica es grande: los datos de antes y los de después no son comparables, así que cualquier serie que cruce esa frontera tiene un escalón artificial en ese punto.

Y si aparecen solo en un mes y luego desaparecen, fue una incidencia puntual. Esos huecos suelen ser lo más parecido al azar que te vas a encontrar, y son los únicos que se pueden borrar con la conciencia tranquila.

El caso que más veces se repite en una empresa es el segundo, y tiene una versión especialmente traicionera: el campo existía antes pero con otro nombre. Los huecos de la columna nueva son todos los registros anteriores a la migración, y los valores están en una columna vieja que nadie te mencionó porque ya nadie la mira. Antes de rellenar nada, merece la pena preguntar si ese dato está en otro sitio.

## La opción que nadie considera: no hacer nada

Hay una salida más que no es borrar ni rellenar, y muchas veces es la correcta: dejar el hueco donde está y hacer la cuenta sin él. pandas ya lo hace por su cuenta — df["sueldo"].mean() ignora los huecos y calcula sobre los valores que existen— así que si lo único que necesitas es una media, no tienes que tocar nada.

Lo que sí tienes que hacer es decir sobre cuántos está calculada. «El sueldo medio es 31.000 sobre los 2.460 que lo declararon, de 2.800 empleados» es una frase completa. «El sueldo medio es 31.000» no lo es, y las dos salen del mismo mean().

Rellenar hace falta cuando algo que viene después no admite huecos: la mayoría de los métodos de predicción se niegan a funcionar con una tabla incompleta, y te lo dicen con un error. Pero hasta ese momento, un hueco honesto vale más que un valor inventado, y el orden correcto es: primero entiende por qué falta, luego calcula lo que puedas sin tocarlo, y solo rellena cuando algo te obligue.

Y cuando rellenes con la media, conviene saber qué le haces a la forma de los datos, porque es un efecto que no se ve en ninguna cifra de resumen.

Rellenar con la media no cambia la media: cambia la forma. Esa torre roja es gente a la que le has puesto el mismo número, y a partir de ahí cualquier medida de dispersión sale más pequeña de lo que debería.

Esa torre tiene dos consecuencias concretas. La primera es que la desviación típica baja, así que cualquier cosa que dependa de cuánto varían los datos te va a parecer más estable de lo que es. La segunda es que si dibujas un histograma de esa columna, el pico va a estar donde no hay nadie, y quien lo mire va a pensar que ahí se concentra la gente. Con un 5% de huecos apenas se nota; con un 30% has cambiado los datos.

## Borrar filas: la opción que parece inofensiva

df.dropna() es la primera cosa que hace todo el mundo, y en el motivo 1 es correcta: tiras un poco de información y a cambio te quedas con una tabla completa. El problema es que en los motivos 2 y 3 no borras filas al azar: borras un grupo.

Y hay un detalle de dropna() que pilla a todo el mundo una vez: sin argumentos, borra la fila si le falta algo en cualquier columna. En una tabla con veinte columnas y un 5% de huecos repartidos, eso puede llevarse la mitad de las filas sin que aparezca ningún aviso. La versión que se usa de verdad lleva subset: df.dropna(subset=["sueldo"]) borra solo cuando falta lo que te importa.

La tabla pasa de 27 filas a 22, un 19% menos, y suena aceptable. Pero el segmento de clientes nuevos no ha perdido el 19%: ha desaparecido entero.

Después de ese dropna(), cualquier cosa que calcules sobre «los clientes» excluye a todos los nuevos. Si además la pregunta que te habían hecho era sobre la captación de este trimestre, has borrado exactamente a las personas de las que ibas a hablar. Y el código no falla, la tabla está limpia, y el informe sale a tiempo.

## Rellenar: cuatro formas y cuándo cada una

La alternativa a borrar es imputar, que es una palabra fea para «poner un valor inventado con criterio». Las cuatro que se usan de verdad:

  • La media (fillna(df["x"].mean())). Solo si la columna es simétrica y los huecos son azar. Tiene un efecto secundario que nadie menciona: reduce la dispersión artificialmente, porque estás metiendo un montón de valores idénticos justo en el centro.
  • La mediana. Lo mismo pero resistente a valores extremos, así que es la opción por defecto en cualquier columna de dinero, tiempos o tamaños — que están casi siempre sesgadas.
  • El valor más frecuente para columnas de texto o categorías. Con una advertencia: si rellenas el 30% de una columna con la categoría más común, esa categoría pasa a dominar y cualquier conclusión sobre ella queda contaminada.
  • El valor anterior (ffill), solo en datos con orden temporal. Si falta la temperatura de las 14:00, la de las 13:00 es una apuesta razonable. En una tabla sin orden, arrastrar el valor de la fila de arriba no tiene ningún sentido y es un error que se ve a menudo.

Y una quinta que no es rellenar y casi siempre es la mejor: añadir una columna que diga que faltaba. Rellenas con la mediana y a la vez creas sueldo_faltaba = df["sueldo"].isna(). Así no pierdes la información de que ese dato no estaba, que en el motivo 2 y en el 3 es una información valiosísima: «no contestó» puede predecir mejor que cualquier valor que inventes.

1import pandas as pd
2
3df = pd.DataFrame({
4 "cliente": ["A", "B", "C", "D", "E", "F"],
5 "sueldo": [28000, None, 31000, None, 45000, 29000],
6})
7
8# Primero la marca, LUEGO el relleno. Al reves, la marca sale toda False.
9df["sueldo_faltaba"] = df["sueldo"].isna()
10df["sueldo"] = df["sueldo"].fillna(df["sueldo"].median())
11
12print(df.to_string(index=False))

El orden de las dos líneas no es indiferente. Fíjate en cuál va primero.

Y aquí va la trampa que más adelante te va a costar un modelo entero, así que conviene oírla ahora aunque todavía no puedas usarla: el valor con el que rellenas se calcula solo con la parte de los datos con la que vas a entrenar, nunca con todos. Si calculas la mediana usando la tabla completa y luego separas una parte para comprobar si tu predicción funciona, esa parte ya ha influido en el relleno, y tu comprobación sale mejor de lo que debería. El resultado es buenísimo y es falso. Cuando llegues a medir un modelo, esta frase va a volver.

## Cómo se cuenta esto en una reunión

  • Nunca digas «he limpiado los nulos». Di qué has hecho y a cuántas filas: «faltaba el sueldo en 340 de 2.800, he puesto la mediana y he marcado cuáles eran».
  • Si has borrado filas, di qué grupo se ha ido. «He quitado las filas sin fecha de entrega, que son los 412 pedidos que todavía están en camino» es una frase que evita una reunión entera de confusión.
  • Si sospechas del motivo 3, dilo antes de presentar la cifra. «El sueldo medio de la muestra es 31.000, pero quien no contesta suele ser quien más gana, así que el real es más alto» es la diferencia entre un dato y un dato con dueño.
  • Cuando alguien te pida «rellena eso con cero y seguimos», la respuesta es preguntar si cero es un valor posible en esa columna. Si lo es, mezclarás dos cosas distintas para siempre.

Los huecos son el primero de los problemas de una tabla sucia y el que más se subestima, porque se ve. Los siguientes son peores en un sentido concreto: no se ven. Una fila duplicada tiene todos sus valores rellenos y parece perfecta, y aun así te duplica la facturación.

Regístrate para guardar tu progreso.