</>waridocu

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.

pythonnumpyarraysndarrayvectorizacionlibrerias

El problema: sumar dos listas no suma nada

Con lo que sabes hasta Listas, multiplicar todos los precios por 1.19 se escribe así:

Ejemplo: la única forma de operar sobre una lista enterapython
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:

pythonpython
precios = [1000, 2500]
otros = [500, 700]
print(precios + otros)
print(precios * 2)
texttext
[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.

bashbash
pip install numpy

Y en cada archivo que lo use:

pythonpython
import numpy as np

as 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:

Ejemplo: de lista a arraypython
import numpy as np

precios = np.array([1000, 2500, 3200, 890])
print(precios)
print(type(precios))
texttext
[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ónQué haceEjemplo
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]
Nota

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 PythonArray de NumPy
Tipos que admiteMezclados: [1, "a", True]Uno solo para todo el array
+ entre dosConcatenaSuma elemento a elemento
* 2Repite la listaMultiplica cada valor por 2
Cambiar de tamañoappend, insert, popTamaño fijo al crearse
Velocidad en cálculosLenta (bucle en Python)Rápida (código C por debajo)

Ese “un solo tipo” tiene consecuencias visibles:

pythonpython
a = np.array([1, 2, 3])
print(a.dtype)

b = np.array([1, 2, 3.5])
print(b.dtype)
texttext
int64
float64

dtype (“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.

Advertencia

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:

Ejemplo: lo mismo que el bucle del principio, en una líneapython
precios = np.array([1000, 2500, 3200, 890])
con_iva = precios * 1.19
print(con_iva)
texttext
[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:

pythonpython
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
texttext
[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.

Ejemplo: quedarse solo con los precios altospython
caros = precios[precios > 2000]
print(caros)
texttext
[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:

pythonpython
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))
texttext
35.0
7.0
9.5 4.0
1.9078784028338913
[2.91547595 2.44948974 3.08220700 2.         2.64575131]
MétodoQué 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.

Ejemplo: notas de 3 alumnos en 4 materiaspython
notas = np.array([
    [8, 6, 9, 7],
    [5, 7, 6, 8],
    [9, 9, 8, 10],
])

print(notas.shape)
print(notas.ndim)
texttext
(3, 4)
2

shape (“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:

pythonpython
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 columnas
texttext
9
[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:

pythonpython
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)
texttext
7.666666666666667
[7.33333333 7.33333333 7.66666667 8.33333333]
[7.5  6.5  9.  ]
Nota

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:

Ejemplo: sumar un bonus distinto a cada materiapython
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)
texttext
[[ 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:

texttext
ValueError: operands could not be broadcast together with shapes (3,4) (3,)
Importante

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

🎯 ¿Qué imprime este código?
pythonpython
import numpy as np

a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print(a + b)
  • Correcto. Al ser arrays de NumPy, + suma elemento a elemento: 1+10, 2+20, 3+30. Y la salida se imprime sin comas, que es la marca visual de un ndarray.

  • Eso sería el resultado con listas de Python, donde + concatena. Con np.array el operador cambia de significado por completo — que es exactamente el motivo por el que existe NumPy.

  • Los valores están bien, pero NumPy imprime los arrays separados por espacios, sin comas. Es un detalle menor que sirve mucho para reconocer de un vistazo qué tipo tienes en la consola.

  • Los dos arrays tienen forma (3,), o sea que coinciden perfectamente. El ValueError de broadcasting aparecería si tuvieran longitudes distintas y ninguna fuera 1.

💡 Predice la salida
pythonpython
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])
texttext
(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 → el 4. 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 > 3 da 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.
✏️ Completa el análisis

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 el código

Ordena las líneas para calcular el promedio de cada alumno a partir de una tabla de notas.

  1. print(promedios)
  2. import numpy as np
  3. [5, 7, 6],
  4. ])
  5. [8, 6, 9],
  6. notas = np.array([
  7. 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.