2026-5 · Minería de Datos

Notebook 1

Creado: 12-07-2026 · Actualizado: 12-07-2026

1. Análisis Exploratorio de Datos (EAD): Diagnóstico de Diabetes

Este cuaderno aborda el flujo completo de un Análisis Exploratorio de Datos (EAD) aplicado a un conjunto de variables clínicas y de salud de una población de mujeres nativas americanas (Pima). El objetivo principal es limpiar, transformar, resumir y visualizar los datos para identificar patrones significativos, evaluar inconsistencias biológicas y verificar los supuestos estadísticos antes de cualquier etapa de modelado predictivo.

Código · Python
# ==============================================================================
# CONFIGURACIÓN DEL ENTORNO E IMPORTACIÓN DE LIBRERÍAS
# ==============================================================================
# Si necesitas instalar los paquetes previos, ejecuta en tu terminal:
# pip install pandas numpy matplotlib seaborn scipy

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats

# Configuración visual global para los gráficos
sns.set_theme(style="whitegrid")
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['axes.titlesize'] = 14
plt.rcParams['axes.labelsize'] = 12

2. Carga y Estructuración del Dataset

El conjunto de datos se importa desde un repositorio centralizado de Machine Learning. El dataset consta de 768 registros y 9 columnas correspondientes a variables biomédicas. Configuración inicial de las variables y tipado correcto:

Código · Python
# URL del repositorio con los datos crudos
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv"

# Definición de las variables clínicas y objetivo
column_names = [
    'pregnant',  # Número de embarazos
    'glucose',   # Concentración de glucosa plasmática a las 2 horas 
    'pressure',  # Presión arterial diastólica (mm Hg)
    'triceps',   # Grosor del pliegue cutáneo del tríceps (mm)
    'insulin',   # Insulina sérica de 2 horas (mu U/ml)
    'mass',      # Índice de masa corporal (peso en kg / (altura en m)^2)
    'pedigree',  # Función de pedigrí de diabetes (historial familiar)
    'age',       # Edad (años)
    'diabetes'   # Variable objetivo (0 = No presenta, 1 = Presenta)
]

# Cargar los datos en un DataFrame de pandas
pima = pd.read_csv(url, names=column_names)

# Transformación de la variable objetivo a tipo categórico descriptivo ('neg', 'pos')
pima['diabetes'] = pima['diabetes'].map({0: 'neg', 1: 'pos'}).astype('category')

print("Dataset estructurado e inicializado correctamente.")
Dataset estructurado e inicializado correctamente.

3. Inspección Inicial de la Estructura de Datos

Antes de realizar operaciones matemáticas, examinamos las primeras filas del DataFrame y sus metadatos (tipos de datos asignados por columna y conteo de valores no nulos).

Código · Python
print("=== VISTA PREVIA DE LOS PRIMEROS REGISTROS ===")
display(pima.head())

print("\n=== ESPECIFICACIONES TÉCNICAS DEL DATAFRAME ===")
pima.info()
=== VISTA PREVIA DE LOS PRIMEROS REGISTROS ===
   pregnant  glucose  pressure  triceps  insulin  mass  pedigree  age diabetes
0         6      148        72       35        0  33.6     0.627   50      pos
1         1       85        66       29        0  26.6     0.351   31      neg
2         8      183        64        0        0  23.3     0.672   32      pos
3         1       89        66       23       94  28.1     0.167   21      neg
4         0      137        40       35      168  43.1     2.288   33      pos

=== ESPECIFICACIONES TÉCNICAS DEL DATAFRAME ===
<class 'pandas.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
 #   Column    Non-Null Count  Dtype   
---  ------    --------------  -----   
 0   pregnant  768 non-null    int64   
 1   glucose   768 non-null    int64   
 2   pressure  768 non-null    int64   
 3   triceps   768 non-null    int64   
 4   insulin   768 non-null    int64   
 5   mass      768 non-null    float64 
 6   pedigree  768 non-null    float64 
 7   age       768 non-null    int64   
 8   diabetes  768 non-null    category
dtypes: category(1), float64(2), int64(6)
memory usage: 48.9 KB

4. Auditoría de Calidad de Datos y Resumen Estadístico

Calculamos las métricas de tendencia central y dispersión de los atributos numéricos para detectar anomalías o inconsistencias físicas en las mediciones.

Código · Python
# Resumen descriptivo de los atributos numéricos y categóricos
pima.describe(include='all')
          pregnant     glucose    pressure     triceps     insulin  \
