Pandas y visualización: explorar un dataset real

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

Cuaderno del tema. La teoría y los ejercicios con todo su código, listos para ejecutar en Colab sin instalar nada.

Los 17 bloques de código de la teoría, en el mismo orden. Si prefieres armar tu propio cuaderno, cópialos y córrelos en orden; 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
# Solo para dibujar:
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))

# Solo para dibujar: una paleta propia y etiquetas cortas
# (\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()