Limpieza de Datos y Manejo de Valores Faltantes
La Realidad de los DatosHay un dicho en data science: "El 80% del tiempo lo pasas limpiando datos". Es cierto. Los datos reales vienen con errores, valores faltantes, duplicados, formatos inconsistentes y outliers. Saber limpiar datos es la habilidad que mas te va a servir en el trabajo.
Valores Faltantes (NaN)
import pandas as pd
import numpy as np
# Simular un dataset con datos faltantes
df = pd.DataFrame({
'nombre': ['Ana', 'Carlos', None, 'Luis', 'Sofia', 'Pedro'],
'edad': [28, np.nan, 25, 42, 31, np.nan],
'salario': [65000, 72000, 85000, np.nan, 71000, 55000],
'departamento': ['Marketing', 'Ventas', 'IT', 'Ventas', None, 'IT'],
'evaluacion': [4.5, 3.8, np.nan, 3.2, 4.1, np.nan]
})
print("Dataset con valores faltantes:")
print(df)
# Detectar valores faltantes
print(f"\nValores faltantes por columna:")
print(df.isnull().sum())
print(f"\nPorcentaje de valores faltantes:")
print((df.isnull().sum() / len(df) * 100).round(1))
# Verificar si hay algun NaN en cada fila
print(f"\nFilas con algun valor faltante: {df.isnull().any(axis=1).sum()}")
Estrategias para valores faltantes
# 1. ELIMINAR filas con NaN
df_sin_nan = df.dropna() # Eliminar todas las filas con algun NaN
df_sin_nan_col = df.dropna(subset=['nombre', 'salario']) # Solo si NaN en estas columnas
# 2. RELLENAR con un valor
df['departamento'] = df['departamento'].fillna('Sin asignar')
# 3. RELLENAR con estadisticas
df['edad'] = df['edad'].fillna(df['edad'].median())
df['salario'] = df['salario'].fillna(df['salario'].mean())
df['evaluacion'] = df['evaluacion'].fillna(df['evaluacion'].median())
# 4. RELLENAR hacia adelante o atras (util en series temporales)
# df['valor'] = df['valor'].fillna(method='ffill') # Forward fill
# df['valor'] = df['valor'].fillna(method='bfill') # Backward fill
# 5. INTERPOLAR (para datos numericos continuos)
# df['temperatura'] = df['temperatura'].interpolate()
print("\nDespues de limpiar:")
print(df)
print(f"\nValores faltantes restantes: {df.isnull().sum().sum()}")
Duplicados
# Crear datos con duplicados
df_dup = pd.DataFrame({
'id': [1, 2, 3, 2, 4, 3],
'nombre': ['Ana', 'Carlos', 'Maria', 'Carlos', 'Luis', 'Maria'],
'monto': [100, 200, 150, 200, 300, 150]
})
# Detectar duplicados
print(f"Filas duplicadas: {df_dup.duplicated().sum()}")
print("\nFilas duplicadas:")
print(df_dup[df_dup.duplicated(keep=False)]) # Mostrar todas las copias
# Eliminar duplicados
df_limpio = df_dup.drop_duplicates()
df_limpio_id = df_dup.drop_duplicates(subset=['id'], keep='first') # Por columna especifica
print(f"\nDespues de eliminar duplicados: {len(df_limpio)} filas")
Limpieza de Strings
# Datos sucios de texto
df_sucio = pd.DataFrame({
'nombre': [' Ana Garcia ', 'carlos LOPEZ', 'MARIA rodriguez', 'luis Fernandez'],
'email': ['[email protected]', '[email protected]', '[email protected]', '[email protected]'],
'ciudad': ['Buenos Aires', 'buenos aires', 'BUENOS AIRES', 'Bs As']
})
# Limpiar strings
df_sucio['nombre'] = df_sucio['nombre'].str.strip().str.title()
df_sucio['email'] = df_sucio['email'].str.lower().str.strip()
df_sucio['ciudad'] = df_sucio['ciudad'].str.strip().str.title()
# Reemplazar valores inconsistentes
df_sucio['ciudad'] = df_sucio['ciudad'].replace({
'Bs As': 'Buenos Aires',
'Bsas': 'Buenos Aires',
'CABA': 'Buenos Aires'
})
print(df_sucio)
Tipos de Datos
# Verificar y corregir tipos
df_tipos = pd.DataFrame({
'fecha': ['2025-01-15', '2025-02-20', '2025-03-10'],
'monto': ['1,500.00', '2,300.50', '980.75'],
'cantidad': ['10', '5', '20'],
'activo': ['True', 'False', 'True']
})
print("Tipos originales:")
print(df_tipos.dtypes) # Todo es 'object' (string)
# Convertir tipos
df_tipos['fecha'] = pd.to_datetime(df_tipos['fecha'])
df_tipos['monto'] = df_tipos['monto'].str.replace(',', '').astype(float)
df_tipos['cantidad'] = df_tipos['cantidad'].astype(int)
df_tipos['activo'] = df_tipos['activo'].map({'True': True, 'False': False})
print("\nTipos corregidos:")
print(df_tipos.dtypes)
print(df_tipos)
Outliers
np.random.seed(42)
salarios = pd.Series(np.random.normal(60000, 15000, 100))
salarios.iloc[10] = 500000 # Outlier alto
salarios.iloc[20] = -5000 # Outlier bajo
# Detectar con IQR
Q1 = salarios.quantile(0.25)
Q3 = salarios.quantile(0.75)
IQR = Q3 - Q1
limite_inferior = Q1 - 1.5 * IQR
limite_superior = Q3 + 1.5 * IQR
outliers = salarios[(salarios < limite_inferior) | (salarios > limite_superior)]
print(f"Outliers detectados: {len(outliers)}")
print(outliers)
# Tratar outliers: clip (limitar al rango)
salarios_limpiados = salarios.clip(lower=limite_inferior, upper=limite_superior)
print(f"\nAntes: Media={salarios.mean():,.0f}, Mediana={salarios.median():,.0f}")
print(f"Despues: Media={salarios_limpiados.mean():,.0f}, Mediana={salarios_limpiados.median():,.0f}")
Pipeline de Limpieza Completo
def limpiar_dataset(df):
"""Pipeline completo de limpieza de datos."""
df = df.copy()
print(f"Forma original: {df.shape}")
# 1. Eliminar duplicados
antes = len(df)
df = df.drop_duplicates()
print(f"Duplicados eliminados: {antes - len(df)}")
# 2. Limpiar strings
for col in df.select_dtypes(include='object'):
df[col] = df[col].str.strip().str.title()
# 3. Manejar valores faltantes
for col in df.select_dtypes(include='number'):
null_pct = df[col].isnull().mean()
if null_pct > 0.5:
df = df.drop(columns=[col])
print(f"Columna '{col}' eliminada ({null_pct:.0%} faltantes)")
elif null_pct > 0:
df[col] = df[col].fillna(df[col].median())
print(f"Columna '{col}' rellenada con mediana")
# 4. Resumen
print(f"Forma final: {df.shape}")
print(f"Valores faltantes restantes: {df.isnull().sum().sum()}")
return df
Video Recomendado
Limpieza de Datos con Pandas - Guia Completa
Ejercicio Practico
Crea un notebook 10_limpieza.ipynb:
- Crea un dataset "sucio" con valores faltantes, duplicados, strings inconsistentes y outliers
- Detecta y reporta problemas de calidad
- Implementa un pipeline de limpieza completo
- Compara estadisticas antes y despues de la limpieza
- Guarda el dataset limpio como CSV
💡 Concepto Clave
Revisemos los puntos más importantes de esta lección antes de continuar.
Resumen
- isnull() / isna(): Detectar valores faltantes
- dropna(): Eliminar filas/columnas con NaN
- fillna(): Rellenar NaN con valores
- drop_duplicates(): Eliminar filas duplicadas
- str.strip().str.title(): Limpiar strings
- astype() / to_datetime(): Corregir tipos de datos
- clip(): Manejar outliers limitando al rango valido
🧠 Pon a prueba tu conocimiento
¿Cuál es el aspecto más importante que aprendiste en esta lección?
- Comprendo el concepto principal y puedo explicarlo con mis palabras
- Entiendo cómo aplicarlo en mi situación específica
- Necesito repasar algunas partes antes de continuar
- Quiero ver más ejemplos prácticos del tema
✅ ¡Excelente! Continúa con la siguiente lección para profundizar más.