NumPy - Avanzado#

Copias y vistas#

Al operar y manipular arrays, sus datos a veces se copian en un nuevo array y otras veces no. Esto puede ser muy confuso. Hay tres casos:

Las asignaciones simples no hacen copia del array#

import numpy as np
A = np.arange(12)
print(A)
B = A            # No se crea un array nuevo
B is A           # A y B son dos nombres para el mismo array
[ 0  1  2  3  4  5  6  7  8  9 10 11]
True
B.shape = 3,4    # por ejemplo, cambiarle la forma a B, le cambia la forma a A
A.shape
print(A)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
B[0,:] = 80

print(A)
[[80 80 80 80]
 [ 4  5  6  7]
 [ 8  9 10 11]]

Python pasa arrays ‘como referencia’, por lo que las llamadas a funciones no hacen copia.

def f(X):
    '''
    Una función que espera un array X y cambia X[0] a 2.0
    '''
    X[0] = 2.0
    

A = np.ones([4,5])   # <--- array de unos, de 4 filas y 5 columnas

print('Antes de llamar a la función:', A[0])          # <--- imprimo la primer fila

f(A)

print('Luego de llamar a la función:', A[0])                        
Antes de llamar a la función: [1. 1. 1. 1. 1.]
Luego de llamar a la función: [2. 2. 2. 2. 2.]

Vistas.#

Diferentes arrays pueden compartir los mismos datos. El método de view crea un nuevo array que “mira” los mismos datos.

