Solución pingüinos

"""
╔══════════════════════════════════════════════════════════════════╗
║       EJERCICIO: ¿QUÉ VARIABLES DE LOS PINGÜINOS SE             ║
║                  RELACIONAN MÁS ENTRE SÍ?                       ║
╚══════════════════════════════════════════════════════════════════╝

Dataset: Palmer Penguins  🐧
  Contiene medidas físicas de 344 pingüinos de 3 especies distintas
  recogidas en las islas Palmer (Antártida).

  Variables numéricas disponibles:
    • bill_length_mm    → longitud del pico (mm)
    • bill_depth_mm     → profundidad del pico (mm)
    • flipper_length_mm → longitud de la aleta (mm)
    • body_mass_g       → masa corporal (gramos)

  Variable categórica:
    • species           → especie (Adelie, Chinstrap, Gentoo)

Objetivo del ejercicio:
  Descubrir qué par de variables numéricas tiene la correlación
  más fuerte (positiva o negativa) usando pandas y stats.

Librerías necesarias:
  pip install pandas matplotlib seaborn

Estructura del ejercicio:
  PASO 0 — Cargar los datos
  PASO 1 — Explorar el dataset
  PASO 2 — Limpiar valores nulos
  PASO 3 — Calcular la correlación entre TODAS las variables
  PASO 4 — Encontrar el par con mayor correlación

  ★ RETO EXTRA — Escribe tus propias conclusiones
"""

# pandas: manejo de tablas de datos (DataFrames)
import pandas as pd

# seaborn: visualización estadística; también incluye datasets de ejemplo
import seaborn as sns

# numpy: operaciones numéricas con arrays (importado por convención,
# aunque en este script no se usa directamente)
import numpy as np

# stats de scipy: funciones estadísticas avanzadas;
# aquí la usamos para calcular la correlación de Pearson con su valor p
from scipy import stats



# ===========================================================================
# PASO 0 — CARGAR LOS DATOS
# ===========================================================================
# seaborn incluye el dataset de pingüinos de forma gratuita.
# load_dataset() lo descarga automáticamente la primera vez.

print("\n" + "="*60)
print("  PASO 0 — CARGAR LOS DATOS")
print("="*60)

# Carga el dataset "penguins" directamente desde seaborn.
# El resultado es un DataFrame de pandas con 344 filas y 7 columnas.
pinguinos = sns.load_dataset("penguins")

# Confirmamos cuántas filas y columnas tiene el DataFrame
# .shape devuelve una tupla (filas, columnas)
print(f"\n  Dataset cargado con {pinguinos.shape[0]} filas y {pinguinos.shape[1]} columnas.")
print(f"  Columnas: {pinguinos.columns.tolist()}")


# ===========================================================================
# PASO 1 — EXPLORAR EL DATASET
# ===========================================================================
# Antes de cualquier análisis, siempre hay que entender qué contiene
# la tabla. Usa .head(), .info() y .describe() para hacerte una idea.

print("\n" + "="*60)
print("  PASO 1 — EXPLORAR EL DATASET")
print("="*60)

# .head() muestra las 5 primeras filas del DataFrame.
# Muy útil para ver rápidamente la estructura y los valores reales.
print("\n── Primeras 5 filas (.head()) ──")
print(pinguinos.head())

# .info() muestra un resumen técnico: nombre de columna, tipo de dato
# (int, float, object…) y cuántos valores NO nulos tiene cada columna.
# Si una columna tiene menos valores que el total de filas, hay nulos.
print("\n── Información general (.info()) ──")
pinguinos.info()

# .describe() calcula automáticamente estadísticas básicas para cada
# columna numérica: media, desviación típica, mínimo, máximo y cuartiles.
# .round(1) redondea a 1 decimal para que sea más legible.
print("\n── Estadísticas descriptivas (.describe()) ──")
print(pinguinos.describe().round(1))

# .value_counts() cuenta cuántas veces aparece cada valor único en la columna.
# Aquí lo usamos para saber cuántos pingüinos hay de cada especie.
print("\n── Pingüinos por especie (.value_counts()) ──")
print(pinguinos["species"].value_counts())


# ===========================================================================
# PASO 2 — LIMPIAR VALORES NULOS
# ===========================================================================
# Algunos pingüinos tienen medidas incompletas (NaN).
# La correlación no funciona bien con valores nulos, así que los eliminamos.

print("\n" + "="*60)
print("  PASO 2 — LIMPIAR VALORES NULOS")
print("="*60)

# Eliminamos la columna "sex" porque no la vamos a usar en el análisis
# y además contiene nulos que nos complicarían el recuento.
# inplace=True modifica el DataFrame original sin necesidad de reasignarlo.
pinguinos.drop(columns=["sex"], inplace=True)

# .isnull() devuelve un DataFrame de True/False indicando dónde hay nulos.
# .sum() suma los True de cada columna (True = 1, False = 0),
# dando el total de nulos por columna.
print("\n── ¿Cuántos nulos hay en cada columna? ──")
print(pinguinos.isnull().sum())

# .dropna() elimina todas las filas que tengan AL MENOS un valor nulo
# en cualquier columna. Devuelve un nuevo DataFrame sin modificar el original.
pinguinos_limpio = pinguinos.dropna()