count   768.000000  768.000000  768.000000  768.000000  768.000000   
unique         NaN         NaN         NaN         NaN         NaN   
top            NaN         NaN         NaN         NaN         NaN   
freq           NaN         NaN         NaN         NaN         NaN   
mean      3.845052  120.894531   69.105469   20.536458   79.799479   
std       3.369578   31.972618   19.355807   15.952218  115.244002   
min       0.000000    0.000000    0.000000    0.000000    0.000000   
25%       1.000000   99.000000   62.000000    0.000000    0.000000   
50%       3.000000  117.000000   72.000000   23.000000   30.500000   
75%       6.000000  140.250000   80.000000   32.000000  127.250000   
max      17.000000  199.000000  122.000000   99.000000  846.000000   

              mass    pedigree         age diabetes  
count   768.000000  768.000000  768.000000      768  
unique         NaN         NaN         NaN        2  
top            NaN         NaN         NaN      neg  
freq           NaN         NaN         NaN      500  
mean     31.992578    0.471876   33.240885      NaN  
std       7.884160    0.331329   11.760232      NaN  
min       0.000000    0.078000   21.000000      NaN  
25%      27.300000    0.243750   24.000000      NaN  
50%      32.000000    0.372500   29.000000      NaN  
75%      36.600000    0.626250   41.000000      NaN  
max      67.100000    2.420000   81.000000      NaN  

Diagnóstico de Inconsistencia Fisiológica

Al inspeccionar el resumen estadístico, se detecta un problema crítico de calidad de datos: las variables glucose, pressure, triceps, insulin y mass muestran un valor mínimo (min) de 0.0. En la práctica médica, un valor de cero en la presión arterial, el grosor de la piel, la insulina o el índice de masa corporal es biológicamente imposible para un paciente vivo. Estos ceros representan valores faltantes ocultos que deben ser tratados adecuadamente.

5. Tratamiento de Valores Faltantes Ocultos

Para corregir la distorsión estadística que provocan los ceros artificiales, los reemplazamos por el indicador estándar de ausencia de datos (NaN). Posteriormente, analizamos visualmente su distribución e imputamos mediante la mediana de cada atributo.

Código · Python
# 1. Reemplazo de ceros inválidos por NaN
invalid_zero_cols = ['glucose', 'pressure', 'triceps', 'insulin', 'mass']
for col in invalid_zero_cols:
    pima[col] = pima[col].replace(0, np.nan)

# 2. Cuantificación de la ausencia de datos
print("=== VALORES NULOS DETECTADOS POR COLUMNA ===")
print(pima.isnull().sum())

# 3. Mapeo visual de la estructura de valores faltantes
plt.figure(figsize=(10, 5))
sns.heatmap(pima.isnull(), cbar=False, cmap='viridis')
plt.title('Distribución Espacial de Valores Faltantes (NaN) en el Dataset')
plt.xlabel('Variables')
plt.ylabel('Registros (Índices)')
plt.show()

# 4. Imputación robusta utilizando la mediana (evita la distorsión por valores extremos)
for col in invalid_zero_cols:
    median_value = pima[col].median()
    pima[col] = pima[col].fillna(median_value)

print("\nProceso de limpieza completado: Valores nulos imputados mediante la mediana.")
=== VALORES NULOS DETECTADOS POR COLUMNA ===
pregnant      0
glucose       5
pressure     35
triceps     227
insulin     374
mass         11
pedigree      0
age           0
diabetes      0
dtype: int64
Gráfico generado por el cuaderno

Proceso de limpieza completado: Valores nulos imputados mediante la mediana.

6. Análisis de Distribución de la Variable Objetivo (diabetes)

Evaluamos el equilibrio de las clases de nuestra variable dependiente mediante un gráfico de frecuencias para entender la proporción de pacientes con diagnóstico positivo y negativo.

Código · Python
plt.figure(figsize=(6, 4))

# Corrección aplicada: se asigna x a hue y se desactiva la leyenda implícita
sns.countplot(data=pima, x='diabetes', hue='diabetes', palette='Set2', legend=False)

plt.title("Proporción y Balance de Clases de la Variable Objetivo")
plt.xlabel("Diagnóstico Clínico")
plt.ylabel("Frecuencia Absoluta")

# Cálculo exacto de las proporciones en el dataset
counts = pima['diabetes'].value_counts()
percentages = pima['diabetes'].value_counts(normalize=True) * 100
for idx, (cnt, pct) in enumerate(zip(counts, percentages)):
    print(f"Clase '{counts.index[idx]}': {cnt} registros ({pct:.2f}%)")

plt.show()
Clase 'neg': 500 registros (65.10%)
Clase 'pos': 268 registros (34.90%)
Gráfico generado por el cuaderno

7. Análisis de Relación Numérica-Categórica e Identificación de Outliers

Utilizamos diagramas de caja (boxplots) para segmentar los atributos numéricos en función del diagnóstico médico. Esto nos permite visualizar simultáneamente la dispersión, la diferencia entre medianas y la presencia de valores atípicos (outliers) en las variables predictoras clave.

