Uso de Librerias Python para IA¶
| Estudiante | Alexander Oviedo Fadul |
| Curso | Fundamentos para IA (NRC-197) |
| Semana | 4 |
| Docente | Breyner Alexander Porras Rojas |
| Fecha | Febrero 2026 |
Preguntas Orientadoras¶
Que librerias de Python existen para calculo numerico y visualizacion de datos?¶
No voy a mentir: cuando empece con Python para analisis de datos, me senti perdido. El ecosistema es enorme. Demasiado, diria. Recuerdo haber abierto una lista de librerias recomendadas y haber pensado esto es imposible de aprender todo. Pero con el tiempo, y sobre todo con la practica de ir rompiendo cosas e intentando arreglarlas, fui armando mi propia caja de herramientas. Estas son las que uso de verdad:
| Libreria | Para que la uso |
|---|---|
| NumPy | La base. Sin ella, nada funciona. Arreglos multidimensionales, operaciones matematicas vectorizadas. Es como los cimientos de una casa: nadie los ve, pero si fallan, todo se cae. |
| SciPy | Cuando necesito algo que NumPy no cubre: tests de hipotesis, optimizacion, integrales. La primera vez que hice un test de normalidad con SciPy me sorprendio lo directo que resulta. |
| Pandas | A decir verdad, es mi favorita. Su DataFrame se parece a una hoja de calculo, pero con esteroides. Filtrar, agrupar, pivotar... todo fluye. |
| Polars | La descubri hace poco, casi por accidente leyendo un foro. Esta escrita en Rust y es notablemente mas rapida que Pandas en datasets grandes. Todavia estoy aprendiendo a usarla bien, pero me convencio rapido. |
| Matplotlib | La veterana. Te da control absoluto sobre cada pixel del grafico, aunque a veces siento que escribo demasiado codigo para algo que deberia ser mas simple. |
| Seaborn | Mi atajo para graficos bonitos. Con tres lineas tengo algo presentable. La uso siempre que quiero explorar datos sin perder media hora configurando ejes. |
Boschetti y Massaron (2018) lo explican bien: estas librerias forman un ecosistema donde cada pieza encaja con las demas, y entender esas conexiones es clave para trabajar de forma eficiente (p. 283).
Cuales son las librerias mas utilizadas para el desarrollo de la Inteligencia Artificial?¶
Algo que me hubiera gustado saber al principio es que las librerias para IA tienen niveles. No tiene sentido saltar a TensorFlow sin antes saber manejar un DataFrame. Me toco aprenderlo por las malas. Asi lo organizo yo:
- Fundamentos de datos: NumPy, Pandas, Polars, SciPy. La parte menos vistosa del trabajo, pero la mas critica. Si los datos estan mal preparados, el mejor modelo del mundo da resultados basura.
- Visualizacion: Matplotlib, Seaborn, Plotly. Un buen grafico me ha salvado varias veces de publicar conclusiones equivocadas. Ver los datos es entenderlos.
- Machine Learning clasico: Scikit-learn. Regresion, clasificacion, clustering. Todo listo para usar.
- Deep Learning: TensorFlow y PyTorch. Redes neuronales complejas. Ahi todavia estoy aprendiendo.
En esta actividad me quedo en las dos primeras capas. Creo firmemente que sin dominar estas bases, lo demas se convierte en una caja negra que usamos sin entender. Y eso, a la larga, es un problema.
Configuracion del Entorno e Importacion de Librerias¶
Esto no es glamoroso, lo se. Pero he aprendido que saltarse este paso siempre sale caro. Una vez pase dos horas depurando un error que resulto ser simplemente que tenia una version vieja de NumPy. Desde entonces, siempre empiezo asi: instalar, importar, verificar. Despues ya viene lo divertido.
# ==============================================================================
# INSTALACION DE DEPENDENCIAS
# ==============================================================================
# Prefiero dejar esta celda siempre, aunque en Colab la mayoria
# de paquetes ya vienen. Una vez me fallo un notebook en otra maquina
# porque asumi que todo estaba instalado. Leccion aprendida.
#
# El '!' ejecuta comandos de terminal desde Python.
# --quiet evita que la pantalla se llene de texto.
!pip install numpy scipy pandas polars matplotlib seaborn --quiet
print("Librerias instaladas correctamente.")
Librerias instaladas correctamente.
# ==============================================================================
# IMPORTACION DE LIBRERIAS
# ==============================================================================
# Los alias (np, pd, pl, plt, sns) son convenciones de la comunidad.
# Al principio me parecian arbitrarios, pero ahora los uso sin pensar.
# NumPy: la base de todo. Arreglos y matematicas.
import numpy as np
# SciPy (stats): tests estadisticos, distribuciones, cosas serias.
import scipy.stats as stats
# Pandas: mi navaja suiza para datos tabulares.
import pandas as pd
# Polars: la alternativa rapida. Escrita en Rust, se nota.
import polars as pl
# Matplotlib: la veterana de los graficos.
import matplotlib.pyplot as plt
# Seaborn: graficos bonitos sin sudar.
import seaborn as sns
# 'whitegrid' pone una cuadricula de fondo. Facilita leer valores.
sns.set_style('whitegrid')
# Para que los graficos se vean dentro del notebook.
%matplotlib inline
# Evito que Pandas me trunque columnas.
pd.set_option('display.max_columns', None)
print("Librerias importadas correctamente.")
print(f"NumPy version: {np.__version__}")
print(f"Pandas version: {pd.__version__}")
print(f"Polars version: {pl.__version__}")
Librerias importadas correctamente. NumPy version: 2.0.2 Pandas version: 2.2.2 Polars version: 1.31.0
Carga y Exploracion del Dataset¶
Elegi el dataset de Diabetes de la North Carolina State University porque es un clasico que aparece en muchos tutoriales y textos academicos. Tiene 442 registros con mediciones biologicas reales de pacientes. No es gigante, pero eso lo hace perfecto para aprender: lo suficientemente grande como para que los patrones estadisticos sean reales, y lo suficientemente chico como para que el notebook no tarde una eternidad en ejecutarse.
Yim et al. (2018) insisten en algo que comparto: antes de lanzarse a hacer graficos bonitos, hay que sentarse a entender que contienen los datos. Que variables hay, de que tipo son, si hay valores faltantes. Es tentador saltarse eso, pero cada vez que lo he hecho me ha tocado volver atras (p. 23).
# ==============================================================================
# CARGA DE DATOS CON PANDAS
# ==============================================================================
# Me gusta que Pandas lea directamente desde URL.
# Asi cualquiera puede correr este notebook sin descargar nada.
url = "https://www4.stat.ncsu.edu/~boos/var.select/diabetes.tab.txt"
# read_csv lee valores separados. En este caso, tabuladores.
datos = pd.read_csv(url, sep='\t')
print("Dataset cargado exitosamente con Pandas.")
print(f"Registros: {datos.shape[0]}")
print(f"Variables: {datos.shape[1]}")
Dataset cargado exitosamente con Pandas. Registros: 442 Variables: 11
# ==============================================================================
# CARGA DE DATOS CON POLARS
# ==============================================================================
# Cargo los mismos datos en Polars para comparar.
# Lo mas practico es convertir desde el DataFrame de Pandas.
# Polars y Pandas se llevan bien, que es un alivio.
datos_pl = pl.from_pandas(datos)
print("Dataset cargado en Polars.")
print(f"Filas: {datos_pl.shape[0]}, Columnas: {datos_pl.shape[1]}")
print(f"\nTipos de datos en Polars:")
print(datos_pl.dtypes)
Dataset cargado en Polars. Filas: 442, Columnas: 11 Tipos de datos en Polars: [Int64, Int64, Float64, Float64, Int64, Float64, Float64, Float64, Float64, Int64, Int64]
# ==============================================================================
# PRIMERAS FILAS DEL DATASET
# ==============================================================================
# head() es lo primero que ejecuto siempre. Es como asomarse
# a un dataset antes de meterse de lleno.
print("Primeras 10 filas del dataset:")
print("=" * 70)
datos.head(10)
Primeras 10 filas del dataset: ======================================================================
| AGE | SEX | BMI | BP | S1 | S2 | S3 | S4 | S5 | S6 | Y | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 59 | 2 | 32.1 | 101.0 | 157 | 93.2 | 38.0 | 4.00 | 4.8598 | 87 | 151 |
| 1 | 48 | 1 | 21.6 | 87.0 | 183 | 103.2 | 70.0 | 3.00 | 3.8918 | 69 | 75 |
| 2 | 72 | 2 | 30.5 | 93.0 | 156 | 93.6 | 41.0 | 4.00 | 4.6728 | 85 | 141 |
| 3 | 24 | 1 | 25.3 | 84.0 | 198 | 131.4 | 40.0 | 5.00 | 4.8903 | 89 | 206 |
| 4 | 50 | 1 | 23.0 | 101.0 | 192 | 125.4 | 52.0 | 4.00 | 4.2905 | 80 | 135 |
| 5 | 23 | 1 | 22.6 | 89.0 | 139 | 64.8 | 61.0 | 2.00 | 4.1897 | 68 | 97 |
| 6 | 36 | 2 | 22.0 | 90.0 | 160 | 99.6 | 50.0 | 3.00 | 3.9512 | 82 | 138 |
| 7 | 66 | 2 | 26.2 | 114.0 | 255 | 185.0 | 56.0 | 4.55 | 4.2485 | 92 | 63 |
| 8 | 60 | 2 | 32.1 | 83.0 | 179 | 119.4 | 42.0 | 4.00 | 4.4773 | 94 | 110 |
| 9 | 29 | 1 | 30.0 | 85.0 | 180 | 93.4 | 43.0 | 4.00 | 5.3845 | 88 | 310 |
# ==============================================================================
# INFORMACION ESTRUCTURAL
# ==============================================================================
# info() da el resumen tecnico: tipos, nulos, memoria.
# Si algo raro va a aparecer, suele ser aqui.
print("Informacion del dataset:")
print("=" * 70)
datos.info()
Informacion del dataset: ====================================================================== <class 'pandas.core.frame.DataFrame'> RangeIndex: 442 entries, 0 to 441 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 AGE 442 non-null int64 1 SEX 442 non-null int64 2 BMI 442 non-null float64 3 BP 442 non-null float64 4 S1 442 non-null int64 5 S2 442 non-null float64 6 S3 442 non-null float64 7 S4 442 non-null float64 8 S5 442 non-null float64 9 S6 442 non-null int64 10 Y 442 non-null int64 dtypes: float64(6), int64(5) memory usage: 38.1 KB
# ==============================================================================
# DESCRIPCION DE VARIABLES
# ==============================================================================
# Esto lo arme manualmente consultando la documentacion del dataset.
# Es un paso que muchos se saltan, pero a mi me parece fundamental:
# no tiene sentido analizar datos si no sabes que significan.
descripcion_variables = {
'AGE': 'Edad del paciente en anios',
'SEX': 'Sexo del paciente (1=masculino, 2=femenino)',
'BMI': 'Indice de Masa Corporal (peso/altura^2)',
'BP': 'Presion arterial media',
'S1': 'TC - Colesterol total serico',
'S2': 'LDL - Colesterol de baja densidad',
'S3': 'HDL - Colesterol de alta densidad',
'S4': 'TCH - Relacion colesterol total / HDL',
'S5': 'LTG - Logaritmo de trigliceridos sericos',
'S6': 'GLU - Nivel de glucosa en sangre',
'Y': 'Variable objetivo: progresion de la diabetes'
}
print("Descripcion de variables:")
print("=" * 70)
for var, desc in descripcion_variables.items():
print(f" {var:5} -> {desc}")
Descripcion de variables: ====================================================================== AGE -> Edad del paciente en anios SEX -> Sexo del paciente (1=masculino, 2=femenino) BMI -> Indice de Masa Corporal (peso/altura^2) BP -> Presion arterial media S1 -> TC - Colesterol total serico S2 -> LDL - Colesterol de baja densidad S3 -> HDL - Colesterol de alta densidad S4 -> TCH - Relacion colesterol total / HDL S5 -> LTG - Logaritmo de trigliceridos sericos S6 -> GLU - Nivel de glucosa en sangre Y -> Variable objetivo: progresion de la diabetes
Exploracion de Datos con Polars¶
Abro un parentesis para hablar de Polars. La descubri por casualidad en un hilo de Reddit donde alguien se quejaba de lo lento que era Pandas con un CSV de 50 millones de filas. Alguien respondio: prueba Polars. Lo probe. Y si, la diferencia es real.
Ahora, seamos honestos: con 442 registros como los de este dataset, la ventaja de velocidad es irrelevante. Pero quiero mostrar como se trabaja con Polars porque su sintaxis de expresiones encadenadas me parece elegante, y creo que vale la pena familiarizarse con ella ahora, antes de que la necesitemos de verdad en un proyecto grande.
# ==============================================================================
# ESTADISTICAS DESCRIPTIVAS CON POLARS
# ==============================================================================
# describe() existe en ambas librerias. El resultado es similar,
# pero por debajo Polars usa un motor completamente distinto.
print("Estadisticas descriptivas con Polars:")
print("=" * 70)
datos_pl.describe()
Estadisticas descriptivas con Polars: ======================================================================
| statistic | AGE | SEX | BMI | BP | S1 | S2 | S3 | S4 | S5 | S6 | Y |
|---|---|---|---|---|---|---|---|---|---|---|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| "count" | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 | 442.0 |
| "null_count" | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| "mean" | 48.5181 | 1.468326 | 26.375792 | 94.647014 | 189.140271 | 115.43914 | 49.788462 | 4.070249 | 4.641411 | 91.260181 | 152.133484 |
| "std" | 13.109028 | 0.499561 | 4.418122 | 13.831283 | 34.608052 | 30.413081 | 12.934202 | 1.29045 | 0.522391 | 11.496335 | 77.093005 |
| "min" | 19.0 | 1.0 | 18.0 | 62.0 | 97.0 | 41.6 | 22.0 | 2.0 | 3.2581 | 58.0 | 25.0 |
| "25%" | 38.0 | 1.0 | 23.2 | 84.0 | 164.0 | 96.0 | 40.0 | 3.0 | 4.2767 | 83.0 | 87.0 |
| "50%" | 50.0 | 1.0 | 25.7 | 93.0 | 186.0 | 113.0 | 48.0 | 4.0 | 4.625 | 91.0 | 141.0 |
| "75%" | 59.0 | 2.0 | 29.3 | 105.0 | 210.0 | 134.6 | 58.0 | 5.0 | 4.9972 | 98.0 | 212.0 |
| "max" | 79.0 | 2.0 | 42.2 | 133.0 | 301.0 | 242.4 | 99.0 | 9.09 | 6.107 | 124.0 | 346.0 |
# ==============================================================================
# SELECCION Y FILTRADO CON POLARS
# ==============================================================================
# Esto es lo que mas me gusta de Polars: la forma de escribir
# filtros. Se lee casi como lenguaje natural.
# Pacientes mayores de 60 con sobrepeso (BMI > 30).
pacientes_riesgo = datos_pl.filter(
(pl.col('AGE') > 60) & (pl.col('BMI') > 30)
)
print(f"Pacientes mayores de 60 con BMI > 30: {pacientes_riesgo.shape[0]}")
print(f"Eso representa el {pacientes_riesgo.shape[0]/datos_pl.shape[0]*100:.1f}% del total")
# Agrupar y calcular estadisticas por sexo.
# En Polars cada operacion se encadena de forma fluida.
resumen_por_sexo = datos_pl.group_by('SEX').agg([
pl.col('AGE').mean().alias('edad_promedio'),
pl.col('BMI').mean().alias('bmi_promedio'),
pl.col('Y').mean().alias('progresion_promedio'),
pl.len().alias('cantidad')
])
print("\nResumen agrupado por sexo (1=M, 2=F):")
print(resumen_por_sexo)
Pacientes mayores de 60 con BMI > 30: 21 Eso representa el 4.8% del total Resumen agrupado por sexo (1=M, 2=F): shape: (2, 5) ┌─────┬───────────────┬──────────────┬─────────────────────┬──────────┐ │ SEX ┆ edad_promedio ┆ bmi_promedio ┆ progresion_promedio ┆ cantidad │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 ┆ f64 ┆ u32 │ ╞═════╪═══════════════╪══════════════╪═════════════════════╪══════════╡ │ 1 ┆ 46.382979 ┆ 26.010638 ┆ 149.021277 ┆ 235 │ │ 2 ┆ 50.942029 ┆ 26.790338 ┆ 155.666667 ┆ 207 │ └─────┴───────────────┴──────────────┴─────────────────────┴──────────┘
# ==============================================================================
# COMPARACION DE RENDIMIENTO: POLARS vs PANDAS
# ==============================================================================
# Con un dataset tan chico la diferencia sera minima, pero
# quiero dejar registro de como se mide. Cuando tenga un
# proyecto con datos reales de millones de filas, esto importa.
import time
# Misma operacion 1000 veces con Pandas.
inicio_pd = time.time()
for _ in range(1000):
resultado_pd = datos.groupby('SEX').agg({'BMI': 'mean', 'Y': 'mean'})
tiempo_pd = time.time() - inicio_pd
# Y ahora con Polars.
inicio_pl = time.time()
for _ in range(1000):
resultado_pl = datos_pl.group_by('SEX').agg([
pl.col('BMI').mean(),
pl.col('Y').mean()
])
tiempo_pl = time.time() - inicio_pl
print("Comparacion de rendimiento (1000 iteraciones de groupby):")
print("=" * 70)
print(f" Pandas: {tiempo_pd:.4f} segundos")
print(f" Polars: {tiempo_pl:.4f} segundos")
if tiempo_pl < tiempo_pd:
print(f" Polars fue {tiempo_pd/tiempo_pl:.1f}x mas rapido")
else:
print(f" Pandas fue {tiempo_pl/tiempo_pd:.1f}x mas rapido")
print("\nOjo: con datasets grandes la brecha se amplifica muchisimo.")
Comparacion de rendimiento (1000 iteraciones de groupby): ====================================================================== Pandas: 0.8622 segundos Polars: 0.1641 segundos Polars fue 5.3x mas rapido Ojo: con datasets grandes la brecha se amplifica muchisimo.
Analisis Estadistico con NumPy y SciPy¶
Llegamos a la parte que mas disfruto. A decir verdad, lo que me atrajo de la ciencia de datos no fueron los graficos sino esto: poder hacerle preguntas a los datos y que los numeros respondan. Puede sonar cursi, pero la primera vez que calcule una correlacion y vi como dos variables se relacionaban, senti que habia descubierto algo.
Lo potente de NumPy es la vectorizacion. En vez de escribir bucles para recorrer cada dato, uno escribe una operacion y NumPy la aplica a todo el arreglo de golpe. Mehta (2015) lo explica mejor que yo: la vectorizacion permite expresar operaciones matematicas complejas de forma concisa sin sacrificar rendimiento (p. 165). En la practica, eso significa menos codigo y menos errores.
# ==============================================================================
# MEDIDAS DE TENDENCIA CENTRAL
# ==============================================================================
# Empiezo por lo basico: donde se concentran los datos.
# Parece simple, pero estos numeros cuentan mucho.
# Saco la columna AGE como arreglo de NumPy.
edades = datos['AGE'].values
# MEDIA: el clasico promedio.
media_edad = np.mean(edades)
# MEDIANA: el valor que queda justo en el medio.
# Es mas robusta que la media cuando hay outliers.
mediana_edad = np.median(edades)
# MODA: el valor que mas se repite.
# NumPy no la trae, asi que toca pedirle ayuda a SciPy.
resultado_moda = stats.mode(edades, keepdims=True)
moda_edad = resultado_moda.mode[0]
frecuencia_moda = resultado_moda.count[0]
print("Medidas de Tendencia Central - Variable AGE:")
print("=" * 70)
print(f" Media (promedio): {media_edad:.2f} anios")
print(f" Mediana (valor central): {mediana_edad:.2f} anios")
print(f" Moda (mas frecuente): {moda_edad} anios (aparece {frecuencia_moda} veces)")
print()
# Que la media y mediana esten tan cerca me dice que la
# distribucion es bastante simetrica. No hay valores locos
# tirando del promedio. Buena senal.
print("La media y la mediana caen muy cerca. Eso sugiere una distribucion")
print("simetrica, sin valores extremos que distorsionen el promedio.")
print("Si hubiese una diferencia grande, tendria que preocuparme por outliers.")
Medidas de Tendencia Central - Variable AGE: ====================================================================== Media (promedio): 48.52 anios Mediana (valor central): 50.00 anios Moda (mas frecuente): 53 anios (aparece 19 veces) La media y la mediana caen muy cerca. Eso sugiere una distribucion simetrica, sin valores extremos que distorsionen el promedio. Si hubiese una diferencia grande, tendria que preocuparme por outliers.
# ==============================================================================
# MEDIDAS DE DISPERSION
# ==============================================================================
# No basta con saber donde se concentran los datos.
# Tambien importa cuanto se esparcen.
# DESVIACION ESTANDAR: cuanto se alejan del promedio.
# El ddof=1 me costo entenderlo: es la correccion de Bessel
# para muestras. Si no lo pones, el resultado esta sesgado.
# Tuve que leer aparte sobre esto, no es obvio.
desviacion_std = np.std(edades, ddof=1)
# VARIANZA: lo mismo pero al cuadrado. Menos intuitiva.
varianza = np.var(edades, ddof=1)
# RANGO: lo mas basico, maximo menos minimo.
valor_minimo = np.min(edades)
valor_maximo = np.max(edades)
rango = valor_maximo - valor_minimo
# CUARTILES: parten los datos en cuatro porciones iguales.
q1 = np.percentile(edades, 25)
q3 = np.percentile(edades, 75)
iqr = q3 - q1 # IQR: lo que cabe entre Q1 y Q3
print("Medidas de Dispersion - Variable AGE:")
print("=" * 70)
print(f" Desviacion estandar: {desviacion_std:.2f} anios")
print(f" Varianza: {varianza:.2f}")
print(f" Minimo: {valor_minimo} anios")
print(f" Maximo: {valor_maximo} anios")
print(f" Rango: {rango} anios")
print(f" Cuartil 1 (Q1): {q1:.2f} anios")
print(f" Cuartil 3 (Q3): {q3:.2f} anios")
print(f" IQR: {iqr:.2f} anios")
Medidas de Dispersion - Variable AGE: ====================================================================== Desviacion estandar: 13.11 anios Varianza: 171.85 Minimo: 19 anios Maximo: 79 anios Rango: 60 anios Cuartil 1 (Q1): 38.25 anios Cuartil 3 (Q3): 59.00 anios IQR: 20.75 anios
# ==============================================================================
# ESTADISTICAS DESCRIPTIVAS COMPLETAS
# ==============================================================================
# describe() hace todo de golpe. Lo transpongo con .T
# para que las variables queden en filas y se lea mejor.
print("Estadisticas descriptivas de todas las variables:")
print("=" * 70)
datos.describe().T.round(2)
Estadisticas descriptivas de todas las variables: ======================================================================
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| AGE | 442.0 | 48.52 | 13.11 | 19.00 | 38.25 | 50.00 | 59.00 | 79.00 |
| SEX | 442.0 | 1.47 | 0.50 | 1.00 | 1.00 | 1.00 | 2.00 | 2.00 |
| BMI | 442.0 | 26.38 | 4.42 | 18.00 | 23.20 | 25.70 | 29.28 | 42.20 |
| BP | 442.0 | 94.65 | 13.83 | 62.00 | 84.00 | 93.00 | 105.00 | 133.00 |
| S1 | 442.0 | 189.14 | 34.61 | 97.00 | 164.25 | 186.00 | 209.75 | 301.00 |
| S2 | 442.0 | 115.44 | 30.41 | 41.60 | 96.05 | 113.00 | 134.50 | 242.40 |
| S3 | 442.0 | 49.79 | 12.93 | 22.00 | 40.25 | 48.00 | 57.75 | 99.00 |
| S4 | 442.0 | 4.07 | 1.29 | 2.00 | 3.00 | 4.00 | 5.00 | 9.09 |
| S5 | 442.0 | 4.64 | 0.52 | 3.26 | 4.28 | 4.62 | 5.00 | 6.11 |
| S6 | 442.0 | 91.26 | 11.50 | 58.00 | 83.25 | 91.00 | 98.00 | 124.00 |
| Y | 442.0 | 152.13 | 77.09 | 25.00 | 87.00 | 140.50 | 211.50 | 346.00 |
# ==============================================================================
# CORRELACIONES
# ==============================================================================
# La correlacion de Pearson va de -1 a +1.
# Cercano a 1: relacion directa fuerte.
# Cercano a -1: relacion inversa fuerte.
# Cercano a 0: no hay relacion lineal (ojo: puede haber otra).
correlacion_bmi_y = np.corrcoef(datos['BMI'], datos['Y'])[0, 1]
correlacion_s5_y = np.corrcoef(datos['S5'], datos['Y'])[0, 1]
correlacion_age_y = np.corrcoef(datos['AGE'], datos['Y'])[0, 1]
print("Correlaciones con Y (progresion de diabetes):")
print("=" * 70)
print(f" BMI (Masa Corporal) <-> Y: {correlacion_bmi_y:.4f}")
print(f" S5 (Trigliceridos) <-> Y: {correlacion_s5_y:.4f}")
print(f" AGE (Edad) <-> Y: {correlacion_age_y:.4f}")
print()
# Esto me sorprendio. Yo habria apostado por BMI como la variable
# mas correlacionada. Pero no. S5 gana. Y la edad, que intuitivamente
# parece obvia, resulta la mas debil de las tres. Leccion:
# nunca confiar en la intuicion sin chequear los datos.
print("Dato curioso: S5 (trigliceridos) gana por goleada.")
print("Yo habria apostado por el BMI. Me equivoque.")
print("Los trigliceridos elevados son factor de riesgo conocido,")
print("pero no esperaba que superaran al peso corporal.")
print("La edad, que parece tan obvia, tiene la correlacion mas debil.")
Correlaciones con Y (progresion de diabetes): ====================================================================== BMI (Masa Corporal) <-> Y: 0.5865 S5 (Trigliceridos) <-> Y: 0.5659 AGE (Edad) <-> Y: 0.1879 Dato curioso: S5 (trigliceridos) gana por goleada. Yo habria apostado por el BMI. Me equivoque. Los trigliceridos elevados son factor de riesgo conocido, pero no esperaba que superaran al peso corporal. La edad, que parece tan obvia, tiene la correlacion mas debil.
# ==============================================================================
# TEST DE NORMALIDAD
# ==============================================================================
# Muchos metodos asumen normalidad. Hay que verificar.
# Uso el test de D'Agostino-Pearson:
# H0 (hipotesis nula): los datos SON normales.
# Si p-valor < 0.05: rechazo H0 -> no son normales.
# Si p-valor >= 0.05: no puedo rechazar H0 -> podrian ser normales.
# (Nota: 'no rechazar' no es lo mismo que confirmar. Matiz importante.)
estadistico_bmi, pvalor_bmi = stats.normaltest(datos['BMI'])
estadistico_y, pvalor_y = stats.normaltest(datos['Y'])
print("Test de Normalidad (D'Agostino-Pearson):")
print("=" * 70)
print("\nVariable BMI:")
print(f" Estadistico: {estadistico_bmi:.4f}")
print(f" P-valor: {pvalor_bmi:.6f}")
if pvalor_bmi < 0.05:
print(" Resultado: Se rechaza normalidad (alpha=0.05)")
else:
print(" Resultado: No se rechaza normalidad (alpha=0.05)")
print("\nVariable Y (Progresion):")
print(f" Estadistico: {estadistico_y:.4f}")
print(f" P-valor: {pvalor_y:.6f}")
if pvalor_y < 0.05:
print(" Resultado: Se rechaza normalidad (alpha=0.05)")
else:
print(" Resultado: No se rechaza normalidad (alpha=0.05)")
Test de Normalidad (D'Agostino-Pearson): ====================================================================== Variable BMI: Estadistico: 23.6656 P-valor: 0.000007 Resultado: Se rechaza normalidad (alpha=0.05) Variable Y (Progresion): Estadistico: 64.1509 P-valor: 0.000000 Resultado: Se rechaza normalidad (alpha=0.05)
Visualizacion de Datos con Matplotlib¶
Algo que aprendi con la experiencia: los numeros solos no convencen a nadie. Una tabla con promedios y desviaciones no genera el aha! que produce un buen grafico. Recuerdo una vez que presente resultados solo con tablas y la gente se perdio a los dos minutos. Desde entonces, siempre grafico primero.
Matplotlib no es la libreria mas bonita por defecto, hay que reconocerlo. Pero tiene una ventaja enorme: el control. Cada eje, cada etiqueta, cada color se puede personalizar. Yim et al. (2018) subrayan ademas que soporta multiples formatos de exportacion, lo cual la hace practica cuando toca incluir graficos en informes o presentaciones (p. 10).
# ==============================================================================
# HISTOGRAMA DE BMI
# ==============================================================================
# Un histograma responde la pregunta: como se reparten los valores?
# Marcar la media y mediana encima ayuda a ver si hay sesgo.
plt.figure(figsize=(10, 6))
# 30 barras, un poco transparentes para que se vean bien.
plt.hist(datos['BMI'], bins=30, color='steelblue', edgecolor='white', alpha=0.7)
# Lineas verticales: media (roja) y mediana (verde).
media_bmi = np.mean(datos['BMI'])
mediana_bmi = np.median(datos['BMI'])
plt.axvline(media_bmi, color='red', linestyle='--', linewidth=2,
label=f'Media: {media_bmi:.2f}')
plt.axvline(mediana_bmi, color='green', linestyle='-', linewidth=2,
label=f'Mediana: {mediana_bmi:.2f}')
plt.xlabel('Indice de Masa Corporal (BMI)', fontsize=12)
plt.ylabel('Frecuencia', fontsize=12)
plt.title('Distribucion del Indice de Masa Corporal', fontsize=14, fontweight='bold')
plt.legend()
plt.tight_layout()
plt.show()
# ==============================================================================
# GRAFICO DE DISPERSION (SCATTER PLOT)
# ==============================================================================
# Cada punto es un paciente. La linea roja es la tendencia.
# Si los puntos se alinean, hay relacion. Si es una nube, no.
plt.figure(figsize=(10, 6))
plt.scatter(datos['BMI'], datos['Y'], c='steelblue', alpha=0.6, s=50, edgecolors='white')
# Ajusto una recta con polyfit (grado 1 = lineal).
coeficientes = np.polyfit(datos['BMI'], datos['Y'], 1)
linea_tendencia = np.poly1d(coeficientes)
x_linea = np.linspace(datos['BMI'].min(), datos['BMI'].max(), 100)
plt.plot(x_linea, linea_tendencia(x_linea), color='red', linewidth=2,
linestyle='--', label=f'Tendencia (r={correlacion_bmi_y:.3f})')
plt.xlabel('Indice de Masa Corporal (BMI)', fontsize=12)
plt.ylabel('Progresion de la Diabetes (Y)', fontsize=12)
plt.title('Relacion entre BMI y Progresion de Diabetes', fontsize=14, fontweight='bold')
plt.legend()
plt.tight_layout()
plt.show()
# ==============================================================================
# MULTIPLES SUBGRAFICOS
# ==============================================================================
# Poner cuatro histogramas juntos permite comparar de un vistazo.
# A veces un solo grafico no cuenta toda la historia.
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
variables = ['AGE', 'BMI', 'BP', 'Y']
colores = ['#3498db', '#2ecc71', '#e74c3c', '#9b59b6']
for idx, (var, color) in enumerate(zip(variables, colores)):
fila = idx // 2
columna = idx % 2
ax = axes[fila, columna]
ax.hist(datos[var], bins=25, color=color, edgecolor='white', alpha=0.7)
ax.set_title(f'Distribucion de {var}', fontsize=12, fontweight='bold')
ax.set_xlabel(var)
ax.set_ylabel('Frecuencia')
plt.suptitle('Distribucion de Variables del Dataset Diabetes',
fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
Visualizacion Avanzada con Seaborn¶
Si Matplotlib es el bisturi del cirujano, Seaborn es la navaja suiza del explorador. No te da el mismo control milimetrico, pero en tres lineas producis un grafico que con Matplotlib te llevaria quince. Para exploracion rapida, donde lo que quiero es ver los datos antes de decidir que hacer con ellos, Seaborn gana por goleada.
Hay algo que Fuentes (2018) menciona y me parece clave: el analisis exploratorio no se trata solo de hacer graficos lindos, sino de aplicar tecnicas de agrupacion y descripcion estadistica para encontrar patrones que a simple vista no se ven (p. 65). Seaborn facilita exactamente eso.
# ==============================================================================
# MAPA DE CALOR DE CORRELACIONES
# ==============================================================================
# Este grafico me encanta. De un vistazo revela que variables
# se llevan bien entre si y cuales no.
plt.figure(figsize=(12, 10))
# corr() calcula correlacion Pearson entre todas las columnas.
matriz_correlacion = datos.corr()
# El heatmap pone color a los numeros. Mucho mas legible.
sns.heatmap(matriz_correlacion,
annot=True, # Muestra el numero en cada celda
cmap='RdYlBu_r', # Rojo = positivo, azul = negativo
center=0, # Blanco en el cero
fmt='.2f', # Dos decimales
linewidths=0.5,
square=True,
cbar_kws={'label': 'Coeficiente de Correlacion'})
plt.title('Matriz de Correlacion - Dataset Diabetes', fontsize=14, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()
print("Guia rapida de lectura:")
print(" Rojo intenso = correlacion positiva fuerte")
print(" Azul intenso = correlacion negativa fuerte")
print(" Blanco/palido = casi no hay relacion")
Guia rapida de lectura: Rojo intenso = correlacion positiva fuerte Azul intenso = correlacion negativa fuerte Blanco/palido = casi no hay relacion
# ==============================================================================
# BOXPLOTS PARA DETECCION DE OUTLIERS
# ==============================================================================
# El boxplot es mi preferido para encontrar datos raros.
# Si un punto sale de los bigotes, merece atencion.
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
variables_analisis = ['AGE', 'BMI', 'BP', 'S1', 'S5', 'Y']
axes_flat = axes.flatten()
for idx, var in enumerate(variables_analisis):
sns.boxplot(y=datos[var], ax=axes_flat[idx], color='steelblue', width=0.5)
axes_flat[idx].set_title(f'{var}', fontsize=12, fontweight='bold')
axes_flat[idx].set_ylabel('')
plt.suptitle('Boxplots para Deteccion de Valores Atipicos',
fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
# Para quien no haya visto un boxplot antes:
print("Como leer esto:")
print(" Linea central = mediana")
print(" Caja = 50% central de los datos (entre Q1 y Q3)")
print(" Bigotes = hasta 1.5 veces el IQR")
print(" Puntos sueltos = outliers, datos sospechosos")
Como leer esto: Linea central = mediana Caja = 50% central de los datos (entre Q1 y Q3) Bigotes = hasta 1.5 veces el IQR Puntos sueltos = outliers, datos sospechosos
# ==============================================================================
# GRAFICOS AVANZADOS: KDE, VIOLIN Y REGRESION
# ==============================================================================
# Tres formas distintas de mirar los mismos datos.
# Cada una revela algo diferente.
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# KDE: como un histograma suavizado. Muestra la 'forma' de los datos.
sns.histplot(datos['BMI'], kde=True, ax=axes[0], color='steelblue', bins=30)
axes[0].set_title('Histograma con KDE de BMI', fontweight='bold')
# Violin: mezcla de boxplot + densidad. Bonito e informativo.
sns.violinplot(y=datos['Y'], ax=axes[1], color='lightgreen')
axes[1].set_title('Violin Plot de Y (Progresion)', fontweight='bold')
# Regresion: la relacion entre S5 y Y con banda de confianza.
sns.regplot(x=datos['S5'], y=datos['Y'], ax=axes[2], color='coral',
scatter_kws={'alpha':0.5}, line_kws={'color':'darkred', 'linewidth':2})
axes[2].set_title('Regresion: S5 vs Y', fontweight='bold')
axes[2].set_xlabel('S5 (Log Trigliceridos)')
axes[2].set_ylabel('Y (Progresion)')
plt.tight_layout()
plt.show()
# ==============================================================================
# PAIRPLOT
# ==============================================================================
# El pairplot es la artilleria pesada: cruza todas las variables
# entre si. Con muchas columnas se pone lento, asi que selecciono
# solo las que me interesan mas.
variables_seleccionadas = ['AGE', 'BMI', 'BP', 'S5', 'Y']
datos_subset = datos[variables_seleccionadas]
print("Generando pairplot... (esto puede tardar unos segundos)")
pairplot = sns.pairplot(datos_subset,
diag_kind='kde', # Densidad en la diagonal
plot_kws={'alpha': 0.5, 's': 30, 'edgecolor': 'white'},
corner=True) # Solo la mitad, evita redundancia
pairplot.figure.suptitle('Relaciones entre Variables Principales',
y=1.02, fontsize=14, fontweight='bold')
plt.show()
Generando pairplot... (esto puede tardar unos segundos)
Conclusiones¶
Termino este notebook con mas preguntas que certezas. Y creo que eso es buena señal.
NumPy y SciPy me confirmaron algo que ya sospechaba: Python es absurdamente eficiente para calculos estadisticos. Lo que en otros lenguajes me llevaria paginas de codigo, aqui se resuelve en dos lineas. Pero no quiero idealizar la experiencia. Tambien me tope con detalles que no entendi a la primera, como el parametro ddof=1 en la desviacion estandar. Tuve que investigar por separado que era la correccion de Bessel y por que importa. Esos huecos de conocimiento no aparecen en los tutoriales, y llenarlos lleva tiempo.
Sobre Matplotlib y Seaborn, mi conclusion es pragmatica: no son rivales, son aliadas. Seaborn para explorar. Matplotlib para pulir. Intente hacer todo con una sola y no funciono. Cada herramienta tiene su momento.
Lo de Polars fue un experimento que valio la pena, aunque tengo reservas. Con 442 registros no note diferencia de velocidad, y la sintaxis, aunque elegante, me obligo a repensar como escribo consultas. No estoy seguro de que vaya a reemplazar a Pandas en mi flujo diario todavia, pero lo tengo en el radar. Para datasets de millones de filas, probablemente sea la mejor opcion disponible hoy.
Del analisis del dataset me quedo con una sorpresa genuina: la variable S5 (trigliceridos) tiene la correlacion mas fuerte con la progresion de la diabetes. Mas que el BMI. Mas que la edad. Yo hubiera apostado por el BMI, y habria perdido. Eso me recordo una leccion que deberia tatuarme: en ciencia de datos, la intuicion es un punto de partida, nunca una conclusion. Los datos mandan.
En definitiva, este ejercicio me dejo claro que sin dominar estas herramientas de base, todo lo que venga despues en inteligencia artificial sera construir sobre arena. Prefiero ir despacio y pisar firme.
Referencias Bibliograficas¶
Recursos Basicos del Curso¶
Boschetti, A. y Massaron, L. (2018). Python Data Science Essentials: A Practitioner's Guide Covering Essential Data Science Principles, Tools, and Techniques (3a ed., pp. 281-331). Packt Publishing.
Yim, A., Chung, C. y Yu, A. (2018). Matplotlib for Python Developers: Effective Techniques for Data Visualization with Python (2a ed., pp. 21-54). Packt Publishing.
Recursos Complementarios del Curso¶
Fuentes, A. (2018). Become a Python Data Analyst: Perform Exploratory Data Analysis and Gain Insight into Scientific Computing Using Python (pp. 69-118). Packt Publishing.
Mehta, H. (2015). Mastering Python Scientific Computing (pp. 163-198). Packt Publishing.
Fuentes Adicionales¶
Boos, D. (s.f.). Diabetes Dataset. North Carolina State University. https://www4.stat.ncsu.edu/~boos/var.select/diabetes.html
Polars Contributors. (2024). Polars: Blazingly Fast DataFrames in Rust and Python. https://pola.rs/