# Mostramos cuántas filas hemos perdido en la limpieza
print(f"\n  Filas antes de limpiar : {len(pinguinos)}")
print(f"  Filas después de limpiar: {len(pinguinos_limpio)}")
print(f"  Filas eliminadas        : {len(pinguinos) - len(pinguinos_limpio)}")


# ===========================================================================
# PASO 3 — CALCULAR LA CORRELACIÓN ENTRE LAS VARIABLES
# ===========================================================================
# El coeficiente de Pearson (r) mide la fuerza de la relación lineal
# entre dos variables numéricas. Siempre está entre -1 y +1:
#   r =  1.0  → correlación perfecta positiva  (cuando X sube, Y sube)
#   r = -1.0  → correlación perfecta negativa  (cuando X sube, Y baja)
#   r =  0.0  → sin correlación lineal
#   |r| > 0.7 se considera correlación FUERTE

print("\n" + "="*60)
print("  PASO 3 — MATRIZ DE CORRELACIÓN")
print("="*60)

# Usamos un alias corto para no repetir "pinguinos_limpio" en cada línea
df = pinguinos_limpio

# stats.pearsonr(x, y) devuelve dos valores:
#   r → el coeficiente de correlación de Pearson (de -1 a +1)
#   p → el valor p (p-value): probabilidad de obtener esta correlación
#       por puro azar. Si p < 0.05, la correlación es estadísticamente
#       significativa (muy poco probable que sea casualidad).

# Correlación entre masa corporal y longitud del pico
r1, p1 = stats.pearsonr(df["body_mass_g"], df["bill_length_mm"])
print(r1, p1)

# Correlación entre masa corporal y profundidad del pico
r2, p2 = stats.pearsonr(df["body_mass_g"], df["bill_depth_mm"])
print(r2, p2)

# Correlación entre masa corporal y longitud de la aleta
r3, p3 = stats.pearsonr(df["body_mass_g"], df["flipper_length_mm"])
print(r3, p3)

# --- PARA EXPERTOS: calcular TODAS las combinaciones posibles ---
# En lugar de comparar solo contra body_mass_g, calculamos el coeficiente
# entre CADA par posible de variables numéricas.

columnas_numericas = ["bill_length_mm", "bill_depth_mm",
                      "flipper_length_mm", "body_mass_g"]

# Lista donde iremos acumulando los resultados de cada par
correlaciones = []

# Doble bucle anidado para generar todos los pares sin repetir.
# El truco está en que j empieza en i+1:
#   cuando i=0 → j recorre 1, 2, 3   (3 pares con bill_length_mm)
#   cuando i=1 → j recorre 2, 3      (2 pares con bill_depth_mm)
#   cuando i=2 → j recorre 3         (1 par  con flipper_length_mm)
# Total: 6 pares únicos sin duplicados ni la diagonal (variable consigo misma)
for i in range(len(columnas_numericas)):
    for j in range(i + 1, len(columnas_numericas)):
        var1 = columnas_numericas[i]
        var2 = columnas_numericas[j]

        # Calculamos r y p para este par concreto
        r, p = stats.pearsonr(df[var1], df[var2])

        # Guardamos la tupla (variable1, variable2, r redondeado, p redondeado)
        # float() convierte los valores numpy a float nativo de Python
        correlaciones.append((var1, var2, float(round(r, 2)), float(round(p, 2))))


# ===========================================================================
# PASO 4 — ENCONTRAR EL PAR CON MAYOR CORRELACIÓN
# ===========================================================================
# Buscamos el par de variables con |r| más alto (en valor absoluto,
# para tratar igual las correlaciones positivas y las negativas).

print("\n" + "="*60)
print("  PASO 4 — PAR CON MAYOR CORRELACIÓN")
print("="*60)

# Ordenamos la lista de menor a mayor según |r| (valor absoluto de r).
# key=lambda x: abs(x[2]) indica que el criterio de orden es el
# tercer elemento de cada tupla (el coeficiente r), tomado en valor absoluto.
# Con esto el último elemento de la lista será el par más correlacionado.
correlaciones.sort(key=lambda x: abs(x[2]))

# Mostramos todos los pares ordenados de menor a mayor correlación
for var1, var2, corr, p in correlaciones:
    print(var1, var2, corr, p)


# ===========================================================================
# ★ RETO EXTRA — PREGUNTAS PARA REFLEXIONAR
# ===========================================================================

print("\n" + "="*60)
print("  ★ RETO EXTRA — RESPONDE ESTAS PREGUNTAS")
print("="*60)
print("""
  Completa estas frases con lo que has descubierto:

  1. El par de variables con MAYOR correlación es:
     __________ y __________ con r = _______

  2. El par de variables con MENOR correlación es:
     __________ y __________ con r = _______

  3. ¿La correlación más alta es positiva o negativa?
     _______________________________________________________

  4. ¿Qué significa en la práctica que esas dos variables
     tengan una correlación tan alta?
     _______________________________________________________

  5. ¿La correlación cambia mucho entre especies?
     ¿Qué especie tiene la correlación más diferente al global?
     _______________________________________________________

  6. Correlación NO implica causalidad. ¿Puedes pensar en una
     razón biológica que explique la relación encontrada?
     _______________________________________________________
""")


Publicado por

Juan Pablo Fuentes

Formador de programación y bases de datos