Python
NumPy: arrays numéricos
Qué problema resuelve NumPy que las listas no pueden, cómo se crean e indexan los arrays, qué significa que las operaciones sean vectorizadas y cómo funciona el broadcasting.
El problema: sumar dos listas no suma nada
Con lo que sabes hasta Listas, multiplicar todos los precios por 1.19 se escribe así:
precios = [1000, 2500, 3200, 890]
con_iva = []
for p in precios:
con_iva.append(p * 1.19)Un bucle entero para una idea de una sola línea. Y si intentas el atajo obvio, Python hace algo distinto de lo que esperabas:
precios = [1000, 2500]
otros = [500, 700]
print(precios + otros)
print(precios * 2)[1000, 2500, 500, 700]
[1000, 2500, 1000, 2500]+ concatena las listas y * las repite: para una lista, esos operadores significan otra cosa. No hay forma de sumar elemento a elemento sin escribir el bucle a mano.
NumPy (de Numerical Python) existe para resolver eso. Aporta un tipo nuevo, el ndarray, donde +, * y compañía sí operan sobre todos los elementos a la vez — y de paso lo hacen entre 10 y 100 veces más rápido que el bucle, porque por debajo el trabajo lo hace código compilado en C, no el intérprete de Python.
Instalarlo e importarlo
NumPy no viene con Python: hay que instalarlo una vez desde la terminal.
pip install numpyY en cada archivo que lo use:
import numpy as npas np le pone un alias corto. Esto es una convención universal: prácticamente todo el código NumPy del mundo escribe np, y desviarse solo hace tu código más difícil de leer para los demás.
Crear arrays
La forma más directa es convertir una lista:
import numpy as np
precios = np.array([1000, 2500, 3200, 890])
print(precios)
print(type(precios))[1000 2500 3200 890]
<class 'numpy.ndarray'>Fíjate en la salida: sin comas. Es la pista visual más rápida para saber si lo que estás mirando en la consola es un array de NumPy o una lista de Python.
Y hay funciones para generar arrays sin escribir los valores:
| Función | Qué hace | Ejemplo |
|---|---|---|
np.zeros(5) | Cinco ceros | [0. 0. 0. 0. 0.] |
np.ones(3) | Tres unos | [1. 1. 1.] |
np.arange(0, 10, 2) | Como range, pero array | [0 2 4 6 8] |
np.linspace(0, 1, 5) | 5 valores repartidos entre 0 y 1 | [0. 0.25 0.5 0.75 1.] |
np.random.rand(3) | Tres aleatorios entre 0 y 1 | [0.51 0.09 0.77] |
arange y linspace se confunden seguido. np.arange(0, 10, 2) dice de cuánto en cuánto avanzar (paso 2); np.linspace(0, 10, 5) dice cuántos valores quieres (5, repartidos parejo, incluyendo los dos extremos). Cuando estés dibujando una curva vas a querer casi siempre linspace.
Un array no es una lista
Las diferencias que importan en la práctica:
| Lista de Python | Array de NumPy | |
|---|---|---|
| Tipos que admite | Mezclados: [1, "a", True] | Uno solo para todo el array |
+ entre dos | Concatena | Suma elemento a elemento |
* 2 | Repite la lista | Multiplica cada valor por 2 |
| Cambiar de tamaño | append, insert, pop | Tamaño fijo al crearse |
| Velocidad en cálculos | Lenta (bucle en Python) | Rápida (código C por debajo) |
Ese “un solo tipo” tiene consecuencias visibles:
a = np.array([1, 2, 3])
print(a.dtype)
b = np.array([1, 2, 3.5])
print(b.dtype)int64
float64dtype (“data type”) es el tipo de todos los elementos. En el segundo caso NumPy promovió los enteros a decimales para que un solo tipo alcanzara para todos.
Ese tipo único también es una trampa: en un array de enteros, a[0] = 3.9 guarda 3, truncando sin avisar. Si vas a trabajar con decimales, créalo así desde el principio — np.array([1, 2, 3], dtype=float) o simplemente escribiendo 1.0.
Vectorización: operar sin bucles
Aquí está la razón de ser de la librería:
precios = np.array([1000, 2500, 3200, 890])
con_iva = precios * 1.19
print(con_iva)[1190. 2975. 3808. 1059.1]A esto se lo llama vectorización: la operación se aplica a todo el array de una sola vez, sin for a la vista. Funciona con todos los operadores y también entre dos arrays del mismo tamaño:
cantidades = np.array([2, 1, 3, 5])
precios = np.array([1000, 2500, 3200, 890])
print(precios + 100) # a cada uno
print(precios * cantidades) # uno contra uno
print(precios > 2000) # comparación, da booleanos[1100 2600 3300 990]
[2000 2500 9600 4450]
[False True True False]Esa última línea es la puerta a algo muy usado: filtrar por condición.
caros = precios[precios > 2000]
print(caros)[2500 3200]precios > 2000 produce un array de True/False, y usarlo como índice devuelve solo las posiciones donde había True. Se llama máscara booleana, y reemplaza a la comprensión de lista [p for p in precios if p > 2000] de forma más corta y mucho más rápida.
Funciones y estadísticas
NumPy trae las operaciones matemáticas típicas, aplicables a un array entero:
notas = np.array([8.5, 6.0, 9.5, 4.0, 7.0])
print(notas.sum())
print(notas.mean())
print(notas.max(), notas.min())
print(notas.std())
print(np.sqrt(notas))35.0
7.0
9.5 4.0
1.9078784028338913
[2.91547595 2.44948974 3.08220700 2. 2.64575131]| Método | Qué calcula |
|---|---|
.sum() | Suma de todos |
.mean() | Promedio |
.max() / .min() | Mayor / menor |
.std() | Desviación estándar |
.argmax() | La posición del mayor, no el valor |
np.sqrt(a), np.sin(a), np.log(a) | La función aplicada a cada elemento |
Arrays de dos dimensiones
Un array puede tener más de una dimensión: una tabla, una imagen, una matriz.
notas = np.array([
[8, 6, 9, 7],
[5, 7, 6, 8],
[9, 9, 8, 10],
])
print(notas.shape)
print(notas.ndim)(3, 4)
2shape (“forma”) es la propiedad que más vas a consultar: (3, 4) significa 3 filas y 4 columnas. ndim es la cantidad de dimensiones.
Para indexar se usan dos índices separados por coma, no dos pares de corchetes:
print(notas[0, 2]) # fila 0, columna 2
print(notas[1]) # la fila 1 entera
print(notas[:, 0]) # la columna 0 entera
print(notas[0:2, 1:3]) # un recorte de filas y columnas9
[5 7 6 8]
[8 5 9]
[[6 9]
[7 6]]: solo significa “todas las posiciones de esta dimensión”, igual que en el rebanado de listas.
Y las estadísticas aceptan un axis para decir en qué dirección calcular:
print(notas.mean()) # promedio de todo
print(notas.mean(axis=0)) # promedio por columna (por materia)
print(notas.mean(axis=1)) # promedio por fila (por alumno)7.666666666666667
[7.33333333 7.33333333 7.66666667 8.33333333]
[7.5 6.5 9. ]La regla para no confundir axis: axis=0 recorre las filas hacia abajo (colapsa las filas, te queda un valor por columna) y axis=1 recorre a lo ancho (colapsa las columnas, te queda un valor por fila). Si el resultado tiene la longitud equivocada, casi siempre es este número al revés.
Broadcasting: cuando las formas no coinciden
Ya usaste esto sin nombrarlo: precios * 1.19 combina un array de 4 elementos con un solo número. NumPy “estira” el valor suelto para que alcance a todos. Esa regla, generalizada, se llama broadcasting (“difusión”), y también funciona entre arrays de distinta forma:
notas = np.array([
[8, 6, 9, 7],
[5, 7, 6, 8],
[9, 9, 8, 10],
])
bonus = np.array([1, 0, 0, 2])
print(notas + bonus)[[ 9 6 9 9]
[ 6 7 6 10]
[10 9 8 12]]bonus tiene 4 elementos y notas es 3×4: NumPy repite la fila bonus para cada una de las tres filas. La condición es que las dimensiones coincidan o una de ellas sea 1; si no, el error es explícito:
ValueError: operands could not be broadcast together with shapes (3,4) (3,)Cuando veas ese ValueError, lo primero que hay que mirar es shape de los dos arrays. El 90% de los errores de NumPy son formas que no encajan, y print(a.shape, b.shape) los resuelve casi siempre.
Pon a prueba
import numpy as np
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print(a + b)import numpy as np
datos = np.array([
[1, 2, 3],
[4, 5, 6],
])
print(datos.shape)
print(datos[1, 0])
print(datos[:, 1])
print(datos.sum(axis=0))
print(datos[datos > 3])(2, 3)
4
[2 5]
[5 7 9]
[4 5 6](2, 3): dos filas, tres columnas. Siempre en ese orden.datos[1, 0]es fila 1, columna 0 → el4. El primer índice es la fila.datos[:, 1]toma todas las filas (:) de la columna 1 →[2 5]. Es la forma de extraer una columna entera.sum(axis=0)colapsa las filas hacia abajo, sumando cada columna:1+4,2+5,3+6.datos > 3da una máscara[[False False False], [True True True]], y usarla como índice devuelve solo esos valores, aplanados en un array de una dimensión. Ese aplanamiento sorprende la primera vez: el resultado de una máscara booleana nunca conserva la forma original, porque la cantidad de elementos que pasan el filtro puede ser distinta en cada fila.
Se calculan las temperaturas en Fahrenheit, el promedio y solo los días que superaron los 25 grados.
import numpy as np
temperaturas = np.([22, 28, 19, 31, 25])
fahrenheit = temperaturas * 9 / 5 + 32
promedio = temperaturas.()
calurosos = temperaturas[temperaturas 25]
print(fahrenheit, promedio, calurosos)Ordena las líneas para calcular el promedio de cada alumno a partir de una tabla de notas.
- print(promedios)
- import numpy as np
- [5, 7, 6],
- ])
- [8, 6, 9],
- notas = np.array([
- promedios = notas.mean(axis=1)
Siguiente paso
Ahora que puedes calcular sobre miles de números sin escribir un solo bucle, el paso natural es verlos: convertir esos arrays en gráficos de líneas, barras o dispersión — ver Matplotlib: graficar con pyplot.