Curso de Python · Bloque 08 de 09

NumPy

Arrays, indexación, vectorización, broadcasting y álgebra.

75–105 min Descargar notebook ↓

Audiencia. Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.
Prerrequisitos. Secuencias, funciones, slicing y operaciones numéricas básicas. Requiere numpy.
Duración orientativa. 60–90 minutos, incluida la práctica.

Objetivos de aprendizaje

  • Crear arrays homogéneos e inspeccionar shape, ndim y dtype.
  • Indexar, filtrar, remodelar y combinar arrays.
  • Aplicar vectorización, broadcasting y reducciones por ejes.
  • Usar generación aleatoria reproducible y álgebra lineal básica.

Itinerario

  1. Arrays
  2. Creación
  3. Indexación
  4. Vectorización
  5. Broadcasting
  6. Ejes
  7. Forma
  8. Azar y álgebra
  9. Práctica

Cómo trabajar: ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.

1. El array de NumPy

Un ndarray es un bloque n-dimensional de valores normalmente homogéneos. Frente a una lista, almacena números de forma compacta y ofrece operaciones vectorizadas. shape describe tamaños por dimensión; ndim, el número de dimensiones; dtype, el tipo almacenado.

Python
import numpy as np

print("NumPy", np.__version__)
matriz = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)
print(matriz)
print("shape:", matriz.shape, "ndim:", matriz.ndim, "dtype:", matriz.dtype, "size:", matriz.size)
Resultado
NumPy 1.26.4
[[1. 2. 3.]
 [4. 5. 6.]]
shape: (2, 3) ndim: 2 dtype: float64 size: 6

2. Creación y tipos

Usa array para datos existentes; zeros, ones y full para inicializar; arange para pasos enteros y linspace para un número exacto de puntos. NumPy promociona tipos para que el array pueda contener todos los valores.

Python
creaciones = {
    "ceros": np.zeros((2, 3)),
    "unos": np.ones(4, dtype=np.int32),
    "relleno": np.full((2, 2), 7),
    "rango": np.arange(0, 10, 2),
    "lineal": np.linspace(0, 1, 5),
    "identidad": np.eye(3),
}
for nombre, array in creaciones.items():
    print(nombre, "→", array, sep="\n")
Resultado
ceros
→
[[0. 0. 0.]
 [0. 0. 0.]]
unos
→
[1 1 1 1]
relleno
→
[[7 7]
 [7 7]]
rango
→
[0 2 4 6 8]
lineal
→
[0.   0.25 0.5  0.75 1.  ]
identidad
→
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

3. Indexación, slicing y vistas

Se usa un índice por eje: a[fila, columna]. Los slices suelen devolver vistas: comparten memoria con el original. .copy() crea datos independientes.

Python
datos = np.arange(12).reshape(3, 4)
print(datos)
print("Elemento:", datos[1, 2])
print("Fila:", datos[1])
print("Bloque:\n", datos[:2, 1:3])

vista = datos[:, 0]
copia = datos[:, 1].copy()
vista[0] = 99
copia[0] = -1
print("Original tras editar vista/copia:\n", datos)
Resultado
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
Elemento: 6
Fila: [4 5 6 7]
Bloque:
 [[1 2]
 [5 6]]
