2.4. Tratamiento de inconsistencias#
Introducción#
La consistencia de datos es un concepto MUY amplio ya que abarca diferentes niveles:
desde el diseño de los datos (BDs) en sí mismos (redundancia-inconsistencia)
desde el punto de vista de transacciones (principios ACID)
desde los tipos y formatos de los datos (mismas medidas de longitud, fechas fuera de rango, etc.)
desde la semántica y relación del dato (un abogado de 5 años)
Cualquiera sea el caso, se deben implementar mecanismos para detectarlas y luego eliminarlas o corregirlas. Dejar este tipo de valores en los datos pueden afectar los resultados de los análisis.
Identificación de inconsistencias#
El primer paso para analizar la existencia de las inscosistencias en los datos es la identificación de las mismos como tales.
Veamos algunos ejemplos, comenzando con el siguiente
dataframe:
import pandas as pd
df_inconsistente = pd.DataFrame({
'nombre': ['Ana', 'Luis', 'Carlos', 'Marta', 'Sofía', 'Juan', 'Andrea', 'Pedro'],
'genero': ['F', 'masculino', 'M', 'femenina', 'F', 'H', 'no binario', 'M'],
'peso': ['70 kg', '154 lbs', '65kg', 'setenta', '60 kilogramos', '0 kg', '52', '80kg'],
'fecha_nacimiento': [
'1990-05-01',
'1880-01-01', # Demasiado antigua
'2025-01-01', # Futuro
'2000-06-15',
'2015-10-10',
'2050-12-31', # Futuro
'2002-02-29', # Válida solo si es año bisiesto
'1985-07-20'
],
'fecha_registro': [
'2021-01-01',
'2022-01-01',
'2020-01-01',
'2019-01-01',
'2017-01-01',
'2010-01-01',
'2020-05-05',
'2021-12-31'
]
})
df_inconsistente
| nombre | genero | peso | fecha_nacimiento | fecha_registro | |
|---|---|---|---|---|---|
| 0 | Ana | F | 70 kg | 1990-05-01 | 2021-01-01 |
| 1 | Luis | masculino | 154 lbs | 1880-01-01 | 2022-01-01 |
| 2 | Carlos | M | 65kg | 2025-01-01 | 2020-01-01 |
| 3 | Marta | femenina | setenta | 2000-06-15 | 2019-01-01 |
| 4 | Sofía | F | 60 kilogramos | 2015-10-10 | 2017-01-01 |
| 5 | Juan | H | 0 kg | 2050-12-31 | 2010-01-01 |
| 6 | Andrea | no binario | 52 | 2002-02-29 | 2020-05-05 |
| 7 | Pedro | M | 80kg | 1985-07-20 | 2021-12-31 |
En df_inconsistente como vemos, tenemos diferentes clases de insconsistencias como:
genero con diferentes formatos
peso con diferente sistema de medidas
fecha_nacimiento que tienen fechas en el futuro o inexistente
fecha_nacimiento <–> fecha_registro erróneas ya que no puede haberse registrado sin haber nacido antes
Vamos primero a detectar los problemas que tenemos.
Para el caso 1 del genero:
# agrupamos por cada valor en el genero
df_inconsistente['genero'].value_counts()
genero
F 2
M 2
masculino 1
femenina 1
H 1
no binario 1
Name: count, dtype: int64
Librería re de expresiones regulares#
El segundo caso en mas complejo ya que debemos detectar medidas diferentes en un dato de peso o altura que esta definido como un string. Entonces debemos usar métodos que nos permitan trabajar sobre ellos.
Librería para operaciones con expresiones regulares
La librería re (Regular expression operations) permite definir expresiones regulares, las cuales especifican un conjunto de cadenas que coinciden con la misma. Los métodos de esta librería permiten comprobar si una determinada cadena coincide con una expresión regular dada, denominada patrón. Por ejemplo:
re.search()busca la primera coincidencia del patrón en un textore.findall()devuelve todas las coincidencias del patrónre.split()divide un texto usando un patrón como separadorre.match()busca una coincidencia al principio del texto, es decir el patrón se encuentra en el primer carácter del texto
Para definir una expresión regula (patrón) tenemos también una cheatsheet de regex.
También pueden crear expresiones regulares mediante asistentes online como:
La documentación de esta librería puede encontrarse aca.
Entonces una expresión regular que busque números con decimales en un string podría ser:
Las expresiones regulares se pueden crear formando grupos, que se escriben con (x), que nos permiten separar diferentes partes de un string. Se obtienen con match.group(i).
Para el caso 2 del peso definimos la expresion regular para separar el numero de la medida en dos nuevas columnas:
import re
# separamos el número y unidad con una expresión regular
# y la guardamos en una nueva tabla
# primero el numero con decimales
df_inconsistente['peso_num'] = df_inconsistente['peso'].str.extract(r'(\d+(?:[\.,]\d+)?)')
#la medida
df_inconsistente['peso_unidad'] = df_inconsistente['peso'].str.extract(r'([a-zA-Z]+)')
# Limpiamos para que quede lindo
df_inconsistente['peso_num'] = df_inconsistente['peso_num'].str.replace(',', '.').astype(float)
df_inconsistente['peso_unidad'] = df_inconsistente['peso_unidad'].str.lower().str.strip()
df_inconsistente
| nombre | genero | peso | fecha_nacimiento | fecha_registro | peso_num | peso_unidad | |
|---|---|---|---|---|---|---|---|
| 0 | Ana | F | 70 kg | 1990-05-01 | 2021-01-01 | 70.0 | kg |
| 1 | Luis | masculino | 154 lbs | 1880-01-01 | 2022-01-01 | 154.0 | lbs |
| 2 | Carlos | M | 65kg | 2025-01-01 | 2020-01-01 | 65.0 | kg |
| 3 | Marta | femenina | setenta | 2000-06-15 | 2019-01-01 | NaN | setenta |
| 4 | Sofía | F | 60 kilogramos | 2015-10-10 | 2017-01-01 | 60.0 | kilogramos |
| 5 | Juan | H | 0 kg | 2050-12-31 | 2010-01-01 | 0.0 | kg |
| 6 | Andrea | no binario | 52 | 2002-02-29 | 2020-05-05 | 52.0 | NaN |
| 7 | Pedro | M | 80kg | 1985-07-20 | 2021-12-31 | 80.0 | kg |
Para las fechas, debemos también detectar casos inconsstentes. Para el caso de la fecha_nacimiento, no debería suceder que haya fechas demasiado antiguas (no estarían vivos), fechas en el futuro (no nacieron todavía) y fechas inválidas (como un 29 de febrero en un año no bisiesto).
Veamos todos estos casos:
#primero debemos pasar la fecha al tipo datetime
df_inconsistente['fecha_nacimiento'] = pd.to_datetime(df_inconsistente['fecha_nacimiento'], errors = 'coerce')
# definimos el rango válido
fecha_min = pd.Timestamp('1900-01-01')
fecha_max = pd.Timestamp.today()
# buscamos las condiciones de inconsistencia
#es nula
cond_fecha_nula = df_inconsistente['fecha_nacimiento'].isna()
#es antigua, no estarían vivos
cond_antigua = df_inconsistente['fecha_nacimiento'] < fecha_min
#fechas en el futuro (no nacieron todavía)
cond_futura = df_inconsistente['fecha_nacimiento'] > fecha_max
# combinamos todas las condiciones con OR lógico
# y creamos una nueva columna llamada fecha_nacimiento_invalida?
df_inconsistente['fecha_nacimiento_invalida?'] = cond_fecha_nula | cond_antigua | cond_futura
# filtramos los casos inválidos
fechas_invalidas = df_inconsistente[df_inconsistente['fecha_nacimiento_invalida?']]
print(f'Las fechas inválidas son: \n{fechas_invalidas.iloc[:,3]}')
df_inconsistente
Las fechas inválidas son:
1 1880-01-01
5 2050-12-31
6 NaT
Name: fecha_nacimiento, dtype: datetime64[ns]
| nombre | genero | peso | fecha_nacimiento | fecha_registro | peso_num | peso_unidad | fecha_nacimiento_invalida? | |
|---|---|---|---|---|---|---|---|---|
| 0 | Ana | F | 70 kg | 1990-05-01 | 2021-01-01 | 70.0 | kg | False |
| 1 | Luis | masculino | 154 lbs | 1880-01-01 | 2022-01-01 | 154.0 | lbs | True |
| 2 | Carlos | M | 65kg | 2025-01-01 | 2020-01-01 | 65.0 | kg | False |
| 3 | Marta | femenina | setenta | 2000-06-15 | 2019-01-01 | NaN | setenta | False |
| 4 | Sofía | F | 60 kilogramos | 2015-10-10 | 2017-01-01 | 60.0 | kilogramos | False |
| 5 | Juan | H | 0 kg | 2050-12-31 | 2010-01-01 | 0.0 | kg | True |
| 6 | Andrea | no binario | 52 | NaT | 2020-05-05 | 52.0 | NaN | True |
| 7 | Pedro | M | 80kg | 1985-07-20 | 2021-12-31 | 80.0 | kg | False |
Por último deberíamos ver la dependecia que existe entre fecha_nacimiento y fecha_registro donde la regla debería ser que fecha_nacimiento debe ser al menos 10 años menor que la fecha_registro.
Vamos a detectar los casos que no cumple la regla:
#pasamos la fecha_registro a datetime
df_inconsistente['fecha_registro'] = pd.to_datetime(df_inconsistente['fecha_registro'], errors='coerce')
#calculamos diferencia en días entre ambas fechas
diferencia = df_inconsistente['fecha_registro'] - df_inconsistente['fecha_nacimiento']
# 10 años ≈ 3650 días (aproximación simple)
diez_anios = pd.Timedelta(days=365 * 10)
# detectamos las inconsistencias
df_inconsistente['no_tiene_mas_de_10_años?'] = diferencia < diez_anios
# Filtramos los que violan la regla
violaciones_regla = df_inconsistente[df_inconsistente['no_tiene_mas_de_10_años?']]
print(f'Las relaciones invalídas son: \n{violaciones_regla.iloc[:,3:5]}')
df_inconsistente
Las relaciones invalídas son:
fecha_nacimiento fecha_registro
2 2025-01-01 2020-01-01
4 2015-10-10 2017-01-01
5 2050-12-31 2010-01-01
| nombre | genero | peso | fecha_nacimiento | fecha_registro | peso_num | peso_unidad | fecha_nacimiento_invalida? | no_tiene_mas_de_10_años? | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | Ana | F | 70 kg | 1990-05-01 | 2021-01-01 | 70.0 | kg | False | False |
| 1 | Luis | masculino | 154 lbs | 1880-01-01 | 2022-01-01 | 154.0 | lbs | True | False |
| 2 | Carlos | M | 65kg | 2025-01-01 | 2020-01-01 | 65.0 | kg | False | True |
| 3 | Marta | femenina | setenta | 2000-06-15 | 2019-01-01 | NaN | setenta | False | False |
| 4 | Sofía | F | 60 kilogramos | 2015-10-10 | 2017-01-01 | 60.0 | kilogramos | False | True |
| 5 | Juan | H | 0 kg | 2050-12-31 | 2010-01-01 | 0.0 | kg | True | True |
| 6 | Andrea | no binario | 52 | NaT | 2020-05-05 | 52.0 | NaN | True | False |
| 7 | Pedro | M | 80kg | 1985-07-20 | 2021-12-31 | 80.0 | kg | False | False |
Tratamiento de inconsistencias#
Una vez que se detectan todas las inconsistencias, deben tratar, que significa se deben colocar reglas para hacer los reemplazos.
Por ejemplo, vamos a reemplazar por pd.NaT las fechas de registro donde la persona tenía menos de 10 años.
Hacemos el reemplazo por nulo en una nueva columna:
#defino la variable donde NO cumple la edad de mayor a 10
condicion_menor_10 = diferencia < diez_anios
# creamos nueva columna que es copia de fecha_registro
df_inconsistente['fecha_registro_OKA'] = df_inconsistente['fecha_registro']
# reemplazamos la fecha de registro por NaT si no cumple la condicion de 10 años
df_inconsistente.loc[condicion_menor_10, 'fecha_registro_OKA'] = pd.NaT
df_inconsistente
| nombre | genero | peso | fecha_nacimiento | fecha_registro | peso_num | peso_unidad | fecha_nacimiento_invalida? | no_tiene_mas_de_10_años? | fecha_registro_OKA | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Ana | F | 70 kg | 1990-05-01 | 2021-01-01 | 70.0 | kg | False | False | 2021-01-01 |
| 1 | Luis | masculino | 154 lbs | 1880-01-01 | 2022-01-01 | 154.0 | lbs | True | False | 2022-01-01 |
| 2 | Carlos | M | 65kg | 2025-01-01 | 2020-01-01 | 65.0 | kg | False | True | NaT |
| 3 | Marta | femenina | setenta | 2000-06-15 | 2019-01-01 | NaN | setenta | False | False | 2019-01-01 |
| 4 | Sofía | F | 60 kilogramos | 2015-10-10 | 2017-01-01 | 60.0 | kilogramos | False | True | NaT |
| 5 | Juan | H | 0 kg | 2050-12-31 | 2010-01-01 | 0.0 | kg | True | True | NaT |
| 6 | Andrea | no binario | 52 | NaT | 2020-05-05 | 52.0 | NaN | True | False | 2020-05-05 |
| 7 | Pedro | M | 80kg | 1985-07-20 | 2021-12-31 | 80.0 | kg | False | False | 2021-12-31 |