print(A)
C = A.view()
C is A
[[2. 2. 2. 2. 2.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]
False
C.shape = 2,6                      # Cambiar la forma de C, no cambia la de A
print(C)
A.shape
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[7], line 1
----> 1 C.shape = 2,6                      # Cambiar la forma de C, no cambia la de A
      2 print(C)
      3 A.shape

ValueError: cannot reshape array of size 20 into shape (2,6)
C[0,:] = 3.0                         # Pero cambiar los datos de C, cambia los datos de A
A
array([ 3,  3,  3,  3,  3,  3,  6,  7,  8,  9, 10, 11])

Hacer un slicing de un array devuelve una vista:

S = A[1:3]     
S[:] = 10           
A
array([ 3, 10, 10,  3,  3,  3,  6,  7,  8,  9, 10, 11])

Copia#

D = A.copy()                          # se crea un nuevo array y se copian los datos
D is A
False
D[0] = 23
print(D)
print(A)
[23 10 10  3  3  3  6  7  8  9 10 11]
[ 3 10 10  3  3  3  6  7  8  9 10 11]

Indexado (más sofisticado)#

NumPy ofrece más técnicas de indexación que las secuencias regulares de Python. Además de indexar por enteros y sectores, como vimos anteriormente, los arrays pueden indexarse mediante arrays de enteros y arrays lógicos.

Indexado con un array de índices¶#

import numpy as np
A = np.arange(12)**2                       # array de cuadrados de los números del 0 al 11
print(A)

I = np.array([1, 1, 3, 8, 5])              # array de índices

A[I]                                       # Los elementos de A en las posiciones dada por I                         
[  0   1   4   9  16  25  36  49  64  81 100 121]
array([ 1,  1,  9, 64, 25])
J = np.array([[3, 4], [9, 7]])      # array bidimensional de índices

A[J]                                # <--- con la misma forma que J
array([[ 9, 16],
       [81, 49]])

Cuando el array indexado es multidimensional, un array de índices se refiere a la primera dimensión del array indexado.

El siguiente ejemplo muestra este comportamiento al convertir una imagen ‘grayscale’ en una imagen ‘truecolor’ (o RGB) usando una paleta.

paleta = np.array( [ [0.0, 0.0, 0.0],              # negro
                      [1.0, 0.0, 0.0],             # rojo
                      [0.0, 1.0, 0.0],             # verde
                      [0.0, 0.0, 1.0],             # azul
                      [1.0, 1.0, 1.0] ] )          # blanco

imagen = np.array([[ 0, 1, 2, 0 ],           # cada valor, sirve como indice en el mapa de colores (paleta)
                   [ 0, 3, 4, 0 ]])

print(imagen.shape)
print(paleta[imagen].shape)                         
import matplotlib.pyplot as plt

plt.figure()
plt.imshow(imagen, cmap = 'gray')
plt.axis('off')
plt.show()

plt.figure()
plt.imshow(paleta[imagen], cmap = 'gray')
plt.axis('off')
plt.show()

También podemos proporcionar índices para más de una dimensión. Los arrays de índices para cada dimensión deben tener la misma forma.

A = np.arange(12).reshape(3,4)
A
I = np.array([[0,1],                        # Índices para la primera dimesión de A
              [1,2]])

J = np.array([[2,1],                        # Índices para la segunda dimesión de A
              [3,3]])

A[I,J]                                      # I y J deben tener la misma forma
A[I,2]
A[:,J]                                     

Podemos poner I y J en una lista y luego hacer la indexación con la lista

L = [I,J]
print(L)

A[L]                                       # equivalente a A[I,J]

Sin embargo, no podemos hacer esto poniendo I y J en un array, ya que este array se interpretará como indexando la primera dimensión de A.

S = np.array([I,J])
print(S)

A[S]                                       # No es lo mismo que lo visto arriba
A[tuple(S)]                                # lo mismo que A[I,J]

Otro ejemplo de indexación con arrays de índices, es la búsqueda del valor máximo de series dependientes del tiempo:

time = np.linspace(20, 145, 5)                 # array arbitrario que simula el tiempo    
data = np.random.random((5,4))                 # 4 series arbitrarias (columnas) que simulan depender del tiempo

print(time)

print(data)
ind = data.argmax(axis=0)                   # índices de los máximos para cada columna
ind
time_max = time[ind]                        # tiempos correspondientes al máximo 
data_max = data[ind, range(data.shape[1])]  # => data[ind[0],0], data[ind[1],1]...

print(time_max)

print(data_max)
# graficando los resultados...

plt.figure()
plt.plot(time, data)
plt.plot(time_max, data_max, 'ok')
plt.show()

También puede usar la indexación para asignar valores a arrays:

A = np.arange(5)
A
A[[1,3,4]] = 0
A

Sin embargo, cuando la lista de índices contiene repeticiones, la asignación se realiza varias veces, pisando el último valor:

A = np.arange(5)
A
A[[0,0,2]] = [1,2,3]
A

Indexado lógico#

A = np.arange(12).reshape(3,4)
B = A > 4

print(A)
print(B)                                          # B es un array lógico de las mismas forma de A
A[B]                                       # array 1D con los elementos de A donde B es True

Esto es muy util para hacer asignaciones basados en un criterio:

A[B] = 0                                   # Esto hace cero todos los elementos de A que son mayores a 4
A

Como ejemplo, de indexado lógico, trataremos de segmentar el “tejido blando” de un fantoma:

import matplotlib.pyplot as plt

I = plt.imread('./Data/cirs_slice.png')[...,0]


plt.figure(figsize = (6,6))
plt.imshow(I, cmap = 'gray')
plt.show()
plt.figure()
plt.hist(I.ravel(), bins=100, normed=1)       # matplotlib version (plot)
plt.show()
Mask = np.zeros(I.shape)

Mask[(I>0.4) & (I<0.45)] = 1 #usar 0.6 y 0.8 para la médula y 0.4 y 0.45 para el tejido blando.¿y para pulmón?

print(Mask.max())
print(Mask.min())
plt.figure(figsize = (6,6))
plt.imshow(Mask, cmap = 'gray')
plt.show()
red_mask = np.dstack([Mask, np.zeros_like(Mask), np.zeros_like(Mask)])

print(red_mask.shape)
plt.figure(figsize = (6,6))
plt.imshow(I, cmap = 'gray')
plt.imshow(red_mask, alpha = 0.5, cmap = 'gray')
plt.show()

Un poco de IO#

De y hacia texto#

data = np.loadtxt('./Data/ejemplo_texto_tabla.txt', delimiter = ',', skiprows = 7)
data.shape
plt.figure()
plt.plot(data[:,0], data[:,1], '-*')
plt.show()
z = -np.cos(data[:,0])

print(z.shape)

nueva_data = np.concatenate([data, z[:,np.newaxis]], axis = 1)

plt.figure()
plt.plot(nueva_data[:,0], nueva_data[:,1], '-*')
plt.plot(nueva_data[:,0], nueva_data[:,2], '-+')
plt.show()
mi_header = "Este es un ejemplo de header\ncon más de una línea\n\nx, y, z\n\n"

np.savetxt('otro_ejemplo.txt', nueva_data, delimiter=',', header = mi_header) 

Formatos propios#

X = np.ones((3, 3))

np.save('mi_array.npy', X)
X2 = np.load('mi_array.npy')

print(X2)
Y = 2*X
Z = Y + 23

np.savez('mis_arrays.npz', a = X, b = Y, c = Z)
data = np.load('mis_arrays.npz')

print(data['a'])
print(data['b'])
print(data['c'])