Original tras editar vista/copia:
 [[99  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

La indexación avanzada con listas de posiciones y máscaras booleanas devuelve selecciones flexibles. Una máscara tiene la misma forma compatible y se puede combinar con &, | y ~; usa paréntesis alrededor de cada comparación.

Python
valores = np.array([12, -3, 7, 20, 5, 18])
mascara = (valores >= 10) & (valores < 20)
print(valores[mascara])
print(valores[[0, 3, 5]])

ajustados = np.where(valores < 0, 0, valores)
print(ajustados)
Resultado
[12 18]
[12 20 18]
[12  0  7 20  5 18]

4. Vectorización y funciones universales

Los operadores actúan elemento a elemento sin bucles Python explícitos. Las ufuncs como sqrt, exp, sin o maximum están implementadas eficientemente y aceptan arrays.

Python
celsius = np.array([-10.0, 0.0, 20.0, 37.0])
fahrenheit = celsius * 9 / 5 + 32
print(fahrenheit)

angulos = np.linspace(0, np.pi, 5)
print(np.round(np.sin(angulos), 3))
Resultado
[14.  32.  68.  98.6]
[0.    0.707 1.    0.707 0.   ]

5. Broadcasting

NumPy combina formas compatibles comparando dimensiones desde la derecha. Son compatibles si coinciden o una vale 1. Esto evita replicar datos manualmente.

Python
ventas = np.array([
    [100, 120, 90],
    [80, 75, 110],
])
factores_mes = np.array([1.0, 1.1, 0.9])
ajustadas = ventas * factores_mes

sesgo_por_tienda = np.array([[5], [-5]])
corregidas = ajustadas + sesgo_por_tienda
print(corregidas)
Resultado
[[105.  137.   86. ]
 [ 75.   77.5  94. ]]

6. Agregaciones y ejes

Una reducción resume datos. Sin axis opera sobre todo el array; axis=0 elimina el eje de filas y resume por columnas; axis=1 elimina columnas y resume por filas. keepdims=True conserva una dimensión de tamaño 1 para facilitar broadcasting posterior.

Python
notas = np.array([
    [8.0, 7.5, 9.0],
    [6.0, 8.5, 7.0],
    [9.5, 9.0, 10.0],
])
print("Media global:", notas.mean())
print("Media por prueba:", notas.mean(axis=0))
print("Media por persona:", notas.mean(axis=1))
print("Máximos por prueba:", notas.max(axis=0))
Resultado
Media global: 8.277777777777779
Media por prueba: [7.83333333 8.33333333 8.66666667]
Media por persona: [8.16666667 7.16666667 9.5       ]
Máximos por prueba: [ 9.5  9.  10. ]

7. Cambiar forma y combinar

reshape cambia la vista lógica si el número total de elementos coincide. T transpone. concatenate une sobre un eje existente; stack crea un eje nuevo.

Python
base = np.arange(1, 13)
tabla = base.reshape(3, 4)
print(tabla)
print("Transpuesta:\n", tabla.T)

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print("Concatenar:", np.concatenate([a, b]))
print("Apilar:\n", np.stack([a, b]))
Resultado
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
Transpuesta:
 [[ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]
 [ 4  8 12]]
Concatenar: [1 2 3 4 5 6]
Apilar:
 [[1 2 3]
 [4 5 6]]

8. Azar reproducible y álgebra lineal

Usa np.random.default_rng(semilla) en código nuevo. Una semilla reproduce los ejemplos. El operador @ realiza producto matricial; np.linalg.solve resuelve sistemas sin calcular una inversa explícita.

Python
rng = np.random.default_rng(2026)
muestra = rng.normal(loc=10, scale=2, size=(3, 4))
print(np.round(muestra, 2))

A = np.array([[2.0, 1.0], [1.0, 3.0]])
b = np.array([8.0, 13.0])
solucion = np.linalg.solve(A, b)
print("Solución:", solucion)
print("Comprobación A @ x:", A @ solucion)
Resultado
[[ 8.41 10.48  6.21 12.79]
 [11.28  9.42  9.38 10.61]
 [ 9.46  9.55 11.44 11.03]]
Solución: [2.2 3.6]
Comprobación A @ x: [ 8. 13.]

9. Valores ausentes y persistencia

np.nan representa un valor flotante ausente. Funciones como nanmean lo ignoran de forma explícita. np.save conserva forma y tipo; aquí usamos un flujo en memoria.

Python
from io import BytesIO

mediciones = np.array([10.0, np.nan, 12.5, 11.0])
print("Media ignorando NaN:", np.nanmean(mediciones))

buffer = BytesIO()
np.save(buffer, mediciones)
buffer.seek(0)
recuperadas = np.load(buffer)
print(recuperadas)
Resultado
Media ignorando NaN: 11.166666666666666
[10.   nan 12.5 11. ]

Errores habituales

  • Confundir * (elemento a elemento) con @ (producto matricial).
  • Olvidar que un slice puede ser una vista y modificar el original.
  • Usar and/or con arrays; usa &/| y paréntesis.
  • Ignorar dtype y provocar truncamiento o desbordamiento.
  • Crear bucles Python donde basta una operación vectorizada.

Extensión opcional: explora einsum, arrays estructurados y perfiles de memoria.

Práctica · Estandarizar columnas

Implementa estandarizar(matriz) para que cada columna tenga media aproximada 0 y desviación típica 1:

  1. calcula por columnas (axis=0);
  2. conserva dimensiones para broadcasting;
  3. rechaza columnas con desviación cero.
Tu turno
import numpy as np

def estandarizar(matriz):
    # TODO: convierte a float, calcula media/desviación y estandariza.
    pass
Ver una solución posible
Solución
import numpy as np

def estandarizar(matriz):
    datos = np.asarray(matriz, dtype=float)
    medias = datos.mean(axis=0, keepdims=True)
    desviaciones = datos.std(axis=0, keepdims=True)
    if np.any(desviaciones == 0):
        raise ValueError("no se puede estandarizar una columna constante")
    return (datos - medias) / desviaciones

datos = np.array([[1, 10], [2, 20], [3, 30], [4, 40]])
normalizados = estandarizar(datos)
print(np.round(normalizados, 3))
assert np.allclose(normalizados.mean(axis=0), 0)
assert np.allclose(normalizados.std(axis=0), 1)
Resultado
[[-1.342 -1.342]
 [-0.447 -0.447]
 [ 0.447  0.447]
 [ 1.342  1.342]]

Comprobación rápida

  • [ ] Puedo explicar forma, dimensiones y tipo de un array.
  • [ ] Sé aplicar máscaras, operaciones vectorizadas y broadcasting.
  • [ ] Elijo correctamente el eje de una reducción.

← Anterior · Volver al índice · Siguiente →