🔍 EDA: Tu Brújula en el Mundo de los Datos
Imagina que eres un detective llegando a la escena de un crimen. Tienes pistas dispersas, testimonios contradictorios, y un montón de evidencia. ¿Por dónde empiezas? Por explorar todo meticulosamente antes de sacar conclusiones.
El Análisis Exploratorio de Datos (EDA) es exactamente eso: ser un detective de datos. Es el proceso de investigar, examinar y visualizar datos para descubrir patrones, detectar anomalías, y formular hipótesis antes de aplicar técnicas de machine learning.
🎯 ¿Por qué EDA es tan importante?
1. Conocer a tu "enemigo"
Los datos nunca son perfectos. Tienen:
- Valores faltantes
- Outliers (valores extremos)
- Errores de entrada
- Distribuciones inesperadas
- Correlaciones ocultas
2. Formular las preguntas correctas
EDA te ayuda a preguntarte:
- ¿Qué patrones veo?
- ¿Qué no tiene sentido?
- ¿Qué correlaciones existen?
- ¿Qué variables son importantes?
3. Evitar errores costosos
Sin EDA, puedes:
- Entrenar modelos con datos corruptos
- Perder variables importantes
- Interpretar mal los resultados
- Tomar decisiones basadas en datos incorrectos
🛠️ El Kit de Herramientas del Detective de Datos
Paso 1: La Primera Impresión
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Cargar datos
df = pd.read_csv('datos.csv')
# Primer vistazo
print("=== INFORMACIÓN BÁSICA ===")
print(f"Forma del dataset: {df.shape}")
print(f"Columnas: {df.columns.tolist()}")
print(f"Tipos de datos:\n{df.dtypes}")
# Primeras filas
print("\n=== PRIMERAS FILAS ===")
print(df.head())
# Últimas filas
print("\n=== ÚLTIMAS FILAS ===")
print(df.tail())
¿Qué buscas aquí?
- Forma: ¿Cuántas filas y columnas?
- Columnas: ¿Los nombres tienen sentido?
- Tipos: ¿Los tipos de datos son correctos?
- Valores: ¿Los datos se ven razonables?
Paso 2: La Investigación de Valores Faltantes
def analizar_valores_faltantes(df):
"""Análisis detallado de valores faltantes"""
# Información básica
faltantes = df.isnull().sum()
porcentaje = (faltantes / len(df)) * 100
# Crear DataFrame resumen
resumen = pd.DataFrame({
'Columna': faltantes.index,
'Valores_Faltantes': faltantes.values,
'Porcentaje': porcentaje.values
})
# Filtrar solo columnas con valores faltantes
resumen = resumen[resumen['Valores_Faltantes'] > 0]
resumen = resumen.sort_values('Valores_Faltantes', ascending=False)
print("=== ANÁLISIS DE VALORES FALTANTES ===")
print(resumen)
# Visualización
if len(resumen) > 0:
plt.figure(figsize=(12, 6))
sns.barplot(data=resumen, x='Porcentaje', y='Columna')
plt.title('Porcentaje de Valores Faltantes por Columna')
plt.show()
return resumen
# Usar la función
valores_faltantes = analizar_valores_faltantes(df)
¿Qué hacer con valores faltantes?
- < 5%: Eliminar o imputar
- 5-20%: Imputar con cuidado
- > 20%: Considerar eliminar la columna
Paso 3: El Perfil Estadístico
def perfil_estadistico(df):
"""Análisis estadístico completo"""
print("=== ESTADÍSTICAS DESCRIPTIVAS ===")
print(df.describe())
print("\n=== INFORMACIÓN DETALLADA ===")
print(df.info())
# Análisis por tipo de columna
columnas_numericas = df.select_dtypes(include=[np.number]).columns
columnas_categoricas = df.select_dtypes(include=['object']).columns
print(f"\n=== COLUMNAS NUMÉRICAS ({len(columnas_numericas)}) ===")
print(columnas_numericas.tolist())
print(f"\n=== COLUMNAS CATEGÓRICAS ({len(columnas_categoricas)}) ===")
print(columnas_categoricas.tolist())
# Distribuciones de columnas numéricas
if len(columnas_numericas) > 0:
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
axes = axes.ravel()
for i, col in enumerate(columnas_numericas[:4]):
if i < len(axes):
df[col].hist(bins=30, ax=axes[i])
axes[i].set_title(f'Distribución de {col}')
plt.tight_layout()
plt.show()
return columnas_numericas, columnas_categoricas
# Usar la función
num_cols, cat_cols = perfil_estadistico(df)
Paso 4: La Caza de Outliers
def detectar_outliers(df, columnas_numericas):
"""Detección de outliers usando IQR"""
outliers_info = {}
for col in columnas_numericas:
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
# Límites para outliers
limite_inferior = Q1 - 1.5 * IQR
limite_superior = Q3 + 1.5 * IQR
# Contar outliers
outliers = df[(df[col] < limite_inferior) | (df[col] > limite_superior)]
outliers_info[col] = {
'cantidad': len(outliers),
'porcentaje': (len(outliers) / len(df)) * 100,
'limites': (limite_inferior, limite_superior)
}
# Crear DataFrame resumen
resumen_outliers = pd.DataFrame(outliers_info).T
resumen_outliers = resumen_outliers[resumen_outliers['cantidad'] > 0]
print("=== ANÁLISIS DE OUTLIERS ===")
print(resumen_outliers)
# Visualización de outliers
if len(resumen_outliers) > 0:
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
axes = axes.ravel()
for i, col in enumerate(resumen_outliers.index[:4]):
if i < len(axes):
sns.boxplot(data=df, y=col, ax=axes[i])
axes[i].set_title(f'Boxplot de {col}')
plt.tight_layout()
plt.show()
return outliers_info
# Usar la función
outliers = detectar_outliers(df, num_cols)
¿Qué hacer con outliers?
- Investigar: ¿Son errores o valores reales?
- Contexto: ¿Tienen sentido en el dominio?
- Impacto: ¿Afectan significativamente el análisis?
Paso 5: El Mapa de Correlaciones
def analizar_correlaciones(df, columnas_numericas):
"""Análisis de correlaciones entre variables"""
if len(columnas_numericas) < 2:
print("Se necesitan al menos 2 columnas numéricas para analizar correlaciones")
return
# Matriz de correlación
corr_matrix = df[columnas_numericas].corr()
# Visualización
plt.figure(figsize=(12, 10))
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
sns.heatmap(corr_matrix,
mask=mask,
annot=True,
cmap='coolwarm',
center=0,
square=True,
fmt='.2f')
plt.title('Matriz de Correlación')
plt.tight_layout()
plt.show()
# Correlaciones fuertes (|r| > 0.7)
correlaciones_fuertes = []
for i in range(len(corr_matrix.columns)):
for j in range(i+1, len(corr_matrix.columns)):
corr_val = corr_matrix.iloc[i, j]
if abs(corr_val) > 0.7:
correlaciones_fuertes.append({
'Variable_1': corr_matrix.columns[i],
'Variable_2': corr_matrix.columns[j],
'Correlacion': corr_val
})
if correlaciones_fuertes:
print("\n=== CORRELACIONES FUERTES (|r| > 0.7) ===")
for corr in correlaciones_fuertes:
print(f"{corr['Variable_1']} - {corr['Variable_2']}: {corr['Correlacion']:.3f}")
return corr_matrix, correlaciones_fuertes
# Usar la función
corr_matrix, corr_fuertes = analizar_correlaciones(df, num_cols)
Paso 6: El Análisis Categórico
def analizar_categoricas(df, columnas_categoricas):
"""Análisis de variables categóricas"""
for col in columnas_categoricas:
print(f"\n=== ANÁLISIS DE {col.upper()} ===")
# Valores únicos
valores_unicos = df[col].nunique()
print(f"Valores únicos: {valores_unicos}")
# Distribución
distribucion = df[col].value_counts()
print(f"Distribución:\n{distribucion}")
# Porcentajes
porcentajes = df[col].value_counts(normalize=True) * 100
print(f"Porcentajes:\n{porcentajes}")
# Visualización
plt.figure(figsize=(10, 6))
sns.countplot(data=df, y=col, order=distribucion.index)
plt.title(f'Distribución de {col}')
plt.tight_layout()
plt.show()
# Usar la función
analizar_categoricas(df, cat_cols)
🎨 Visualizaciones que Cuentan Historias
1. Distribuciones
# Histogramas múltiples
def plot_distribuciones(df, columnas_numericas):
n_cols = min(3, len(columnas_numericas))
n_rows = (len(columnas_numericas) + n_cols - 1) // n_cols
fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, 5*n_rows))
axes = axes.ravel() if n_rows > 1 else [axes] if n_cols == 1 else axes
for i, col in enumerate(columnas_numericas):
if i < len(axes):
df[col].hist(bins=30, ax=axes[i], alpha=0.7)
axes[i].set_title(f'Distribución de {col}')
axes[i].set_xlabel(col)
axes[i].set_ylabel('Frecuencia')
plt.tight_layout()
plt.show()
2. Relaciones entre Variables
# Scatter plots para relaciones
def plot_relaciones(df, columnas_numericas, target_col=None):
if len(columnas_numericas) < 2:
print("Se necesitan al menos 2 columnas numéricas")
return
# Si hay variable target, usarla para colorear
hue = target_col if target_col and target_col in df.columns else None
# Pairplot
if len(columnas_numericas) <= 5: # Para no sobrecargar
sns.pairplot(df[columnas_numericas + ([target_col] if target_col else [])],
hue=hue, diag_kind='hist')
plt.show()
# Scatter plot de las dos variables más correlacionadas
corr_pairs = []
for i in range(len(columnas_numericas)):
for j in range(i+1, len(columnas_numericas)):
corr = df[columnas_numericas[i]].corr(df[columnas_numericas[j]])
corr_pairs.append((columnas_numericas[i], columnas_numericas[j], abs(corr)))
if corr_pairs:
corr_pairs.sort(key=lambda x: x[2], reverse=True)
var1, var2, corr = corr_pairs[0]
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x=var1, y=var2, hue=hue)
plt.title(f'Relación entre {var1} y {var2} (r = {corr:.3f})')
plt.show()
🧠 Patrones que Debes Buscar
1. Patrones Temporales
# Si tienes datos temporales
if 'fecha' in df.columns:
df['fecha'] = pd.to_datetime(df['fecha'])
df['mes'] = df['fecha'].dt.month
df['año'] = df['fecha'].dt.year
# Tendencia temporal
plt.figure(figsize=(12, 6))
df.groupby('mes')['ventas'].sum().plot(kind='line')
plt.title('Tendencia de Ventas por Mes')
plt.show()
2. Patrones por Categorías
# Análisis por grupos
def analizar_por_grupos(df, grupo_col, target_col):
print(f"\n=== ANÁLISIS POR {grupo_col.upper()} ===")
# Estadísticas por grupo
stats_por_grupo = df.groupby(grupo_col)[target_col].agg(['mean', 'median', 'std', 'count'])
print(stats_por_grupo)
# Visualización
plt.figure(figsize=(12, 6))
sns.boxplot(data=df, x=grupo_col, y=target_col)
plt.title(f'{target_col} por {grupo_col}')
plt.xticks(rotation=45)
plt.show()
📊 EDA Automatizado con Pandas Profiling
# Instalación: pip install pandas-profiling
from pandas_profiling import ProfileReport
# Generar reporte automático
profile = ProfileReport(df, title="EDA Report", explorative=True)
profile.to_file("eda_report.html")
🎯 Checklist de EDA
✅ Información Básica
- [ ] Forma del dataset
- [ ] Tipos de datos correctos
- [ ] Nombres de columnas claros
- [ ] Valores faltantes identificados
✅ Calidad de Datos
- [ ] Outliers detectados y analizados
- [ ] Duplicados identificados
- [ ] Inconsistencias en datos categóricos
- [ ] Rangos de valores razonables
✅ Distribuciones
- [ ] Distribuciones de variables numéricas
- [ ] Distribuciones de variables categóricas
- [ ] Asimetría y curtosis identificadas
- [ ] Transformaciones necesarias identificadas
✅ Relaciones
- [ ] Correlaciones calculadas
- [ ] Relaciones no lineales exploradas
- [ ] Interacciones entre variables
- [ ] Variable objetivo analizada
✅ Insights Generados
- [ ] Hipótesis formuladas
- [ ] Patrones interesantes identificados
- [ ] Próximos pasos definidos
- [ ] Decisiones de preprocesamiento tomadas
💡 Consejos Avanzados
1. EDA Iterativo
# No hagas todo de una vez, itera
def eda_iterativo(df, max_iteraciones=3):
for i in range(max_iteraciones):
print(f"\n=== ITERACIÓN {i+1} ===")
# Hacer análisis
insights = analizar_datos(df)
# Preguntar al usuario
continuar = input("¿Continuar con más análisis? (y/n): ")
if continuar.lower() != 'y':
break
2. EDA por Dominio
# Adapta tu EDA al dominio específico
def eda_financiero(df):
# Métricas específicas para finanzas
df['rendimiento'] = df['precio_final'] / df['precio_inicial'] - 1
df['volatilidad'] = df.groupby('activo')['rendimiento'].rolling(30).std()
def eda_marketing(df):
# Métricas específicas para marketing
df['conversion_rate'] = df['conversiones'] / df['visitas']
df['lifetime_value'] = df['compras_promedio'] * df['frecuencia_compra']
🎯 Reflexión Práctica
Ejercicio: Toma cualquier dataset y aplica estos pasos de EDA. Al final, pregúntate:
- ¿Qué aprendí sobre los datos que no sabía antes?
- ¿Qué patrones me sorprendieron?
- ¿Qué decisiones de preprocesamiento debo tomar?
- ¿Qué hipótesis puedo formular para el modelado?
🔗 Lo que viene después
En el próximo post exploraremos el Preprocesamiento de Datos, donde aplicaremos los insights del EDA para preparar nuestros datos para el modelado.
💭 Pregunta para reflexionar
¿Qué tipo de patrón te resulta más fascinante descubrir en los datos? ¿Son las correlaciones inesperadas, los outliers que cuentan historias, o las distribuciones que revelan comportamientos ocultos?
El EDA no es solo un paso obligatorio, es donde la magia de la ciencia de datos realmente comienza. Es donde los datos dejan de ser números y se convierten en historias que podemos entender y actuar.
Top comments (0)