Hamburger Icon
La Saga Polkien de NumPy (II): Desentrañando el Poder de los Arrays

La Saga Polkien de NumPy (II): Desentrañando el Poder de los Arrays

En una Tierra Lejana: Las Diferencias Entre Una Lista de Python y un Array de NumPy

En una tierra lejana, en el reino de la programación, se alzan dos poderosas entidades: las listas de Python y los arrays de NumPy. Ambos, con propósitos nobles, pero portadores de diferencias fundamentales que dan forma a sus destinos.

En el mundo de Python, las listas, flexibles y versátiles, abrazan con benevolencia la diversidad de tipos de datos, aceptando en su seno a enteros, flotantes, cadenas y más. Su espíritu es generoso, pero su virtud se desvanece cuando se trata de operaciones matemáticas, y la eficiencia se escapa entre sus dedos.

Los arrays de NumPy, por otro lado, son criaturas más resistentes, rápidas y compactas. Exigen homogeneidad en su interior, un sacrificio para algunos, pero a cambio, otorgan poderes sobrenaturales en forma de eficiencia y capacidad de especificar el tipo de datos que albergan.

El Núcleo de NumPy: El Misterioso "Array"

En el corazón de NumPy yace el "array", una cuadrícula de valores, una matriz de conocimiento que se asemeja a las celdas de un libro de cuentas en el mundo de Excel. Cada casilla, un tesoro de información cruda, aguarda ser explorada y manipulada.

En esta tierra, los arrays de una sola dimensión, similares a las listas de Python, se presentan con gracia, accesibles como páginas en un antiguo pergamino:

Domando el "Array": Arrays de Una Sola Dimensión:

a = np.array([1,2,3,4,5,6])  # Un conjuro para inicializarlos

a[2]  # Un hechizo para acceder a la tercera posición: 3

a[3:5]  # Un deslizamiento para obtener un fragmento: [4, 5]

# Otro hechizo, para recorrerlos a todos
for i in a:
    print(i)
# 1
# 2
# 3
# 4
# 5
# 6

El Despliegue de Poder: Arrays Multidimensionales.

Las huestes multidimensionales son las joyas de NumPy, estructuras con índices en múltiples direcciones, como mapas en un reino vasto. Las filas y columnas se extienden como territorios conquistados.

def f(x, y):
    return 10 * x + y

a = np.fromfunction(f, (5,4), dtype=int)
"""
el array que estamos creando es este:

array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])

A partir de la función "f", creamos una estructura multidimensional de 5x4 celdas con valores de tipo entero.
Los valores se calculan a partir de la función. En la primera fila, (0, 0), (0, 1), (0, 2) y (0, 3) salen los valores 0, 1, 2 y 3
resultado de multiplicar la primera posición de la tupla y sumar el resultado con la segunda posición.
Lo mismo con el resto de filas.
"""

a[2,3] # acceso a la tercera fila, cuarto valor: 23
a[0:5, 1] # acceso a todos los valores de la columna 2 array([1, 11, 21, 31, 41])
a[1:3, :] # acceso a las filas 2 y 3, al valor de todas las columnas
"""
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])
"""

# si solo indicamos una posición, se entiende que es la fila y devolverá los valores de todas las columnas
a[3] # array([30, 31, 32, 33])

Las travesías son igualmente posibles, incluso más allá de nuestro entendimiento, como la creación de arrays en 3D, una pirámide de información.

array_3d = np.array([
    [
        [0, 1, 2],
        [10, 11, 12]
    ],
    [
        [100, 101, 102],
        [110, 111, 112]
    ]
])

# la propiedad "shape" nos informa de su forma
array_3d.shape # (2, 2, 3) nos indica que es un array de 2 pilas, con 2 filas y 3 valores cada una
array_3d[1, ...]
"""
array([[100, 101, 102],
       [110, 111, 112]])
La notación con "..." es equivalente a array_3d[1, :, :] o array_3d[1]
"""

