NumPy básico: arrays y operaciones numéricas en Python

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 15 min de lectura

Si Python es el idioma, NumPy es la gramática matemática que lo hace potente. Antes de NumPy, hacer cálculos numéricos serios en Python era lento y verboso. Después de NumPy, operaciones que en C requerían decenas de líneas se expresan en una sola, y se ejecutan a velocidad compilada. Sin entender NumPy no hay Pandas, no hay scikit-learn, no hay TensorFlow. Todo el ecosistema científico de Python está construido sobre esta librería.

Medallón de Pitágoras, matemático y filósofo griego fundador de la escuela pitagórica
Arithmos estin arche ton panton
«El número es el principio de todas las cosas»
Pitágoras · Matemático y filósofo griego · 570 a.C. – 495 a.C.
Para Pitágoras, la realidad entera podía describirse mediante relaciones numéricas: los astros, la música, la geometría, la naturaleza. Su escuela del número como fundamento ontológico resulta premonitoria. NumPy extiende esa idea al cómputo: cualquier dato del mundo real (una imagen, una señal de audio, el precio de una acción, la temperatura de un sensor) puede representarse como un ndarray, y una vez que está ahí, se puede analizar, transformar y predecir con las mismas operaciones matemáticas que Pitágoras consideró el lenguaje de la realidad.

¿Qué es NumPy y por qué existe?

NumPy (Numerical Python) es la librería fundamental para el cómputo numérico en Python. Apareció en 2005 para resolver un problema concreto: Python es un lenguaje interpretado y dinámico, lo que lo hace flexible y legible pero lento para operaciones numéricas masivas. NumPy resuelve esto introduciendo el ndarray, un array multidimensional homogéneo implementado en C que permite operar sobre millones de datos sin un solo bucle Python.

Unas gafas frente a una pantalla con múltiples matrices de datos y código
Leer matrices de datos sin las herramientas adecuadas es como intentar descifrar una pared de código sin gafas: la información está ahí, pero necesitas el instrumento correcto para verla con claridad. NumPy es ese instrumento: convierte estructuras de datos numéricas en objetos que puedes manipular, analizar y transformar con una sintaxis clara y eficiente. Fuente: Pexels (licencia libre).

El impacto de NumPy en el ecosistema Python es difícil de exagerar. Pandas usa ndarrays internamente para sus Series y DataFrames. scikit-learn espera ndarrays como entrada y salida. TensorFlow y PyTorch tienen interfaces compatibles con el formato NumPy. Matplotlib acepta arrays NumPy directamente. Aprender NumPy no es aprender una librería más: es aprender el formato universal del dato numérico en Python.

pip install numpy
import numpy as np  # la convención universal: siempre np
print(np.__version__)  # 1.26.x o 2.x

El ndarray: la estructura central

El ndarray (N-dimensional array) es la piedra angular de NumPy. A diferencia de las listas Python, un ndarray almacena todos sus elementos en un bloque contiguo de memoria, todos del mismo tipo de dato. Esto elimina el overhead de la gestión dinámica de Python y permite que las operaciones se ejecuten en C o Fortran optimizados.

import numpy as np

# Crear un array
arr = np.array([10, 20, 30, 40, 50])

# Atributos fundamentales
print(arr.shape)   # (5,)         → forma: 5 elementos en 1 dimensión
print(arr.ndim)    # 1            → número de dimensiones
print(arr.dtype)   # int64        → tipo de cada elemento
print(arr.size)    # 5            → número total de elementos
print(arr.nbytes)  # 40           → bytes en memoria (5 × 8 bytes por int64)

# Array 2D (matriz)
matriz = np.array([[1, 2, 3],
                   [4, 5, 6]])
print(matriz.shape)  # (2, 3)  → 2 filas, 3 columnas
print(matriz.ndim)   # 2

Tipos de dato (dtype)

# NumPy infiere el dtype automáticamente
np.array([1, 2, 3])            # int64 (enteros)
np.array([1.0, 2.5, 3.7])     # float64 (flotantes)
np.array([True, False, True])  # bool

