Pandas y visualización: explorar un dataset real

Tema 4 · Fase 1, Programar con datos · Toda la ruta

Los 17 bloques de código de la teoría, en el mismo orden. Cópialos y córrelos en tu Python o en Colab; en la ficha Teoría está la explicación de cada uno.

De arreglos a tablas: Series y DataFrame

import numpy as np
import pandas as pd

# Un DataFrame se puede armar con un diccionario: una lista por columna
curso = pd.DataFrame({
    "nombre": ["Ana", "Luis", "Sara", "Iván", "Rosa"],
    "edad": [15, 16, 15, 17, 16],
    "ciudad": ["La Paz", "Sucre", "Oruro", "La Paz", "Tarija"],
    "nota": [78, 65, 91, 70, 84],
})
print(curso)
print()

# Una columna es una Series: un arreglo con nombre e índice
notas = curso["nota"]
print(type(notas).__name__, notas.shape)
print(notas.to_numpy())
print("Promedio:", notas.mean())

Cargar el dataset y mirarlo

import seaborn as sns

# Descarga el CSV la primera vez: necesita internet
pinguinos = sns.load_dataset("penguins")

pinguinos = pinguinos.rename(columns={
    "species": "especie",
    "island": "isla",
    "bill_length_mm": "pico_largo_mm",
    "bill_depth_mm": "pico_alto_mm",
    "flipper_length_mm": "aleta_mm",
    "body_mass_g": "masa_g",
    "sex": "sexo",
})
# map cambia cada valor según el diccionario
pinguinos["sexo"] = pinguinos["sexo"].map(
    {"Male": "macho", "Female": "hembra"})

print(pinguinos.shape)
print(pinguinos.head())
pinguinos.info()
print(pinguinos.describe().round(1))
print()
print(pinguinos["especie"].value_counts())

Elegir filas y columnas

print(pinguinos.loc[0, "especie"])
print(pinguinos.loc[0:2, ["especie", "masa_g"]])
print(pinguinos.iloc[0:2, 0:3])

# Filtrar con una condición
pesados = pinguinos[pinguinos["masa_g"] > 5500]
print(len(pesados), "pingüinos pesan más de 5,5 kg:")
print(pesados["especie"].value_counts())

# Varias condiciones: & (y), | (o), cada una entre paréntesis
dream = pinguinos[(pinguinos["isla"] == "Dream")
                  & (pinguinos["aleta_mm"] > 200)]
print(len(dream), "pingüinos de Dream con aleta de más de 200 mm")

Valores faltantes

print(pinguinos.isna().sum())

medidas = ["pico_largo_mm", "pico_alto_mm", "aleta_mm", "masa_g"]
# any(axis=1): True en las filas con al menos un faltante
sin_medidas = pinguinos[pinguinos[medidas].isna().any(axis=1)]
print(sin_medidas[["especie", "isla"] + medidas])
pinguinos = pinguinos.dropna(subset=medidas)
print(pinguinos.shape)
print("Sin sexo anotado:", pinguinos["sexo"].isna().sum())

Agrupar y resumir: groupby

por_especie = pinguinos.groupby("especie")
print(por_especie["masa_g"].mean().round(0))
print()
# agg aplica varios resúmenes a la vez
print(por_especie[["aleta_mm", "masa_g"]].agg(["mean", "std"]).round(1))
print()
# Cuántos pingüinos hay de cada especie en cada isla
print(pinguinos.groupby(["especie", "isla"]).size())

Unir tablas: merge

especies = pd.DataFrame({
    "especie": ["Adelie", "Chinstrap", "Gentoo"],
    "nombre_comun": ["pingüino de Adelia", "pingüino barbijo",
                     "pingüino papúa"],
    "cientifico": ["Pygoscelis adeliae", "Pygoscelis antarcticus",
                   "Pygoscelis papua"],
})

# how="left": quedan todas las filas de la tabla de la izquierda
completa = pinguinos.merge(especies, on="especie", how="left")
print(completa.shape)
print(completa[["especie", "nombre_comun", "masa_g"]]
      .sample(4, random_state=0))

Graficar: la figura y los ejes

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.hist(pinguinos["aleta_mm"], bins=25, color="C0")
ax.set_xlabel("Largo de la aleta (mm)")
ax.set_ylabel("Cantidad de pingüinos")
ax.set_title("Las aletas de 342 pingüinos")
plt.show()
fig, ejes = plt.subplots(3, 1, figsize=(4.8, 4.8), sharex=True,
                         sharey=True)
intervalos = range(170, 236, 3)    # los mismos en los tres
for ax, (especie, grupo) in zip(ejes, pinguinos.groupby("especie")):
    color = "C1" if especie == "Gentoo" else "C0"
    ax.hist(grupo["aleta_mm"], bins=intervalos, color=color)
    ax.text(0.02, 0.75, especie, transform=ax.transAxes)
ejes[-1].set_xlabel("Largo de la aleta (mm)")
fig.supylabel("Cantidad de pingüinos", fontsize=15)
plt.show()

Dispersión: la relación entre dos medidas