array_3d[..., 2]
"""
array([[2, 12],
       [102, 112]])
"""

# las iteraciones se hacen con respecto al primer eje:
for row in array_3d:
    print(row)

"""
[[ 0  1  2]
 [10 11 12]]
[[100 101 102]
 [110 111 112]]
"""

El Poder de la Nomenclatura: Vectores, Matrices y Tensores

Las joyas en el arsenal de NumPy tienen nombres mágicos: vectores, matrices y tensores, definidos por el número de dimensiones que abrazan. Cada dimensión, un eje que da forma y propósito a la estructura.

Vector: array de una sola dimensión.

Matriz: array con dos dimensiones.

Tensor: array con tres o más dimensiones.

En los anales de la programación, el número de dimensiones y elementos en un array se forja a través de su forma, un misterio en sí mismo. Allí, en la oscuridad del código, aguarda un atributo de gran poder: shape, que se ha mencionado en antiguos ejemplos. Cuando se invoca, revela una tupla de enteros, un mapa de dimensiones que trasciende lo ordinario.

En NumPy, cada dimensión es más que una simple medida; se convierte en un "eje". Dentro de un array de dos dimensiones, como el que nos rodea en este momento, cada dimensión se convierte en un eje, trazando una senda de conocimiento que nos guía a través del laberinto de datos. El eje se convierte en un faro, iluminando el camino hacia el entendimiento y la sabiduría en esta vasta tierra de programación.

[[0., 0., 0.],
 [1., 1., 1.]]

El primer eje, el vertical, tiene 2 elementos. El segundo eje, el horizontal, tiene 3 elementos.

Crear arrays sencillos

Los rituales para la creación son variados, desde forjar un array lleno de ceros hasta invocar un rango de números.

# crear un array rellenado con ceros
np.zeros(2) # array([0., 0.])

# crear un array con unos
np.ones(3) # array([1., 1., 1.])

# crear un array con un rango de elementos
np.arange(4) # array([0, 1, 2, 3])

# crear un array con elementos espaciados
# indicamos el primer número, el último y el salto entre cada uno
np.arange(2, 9, 2) # array([2, 4, 6, 8])

Añadir, eliminar y ordenar elementos

Dos de estas herramientas que los mortales como nosotros poseemos, sort y concatenate, se alzan como artefactos mágicos para la manipulación de los arrays.

Cuando el deseo es imponer orden y disciplina a los elementos de un array, invocamos sort. Este hechizo armoniza los elementos, colocándolos en una secuencia ascendente o descendente, permitiendo que la información fluya como un río en calma.

a = np.array([2, 1, 5, 3, 7, 4, 6, 8])
np.sort(a) # array([1, 2, 3, 4, 5, 6, 7, 8])

La magia de la concatenación, conocida como concatenate, nos permite unir dos o más arrays en una sola entidad. Como los hilos de un tapiz, se entrelazan, creando patrones y estructuras más complejas.

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

np.concatenate((a, b)) # array([1, 2, 3, 4, 5, 6, 7, 8])

x = np.array([[1, 2], [3, 4]])
y = np.array([[5, 6]])

np.concatenate((x, y), axis=0)
""" Con el parámetro axis indicamos en qué eje se concatena
array([[1, 2],
       [3, 4],
       [5, 6]])
"""

Para eliminar elementos no deseados de un array, recurrimos a los mecanismos de indexación, una técnica ancestral que nos permite retener solo la parte que anhelamos conservar.

Los Secretos del Cambio de Forma: Tamaño y Dimensión

En esta tierra, el tamaño y la dimensión son revelados por encantamientos específicos. El atributo shape permite vislumbrar la estructura de los arrays.

a = np.array([[[0, 1, 2, 3],
               [4, 5, 6, 7]],

              [[0, 1, 2, 3],
               [4, 5, 6, 7]],

              [[0 ,1 ,2, 3],
               [4, 5, 6, 7]]])


