2.1. Tratamiento de valores perdidos o faltantes#

Introducción#

Un valor perdido o faltante es un valor nulo, que no representa ni la cadena vacía ni el valor 0. Como vimos en la teoría es un valor que puede tener un significado en sí mismo o no. Pandas utiliza diferentes valores para representar un valor faltante, que depende justamente del tipo de dato. Además, no todas las formas de representar nulos son puras de Pandas, sino que varias de ellas usan otras librerías.

Entonces, como en los tipos de datos, hay diferentes maneras de representar nulos.

Valores nulos en las librerías#

Valores null según la librería

  • Python nativo: se representan con None y significa la ausencia de un valor.

  • NumPy: se representan con np.nan (Not a Number). Se utiliza en conjuntos de datos numéricos. NaN es un valor de punto flotante que significa datos numéricos faltantes o indefinido. El significado específico de este valor lo podemos encontrar en la Norma IEEE 754

  • Pandas: se representan con pd.NA (Not Available) para nulos de boolean, Int64 (8,16,32), ‘Float64ystrings, y pd.NaT`(Not a Time) para nulos de fechas/horas. La información completa de estos valores esta en Valores faltantes en Pandas

Tipo NaN (np.nan)

NaN (No es un número) es un valor de punto flotante que indica datos numéricos faltantes o indefinidos. NaN es que no es igual a nada, ni siquiera a sí mismo. Comparar NaN con el operador == siempre devuelve Falso. Según la norma IEEE 754 las operaciones aritméticas que involucran un NaN producen un NaN, permitiendo que este valor se propague a través de los cálculos.

A pesar de tener varias representaciones, las formas de identificar y trabajar con nulos es bastante simple.

Creamos un dataframe que tenga nulos de todos los tipos:

import pandas as pd
import numpy as np

df_con_nulos = pd.DataFrame({
    "id": ["1", "2", np.nan, "4", None, pd.NA],
    "precio": ["100.5", None, "300.1", "400", np.nan, 300],
    "fecha": ["2024-01-01", "no válida", pd.NaT, "2024-01-04", None, "2025-03-15"],
    "cliente": ["Ana", "Luis", "None", "Sofía", pd.NA, "Juan"],
    "producto": ["Remera", None, "Campera", pd.NA, "Pantalon", "Camisa"],
    "pagoCash": ["True", True, None, False, np.nan, pd.NA]    
})
df_con_nulos
id precio fecha cliente producto pagoCash
0 1 100.5 2024-01-01 Ana Remera True
1 2 None no válida Luis None True
2 NaN 300.1 NaT None Campera None
3 4 400 2024-01-04 Sofía <NA> False
4 None NaN None <NA> Pantalon NaN
5 <NA> 300 2025-03-15 Juan Camisa <NA>

Vamos a analizar los tipos de datos de cada columna:

df_con_nulos.dtypes
id          object
precio      object
fecha       object
cliente     object
producto    object
pagoCash    object
dtype: object

Como podemos observar son todos object, es decir Pandas no pudo detectar el tipo de dato de cada uno.

Por ahora lo vamos a dejar así y vamos a analizar los métodos que existen para trabajar con estos valores.

Librerías para manejo de valores nulos#

Librerías para detectar y tratar nulos

  • Python nativo:

    • is None : verifica si un valor es None y devuelve True o False. No es recomendable usar == None

    • El reemplazo de los valores nulos debe hacerse en forma manual, verificando con condicionales como: if x is None

  • NumPy:

    • np.isnan(array): devuelve el mismo arreglo pero con True o False por cada valor, dependiendo si es nan o no. No realiza ninguna operación aritmética. Sólo funciona si tiene valores numéricos. No funciona con None.

    • np.nan_to_num(): reemplaza NaN con el valor numérico especificado (el valor predeterminado es 0). Por ejemplo, np.nan_to_num(arreglo, nan=-1, copy=False) reemplaza los valores nan del arreglo por -1 modificando el mismo arreglo, es decir sin hacer una copia

  • Pandas:

    • pd.isna()/pd.isnull(): verifica si un valor es tanto NaN, NaT o None dentro de un dataFrame o serie y devuelve True o False de la misma forma. Otra similar pero al revés es pd.notnull()

    • df.fillna(): reemplaza nulos, de las filas (axis=0) o columnas(axis=1), con el valor especificado. También se puede usar df.replace() que funciona en forma general para reemplazar cualquier valor. Si lo queremos hacer para los nulos, hacemos df.replace(to_replace=np.nan, value=0)

    • df.dropna(): elimina las filas que contienen nulos (NaN o None). Devuelve un nuevo dataframe a no ser que sea indicado inplace = True donde modifica el original. Con axis=1 elimina columnas

Nos vamos a centrar principalmente en los métodos de Pandas para trabajar con nulos.

MOMENTO CHEATSHEET

Este es el momento de recurrir a una nueva parte de la cheatsheet de Pandas para los nulos. En este caso que sólo describe dos de los métodos que describimos previamente.

Ahora que ya conocemos todas vamos a utilizar los métodos de Pandas.

Usando el df_con_nulos anterior, probamos las funciones de verificación:

# volvemos al df creado
df_con_nulos
id precio fecha cliente producto pagoCash
0 1 100.5 2024-01-01 Ana Remera True
1 2 None no válida Luis None True
2 NaN 300.1 NaT None Campera None
3 4 400 2024-01-04 Sofía <NA> False
4 None NaN None <NA> Pantalon NaN
5 <NA> 300 2025-03-15 Juan Camisa <NA>
# usamos el método de pandas para verificar que valores nulos hay

df_con_nulos.isna()
id precio fecha cliente producto pagoCash
0 False False False False False False
1 False True False False True False
2 True False True False False True
3 False False False False True False
4 True True True True False True
5 True False False False False True

Métodos .fillna() y .dropna()#

Para reemplazar los valores nulos podemos usar el método fillna().

Método .fillna() para tratar nulos

Algunos de los parámetros mas importantes del método .fillna() son:

  • value: especifica el valor con el que se reemplazarán los valores NaN y ‘NA. Puede ser un valor escalar, un dictionary, o una serie. La estructura debe coincidir con la cantidad de NaN`.

  • axis: indica si el reemplazo se debe realizar a lo largo de las filas (axis = 0) o de las columnas (axis = 1). Por defecto, se establece en 0 (filas).

  • inplace: determina si los cambios se deben realizar directamente sobre el dataframe original. True significa que se modificará el original, mientras que False (predeterminado) devolverá un nuevo dataframe con los reemplazos.

