36.1 Método más general

La función FuncAnimation() del módulo matplotlib.animation genera una película del conjunto de gráficas que se desean animar.

La siguiente idea pertenece al curso de Machine Learning que enseñé junto con Jose Fernando Zea en el año 2021.

import pandas as pd
import numpy as np
datos = pd.read_csv("E:/BD Varias/houses_portland.csv",
    sep=",",
    header=0,
    na_values=["", "NA"],
    dtype=None,
    encoding="utf-8")
datos["area_st"]  = (datos["area"]  - datos["area"].mean())  / datos["area"].std(ddof=1)
datos["price_st"] = (datos["price"] - datos["price"].mean()) / datos["price"].std(ddof=1)
datos.head()
##    area  bedroom   price   area_st  price_st
## 0  2104        3  399900  0.130010  0.475747
## 1  1600        3  329900 -0.504190 -0.084074
## 2  2400        3  369000  0.502476  0.228626
## 3  1416        2  232000 -0.735723 -0.867025
## 4  3000        4  539900  1.257476  1.595389
x = datos["area_st"].to_numpy(dtype=float)
y = datos["price_st"].to_numpy(dtype=float)
def gduniv(beta_inicial, alpha, tolerancia=1e-16, max_iter=10_000_000):
    beta_prev = np.asarray(beta_inicial, dtype=float)  # [b0, b1]
    betas = [beta_prev.copy()]
    # primer paso
    r = y - beta_prev[0] - beta_prev[1] * x
    beta_i = beta_prev + 2 * alpha * np.array([r.mean(), (x * r).mean()])
    iteracion = 1
    while np.abs(np.sum((beta_i - beta_prev) * (beta_i - beta_prev))) > tolerancia and iteracion < max_iter:
        beta_prev = beta_i
        r = y - beta_prev[0] - beta_prev[1] * x
        beta_i = beta_prev + 2 * alpha * np.array([r.mean(), (x * r).mean()])
        iteracion += 1
        betas.append(beta_i.copy())

    return {"beta_i": beta_i, "iter": iteracion, "betas": betas}
resumen_gradDesc = gduniv(beta_inicial=[-10, 10], alpha=0.001)
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter

x = datos["area_st"].to_numpy(float)
y = datos["price_st"].to_numpy(float)
betas = resumen_gradDesc["betas"]  # lista de arrays [b0, b1]
iters = int(resumen_gradDesc["iter"])

corrector = 60
step = int(round(iters / corrector, 0))
step = max(step, 1)
# índices de betas a graficar (i*corrector - 1), cuidando no exceder longitud
frames_idx = []
for i in range(1, step):
    idx = i * corrector - 1
    if idx < len(betas):
        frames_idx.append(idx)
# si por alguna razón no queda ninguno, asegura al menos el último
if not frames_idx:
    frames_idx = [len(betas) - 1]

# --- Preparar figura y elementos gráficos ---
fig, ax = plt.subplots()
_ = ax.scatter(x, y, s=20)  # puntos de datos
_ = ax.set_xlabel("Área del predio (estandarizada)",
    fontsize=10, labelpad=8)
_ = ax.set_ylabel("Precio del predio (estandarizado)",
    fontsize=10, labelpad=8)
# límites para que el blit sea estable
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
# Margen visual
xm = 0.05 * (xmax - xmin if xmax > xmin else 1.0)
ym = 0.05 * (ymax - ymin if ymax > ymin else 1.0)
_ = ax.set_xlim(xmin - xm, xmax + xm)
_ = ax.set_ylim(ymin - ym, ymax + ym)

# Línea a animar (sin colores específicos, usa el default)
(linea,) = ax.plot([], [], linewidth=2)
# Eje x para dibujar cada recta
xs = np.array([xmin, xmax], dtype=float)

def init():
    linea.set_data([], [])
    return (linea,)

def update(frame_idx):
    b0, b1 = betas[frame_idx][0], betas[frame_idx][1]
    ys = b0 + b1 * xs
    linea.set_data(xs, ys)
    ax.set_title(f"Descenso por gradiente: iter = {frame_idx + 1}")
    ax.set_title(f"Evolución de la recta por el método de\n"
    f"gradiente descendente: iteración = {frame_idx + 1}",
    fontsize=11, pad=8)
    return (linea,)
ani = FuncAnimation(fig,
    update,
    frames=frames_idx,
    init_func=init,
    interval=120, # milisegundos
    blit=True,
    repeat=False)
ani.save("grad_desc_anim.gif", writer=PillowWriter(fps=8))
plt.close(fig)