Código · Python
# Selección de variables clínicas de alto impacto predictivo
key_features = ['glucose', 'mass', 'age']
colors = ['Pastel1', 'Pastel2', 'Set3']

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

for i, var in enumerate(key_features):
    # Corrección aplicada: se especifica hue='diabetes' y legend=False para silenciar los FutureWarnings
    sns.boxplot(data=pima, x='diabetes', y=var, hue='diabetes', ax=axes[i], palette=colors[i], legend=False)
    
    axes[i].set_title(f'Distribución de {var.capitalize()} por Diagnóstico')
    axes[i].set_xlabel('Resultado (Diabetes)')
    axes[i].set_ylabel(var)

plt.tight_layout()
plt.show()
Gráfico generado por el cuaderno

Interpretación de Gráficos de Caja

Se evidencia una separación visual marcada en las medianas de la concentración de glucose (glucosa) y mass (IMC) entre ambos grupos diagnósticos. El grupo identificado con diabetes (pos) exhibe rangos intercuartílicos significativamente más altos, lo que perfila a estas variables como fuertes predictores clínicos.

8. Evaluación de Supuestos Estadísticos: Normalidad

Para fundamentar la selección de futuras pruebas estadísticas (paramétricas o no paramétricas), determinamos si los atributos numéricos provienen de poblaciones con distribuciones Gaussianas (Normales).

A. Evaluación Analítica:

Prueba de Shapiro-Wilk

Definición del contraste de hipótesis con un nivel de significancia α=0.05\alpha = 0.05:

  • H0H_0: Los datos siguen una distribución normal.
  • H1H_1: Los datos no siguen una distribución normal.
Código · Python
numeric_cols = pima.select_dtypes(include=[np.number]).columns

for col in numeric_cols:
    stat, p_value = stats.shapiro(pima[col])
    decision = "No Normal (Rechaza H0)" if p_value < 0.05 else "Normal (No rechaza H0)"
    print(f"Variable: {col:10} | Estadística W: {stat:.4f} | p-value: {p_value:.5f} -> {decision}")
Variable: pregnant   | Estadística W: 0.9043 | p-value: 0.00000 -> No Normal (Rechaza H0)
Variable: glucose    | Estadística W: 0.9696 | p-value: 0.00000 -> No Normal (Rechaza H0)
Variable: pressure   | Estadística W: 0.9878 | p-value: 0.00001 -> No Normal (Rechaza H0)
Variable: triceps    | Estadística W: 0.9278 | p-value: 0.00000 -> No Normal (Rechaza H0)
Variable: insulin    | Estadística W: 0.6415 | p-value: 0.00000 -> No Normal (Rechaza H0)
Variable: mass       | Estadística W: 0.9794 | p-value: 0.00000 -> No Normal (Rechaza H0)
Variable: pedigree   | Estadística W: 0.8365 | p-value: 0.00000 -> No Normal (Rechaza H0)
Variable: age        | Estadística W: 0.8748 | p-value: 0.00000 -> No Normal (Rechaza H0)

Al arrojar todas las variables un p-value inferior a 0.050.05, se concluye analíticamente que ninguno de los componentes predictivos se distribuye de manera estrictamente normal.

B. Evaluación Gráfica: Histograma y Gráfico Q-Q (Caso de Estudio: glucose)

Para comprender la desviación matemática detectada en la prueba analítica, graficamos el comportamiento de los residuos y las colas de la variable glucose.

Código · Python
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# 1. Histograma empírico acoplado con Estimación de Densidad Kernel (KDE)
sns.histplot(pima['glucose'], kde=True, ax=axes[0], color='skyblue', stat="density")
axes[0].set_title('Histograma y Curva de Densidad para Glucosa')
axes[0].set_xlabel('Glucosa (mg/dL)')
axes[0].set_ylabel('Densidad Probabilística')

# 2. Gráfico Cuantil-Cuantil (Q-Q Plot) contra una distribución teórica normal
stats.probplot(pima['glucose'], dist="norm", plot=axes[1])
# Ajustes estéticos para identificar la tendencia lineal
axes[1].get_lines()[0].set_markerfacecolor('gray')
axes[1].get_lines()[0].set_markeredgecolor('gray')
axes[1].get_lines()[1].set_color('red') 
axes[1].set_title('Gráfico de Probabilidad Cuantil-Cuantil (Q-Q) para Glucosa')

plt.tight_layout()
plt.show()
Gráfico generado por el cuaderno

Análisis Gráfico de la Distribución

Los puntos extremos en las zonas terminales del gráfico Q-Q se desvían de la recta diagonal roja. El desprendimiento en el extremo superior derecho evidencia una cola derecha alargada (asimetría positiva). Clínicamente, este comportamiento es coherente con la naturaleza de la población bajo estudio, donde existe un subgrupo de individuos con niveles glucémicos crónicos e inusualmente elevados como consecuencia patológica directa de la diabetes.