sábado, 23 de enero de 2021

5) Manipulacion de arrays y Transmisión.


Anteriormente. Operaciones con Arrays o Vectorización.


Manipulación de Arrrays y Transmisión.

Conceptos Previos.

Hasta ahora hemos visto:

- Arrays de 1 dimensión o unidimensionales:

[1,3,5,3,6]

- Arrays multidimensionales con 2 dimensiones a la que hemos llamado "matrices" ya que podemos asociarlas como que están formadas por filas y columnas.

[[1 2 3]

 [4 5 6]

 [7 8 9]]

- Pero Numpy nos permite trabajar con arrays de más dimensiones que las que hemos visto hasta ahora, como por ejemplo, de tres dimensiones. La forma de hacerlo es la misma que la de 2 dimensiones si bien el truco esta en anidar listas a 3 niveles de profundidad. Vamos a verlo paso a paso:

# importamos la libreria numpy para crear los array
import numpy as np

# array de 1 dimensión o unidimensional.
array1d = np.array([1,2])

# array de 2 dimensiones. 2 elementos de ancho por 2 de alto.
array2d = np.array([[1,2],[3,4]])

# ARRAY DE 3 DIMENSIONES. Pej 2 elementos de ancho * 2 de largo y 2 de 
# profundidad
# 8 elementos en total
array3d = np.array([[[1,2],[3,4,]],[[5,6],[7,8]]])
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]

La verdad es que el concepto es algo complicado de entender, ya que los humanos no solemos percibir más que las 3 dimensiones clásicas, pero si lo ves como lo que son una lista de lista la cosa es más sencilla. En nuestro ejemplo anterior tenemos una lista global que abarca el resto de las listas:

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

Un segundo nivel de listas:

[[1,2][3,4]] y [[5,6][7,8]]

y finalmente un tercer nivel que son las últimas listas anidadas:

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

y podríamos seguir creando arrays de 4, 5 o más dimensiones.

Para saber el número de dimensiones que tiene un array se utiliza la función .ndim

>>>print(array3d.ndim)
3

Manipulación de arrays.

Cuando estamos trabajando con matrices en Numpy hay veces que es necesario cambiar su forma o el tamaño de las mismas.

Por ejemplo, se puede modificar la forma de una matriz, así como dividir o unir matrices en una nueva.

Veamos las tres formas de manipular los arrays:

1.- Cambiar la forma de una matriz.

2.- Unir dos o más matrices.

3.- Dividir una matriz


1.- Cambiar la forma de una matriz.

Aunque, como ya vimos previamente, el número de elementos de un array es fijo, se determina en el momento de crearla, su forma o dimensión no lo es. Podemos modificar libremente la forma de la matriz siempre y cuando se conserve el mismo número de elementos.

Por ejemplo, podemos remodelar una matriz con unas dimensiones iniciales de 4 filas por 2 columnas (4x2) en una matriz de 2 filas por cuatro columnas (2x4)

>>># importamos la libreria numpy para crear los array
>>>import numpy as np

>>>a = np.array([[2,3],[4,5],[6,7],[8,9]])
>>>print(a.shape)
(4, 2)
>>># Cambiamos su forma a una matriz de 2 filas y 4 columnas
>>>a.shape=(2,4)
>>>print(a)
[[2 3 4 5]
 [6 7 8 9]]

De forma similar podemos también modificar las dimensiones utilizando el metodo reshape() de un array, lo que nos devolverá una nueva matriz de la forma que buscamos:


>>>import numpy as np

>>>a = np.array([[2,3],[4,5],[6,7],[8,9]])
>>>print(a.shape)
(4, 2)
>>># Cambiamos su forma a una matriz de 2 filas y 4 columnas
>>>b=a.reshape(2,4)
>>>print(a)
[[2 3]
 [4 5]
 [6 7]
 [8 9]]
>>>print(b)
[[2 3 4 5]
 [6 7 8 9]]


También existe un método abreviado especial llamado ravel(), que se utiliza para convertir cualquier matriz en unidimensional:

>>>c=b.ravel()
>>>print(c)
[2 3 4 5 6 7 8 9]


En vez de usar este método ravel(), puedes conseguir lo mismo usando b.reshape(b.size). Lo que hacemos es primero calcular el número de elementos de la matriz b con b.size, lo que utilizaremos luego como argumento para redimensionar la matriz, consiguiendo una nueva unidimensional.


2.- Unir dos o más matrices.


