NumPy: pensar en arreglos
Cuaderno del tema. La teoría y los ejercicios con todo su código, listos para ejecutar en Colab sin instalar nada.
Los 20 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 la lista al arreglo
import numpy as np
lista = [1, 2, 3, 4]
arreglo = np.array([1, 2, 3, 4])
print(lista * 2) # la lista repetida dos veces
print(arreglo * 2) # cada número multiplicado por 2
print(arreglo + 10)
print(arreglo ** 2)materias = ["Matemática", "Física", "Lenguaje", "Biología"]
# Notas sobre 100: una fila por estudiante, una columna por materia
notas = np.array([
[72, 65, 80, 58],
[45, 50, 62, 70],
[88, 92, 75, 81],
[60, 48, 55, 66],
[95, 85, 90, 88],
[38, 52, 47, 61],
])
print("shape:", notas.shape) # (filas, columnas)
print("ndim: ", notas.ndim) # cuántas dimensiones tiene
print("size: ", notas.size) # cuántos números hay en total
print("dtype:", notas.dtype) # de qué tipo es cada número
print("En décimos:", (notas / 10).dtype)print(np.zeros((2, 3))) # una tabla de ceros de 2 × 3
print(np.ones(4)) # cuatro unos
print(np.arange(0, 10, 2)) # como range, pero da un arreglo
print(np.linspace(0, 1, 5)) # 5 números parejos de 0 a 1
# Números al azar, con semilla fija para que salgan siempre iguales
rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=10)) # diez dados
# Números al azar alrededor de 0, casi todos entre -2 y 2: la
# «campana» de la distribución normal, que verás en el tema 5
print(rng.normal(0, 1, size=(2, 3)).round(2))Indexar, rebanar y filtrar
print(notas[0]) # la fila 0: el primer estudiante
print(notas[0, 2]) # fila 0, columna 2: su nota de Lenguaje
print(notas[-1]) # la última fila
print(notas[:, 0]) # todas las filas, columna 0: Matemática
print(notas[1:3, :2]) # filas 1 y 2, columnas 0 y 1
print(notas[::2, -1]) # una fila sí y otra no, última columnaaprobado = notas >= 51
print(aprobado[:2]) # las dos primeras filas
print("Notas reprobadas:", notas[notas < 51])
print("Cuántas notas aprobadas:", aprobado.sum())
# Las notas de Matemática de quienes aprobaron Física
print(notas[notas[:, 1] >= 51, 0])Los ejes
M = np.array([[2, 5, 1, 4],
[3, 0, 6, 2],
[1, 4, 2, 7]])
print("Sin eje: ", M.sum())
print("axis=0: ", M.sum(axis=0), "forma", M.sum(axis=0).shape)
print("axis=1: ", M.sum(axis=1), "forma", M.sum(axis=1).shape)import matplotlib.pyplot as plt
filas, columnas = M.shape
fig, ax = plt.subplots()
for i in range(filas):
for j in range(columnas):
# La fila i se dibuja más abajo cuanto mayor es i
ax.add_patch(plt.Rectangle((j, -i), 1, 1, fill=False,
edgecolor="C0", linewidth=1.5))
ax.text(j + 0.5, -i + 0.5, M[i, j], ha="center", va="center")
for j, s in enumerate(M.sum(axis=0)):
ax.text(j + 0.5, -filas + 0.1, s, ha="center", va="center",
color="C2")
for i, s in enumerate(M.sum(axis=1)):
ax.text(columnas + 0.6, -i + 0.5, s, ha="center", va="center",
color="C2")
# Las flechas de los ejes
flecha = dict(arrowstyle="-|>", color="C0", linewidth=2)
ax.annotate("", xy=(-0.4, -filas + 1), xytext=(-0.4, 1),
arrowprops=flecha)
ax.annotate("", xy=(columnas, 1.4), xytext=(0, 1.4), arrowprops=flecha)
ax.text(-0.7, -0.5, "eje 0", rotation=90, ha="center", va="center")
ax.text(columnas / 2, 1.75, "eje 1", ha="center")
ax.text(columnas / 2, -filas - 0.5, "sum(axis=0): forma (4,)",
ha="center", color="C2")
ax.text(columnas + 1.2, -filas / 2 + 1, "sum(axis=1): forma (3,)",
rotation=-90, ha="center", va="center", color="C2")
ax.set_xlim(-1, columnas + 1.5)
ax.set_ylim(-filas - 0.9, 2.1)
ax.set_aspect("equal")
ax.axis("off")
plt.show()prom_materia = notas.mean(axis=0) # un promedio por materia
prom_estudiante = notas.mean(axis=1) # un promedio por estudiante
for materia, p in zip(materias, prom_materia):
print(f"{materia:<11} {p:5.1f}")
print("Por estudiante:", prom_estudiante.round(1))
print("Mejor estudiante: fila", prom_estudiante.argmax())
print("Nota más alta de cada materia:", notas.max(axis=0))Cambiar la forma: reshape y transpuesta
a = np.arange(12)
print(a, a.shape)
b = a.reshape(3, 4)
print(b)
print("b.T:", b.T.shape)
print(b.T)
print(a.reshape(2, 2, 3).shape)
print(a.reshape(-1, 6).shape) # -1: «calcula tú este número»Broadcasting: operar con formas distintas
print(notas.shape, prom_materia.shape)
diferencia = notas - prom_materia # (6, 4) - (4,)
print(diferencia[:2].round(1))prom_col = notas.mean(axis=1, keepdims=True)
print(prom_col.shape) # (6, 1)
print((notas - prom_col)[:2].round(1))
# Lo mismo, agregando un eje al vector de forma (6,)
print(prom_estudiante[:, np.newaxis].shape)Producto punto: medir el parecido
# fútbol, música, lectura, videojuegos
ana = np.array([5, 1, 0, 4])
beto = np.array([4, 0, 1, 5])
caro = np.array([0, 5, 4, 1])
print("Ana · Beto =", ana @ beto)
print("Ana · Caro =", ana @ caro)
print("A mano: ", np.sum(ana * beto))# Solo los dos primeros gustos (fútbol y música), para dibujar en el plano
for nombre, v in [("Ana", ana[:2]), ("Beto", beto[:2]),
("Caro", caro[:2])]:
plt.annotate("", xy=v, xytext=(0, 0),
arrowprops=dict(arrowstyle="-|>", color="C0", lw=2))
plt.text(v[0] + 0.15, v[1] + 0.15, nombre)
# Solo para dibujar:
plt.gca().set_aspect("equal")
plt.xlim(-0.5, 6)
plt.ylim(-0.5, 6)
plt.xlabel("Fútbol")
plt.ylabel("Música")
plt.title("Cada estudiante es una flecha")
plt.show()def parecido(a, b):
"""Similitud coseno: el producto punto de los vectores normalizados."""
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"Norma de Ana: {np.linalg.norm(ana):.2f}")
print(f"Ana y Beto: {parecido(ana, beto):.2f}")
print(f"Ana y Caro: {parecido(ana, caro):.2f}")G = np.array([ana, beto, caro]) # forma (3, 4)
normas = np.linalg.norm(G, axis=1, keepdims=True) # forma (3, 1)
G_unit = G / normas # cada fila mide 1
print(np.linalg.norm(G_unit, axis=1))
similitudes = G_unit @ G_unit.T # (3, 4) @ (4, 3)
print(similitudes.shape)
print(similitudes.round(2))T = np.array([[1.5, 0.5],
[0.0, 1.0]])
angulos = np.linspace(0, 2 * np.pi, 100)
# 100 puntos de un círculo, uno por columna: forma (2, 100)
circulo = np.array([np.cos(angulos), np.sin(angulos)])
transformado = T @ circulo # (2, 2) @ (2, 100)
v = np.array([0.0, 1.0])
print("T @ v =", T @ v)
plt.plot(circulo[0], circulo[1], color="C2", label="Antes")
plt.plot(transformado[0], transformado[1], color="C0",
label="Después de T")
plt.annotate("", xy=v, xytext=(0, 0),
arrowprops=dict(arrowstyle="-|>", color="C2", lw=2))
plt.annotate("", xy=T @ v, xytext=(0, 0),
arrowprops=dict(arrowstyle="-|>", color="C1", lw=2.5))
plt.text(0.5, 0.35, "T @ v")
plt.gca().set_aspect("equal")
plt.ylim(-1.2, 1.9)
plt.title("Una matriz transforma el plano")
# Muestras cortas para que la leyenda quepa dentro del eje
plt.legend(loc="upper center", ncol=2, handlelength=1.0,
handletextpad=0.4, columnspacing=0.8)
plt.show()Una imagen es un arreglo
from sklearn.datasets import load_sample_image
foto = load_sample_image("china.jpg")
print(type(foto).__name__, foto.shape, foto.dtype)
print("Píxel de arriba a la izquierda (R, G, B):", foto[0, 0])
print("Valor más chico y más grande:", foto.min(), foto.max())recorte = foto[60:300, 150:470] # filas 60 a 299, columnas 150 a 469
espejo = foto[:, ::-1] # las columnas, de atrás para adelante
pesos = np.array([0.299, 0.587, 0.114])
gris = foto @ pesos # un número por píxel
print(recorte.shape, espejo.shape, gris.shape)
fig, ejes = plt.subplots(2, 2, figsize=(4.8, 4.8))
ejes[0, 0].imshow(foto)
ejes[0, 0].set_title("Original")
ejes[0, 1].imshow(recorte)
ejes[0, 1].set_title("Recortada")
ejes[1, 0].imshow(espejo)
ejes[1, 0].set_title("Volteada")
ejes[1, 1].imshow(gris, cmap="gray")
ejes[1, 1].set_title("En gris")
for eje in ejes.flat:
eje.axis("off")
plt.show()lote = np.stack([foto, espejo, foto[::-1]]) # tres fotos
print("lote:", lote.shape) # (N, alto, ancho, 3)
print("una foto:", lote[1].shape)
print("el píxel (0, 0) de cada foto:", lote[:, 0, 0].shape)
# El color promedio de cada foto: se tachan el alto y el ancho
print(lote.mean(axis=(1, 2)).round(1))Vectorizar: cambiar bucles por arreglos
import time
grande = rng.random(1_000_000) # un millón de números entre 0 y 1
inicio = time.perf_counter()
total = 0.0
for valor in grande:
total += valor * valor
t_bucle = time.perf_counter() - inicio
inicio = time.perf_counter()
total_np = np.sum(grande * grande)
t_numpy = time.perf_counter() - inicio
print("¿Dan lo mismo?", np.isclose(total, total_np))
print("¿NumPy fue más de 10 veces más rápido?", t_bucle > 10 * t_numpy)¿Terminaste este tema?
Márcalo y en la ruta verás cuánto llevas. Se guarda solo en este navegador.