# número de dimensiones
a.ndim # 3

# número total de elementos
a.size # 24

# forma del array
a.shape # (3, 2, 4)

El ritual de reshape puede cambiar su forma a voluntad:

a = np.arange(6) # array([0 1 2 3 4 5])

b = a.reshape(3, 2)
"""
array([[0 1]
       [2 3]
       [4 5]])
"""

Los ejes nuevos pueden ser añadidos, extendiendo las fronteras del conocimiento.

a = np.array([1, 2, 3, 4, 5, 6])
a2 = a[np.newaxis, :] # convertimos el array "a" en un vector de fila
a2.shape # (1, 6)
"""
array([[1 2 3 4 5 6]])
"""

a3 = a[:, np.newaxis] # convertimos el array "a" en un vector de columna
a3.shape # (6, 1)
"""
array([[1]
       [2]
       [3]
       [4]
       [5]
       [6]])
"""

b = np.expand_dims(a, axis=0) # expandimos las dimensiones añadiendo un eje en la posición 0
b.shape # (1, 6)
"""
array([[1 2 3 4 5 6]])
"""

Operando con Poder: Operaciones Básicas con Arrays

Las operaciones básicas, la esencia de la magia numérica, se manifiestan en la suma, resta, multiplicación y división.


# La suma de matrices consiste en sumar la misma posición de cada celda entre cada matrix
a = np.array([1, 2])
b = np.ones(2, dtype=int)

res = a + b # array([2, 3])

# Funciona igual con la resta:
res = a - b # array([0, 1])

# la multiplicación:
res = a * a # array([1, 4])

# y la división:
res = a / a # array([1, 1])

En los anales de la programación, cuando nuestros arrays se extienden como imperios de datos, surge la necesidad de conocer el resumen de sus secretos. Nos encontramos en un punto donde anhelamos descubrir la suma de los elementos que conforman un array, un misterio que requiere ser desvelado.

a = np.array([1, 2, 3, 4])

a.sum() # 10

Cuando deseamos sumar una fila o columna en un array, nuestros conocimientos ancestrales nos guían hacia la solución: la especificación del eje:

a = np.array([[1, 1], [2, 2]])

a.sum(axis=0) # array([3, 3])
a.sum(axis=1) # array([2, 4])

Existe una regla fundamental que gobierna el flujo de energía numérica: la especificidad del eje. Pero, oh, en ocasiones, los aventureros olvidan esta verdad y se aventuran sin guía, sin especificar un eje en sus operaciones.

Cuando la especificidad se desvanece y la dirección se vuelve difusa, una fuerza invisible entra en juego. Esta fuerza es la suma de todas las celdas, una entidad poderosa que se eleva como un coloso en el centro del array multidimensional.

En momentos oportunos, es posible realizar operaciones entre un array y un número adicional, y esto está al alcance de tu poder:

a = np.array([1.0, 2.0])
a * 1.6 # array([1.6, 3.2])

NumPy despliega su magia al operar con cada celda junto al número, una hazaña que se conoce como broadcasting.

Y en el juego de los extremos, min y max revelan los límites del conocimiento.

a = np.array([[0.45053314, 0.17296777, 0.34376245, 0.5510652],
              [0.54627315, 0.05093587, 0.40067661, 0.55645993],
              [0.12697628, 0.82485143, 0.26590556, 0.56917101]])
a.min() # 0.05093587
a.max() # 0.82485143

En esta tierra de NumPy, estos son los cimientos de un vasto conocimiento, un tesoro de herramientas para la exploración y el descubrimiento. Las puertas del reino de la programación se abren de par en par, y el viaje continúa hacia horizontes aún más misteriosos y poderosos.

En el próximo post ya vamos a ponernos manos a la obra con un ejemplo real donde usaremos muchos de los conceptos vistos hoy junto con algún otro nuevo.