# También puedes forzarlo
np.array([1, 2, 3], dtype=np.float32)   # 4 bytes por elemento (ahorra RAM)
np.array([1, 2, 3], dtype=np.int32)     # 4 bytes por elemento

# Convertir el tipo de un array existente
arr = np.array([1, 2, 3])
arr_float = arr.astype(float)   # crea nuevo array float64
💡 Rendimiento y dtype: En Data Science y Machine Learning, el uso de float32 en lugar de float64 puede reducir el uso de RAM a la mitad sin pérdida perceptible de precisión en la mayoría de casos. Para entrenamiento de redes neuronales, float32 es el estándar.

Crear arrays: las funciones esenciales

import numpy as np

# ── Desde datos ──────────────────────────────────────────────────
np.array([1, 2, 3, 4])               # desde lista Python
np.array([[1, 2], [3, 4]])           # 2D desde lista de listas

# ── Arrays predefinidos ──────────────────────────────────────────
np.zeros((3, 4))          # matriz 3×4 de ceros (float64)
np.ones((2, 3))           # matriz 2×3 de unos
np.full((3, 3), 7)        # matriz 3×3 con valor 7
np.eye(4)                 # matriz identidad 4×4
np.empty((2, 2))          # sin inicializar (rápido, valores aleatorios)

# ── Rangos y secuencias ──────────────────────────────────────────
np.arange(0, 10, 2)       # [0, 2, 4, 6, 8]  (inicio, fin_excl, paso)
np.arange(5)              # [0, 1, 2, 3, 4]
np.linspace(0, 1, 5)      # [0.0, 0.25, 0.5, 0.75, 1.0]  (N puntos equiespaciados)
np.linspace(0, 2*np.pi, 100)  # 100 puntos para una función trigonométrica

# ── Números aleatorios ───────────────────────────────────────────
np.random.seed(42)            # fijar semilla para reproducibilidad
np.random.rand(3, 3)          # uniformes en [0, 1)
np.random.randn(3, 3)         # normales con media 0 y desviación 1
np.random.randint(0, 10, (4,))  # enteros aleatorios en [0, 10)
np.random.choice([10, 20, 30, 40], size=5)  # muestreo con reemplazo

La distinción entre arange y linspace importa más de lo que parece: arange especifica el paso y no garantiza el número de puntos; linspace especifica el número de puntos y garantiza que incluye el extremo final. Para generar valores de función continua (graficar un seno, calcular una integral), usa siempre linspace.

Operaciones vectorizadas: adiós a los bucles

La diferencia más importante entre NumPy y las listas Python es la vectorización: cuando escribes arr * 2, NumPy no itera elemento a elemento en Python. En cambio, delega la operación completa a una función en C que procesa el bloque de memoria de una vez. El resultado puede ser 10, 100 o 1000 veces más rápido, según el tamaño del array.

a = np.array([1, 2, 3, 4, 5])
b = np.array([10, 20, 30, 40, 50])

# Operaciones elemento a elemento (no hace falta bucle)
a + b          # [11, 22, 33, 44, 55]
a * b          # [10, 40, 90, 160, 250]
a ** 2         # [1, 4, 9, 16, 25]
a / b          # [0.1, 0.1, 0.1, 0.1, 0.1]
a % 2          # [1, 0, 1, 0, 1]

# Con escalares (broadcasting implícito)
a * 2          # [2, 4, 6, 8, 10]
a + 100        # [101, 102, 103, 104, 105]
a > 3          # [False, False, False, True, True]  ← array booleano

Funciones universales (ufuncs)

arr = np.array([1.0, 4.0, 9.0, 16.0])

np.sqrt(arr)      # [1.0, 2.0, 3.0, 4.0]
np.log(arr)       # logaritmo natural
np.exp(arr)       # e^x
np.abs(arr)       # valor absoluto
np.round(arr, 2)  # redondear a 2 decimales

