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,ndimydtype. - 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
- Arrays
- Creación
- Indexación
- Vectorización
- Broadcasting
- Ejes
- Forma
- Azar y álgebra
- 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.
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)
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.
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")
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.
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)
[[ 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.
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)
[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.
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))
[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.
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)
[[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.
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))
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.
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]))
[[ 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.
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)
[[ 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.
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)
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/orcon arrays; usa&/|y paréntesis. - Ignorar
dtypey 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:
- calcula por columnas (
axis=0); - conserva dimensiones para broadcasting;
- rechaza columnas con desviación cero.
import numpy as np
def estandarizar(matriz):
# TODO: convierte a float, calcula media/desviación y estandariza.
pass
Ver una solución posible
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)
[[-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.