También existen dos métdods que permiten rellenar nulos:

  • ffil() (forward fill): Rellena los valores faltantes np.nan y pd.NA hacia abajo usando el valor anterior

  • bfill() (backward fill): Rellena hacia arriba usando el valor siguiente

La documentación completa del método, la podemos encontrar en pandas.DataFrame.fillna

Vamos a llenar los nulos de la columna id:

# hago una copia, con copy para que sea una nueva instancia
df_sin_id_nulos = df_con_nulos.copy()

#completo los nulos con el valor 0 y creo un nuevo DF
df_sin_id_nulos["id"] = df_sin_id_nulos["id"].fillna(0)
df_sin_id_nulos
id precio fecha cliente producto pagoCash
0 1 100.5 2024-01-01 Ana Remera True
1 2 None no válida Luis None True
2 0 300.1 NaT None Campera None
3 4 400 2024-01-04 Sofía <NA> False
4 0 NaN None <NA> Pantalon NaN
5 0 300 2025-03-15 Juan Camisa <NA>
# vamos a reemplazar los nulos del precio por el valor "sin precio" y "nose"
# como vemos en la columna precio hay un None en el indice 1
# y un NaN en el indice 4
# entonces la serie debe tener lo que quiero en esos mismos indices
# creo un nuevo DF
df_sin_id_precio_nulos = df_sin_id_nulos.copy()

#reemplazo
df_sin_id_precio_nulos["precio"] = df_sin_id_precio_nulos["precio"].fillna(pd.Series([0,"sin precio",0,0,"nose",0]))
df_sin_id_precio_nulos
id precio fecha cliente producto pagoCash
0 1 100.5 2024-01-01 Ana Remera True
1 2 sin precio no válida Luis None True
2 0 300.1 NaT None Campera None
3 4 400 2024-01-04 Sofía <NA> False
4 0 nose None <NA> Pantalon NaN
5 0 300 2025-03-15 Juan Camisa <NA>

Ahora vamos a eliminar las filas que tengan nulos:

# creo un nuevo df
df_sin_nulos = df_sin_id_precio_nulos.copy()

#borro todas las filas que tengan al menos un nulo
df_sin_nulos.dropna(inplace=True)
df_sin_nulos
id precio fecha cliente producto pagoCash
0 1 100.5 2024-01-01 Ana Remera True

y ahora las columnas que tengan nulos:

# creo un nuevo df
df_sin_nulos = df_sin_id_precio_nulos.copy()

#borro todas las columnas que tengan al menos un nulo
df_sin_nulos.dropna(inplace=True, axis = 1)
df_sin_nulos
id precio
0 1 100.5
1 2 sin precio
2 0 300.1
3 4 400
4 0 nose
5 0 300