# Trigonometría
angulos = np.linspace(0, 2 * np.pi, 5)
np.sin(angulos)
np.cos(angulos)

# Comparar rendimiento (timeit mental):
# Lista Python: [x**2 for x in range(1_000_000)] → ~200ms
# NumPy:        np.arange(1_000_000)**2            → ~2ms
📐 Álgebra lineal: Para multiplicación matricial (no elemento a elemento), usa el operador @ o np.dot(). La multiplicación con * en NumPy es siempre elemento a elemento: A @ B es el producto matricial estándar.
Mano moviendo una pieza de ajedrez sobre el tablero, capturando una pieza rival
El tablero de ajedrez es una matriz perfecta: 8 filas, 8 columnas, cada casilla identificada por [fila, columna]. Para acceder a e5 en notación NumPy sería tablero[4, 4] (índice base 0). Cada pieza tiene un dtype: puede ser un entero que codifica el tipo, un string, un objeto. NumPy te da las herramientas para representar, indexar y transformar cualquier estructura matricial con precisión quirúrgica. Fuente: Pexels (licencia libre).

Indexing y slicing en arrays multidimensionales

El sistema de indexing de NumPy es más potente que el de las listas Python. Para arrays 1D funciona igual; para 2D y más, la sintaxis arr[fila, columna] es más expresiva y eficiente que la doble indexación de listas.

m = np.array([[10, 20, 30, 40],
              [50, 60, 70, 80],
              [90,100,110,120]])

# ── Elemento individual ──────────────────────────────────────────
m[0, 1]      # 20  (fila 0, columna 1)
m[1, 2]      # 70
m[-1, -1]    # 120 (última fila, última columna)

# ── Fila / columna completa ──────────────────────────────────────
m[1, :]      # [50, 60, 70, 80]  → fila 1 entera
m[:, 2]      # [30, 70, 110]     → columna 2 entera

# ── Submatriz (slicing) ──────────────────────────────────────────
m[0:2, 1:3]  # [[20, 30], [60, 70]]  → filas 0-1, cols 1-2
m[::2, :]    # [[10,20,30,40],[90,100,110,120]]  → filas pares

# ── Indexado booleano (filtrado) ─────────────────────────────────
m[m > 50]         # [60, 70, 80, 90, 100, 110, 120]
m[m % 20 == 0]    # elementos divisibles por 20

# ── Fancy indexing ───────────────────────────────────────────────
indices = [0, 2]
m[indices, :]     # filas 0 y 2 → [[10,20,30,40],[90,100,110,120]]

# ── Asignar con indexado ─────────────────────────────────────────
m[m < 50] = 0     # poner a 0 todos los elementos menores de 50
⚠️ Vista vs. copia: sub = m[0:2, :] devuelve una vista: modificar sub modifica m. Si necesitas independencia, usa m[0:2, :].copy(). El fancy indexing (m[[0,2], :]) siempre devuelve copia.
Diagrama completo de NumPy: anatomía del ndarray 1D y 2D con ejes, broadcasting entre arrays de formas distintas, comparación de rendimiento vectorización vs listas Python, e indexing y slicing con referencias de color sobre una matriz 3x4
El mapa completo del ndarray: estructura de 1D y 2D con sus ejes, el broadcasting de formas compatibles, la diferencia de rendimiento con las listas Python, e indexing de elementos, filas, columnas y submatrices sobre una matriz real. Infografía: Ciberaula.

Reshape, flatten y combinación de arrays

reshape cambia la forma de un array sin cambiar sus datos. Es una operación que aparece constantemente en Machine Learning, donde los modelos esperan entradas con formas muy específicas.

arr = np.arange(12)  # [0, 1, 2, ..., 11]

# Cambiar forma
arr.reshape(3, 4)    # 3 filas × 4 columnas
arr.reshape(2, 6)    # 2 filas × 6 columnas
arr.reshape(4, 3)    # 4 filas × 3 columnas
arr.reshape(2, 2, 3) # 3D: 2 bloques de 2×3