Si tienes dos o más matrices y quieres unirlas puedes usar le método concatenate(), lo que tienes que tener en cuenta, es que tengan la misma forma esperada en la dimensión a lo largo de la cual se unirán. Es decir, si por ejemplo tienes dos arrays de (2x3) puedes combinarlas formando una nueva matriz cuya dimensión será de (4x3). (Se trata de unirlas no de sumarlas)


>>># importamos la libreria numpy para crear los array
>>>import numpy as np

>>>a = np.array([[1,2,3],[4,5,6]])
>>>b = np.array([[7,8,9],[10,11,12]])

>>>c = np.concatenate((a,b))
>>>print(c)
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

(2x3) U (2x3) = (4x3)


También se puede especificar el eje (dimensión) a lo largo del cual se unirán las matrices, siendo axis=0 vertical (nuevas filas, si no lo pones se considera por defecto) y axis=1 horizontal (nuevas columnas):




>>>c = np.concatenate((a,b), axis=1)
>>>print(c)
[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]

axis = 1 unión por filas

(2x3) U (2x3) = (2x6)


3.- Dividir una matriz.

También podemos dividir una matriz en otras más pequeñas que sean del mismo tamaño utilizando el método split(). El único requisito que existe es que las matrices resultantes sean iguales, es decir que tengan la misma forma.

Por ejemplo una matriz de 2 filas y 3 columnas (2x3) se puede dividir en 2 matrices de 1 fila y 3 columnas (1x3) o bien, en tres matrices de  2 filas por 1 columna (2x1)


>>> import numpy as np

>>> # a es la matriz original de tamaño 2x3
>>> a = np.array([[6, 5, 4], [3, 2, 1]])

>>> # x, son dos arrays de tamaño 1x3. El primer argumento es la matriz
>>> # a dividir y el segundo el número de matrices a obtener.
>>> x = np.split(a, 2)
>>> print(x)
[array([[6, 5, 4]]), array([[3, 2, 1]])]

>>> # y, son tres arrays de tamaño 2x1
>>> # el tercer argumento indica el eje del corte.
y = np.split(a, 3, axis=1)
[array([[6],[3]]), array([[5],[2]]), array([[4],[1]])]



EJERCICIOS


En este ejercicio practicaremos como dividir y combinar los arrays en Numpy.

Crearemos una nueva matriz de 8 x 8 con algunos valores,.

Usaremos la función np.split() para dividir el array en dos nuevos de un tamaño de 4 x 8. Luego reconstruiremos el original de nuevo usando la función np.concadenate().

Repetiremos el ejercicio anterior pero creando ahora dos nuevos arrays de 4 x 8 y luego combinándolos en una nueva de tamaño 4 x 16.


SOLUCION

>>> import numpy as np
>>> m=np.random.randint(1,10,(8,8)) 
>>> #crea una matriz aleatoria de 8*8 con números enteros entre 1 y 10
>>> print(m)
[[5 3 8 5 8 4 6 4]
 [1 5 1 8 3 4 5 2]
 [9 9 7 6 3 5 3 4]
 [6 7 2 8 3 6 3 5]
 [6 8 7 3 9 1 5 2]
 [6 7 7 3 2 3 8 4]
 [8 5 3 1 2 9 2 5]
 [1 5 2 2 1 8 2 1]]

>>> #usamos split para crear dos matrices de 4*8
>>> n,p=np.split(m,2)
>>> print(n)
[[5 3 8 5 8 4 6 4]
 [1 5 1 8 3 4 5 2]
 [9 9 7 6 3 5 3 4]
 [6 7 2 8 3 6 3 5]]
>>> print(p)
[[6 8 7 3 9 1 5 2]
 [6 7 7 3 2 3 8 4]
 [8 5 3 1 2 9 2 5]
 [1 5 2 2 1 8 2 1]]

>>> #ahora tenemos que concatenarlas para obtener la matriz inicial
>>> q=np.concatenate((n,p),axis=0) #el axis cero es opcional
>>> print(q)
[[5 3 8 5 8 4 6 4]
 [1 5 1 8 3 4 5 2]
 [9 9 7 6 3 5 3 4]
 [6 7 2 8 3 6 3 5]
 [6 8 7 3 9 1 5 2]
 [6 7 7 3 2 3 8 4]
 [8 5 3 1 2 9 2 5]
 [1 5 2 2 1 8 2 1]]


