36 · Arrays de NumPy: dtype, shape, slicing
Deja atrás las listas de Python — el ndarray de NumPy es un buffer contiguo y tipado que ejecuta aritmética en C. Domina la creación, manipulación de shape y los patrones de slicing que usa toda pipeline de ML.
Una `list` de floats en Python almacena cada número como un objeto CPython separado de 28 bytes más un puntero de 8 bytes. Un `ndarray` de NumPy los almacena **empaquetados uno al lado del otro en un único buffer C** — cada float de 64 bits ocupa exactamente 8 bytes. Esa disposición compacta es la razón por la que NumPy puede sumar dos arrays de un millón de elementos miles de veces más rápido que un bucle Python: las unidades vectoriales de la CPU pueden barrer el buffer sin el overhead de desempaquetar objetos.
Sin esto:
Sin NumPy, cada algoritmo numérico recorrería listas de Python en bucle — cientos de veces más lento para cualquier cosa con forma matricial. NumPy es el sustrato sobre el que se construyen pandas, scikit-learn, PyTorch y TensorFlow. Conocer la semántica de ndarray (dtype, shape, strides, views) te permite leer el código fuente de los frameworks, depurar errores de shape y escribir preprocesamiento eficiente.
NumPy (Numerical Python) existe porque los objetos de CPython son costosos. Un float de Python puro es un objeto completo — encabezado, contador de referencias, puntero de tipo y luego los 8 bytes reales del valor. Almacenado en una lista, también pagas un puntero por elemento para vincular el objeto. Para un millón de números eso son ~36 MB de overhead de bookkeeping.
Un ndarray de NumPy evita todo eso. Asigna un bloque contiguo de memoria y escribe valores de extremo a extremo sin metadatos por elemento. Para un millón de valores float64 eso son exactamente 8 MB. El shape, dtype y strides se almacenan una vez en un pequeño objeto de encabezado.
Más allá de la memoria, la disposición contigua permite a NumPy delegar a rutinas BLAS/LAPACK altamente optimizadas y, fundamentalmente, libera el GIL de Python durante la duración de los bucles a nivel C. Múltiples núcleos pueden procesar números en paralelo. Esta es la razón por la que el stack de ML de Python es rápido a pesar de que el intérprete de Python es de un solo hilo.
Python (in browser)
`np.arange(10)` espeja el `range(10)` de Python pero devuelve un ndarray. `np.linspace(0, 1, 5)` es indispensable para ML: genera grillas de features espaciadas uniformemente y planificaciones de learning rate. Observa cómo `.shape` siempre es una tupla — incluso un array 1D reporta `(4,)`, no `4`.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
La sintaxis de coma dentro de corchetes `A[1, 2]` es el indexado multidimensional de NumPy — equivalente a `A[1][2]` pero más rápido porque evita crear un array intermedio. `A[:, 0]` (todas las filas, columna 0) es el idioma para extraer toda una columna de features de una matriz de datos.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
El enmascaramiento booleano devuelve un nuevo array que contiene solo los elementos donde la máscara es `True` — sin necesidad de bucle Python. `np.maximum(0, x)` es la función de activación ReLU vectorizada usada en prácticamente toda red neuronal. El indexado fancy (`a[[0, 2, 4]]`) selecciona filas/elementos arbitrarios y es como se implementa el muestreo de mini-batches en los data loaders.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
`reshape(-1, cols)` está en prácticamente cada método `forward` de un modelo PyTorch — cuando un batch de imágenes `(B, C, H, W)` necesita convertirse en un vector de features plano para una capa densa escribes `x.reshape(B, -1)` y NumPy (o PyTorch) calcula la dimensión restante automáticamente.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
¿Qué hace `a.reshape(-1, 2)` cuando `a` es un array 1D con 10 elementos?
La lección 2 de MML introduce los vectores como tuplas ordenadas en Rⁿ — exactamente lo que representa un array 1D de NumPy. Un array de shape `(n,)` *es* un vector; un array de shape `(m, n)` *es* una matriz. El dtype es la elección del campo (float64 ≈ ℝ, complex128 ≈ ℂ). La misma notación de índice `v[i]` y la notación de slice `v[i:j]` se mapean directamente al subíndice matemático `vᵢ`.
- Un `ndarray` de NumPy es un buffer C tipado y contiguo — cada elemento ocupa exactamente `itemsize` bytes, sin overhead de objeto Python por elemento. Por eso es 100–1000× más rápido que una lista Python para trabajo numérico.
- Crea arrays con `np.array`, `np.zeros`, `np.ones`, `np.arange` o `np.linspace`. Inspecciónalos con `.shape`, `.ndim`, `.dtype`, `.size`.
- El indexado multidimensional usa `A[fila, col]`; el slicing usa `A[r_start:r_end, c_start:c_end]`. El enmascaramiento booleano (`a[a > 0]`) y el indexado fancy (`a[[0, 2, 4]]`) seleccionan subconjuntos sin bucles.
- `a.reshape(filas, cols)` reinterpreta el buffer en un nuevo shape. Usa `-1` para una dimensión y deja que NumPy la infiera — el idioma `reshape(-1, ...)` aparece en prácticamente cada método `forward()` de PyTorch.
Cada pipeline de ML numérico es una cadena de transformaciones de ndarray. Un CSV de datos de entrenamiento se convierte en un array `(n_samples, n_features)`; las etiquetas en un array `(n_samples,)`; un mini-batch es un slice `(batch_size, ...)`. `Tensor` de PyTorch y `tf.Tensor` de TensorFlow replican la API de ndarray de NumPy — `.shape`, `.dtype`, `.reshape`, indexado booleano — por lo que la fluidez aquí se transfiere directamente.
Si lo quitas: Sin entender los ndarrays, cada error de forma en PyTorch o scikit-learn es un misterio. El mensaje `RuntimeError: shape '[64, -1]' is invalid` solo tiene sentido si sabes qué hace `.reshape` y por qué -1 a veces falla. Conocer los fundamentos de ndarray convierte los errores crípticos de los frameworks en aritmética de shapes inmediatamente diagnosticable.