colores = {"Adelie": "C0", "Chinstrap": "C2", "Gentoo": "C1"}
ax = sns.scatterplot(data=pinguinos, x="pico_largo_mm",
                     y="pico_alto_mm", hue="especie",
                     style="especie", palette=colores)
ax.set_xlabel("Largo del pico (mm)")
ax.set_ylabel("Alto del pico (mm)")
ax.set_title("Cada especie forma su propia nube")
# Un poco de espacio arriba para la leyenda, en una sola fila
ax.set_ylim(13, 24)
ax.legend(title=None, fontsize=13, ncols=3, loc="upper left",
          columnspacing=0.8, handletextpad=0.1)
plt.show()

Diagrama de caja y valores atípicos

def atipicos(serie):
    """True donde el valor cae fuera de los bigotes (regla 1,5·RIC)."""
    q1, q3 = serie.quantile(0.25), serie.quantile(0.75)
    ric = q3 - q1
    return (serie < q1 - 1.5 * ric) | (serie > q3 + 1.5 * ric)

print("Atípicos con todos juntos:", atipicos(pinguinos["masa_g"]).sum())
# transform aplica la función a cada especie por separado
es_atipico = pinguinos.groupby("especie")["masa_g"].transform(atipicos)
print(pinguinos.loc[es_atipico, ["especie", "isla", "masa_g", "sexo"]])

orden = ["Adelie", "Chinstrap", "Gentoo"]
ax = sns.boxplot(data=pinguinos, x="especie", y="masa_g", order=orden,
                 color="C3", linecolor="C0", saturation=1,
                 showfliers=False)
# Los atípicos, dibujados aparte para resaltarlos
sns.stripplot(data=pinguinos[es_atipico], x="especie", y="masa_g",
              order=orden, color="C1", size=9, jitter=False, ax=ax)
ax.set_xlabel("")
ax.set_ylabel("Masa (g)")
ax.set_title("Masa por especie")
plt.show()

Correlación y mapa de calor

from matplotlib.colors import LinearSegmentedColormap

corr = pinguinos[medidas].corr()
print(corr.round(2))

# \n parte la etiqueta en dos líneas
nombres = ["Pico\nlargo", "Pico\nalto", "Aleta", "Masa"]
blanco_tinta = LinearSegmentedColormap.from_list("bt", ["white", "C0"])
fig, ax = plt.subplots(figsize=(4.8, 4.2))
sns.heatmap(corr.abs(), annot=corr, fmt=".2f", cmap=blanco_tinta,
            vmin=0, vmax=1, square=True, cbar=False,
            xticklabels=nombres, yticklabels=nombres, ax=ax)
ax.tick_params(axis="both", labelrotation=0)
ax.set_title("Correlación entre las medidas")
plt.show()

Barras y líneas

conteo = pd.crosstab(pinguinos["isla"], pinguinos["especie"])
print(conteo)

ax = conteo.plot.bar(stacked=True, color=["C0", "C2", "C1"], rot=0)
ax.set_xlabel("Isla")
ax.set_ylabel("Cantidad de pingüinos")
ax.set_title("¿Quién vive en cada isla?")
ax.legend(title=None, fontsize=13)
plt.show()
vuelos = sns.load_dataset("flights")
print(vuelos.head(3))

# "1949" + "-" + "Jan" → fecha; %Y es el año y %b, el mes abreviado
texto = vuelos["year"].astype(str) + "-" + vuelos["month"].astype(str)
vuelos["fecha"] = pd.to_datetime(texto, format="%Y-%b")
print(vuelos["fecha"].dtype)

mayor = vuelos.loc[vuelos["passengers"].idxmax()]
print("Mes con más pasajeros:", mayor["fecha"].strftime("%m/%Y"),
      "con", mayor["passengers"], "mil")

fig, ax = plt.subplots()
ax.plot(vuelos["fecha"], vuelos["passengers"], color="C0")
ax.scatter(mayor["fecha"], mayor["passengers"], color="C1", s=60,
           zorder=3)
ax.set_ylabel("Pasajeros (miles)")
ax.set_title("Pasajeros de vuelos por mes")
plt.show()

El cuarteto de Anscombe

anscombe = sns.load_dataset("anscombe")
resumen = anscombe.groupby("dataset").agg(
    media_x=("x", "mean"), media_y=("y", "mean"),
    desv_x=("x", "std"), desv_y=("y", "std"))
resumen["correlacion"] = [g["x"].corr(g["y"])
                          for _, g in anscombe.groupby("dataset")]
print(resumen.round(2))

fig, ejes = plt.subplots(2, 2, figsize=(4.8, 4.8), sharex=True,
                         sharey=True)
x_recta = np.array([3, 20])
for ax, (nombre, g) in zip(ejes.flat, anscombe.groupby("dataset")):
    ax.scatter(g["x"], g["y"], color="C0", s=30)
    # La recta que mejor se ajusta (la vas a calcular en el tema 6)
    pendiente, corte = np.polyfit(g["x"], g["y"], 1)
    ax.plot(x_recta, pendiente * x_recta + corte, color="C2",
            linewidth=1.6)
    ax.set_title(nombre)
plt.show()