>>> #ahora creamos 2 matrices de 4*8 y las combinamos en otra de 4*16
>>> r=np.random.randint(1,10,[4,8])
>>> s=np.random.randint(1,10,[4,8])
>>> print(r)
[[7 8 9 2 8 2 9 3]
 [1 6 6 6 8 4 3 7]
 [6 4 2 9 9 8 4 7]
 [4 2 4 5 8 9 7 3]]
>>> print(s)
[[5 6 9 5 7 1 7 6]
 [8 9 8 8 6 3 5 3]
 [8 9 1 9 6 7 3 3]
 [7 6 1 4 6 1 1 1]]
>>> t=np.concatenate((r,s),axis=1)
>>> print(t)
[[7 8 9 2 8 2 9 3 5 6 9 5 7 1 7 6]
 [1 6 6 6 8 4 3 7 8 9 8 8 6 3 5 3]
 [6 4 2 9 9 8 4 7 8 9 1 9 6 7 3 3]
 [4 2 4 5 8 9 7 3 7 6 1 4 6 1 1 1]]



Transmisión "Broadcasting".

Como hemos visto hasta ahora, las operaciones matemáticas que se realizan con matrices en Numpy, se hacen elemento a elemento. Es decir, cuando realizamos una operación con dos matrices, es necesario que las dos matrices tengan la misma forma, ya que por ejemplo cuando sumamos la matriz A con la matriz B, esta suma se hace sumando cada elemento de a con su correspondiente elemento en b que este en la misma fila y columna, que ocupe la misma posición.

Ahora pensemos en que ocurriría si las matrices tuvieran diferentes dimensiones. Si las forma de ambas matrices es totalmente distinta, no tienen el mismo número de filas o de columnas, no tenemos nada que hacer y cualquier operación que hagamos con las mismas no dará error. Ahora bien, si su forma son las mismas en una o más dimensiones, es posible que Numpy pueda hacer lo que se llama "broadcasting" (nota: transmisión en español)

El ejemplo más simple de una transmisión es cuando multiplicamos una matriz por un escalar, es decir por un número.


>>> b = numpy.arange(5)
>>> print(b*2)
[0,2,4,6,8]


En este caso, el valor del número se transmite a la matriz, es decir se usa en la operación de multiplicación aplicándose uno por uno a cada uno de sus elementos. En el ejemplo anterior, lo puedes imaginar como que hay otra matriz de la misma dimensión que b y llena del valor escalar, el 2 en nuestro ejemplo, en el que el resultado se realiza multiplicando elemento a elemento de ambas matrices:

b=[0,1,2,3,4] * [2,2,2,2,2]

Sin embargo Numpy es tan eficiente que realmente no desperdicia la memoria del ordenador creando una matriz real para hacer esto.

Vamos a ver unos ejemplos en los que se pueda realizar la transmisión de forma exitosa en Numpy.


Ejemplo: una matriz de 3x2 multiplicada por una matriz de 1x2:

>> import numpy as np

>>> # creamos una matriz a con 6 elementos y la redimensionamos para que 
>>> # tenga una forma de 2x3
>>> a = np.arange(6).reshape(3*2)

>>> # creamos una matriz b unidimensional pero para poder multiplicarlas
>>> # ambas tienen que tener la misma dimensión por lo que la redimensionaremos
>>> # a un array de (1x2)
b = np.arrray([12,6]).reshape(1,2)

>>> print(a)
[[0 1]
 [2 3]
 [4 5]]

>>> print(b)
[[12  6]]

>>> print(a*b)
[[ 0  6]
 [24 18]
 [48 30]]


Ejemplo: una matriz de 4x1x6 multiplicada por una matriz de 2x6:


>>> import numpy as np
>>> a = np.arange(4*6).reshape(4,1,6)
>>> b = np.arange(2*6).reshape(2,6)
>>> print(a)
[[[ 0  1  2  3  4  5]]

 [[ 6  7  8  9 10 11]]

 [[12 13 14 15 16 17]]

 [[18 19 20 21 22 23]]]

>>> print(b)
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]

>>> print(a*b)
[[[  0   1   4   9  16  25]
  [  0   7  16  27  40  55]]

 [[  0   7  16  27  40  55]
  [ 36  49  64  81 100 121]]

 [[  0  13  28  45  64  85]
  [ 72  91 112 135 160 187]]

 [[  0  19  40  63  88 115]
  [108 133 160 189 220 253]]]