# Usar -1 para dejar que NumPy calcule una dimensión
arr.reshape(3, -1)   # 3 filas, columnas = 12/3 = 4
arr.reshape(-1, 4)   # columnas=4, filas = 12/4 = 3
arr.reshape(-1)      # equivalente a flatten

# De 2D a 1D
m = np.array([[1,2,3],[4,5,6]])
m.flatten()   # [1,2,3,4,5,6]  → siempre copia
m.ravel()     # [1,2,3,4,5,6]  → vista cuando sea posible (más eficiente)

# Transponer
m.T           # [[1,4],[2,5],[3,6]]  → intercambia ejes

# Combinar arrays
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.concatenate([a, b])           # [1,2,3,4,5,6]
np.vstack([m, m])                # apilar verticalmente (más filas)
np.hstack([m, m])                # apilar horizontalmente (más columnas)

# Dividir
np.split(a, 3)     # [array([1]), array([2]), array([3])]
np.hsplit(m, 3)    # dividir m en 3 columnas

Estadística y reducción por ejes

Las funciones estadísticas de NumPy pueden operar sobre el array completo (devuelven un escalar) o a lo largo de un eje específico (devuelven un array de menor dimensión). El concepto de eje es esencial: axis=0 opera sobre las filas (reduce a una sola fila), axis=1 opera sobre las columnas (reduce a una sola columna).

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

# ── Reducción total (escalar) ────────────────────────────────────
np.sum(m)      # 45
np.mean(m)     # 5.0
np.std(m)      # 2.58
np.min(m)      # 1
np.max(m)      # 9
np.median(m)   # 5.0

# ── Reducción por eje ────────────────────────────────────────────
np.sum(m, axis=0)   # [12, 15, 18]  → suma de cada columna (colapsa filas)
np.sum(m, axis=1)   # [6, 15, 24]   → suma de cada fila (colapsa columnas)
np.mean(m, axis=0)  # [4.0, 5.0, 6.0]  → media de cada columna

# ── Índices ──────────────────────────────────────────────────────
np.argmax(m)         # 8  → índice del máximo en el array aplanado
np.argmin(m, axis=1) # [0, 0, 0]  → índice del mínimo en cada fila
np.argsort(m, axis=1)  # índices que ordenarían cada fila

# ── Acumuladas ───────────────────────────────────────────────────
arr = np.array([1, 2, 3, 4])
np.cumsum(arr)   # [1, 3, 6, 10]
np.diff(arr)     # [1, 1, 1]  → diferencias entre consecutivos
np.cumprod(arr)  # [1, 2, 6, 24]

Broadcasting: la magia de las formas compatibles

El broadcasting es el mecanismo que permite a NumPy realizar operaciones entre arrays de formas distintas, siempre que esas formas sean compatibles según un conjunto de reglas. En la práctica, evita tener que duplicar datos solo para que las dimensiones coincidan.

# Caso 1: array + escalar (broadcasting trivial)
arr = np.array([1, 2, 3])
arr + 10   # [11, 12, 13]  → el 10 se "expande" a [10, 10, 10]

# Caso 2: matriz + vector fila
m = np.array([[1, 2, 3],
              [4, 5, 6]])          # shape (2, 3)
v = np.array([10, 20, 30])        # shape (3,) → se trata como (1,3)
m + v
# [[11, 22, 33],
#  [14, 25, 36]]

# Caso 3: columna + fila → tabla de combinaciones
col = np.array([[1], [2], [3]])   # shape (3, 1)
fil = np.array([10, 20, 30])      # shape (3,) → (1, 3)
col + fil
# [[11, 21, 31],
#  [12, 22, 32],
#  [13, 23, 33]]

# Error típico: formas incompatibles
a = np.array([1, 2, 3])   # (3,)
b = np.array([1, 2])      # (2,)
# a + b → ValueError: operands could not be broadcast

La regla de broadcasting de NumPy: compara las formas de derecha a izquierda. Dos dimensiones son compatibles si son iguales, o si una de ellas es 1. Si tienen distinto número de dimensiones, se completa con 1s por la izquierda. Si no hay ningún 1 para expandir, se lanza un ValueError.

Ficha de referencia rápida de NumPy: cuatro columnas con creación de arrays, operaciones matemáticas y ufuncs, indexing y reshape, y funciones estadísticas y filtrado booleano
La referencia completa de NumPy en una página: cómo crear arrays con cualquier forma y tipo, las operaciones matemáticas elementales y de álgebra lineal, todo el sistema de indexing y reshape, y las funciones estadísticas con reducción por eje. Ficha de referencia: Ciberaula.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre NumPy básico: arrays y operaciones numéricas en Python

Las dudas más comunes respondidas de forma clara y directa.

La regla práctica es: si necesitas hacer operaciones matemáticas sobre colecciones de números (multiplicar todos los elementos, calcular la media, resolver sistemas de ecuaciones, procesar señales, manipular imágenes), NumPy siempre será la elección correcta. Si necesitas una colección heterogénea (mezclar strings, objetos, números), usa listas. El límite está en la naturaleza del dato: NumPy está optimizado para datos homogéneos del mismo tipo. En términos de rendimiento, para arrays de más de unos pocos cientos de elementos, NumPy es órdenes de magnitud más rápido que los bucles Python equivalentes.
Cuando haces un slice básico de un array (arr[1:3]), NumPy devuelve una vista: un objeto que apunta a los mismos datos en memoria que el array original. Si modificas la vista, modificas el array original. Esto es eficiente en memoria pero puede sorprender. Si necesitas independencia, usa arr[1:3].copy() para obtener una copia independiente. El fancy indexing (arr[[0,2,4]]) y los índices booleanos siempre devuelven copias. Puedes comprobar si un array es vista con arr.base: si devuelve el array original, es una vista; si devuelve None, es el dueño de sus datos.
El broadcasting es el mecanismo que permite a NumPy operar con arrays de formas distintas siguiendo un conjunto de reglas. La regla principal: NumPy compara las formas de los arrays de derecha a izquierda. Dos dimensiones son compatibles si son iguales, o si una de ellas es 1 (en cuyo caso se expande para coincidir con la otra). Por ejemplo, un array (3,4) y un vector (4,) son compatibles porque 4==4, y el vector se trata como (1,4) expandiéndose a (3,4). El broadcasting no copia datos en memoria: es una abstracción que NumPy resuelve en C. Si las formas no son compatibles, obtendrás un ValueError: operands could not be broadcast.
El dtype (tipo de dato) determina cómo se almacena cada elemento del array: cuántos bytes ocupa y qué valores puede representar. float64 ocupa 8 bytes por elemento y ofrece alta precisión; float32 ocupa 4 bytes con precisión ligeramente menor. En redes neuronales y GPU computing se usa mucho float32 porque ocupa la mitad de memoria y las GPUs suelen ser más rápidas con ese tipo. int32 versus int64 importa cuando trabajas con arrays enormes donde el ahorro de memoria es significativo. Para convertir: arr.astype(np.float32). Cuidado: al convertir de float a int, NumPy trunca (no redondea). Si tienes dudas, empieza con float64 y ajusta si el consumo de memoria es un problema.
Sí, el ndarray soporta cualquier número de dimensiones. Los arrays 3D son muy habituales en procesamiento de imágenes (alto × ancho × canales_color) o series temporales multivariadas. Un array de 100 imágenes RGB de 28×28 píxeles tiene forma (100, 28, 28, 3). Las mismas operaciones de indexing, slicing, broadcasting y funciones estadísticas funcionan igual para cualquier número de dimensiones. La regla de los ejes también se mantiene: axis=0 opera sobre la primera dimensión, axis=-1 sobre la última. Pandas, PyTorch, TensorFlow y muchas otras librerías usan ndarrays como base o tienen una interfaz compatible con ellos.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre NumPy básico: arrays y operaciones numéricas en Python? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!