Y para finalizar vamos a ver un ejemplo matemático práctico como es el calcular la distancia que hay desde un punto fijo, al que llamaremos origen con respecto a otros puntos de una lista de coordenadas en 3d. 

>>> # Vamos a crear 100 coordenadas aleatorias en 3d (x,y,z)
>>> puntos = np.random.random((100,3))
>>> puntos
array([[0.23154991, 0.82657602, 0.81438918],
       [0.49690704, 0.84160337, 0.91788229],
       [0.95621859, 0.96536974, 0.46902862],
       ....................................,
       ....................................,
       [0.7396999 , 0.12686013, 0.85540038],
       [0.778411  , 0.01367006, 0.53266504],
       [0.51195094, 0.91353227, 0.4819603 ]])

Ahora establecemos las coordenadas de origen:

 >>> origen = np.array((2.0,3.2,-1.1))

Para calcular la distancia, tenemos en cuenta lo siguiente. Si tenemos dos puntos en el espacio a=(a1,a2,a3) y b=(b1,b2,b3) la distancia se calcula como:



Si esto lo aplicamos a nuestro ejemplo, el código en python sería el siguiente:

>>> distancias = (puntos - origen)**2
>>> distancias = np.sqrt(np.sum(distancias, axis=1))

>>> distancias
array([3.45714954, 3.13167248, 3.27924038, 3.50113825, 3.60435357,
       3.90598647, 3.32016053, 3.17716632, 3.68095709, 3.45738749,
       ...........................................................
        ..........................................................
       3.19249369, 3.75821933, 3.42780899, 3.10809282, 3.4557802 ,
       3.61527044, 3.37636469, 3.33940144, 3.55850126, 3.36327491])

Si quisiéramos saber cual es el punto más distante:

>>> pto_max = np.argmax(distancias)
>>> print(pto_max)
43


EJERCICIO

En este ejercicio repasaremos el concepto de "transmisión" de Numpy que es una herramienta poderosa para tratar con arrays diferentes pero que sean compatibles para poder operar con ellos.

Crea un archivo de texto que contenga los siguientes datos, lo puedes llamar circulo.dat

 0.012  1.500
 1.338  0.678
 1.177  0.930
 1.431  0.451
 1.232  0.856
 0.697  1.328
 1.499  0.058
 1.403  0.532
 1.173  0.935
 0.555  1.393

Estos datos son coordenadas x,y a lo largo de un círculo. Traduciremos estas coordenadas a un vector y luego representaremos en un gráfico los puntos originales con los vectores creados. Lo entenderás más facilmente al ver el desarrollo del ejercicio.

SOLUCIÓN

>>> # Importamos las librerías necesarias
>>> import numpy as np
>>> import matplotlib.pyplot as plt

>>> #numpy permite cargar datos de texto como veremos en el 
>>> # capitulo 6 
>>> matriz=np.loadtxt("circulo.dat")
>>> print(matriz.shape)
(10, 2)

>>> # Ya tenemos los puntos x e y en forma de vector.
>>> print(matriz)
[[0.012 1.5  ]
 [1.338 0.678]
 [1.177 0.93 ]
 [1.431 0.451]
 [1.232 0.856]
 [0.697 1.328]
 [1.499 0.058]
 [1.403 0.532]
 [1.173 0.935]
 [0.555 1.393]]

>>> # Ahora los separamos
>>> x,y=np.split(matriz,2,axis=1)
>>> print(x)
[[0.012]
 [1.338]
 [1.177]
 [1.431]
 [1.232]
 [0.697]
 [1.499]
 [1.403]
 [1.173]
 [0.555]]
>>> print(y)
[[1.5  ]
 [0.678]
 [0.93 ]
 [0.451]
 [0.856]
 [1.328]
 [0.058]
 [0.532]
 [0.935]
 [1.393]]

>>> # Y los representamos gráficamente.
>>> plt.plot(x,y,".")
>>> plt.show()




En resumen, es posible hacer operaciones con arrays de diferente tamaño si Numpy puede transformar estos arreglos en arreglos del mismo tamaño: está conversión se llama Transmisión o Broadcasting en Inglés.

La siguiente imagen nos muestra un ejemplo de transmisión o broadcasting.




En la práctica la transmisión o broadcasting se suele utilizar cuando tenemos que resolver un problema cuya salida de datos es una matriz con más dimensiones que las de entrada.





No hay comentarios:

Publicar un comentario