viernes, 29 de enero de 2021

7) Números aleatorios con Numpy














Números Aleatorios con Numpy.


Numpy nos proporciona una amplia gama de funciones para generar números aleatorios en matrices. Estas funciones están disponibles en el módulo numpy.random. Este módulo nos proporciona varias funciones para construir matrices aleatorias, entre las que se incluyen:

- random = Números aleatorios uniformes.

- normal = Los números aleatorios se agrupan formando una distribución normal.

- choice = Muestra aleatoria de una matriz dada.

Veamos algunos ejemplos.

>>> import numpy as np
>>> a = np.random.random((2,2))
>>> print(a)
[[0.01642991 0.53496398]
 [0.43409542 0.88082714]]

# Si queremos elegir 100 números entre el 0 y el 9
>>> b = np.random.choice(np.arange(10), 100)
>>> print(b)
[2 8 3 7 3 5 5 3 2 7 6 1 2 4 1 4 1 5 0 0 5 6 1 1 5 3 0 2 4 5 7 7 4 9 7 5 3
 7 3 9 1 9 4 4 6 6 9 5 6 2 1 2 6 9 6 0 7 5 0 3 1 0 1 1 6 5 9 9 2 3 1 4 1 1
 0 8 0 7 8 8 3 2 0 4 2 9 4 5 5 1 0 9 0 6 4 5 9 5 2 0]


Esto sería lo básico para construir arrrays con números aleatorios, si necesitas más información la puedes encontrar en el manual de numpy aquí.


PROBLEMA

Generaremos una matriz unidimensional de 1_000 números aleatorios distribuidos uniformemente para lo que utilizaremos el módulo numpy.random

Luego usaremos numpy.mean() y numpy.std() para obtener la media y la desviación estandar de dichos números.

Calcularemos la media y la desviación estándar de los datos.

Para terminar visualizaremos los datos en forma de distribución de frecuencias usando un histograma mediante la función plt.hist()


>>> import numpy as np
>>> import matplotlib.pyplot as plt

>>> #las funciones que se suelen utilizar para generar números aleatorios son:
>>> # random rand randint
>>> # normal, randn
>>> # choice
>>> # la diferencia entre random y rand es que el primero solo le puedes decir el tamaño
>>> # y al segundo el tamaño y la forma del array
>>> matriz=np.random.rand(1000)

>>> #creamos un histograma
>>> plt.hist(matriz)
>>> print(matriz)
>>> print('media = ', np.mean(matriz)) #media
>>> print('desviación standar = ', np.std(matriz)) #desviacion standar
>>> plt.show()

[0.32448446 0.17029493 0.88975758 0.58518918 0.03291183 0.75361128
 0.52061019 0.10227153 0.04804696 0.5571141  0.95852831 0.35740444
 0.74243456 0.9005181  0.02441245 0.55563885 0.01789706 0.78057959
 0.95879197 0.58012514 0.42234119 0.45749953 0.20109267 0.83788592
 0.74747628 0.40081211 0.32264574 0.18459283 0.85711583 0.40135899
 0.5850813  0.11131699 0.41030881 0.29485806 0.35575143 0.17555042
 0.07440257 0.27566318 0.74319726 0.59116089 0.8988699  0.88648433
 0.57924085 0.71687762 0.37703163 0.00550032 0.66757755 0.36496853
 0.86996948 0.75704079 0.86827924 0.60565516 0.73171818 0.25055696
 0.26490559 0.36708881 0.27852253 0.27251585 0.73235831 0.50528246
 0.07364568 0.10426392 0.75029462 0.04938492 0.85746969 0.13634836
 0.18648673 0.02747872 0.6551644  0.88502587 0.22726501 0.74367221
 0.7782758  0.59613769 0.73653628 0.35454465 0.35902062 0.78905887
 0.78022244 0.29818898 0.44463228 0.21355751 0.07544048 0.15893986
 0.44313512 0.08344274 0.51677987 0.32827173 0.83921191 0.45044779
 0.73769016 0.35815145 0.3040427  0.00884398 0.00102806 0.20599428
 0.29262636 0.56232598 0.11748816 0.65353648]
media =  0.4549525097391814
desviación standar =  0.28265017630941747







Próximo Post. Nociones de Algebra lineal y Polinomial con Numpy.


domingo, 24 de enero de 2021

6) Lectura y escritura de datos con Numpy.





Lectura y Escritura de datos.



En este post veremos brevemente como leer datos de un archivo con numpy y también como escribirlos. Es algo que usaremos muy frecuentemente, ya sea porque hemos recogido datos de un sensor de la rapsberry pi que tenemos guardado en un fichero, porque nos lo han mandado para que los utilicemos o porque queremos separar la parte que se refiere al programa de la parte de los datos.

Numpy nos ofrece varias funciones para poder leer y escribir datos numéricos en archivos simples que tengan un diseño en formato columna. Los comentarios y los delimitadores de columna se manejan automáticamente.

Lectura.


Numpy nos ofrece varias funciones para poder cargar datos, pero la que usaremos con mayor frecuencia es la función loadtxt.

Vamos a verlo con un ejemplo. Supón que tenemos los siguientes datos en un archivo llamado datos.dat. No es más que un archivo con los siguientes datos:

# x y
-4 5
3 9
2 12
# -4 11 >> dato invalido!!
7 nan
5 21
6 20

Vamos a leer los datos en forma de array de Numpy con tan solo llamar a la función loadtxt().

>>> import numpy as np
>>> xy = np.loadtxt("datos.dat")
>>> print(xy)
[[-4.  5.]
 [ 3.  9.]
 [ 2. 12.]
 [ 7. nan]
 [ 5. 21.]
 [ 6. 20.]]


Como puedes ver es muy sencillo. Comentemos algunos aspectos de como funciona loadtxt:

- La función lee todas las líneas, saltándose las líneas que contengan comentarios es decir las que comiencen por #. Esto se podría cambiar utilizando el parámetro comments. También podrías saltar las n primeras líneas, utilizando el parámetro skiprows.

- El valor indefinido (nan) se reconoce automáticamente.

- El programa espera que los datos dentro de cada línea estén separados por espacios. Esto se podría cambiar usando el parámetro delimeter

- El tipo de datos de la matriz Numpy se elige automáticamente en función de los valores, aunque por defecto nos devolverá un array con datos de tipo float. (Si quieres cambiarlo puedes usar el argumento dtype).

Otras funciones que también sirven para leer datos son:

- La función load sirve para leer datos en el formato comprimido de Numpy, que suele tener las extensiones npy o npz.

- La función fromfile sirve para leer los datos en formato binario.

- La función genfromtxt es mucho más flexible que loadtxt y es muy útil cuando el archivo está mal formateado o faltan datos. Sin embargo en la mayor parte de los casos loadtxt es más que suficiente.


Escritura.


Si la función para leer datos es loadtxt te puedes imaginar que para escribir datos a un archivo la función será savetxt. Tiene dos argumentos obligatorios el nombre del archivo y el array a guardar.

Podemos poner un encabezado al archivo con el argumento header.

También podemos usar un delimitador distinto de los espacios, que es el establecido por defecto, usando el argumento delimiter. 

 Y tambien podemos definir el fomato de los datos que por defecto se guardan con 18 cifras decimales con el parámetro fmt.

Veámoslo todo esto en un ejemplo.

Podemos poner los argumentos de esta forma o también lo puedes poner dentro de la función como normalmente hacemos:

>>> import numpy as np

>>> argumentos = {
    'header':'ejemplo de archivo',
    'fmt':'%7.3f',
    'delimiter':','
}

Creamos el array y lo guardamos.

>>> xy = np.array([[123.3434,34.55656],[-123233.232323,223323.32],[58,4545]])
>>> np.savetxt('archivo.dat', xy, **argumentos)

Si abrimos el archivo con un editor de textos vemos que el contenido es:

# ejemplo de archivo
123.343, 34.557
-123233.232,223323.320
 58.000,4545.000

y hasta aquí este capítulo.


EJERCICIO

En este ejercicio vamos a repasar lo que hemos visto sobre como escribir y leer arrays en el disco.

Lo primero que tenemos que hacer es crear un archivo de texto que contenga los siguientes datos.

0.012 1.243

1.338 0.321

1.177 0.742

1.431 0.500

1.232 1.901

0.697 1.432

1.499 0.725

1.403 0.543

1.173 0.123

0.555 1.054

Le llamaré prueba.dat.  Como veis, este archivo, contiene pares de valores de coordenadas x e y. 

Lo primero que haremos será leer los datos del archivo en Numpy con la función numpy.loadtxt().

Lugo vamos a sustituir todos los valores y por el número 4.5 para luego volver a guardar los arrays en un nuevo archivo con la función numpy.savetxt().


SOLUCIÓN


>>> import numpy as np

>>> archivo=np.loadtxt("prueba.dat") #cargamos los datos
print(archivo)
[[0.012 1.243]
 [1.338 0.321]
 [1.177 0.742]
 [1.431 0.5  ]
 [1.232 1.901]
 [0.697 1.432]
 [1.499 0.725]
 [1.403 0.543]
 [1.173 0.123]
 [0.555 1.054]]

>>> archivo[:,1:]=4.5 #cambiamos todos los valores de y por 4.5
>>> print(archivo)
[[0.012 4.5  ]
 [1.338 4.5  ]
 [1.177 4.5  ]
 [1.431 4.5  ]
 [1.232 4.5  ]
 [0.697 4.5  ]
 [1.499 4.5  ]
 [1.403 4.5  ]
 [1.173 4.5  ]
 [0.555 4.5  ]]

# para guardar solo existen 2 argumentos obligatorios que es nombre del archivo
# y array para guardar los datos que por defecto lo hace con 18 números decimales
# pero se puede modificar con el comando fmt '%entero.decimalf' parecido al format de strings
>>> np.savetxt("ejercicio.dat",archivo, fmt='%1.3f')



Próximo Post. Números aleatorios en Numpy.


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.





sábado, 9 de enero de 2021

4) Operaciones con vectores en arrays o vectorización.



Operaciones con vectores en arrays o vectorización.


El trabajar con bucles "for" en python resulta muy lento. Si tienes que repetir una operación matemática en muchos elementos consecutivos de una matriz, siempre resulta mejor utilizar una operación vectorizada si es posible, es hasta un 80% más rápido. 

En la práctica una operación vectorizada, significa rehacer nuestro código para evitar el uso de bucles y utilizar en su lugar slicing, rebanadas o vectores de arrays de Numpy para aplicar la operación a todo o parte del array.

Vamos a ver la diferencia que existe con un ejemplo. Supongamos que tienes que calcular la diferencia existente entre los elementos consecutivos de una matriz. 

Como en este ejemplo solo queremos ver la diferencia de tiempos de ejecución que hay con ambos sistemas vamos a crear una matriz "a" que va a contener todos los números desde el cero al 999. 

La matriz "a" seria algo así:

a = [0,1,2,3,4,5,6,..........,998,999], tiene 1000 elementos

Lo que queremos hacer es obtener otra matriz de resultados, a la que llamaremos "b", con la diferencia entre los elementos consecutivos de la matriz. Es decir, la matriz "b" seria algo como esto:

b = [(1-0),(2-1),(3-2),(4-3).....................(998-997),(999-998)] que tiene 999 elementos.

Como ves en esta matriz, todos sus elementos van a ser unos. Pero esto no nos interesa, no nos interesa el resultado, sino el proceso que hay que seguir en python para hacerlo.


Para ello vamos a crear un archivo en python, yo lo llamaré consecutivos.py, con el siguiente código:


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

# Creamos la matriz a con 1000 números consecutivos del 0 al 999
a = np.arange(1000)

# Creamos la matriz b rellena con ceros para luego almacenar los resultados.
# Al estar formada con la diferencia entre 2 elementos consecutivos, tendrá
# 1000 - 1 elementos, es decir 999
b = np.zeros(999, int)

# Bucle for que calcula la diferencia entre un elemento (i) y el anterior
# (i-1), recorre haciendo el calculo con un bucle for la matriz a
# y asigna el resultado en la matriz b.
# e.j b[0]= a[1] que es 1 menos a[0] que es el cero 
for i in range(1,len(a)):
    b[i-1]=a[i]-a[i-1]


Pues bien, esto mismo se puede conseguir usando una operación vectorizada, con el siguiente código:

c = a[1:] - a[:-1]

Con esto estaríamos restando, elemento a elemento, la siguiente rebanada de la matriz a:

a[1:] = [1,2,3,4,...,998,999] desde el elemento segundo, cuyo valor es 1, hasta el final. (se empieza a contar en el cero)

a[: -1] = [0,1,2,3,....,997,998] desde el primer elemento que es el 0 hasta el penúltimo, el 998.


Si no lo acabas de ver bien, imagínatelo en pequeñito, con este ejemplo.

[1,2,3,4,5]

arr[1:]   → [2,3,4,5]

arr[:-1] →  [1,2,3,4]

dif =           [1,1,1,1]


Vamos ahora a juntarlo todo y ver lo que se tarda en ejecutar de las dos formas. Pero para que se note un poco más y se vea claramente la diferencia vamos a realizarlo, no con una matriz no de 1.000 elementos sino una de 1.000.000 de números.


# importamos la libreria numpy para crear los array
import numpy as np
# importamos time para medir el tiempo de ejecución
from time import time

# Creamos la matriz a con 1.000.000 de números consecutivos del 0 al 999.999
# en python el guion bajo _ nos permite separar los miles para verlo mejor.
a = np.arange(1_000_000)

# Creamos la matriz b rellena con ceros para luego almacenar los resultados.
# Al estar formada con la diferencia entre 2 elementos consecutivos, tendrá
# 1.000.000 - 1 elementos, es decir 999.999
b = np.zeros(len(a-1), int)

# Bucle for que calcula la diferencia entre un elemento (i) y el anterior
# (i-1), recorre haciendo el calculo con un bucle for la matriz a
# y asigna el resultado en la matriz b.
# e.j b[0]= a[1] que es 1 menos a[0] que es el cero 
t0 = time()
for i in range(1,len(a)):
    b[i-1]=a[i]-a[i-1]
t1 = time()

# Utilizando vectores de la matriz o slicing en el array.
t2 = time()
c = a[1:]-a[:-1]
t3 = time()

print("tiempo del bucle for ", (t1-t0))
print("tiempo con vectorización", (t3-t2))
print((t1-t0)>(t3-t2))

Yo, en mi equipo obtengo los siguientes resultados:

tiempo del bucle for  0.6465959548950195
tiempo con vectorización 0.0030362606048583984
True

Como puedes ver, la diferencia de tiempo es MUY significativa.


Seleccionar elementos que cumplan una determinada condición en un vector.


Una tarea que se realiza con bastante frecuencia es seleccionar elementos de un vector. Esta selección se pude llevar a cabo en base a índices o en base a un determinado criterio (por ejemplo que los elementos a seleccionar sean mayores que un determinado valor o que se encuentren en un determinado rango)

Vamos a realizar un ejemplo. Creamos una matriz de ejemplo en Numpy:

>>> import numpy as np

>>> matriz = np.arange(10)

>>> matriz
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Ahora vamos a seleccionar todos los elementos del array que sean mayores que 5. Al utilizar operadores de igualdad o comparación vamos a obtener un nuevo array con valores booleanos.

>>> matriz > 5
array([False, False, False, False, False, False,  True,  True,  True,
        True])
Como ves obtenemos un nuevo vector en el que los 6 primeros elementos son falsos y los 4 últimos verdaderos. Este resultado se puede guardar en una variable y utilizarlo para seleccionar los elementos que cumplen la condición.

Si a un array de Numpy se le pasa un vector de valores booleanos el resultado es un nuevo vector en el que solo estarán los elementos cuyas condiciones sean ciertas. Vamos a verlo en nuestro ejemplo:

>>> mayor_5 = matriz > 5

# seleccionamos los elementos de matriz en base a matriz > 5
>>> matriz[mayor_5]
array([6, 7, 8, 9])
Y como puedes ver hemos seleccionado los elementos de nuestra matriz original que cumplen la condición de ser mayores de 5.


Seleccionar elementos en base a múltiples condiciones en Numpy


Una vez que hemos visto como seleccionar elementos de un vector en base a una condición, podemos hacer las selecciones complejas que queramos utilizando operadores lógicos igual que en Python. Por ejemplo vamos como obtendríamos todos los números mayores que 5 pero a la vez que sean pares. Recordemos que para que un número sea para el resto de dividir ese número entre dos tiene que ser cero. Pues bien vamos a utilizar la función np.mod(matriz, divisor) que nos va a proporcionar el resto de dividir cada elemento de la matriz entre el divisor que elijamos.

Con todo lo anterior para elegir cual de los elementos son pares y mayores que 5 el código sería el siguiente:

>>> matriz[np.mod(matriz,2) & matriz > 5]
array([6, 8])

Le estamos diciendo Numpy que selecciones los elementos de la matriz que cumplan 2 criterios:
- Que el resto de su división entre 2 sea cero o lo que es lo  mismo que el número es par.
- Que sea mayor que 5.

Como hemos visto, con está técnica podemos filtrar grandes cantidades de datos en python de forma muy eficiente ya podemos implementar fácilmente filtros complejos. 



EJERCICIOS.

Calculo Diferencial

Con este ejercicio vamos a practicar también la vectorización, lo cual es crucial para obtener buenos resultados en términos de velocidad de cálculo y rendimiento con Numpy.

Para el que lo haya visto recordar que las derivadas, en matemáticas, se pueden calcular numéricamente con el método de las diferencias finitas como:

f′(xi)=(f(xi+Δx)−f(xi−Δx))/2Δx

Vamos a construir una matriz unidimensional que contenga los valores de xi en el intervalo 0 y π/2 con incrementos de 0.10.

Luego calcularemos numéricamente la derivada de la función seno y coseno en ese intervalo (excluyendo los puntos que constituyen los extremos) y para terminar las representaremos gráficamente.

Primeramente importamos las librerías necesarias:

>>> import numpy as np
>>> import matplnotlib.pyplot as plt
Con un array de Numpy realizamos los cálculos y construimos las funciones seno y coseno entre 0 y pi/2.

>>> dx=0.10
>>> x=np.arange(0,np.pi/2,dx) # Intervalo entre 0 y pi/2 con incrementos de 0.10
>>> f=np.sin(x) # función seno en ese intervalo
>>> g=np.cos(x) # función coseno en ese intervalo.

>>> print(x)
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.  1.1 1.2 1.3 1.4 1.5]

>>> print(f) # Seno
[0.         0.09983342 0.19866933 0.29552021 0.38941834 0.47942554
 0.56464247 0.64421769 0.71735609 0.78332691 0.84147098 0.89120736
 0.93203909 0.96355819 0.98544973 0.99749499]

>>> print(g) # Coseno
[1.         0.99500417 0.98006658 0.95533649 0.92106099 0.87758256
 0.82533561 0.76484219 0.69670671 0.62160997 0.54030231 0.45359612
 0.36235775 0.26749883 0.16996714 0.0707372 ]


Ahora calcularemos las derivadas de ambas funciones por el método de las diferencias finitas.

>>> df_sen=(f[2:]-f[:-2])/2*dx
>>> print(df_sen)
[0.00993347 0.00978434 0.00953745 0.00919527 0.00876121 0.00823961
 0.00763568 0.00695546 0.00620574 0.00539402 0.00452841 0.00361754
 0.00267053 0.00169684]

>>> df_cos=(g[2:]-g[:-2])/2*dx
>>> print(df_cos)
[-0.00099667 -0.00198338 -0.00295028 -0.0038877  -0.00478627 -0.00563702
 -0.00643145 -0.00716161 -0.00782022 -0.00840069 -0.00889723 -0.00930486
 -0.00961953 -0.00983808]
Para entenderlo mejor vamos a desarrollar los arreglos de la función seno para que veas como funcionan:

# función f(seno)
>>> print(f)
[0.         0.09983342 0.19866933 0.29552021 0.38941834 0.47942554
 0.56464247 0.64421769 0.71735609 0.78332691 0.84147098 0.89120736
 0.93203909 0.96355819 0.98544973 0.99749499]

>>> print(f[2:])
[0.19866933 0.29552021 0.38941834 0.47942554 0.56464247 0.64421769
 0.71735609 0.78332691 0.84147098 0.89120736 0.93203909 0.96355819
 0.98544973 0.99749499]

>>> print(f[:-2])
[0.         0.09983342 0.19866933 0.29552021 0.38941834 0.47942554
 0.56464247 0.64421769 0.71735609 0.78332691 0.84147098 0.89120736
 0.93203909 0.96355819]

# Como ves ambos arreglos tienen el mismo número de elementos para poder
# luego restarlos y acabar de aplicar la fórmula.
Para dibujar las funciones y su relación con las derivadas:

plt.plot(x[1:-1], df_sen)
plt.plot(x[1:-1], f[1:-1])
plt.plot(x[1:-1], df_cos)
plt.plot(x[1:-1], g[1:-1])
plt.show()






Próximo Post. Manipulación de Arrays y Transmisión en Numpy.

viernes, 8 de enero de 2021

3) Matemáticas con Numpy












Matemáticas con Numpy


Operaciones aritméticas con un mismo valor.

Todas las operaciones aritméticas básicas (sumar, restar, multiplicar y dividir) que se puedan hacer con matrices se pueden hacer en Numpy. Se puede operar un  único valor con todos los elementos del array, elemento a elemento, usando los signos matemáticos + (sumar), - (restar),  * (multiplicar), / (dividir), ** (potenciación), // (división, cociente como entero).

Puedes encontrar todas las operaciones matemáticas que se pueden hacer con Numpy en su documentación en este enlace.

Por ejemplo:

>>> import numpy as np
>>> a=np.array([3,4,5])
>>> b=3
>>> print(a*b)
[ 9 12 15]
>>> print(a+b)
[6 7 8]
>>> print(a-b)
[0 1 2]
>>> print(a/b)
[1.         1.33333333 1.66666667]
>>> print(a**b)
[ 27  64 125]
>>> print(a*a)
[ 9 16 25]


Operaciones elementales.


Esta biblioteca nos proporciona un montón de funciones matemáticas elementales (seno, coseno, exponencial, raíz cuadrada, logaritmos...) que se pueden utilizar con matrices o tambien con un solo elemento o número. Podrías utilizar Numpy como alternativa al módulo maths que viene por defecto en Python.

Veámoslo con ejemplos:

Creemos una matriz con 8 elementos que se distribuyan de forma uniforme desde -pi a +pi. Pi lo obtendremos usando el módulo math de Python.

>>> import numpy as np, math
>>> a=np.linspace(-math.pi,math.pi,8)
>>> print(a)
[-3.14159265 -2.24399475 -1.34639685 -0.44879895  0.44879895  1.34639685
  2.24399475  3.14159265]

Y fácilmente podemos calcular el seno de cada uno de los valores:

>>> print(np.sin(a))
[-1.22464680e-16 -7.81831482e-01 -9.74927912e-01 -4.33883739e-01
  4.33883739e-01  9.74927912e-01  7.81831482e-01  1.22464680e-16]


Esto no lo podríamos hacer de forma tan sencilla usando el módulo math, porque te daría error.

>>> print(math.sin(a))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-6e5434b920e7> in <module>
----> 1 print(math.sin(a))

TypeError: only size-1 arrays can be converted to Python scalars


Operaciones con matrices de la misma dimensión.


Al igual que hemos visto antes, podemos hacer las mismas operaciones aritméticas sencillas con matrices que tengan la misma dimensión.

Por ejemplo creamos una matriz de dimensiones 2x3 llamada "a" con unos valores ya dados y otra "b" de dimensiones 2x3 rellena con unos. Y vamos a sumarlas y restarlas elemento a elemento.





Y al igual que con la suma y la resta podríamos hacerlo con la multiplicación y división.


Operaciones con matrices de diferentes dimensiones.


Aunque para sumar o restar matrices estas tienen que ser de la misma dimensión, lo que si nos permite Numpy es "sumar o restar" arrays de diferentes dimensiones siempre que el array resultante tenga la misma dimensión que uno de los arrays operados. Y lo hace de la siguiente forma. Veámoslo con un ejemplo. 

El array "a" tiene una dimensión de (2,3) y el "b" de (3,1). Si las sumamos, Numpy sumará elemento a elemento la primera fila de "a" con los elementos de "b" y luego sumará la segunda fila de "a" con los elemnentos de "b" lo que nos dará el array "c"



Para multiplicar matrices tenemos que tener en cuenta que el numero de columnas de la primera matriz tiene que ser igual al numero de filas de la segunda.

Si utilizamos las matrices del ejemplo anterior podemos multiplicarlas con el siguiente código ya que se cumple la condición que hemos dicho a=(2,3) b=(3,1). El número de columnas de a es igual al numero de filas de b.



y lo mismo ocurriría con la división.


Funciones matemáticas para operar con arrays.


Después de haber visto las operaciones básicas que podemos hacer con los arrays, Numpy tiene también una gran cantidad de funciones que podemos aplicar a las matrices. Puedes ver en este enlace la relación de ellas. 




Básicamente todas funcionan de la misma forma así que veamos algunas de ellas.

Por ejemplo, si queremos saber el valor máximo de un array podemos utilizar la función max()


>>> import numpy as np
>>> a=np.array([[2,5,7],[4,3,8]])
>>> print(np.max(a))
8

u obtener la suma de todos los valores del array con la función sum()

>>> import numpy as np
>>> a=np.array([[2,5,7],[4,3,8]])
>>> print(np.sum(a))
29

u operaciones estadísticas como obtener la media de los elementos de un array con mean()


>>> import numpy as np
>>> a=np.array([[2,5,7],[4,3,8]])
>>> print(np.mean(a))
4.833333333333333


Hay muchísimas funciones para cualquier cosa que necesites hacer con los array con lo cual lo mejor es consultar la documentación y ver la información sobre la función que buscas.


Gráficos con Numpy.


Esta librería se integra perfectamente con MatPlotLib.pyplot a la hora de realizar gráficos de forma sencilla con nuestros datos.  Solo tenemos que pasar los arrays con los datos a MatPlotLib.pyplot que se encargará de todo. 

Por ejemplo si queremos hacer una gráfica de la función seno, la podríamos realizar de la siguiente manera.


import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,2*np.pi,200)
y = np.sin(x)
plt.plot(x,y,label='funcion seno')
plt.legend()
plt.xlabel('Eje de las X')
plt.ylabel('Eje de las Y')
plt.title('Función seno Radianes')
plt.show()



Puedes encontrar más información sobre realizar graficos en Python con MatPlotLib en este post.


Vamos a repasar lo que hemos visto hasta ahora y ampliar de forma somera algunos conceptos, a forma de resumen donde puedas buscar las operaciones más comunes que se pueden hacer con arrays. 


OPERACIONES ELEMENTO A ELEMENTO


Hemos visto que en las operaciones que se realizan entre un array y un escalar,  todas se hacen elemento a elemento.

Con escalares


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

>>> a + 1

array([2, 3, 4, 5])

>>> 2**a

array([ 2, 4, 8, 16])


Toda las operaciones aritméticas son elemento a elemento


>>> b = np.ones(4) + 1

>>> a - b

array([-1., 0., 1., 2.])

>>> a * b

array([ 2., 4., 6., 8.])

>>> j = np.arange(5)

>>> 2**(j + 1) - j

array([ 2, 3, 6, 13, 28])



Advertencia Importante: Multiplicación de arreglos no es multiplicación matricial:

>>> a = np.array([[2,0,1],[3,0,0],[5,1,1]])

>>> b = np.array([[1,0,1],[1,2,1],[1,1,0]])

>>> print(a*b)

[[2 0 1]

 [3 0 0]

 [5 1 0]]

>>> # ¡no es multiplicación matricial!

Si queremos multiplicar matrices tenemos que usar el método np.dot()

>>> print(np.dot(a,b))

[[3 1 2]
 [3 0 3]
 [7 3 6]]


Comparaciones de Arrays.


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

>>> b = np.array([4, 2, 2, 4])

>>> a == b

array([False, True, False, True], dtype=bool)

>>> a > b

array([False, False, True, False], dtype=bool)


Operaciones Lógicas



>>> a = np.array([1, 1, 0, 0], dtype=bool)

>>> b = np.array([1, 0, 1, 0], dtype=bool)

>>> np.logical_or(a, b)
array([ True, True, True, False], dtype=bool)

>>> np.logical_and(a, b)
array([ True, False, False, False], dtype=bool)


Transpuesta de un matriz

La transpuesta de la matriz:

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

será:

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

>>> a.T  # también, a.transpose()
array([[1, 3, 5],
           [2, 4, 6]])



REDUCCIONES BÁSICAS.


Sumando los elementos de un array.

>>> x = np.array([1, 2, 3, 4])
>>> np.sum(x)
10
>>> x.sum()
10

Sumando los elementos de un array por filas o por columnas.

Para el tema de los ejes tienes que tener en cuenta los siguiente:

axis = 0 Se refiere al vertical. Suma los elementos de la misma columna.
axis = 1 Se refiere al eje horizontal. Suma los elementos de la misma fila.

Vamos a aplicarlo a un ejemplo.


>>> x = np.array([[1, 1], [2, 2]])
>>> x
array([[1, 1],
           [2, 2]])
>>> x.sum(axis=0) # columna (primera dimensión)
array([3, 3])

>>> x[:, 0].sum(), x[:, 1].sum()
(3, 3)

>>> x.sum(axis=1) # fila (segunda dimensión)
array([2, 4])
>>> x[0, :].sum(), x[1, :].sum()
(2, 4)


Reducciones Estadísticas.

Las reducciones estadísticas funcionan de la misma manera que las sumas.

Veamos unos ejemplos con las funciones estadísticas más significativas.

Calculo de la media.

>>> x = np.array([1, 2, 3, 1])

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

>>> x.mean()
1.75

Cálculo de la mediana.

>>> np.median(x)
1.5

>>> np.median(y, axis=1)
array([ 2., 5.])


Cálculo de la desviación estandar

>>> x.std() # desviación estándar de población (no de la muestra).
0.82915619758884995


Valor mínimo y máximo de un array.

>>> x = np.array([1, 3, 2])

# Valor mínimo
>>> x.min()
1

# Valor máximo
>>> x.max()
3

x.argmin() # índice del valor mínimo en el array
0
x.argmax() # índice del valor máximo en el array
1


Operaciones Lógicas.

numpy.all() -> Comprueba si todos los elementos a lo largo de un eje dado son igual a True.

>>> y = np.array([1,2,3,4,0,9])
>>> np.all(y)
False

numpy.any() -> Comprueba si al menos algún elemento del array a lo largo de un eje dado es igual a True.

>>> np.any(y)
True


Ordenando datos.


- Ordenando los elementos a lo largo de un eje.

>>> a = np.array([[4, 3, 5], [1, 2, 1]])
>>> b = np.sort(a, axis=1)
>>> b
array([[3, 4, 5],
           [1, 1, 2]])

¡ Ten en cuenta que se ordenan filas y columnas por separado!


- Ordenando y reemplazando los elementos.

>>> a.sort(axis=1)
>>> a
array([[3, 4, 5],
           [1, 1, 2]])


domingo, 3 de enero de 2021

2) Accediendo a los datos de las matrices con Numpy. Copia de Matrices. Añadir elementos a un array con np.append()




Accediendo a los datos de las matrices con Numpy. Copia de Matrices. Añadir elementos a un array con numpy.append()


La forma en la que se accede a los datos de las matrices en Numpy es similar a como se hace con las listas en Python. La diferencia está en que mientras que las listas son unidimensionales las matrices son ciertamente multidimensionales. Esto significa que mientras que en una lista solo hay un índice para cada elemento, con Numpy al tratarse de matrices puede haber varios valores índices (uno para cada dimensión)

Vamos a explicar esto mejor, con un ejemplo.

Para acceder a un elemento concreto de una matriz de 3 dimensiones, tenemos que usar el valor del índice de cada dimensión separada por comas:

>>> import numpy as np
# Importamos la librería Numpy

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


Ahora imaginemos que queremos seleccionar el número 3 de la primera fila, el 5 de la segunda y el 7 de la tercera fila. Lo primero que tendremos que hacer es, indicar en que fila está el elemento y posteriormente, después de poner la coma, indicar la posición del elemento en la fila. Ten en cuenta que en Python los elementos de una lista si se  empiezan a contar por la izquierda se comienza a contar desde el 0.

>>> print(a[0,2])
3
>>> print(a[1,1])
5
# Seleccionamos el elemento contando desde la izquierda.
>>> print(a[2,-3])
7

Otra forma de acceder a un elemento de estas matrices anteriores es usando una forma muy parecida a la anterior, usando [][]. Por ejemplo el elemento 8 de la matriz a anterior que está en la 3ª fila y 2ª columna se puede buscar también así:

>>> print(a[3,-1][2,-1])
8
# realmente se pondría print(a[2][1]) pero he preferido poner la resta
# ya que los elementos se empiezan a contar en el cero, con lo que el 
# elemento 8, estaría en la fila 2 y columna 1 en python.

También podemos hacer "slicing" o arreglos con las matrices, podemos seleccionar una parte o "rebanada" de los datos de una matriz incluso con múltiples dimensiones.

Pongamos un ejemplo. Creemos un array unidimensional con 10 elementos y seleccionemos los elementos que están desde la posición 1 al 2, funciona como las listas de python. La selección va desde el primer número indicado hasta el último sin incluir este.

>>> m = np.arange(10)
>>> m
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> m[1:3]
array([1, 2])

o una selección que vaya desde la posición 4ª hasta el final

>>> m[3:]
array([3, 4, 5, 6, 7, 8, 9])

o que incluya todos los elementos menos el último

>>> m[:-1]
array([0, 1, 2, 3, 4, 5, 6, 7, 8])

o cambiar los elementos que van desde el 4 al final con un 0

>>> m[4:] = 0
array([0, 1, 2, 3, 0, 0, 0, 0, 0, 0])

o seleccionar los elementos del 1 al 7 (sin incluir) con un salto de dos elementos:

m[1:7:2]
array([1, 3, 0])

Veamos también el caso de una matriz multidimensional con varias filas. Imaginemos que creamos una matriz de 5x5 y la rellenamos con ceros. Después queremos insertar el número 3 en los elementos que ocupan la primera y segunda fila y también  la primera y segunda columna (recordando que tanto filas como columnas se empiezan a contar en el cero)

>>> n = np.zeros((5,5))
>>> n
array([[0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.]])
>>> n[1:3,1:3]=3
>>> n
array([[0., 0., 0., 0., 0.],
       [0., 3., 3., 0., 0.],
       [0., 3., 3., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.]])

Matrices y copia de matrices.


Si queremos copiar una matriz no basta solamente con elegir un nuevo nombre y asignárselo. Si modificamos la matriz usando la nueva referencia, los cambios son visibles en cualquiera de las asignaciones que hagan referencia a la misma matriz. Veámoslo con un ejemplo:

>>> matriz_a = np.array([1,3,5,7,9])
>>> matriz_b = matriz_a
>>> matriz_b[2:]=-7
>>> matriz_a
array([ 1,  3, -7, -7, -7])


Si realmente queremos copiar una matriz y que sea realmente independiente del original hay que utilizar el método copy()

>>> matriz_a = np.array([1,3,5,7,9])
>>> matriz_b = matriz_a.copy()
# Esto copia la matriz_a en matriz_b y hace a esta independiente.

Hay que tener en cuenta que si hacemos slicing a una matriz, cualquier modificación que hagamos quedará reflejada en la matriz. Por tanto si queremos hacer una copia de una parte de una matriz debemos utilizar el método copy()

>>> original = np.arange(10)
>>> una_parte = original[1:4] # Esto es una vista, si cambiamos algún elemento
de una_parte los cambios se reflejaran en los elementos [1:4] de la original.
>>> una_parte = original[1:4].copy() # Esto es una verdadera copia de una parte
de la matriz original.


Añadir elementos a un array con np.append()


Con numpy.append() podemos añadir nuevos elementos al final de los arrays de numpy. Este método tiene la siguiente forma:

np.append(arr, values, axis=None)

En donde:

arr es un array o matriz de Numpy.

values son los elementos que se agregaran al final del array que se pasa en el argumento anterior. Este elemento no tiene porque ser solo unos números ya que también puede ser un array o matriz.

axis (opcional) es el eje en el que se agregarán los valores.

IMPORTANTE:  ¡Este método no MODIFICA el array original, sino que crea una copia a la que se agregan los nuevos valores.!

Por otro lado si no especificamos un eje en el que añadir los elementos, la matriz que le hayamos pasado se aplanará antes de agregar los nuevos elementos, para así poder añadirlos al final.

Recordamos que si:

axis = 0 estamos refiriéndonos al eje vertical por lo que si es posible se añadirán nuevas filas.
axis = 1 estamos refiriéndonos al eje horizontal por lo que si es posible se añadirán nuevas columnas.

Veamos unos cuantos ejemplos de todo esto.


Añadir un número al final de un array unidimensional.


>>> import numpy as np
>>> arr = np.array([1, 2, 3, 4, 5])

# Queremos añadir el número 10 al final de este array
>>> np.append(arr, 10)
array([ 1,  2,  3,  4,  5, 10])

#O también podríamos agregar otro arrray.
>>> np.append(arr, [11, 12])
array([ 1,  2,  3,  4,  5, 11, 12])

# Como puedes ver en este último caso el array original no se modifica,
# ya que como ves no aparece el número 10 del caso anterior.

Agregar elementos a matrices o arrays que no sean unidimensionales.

Cuando agregamos elementos a una matriz  es necesario que indiquemos mediante la propiedad axis si los nuevos elementos se agregan por filas o por columnas. En el caso de que no lo hagamos, la matriz se aplanará antes de añadir los elementos, obteniéndose un array unidimensional.

Por ejemplo:

>>> mat = np.array([[1, 2, 3],[4, 5, 6]])
>>> np.append(mat, [11, 12])
array([ 1,  2,  3,  4,  5,  6, 11, 12])

Como veremos más adelante, para poder añadir una nueva fila a la matriz esta tiene que ser compatible, es decir tiene que tener el mismo número de columnas que la original. Sino es así Numpy te dará un error. Si en el ejemplo anterior intentamos añadir estos dos elementos a la matriz original, nos dará un error ya que nuestra matriz tiene 3 columnas y le estamos tratando de añadir solo 2 elementos en vez de 3.

>>> np.append(mat, [[11, 12]], axis=0)
ValueError: all the input array dimensions for the concatenation axis 
must match exactly, but along dimension 1, the array at index 0 has 
size 3 and the array at index 1 has size 2

Por tanto si queremos añadirle una nueva fila a nuestra matriz esta tiene que tener 3 elementos para poder hacerlo, además de establecer el parámetro axis=0. Vamos a añadir entonces [11, 12, 13]  a nuestra matriz.

>>> np.append(mat, [[11, 12, 13]], axis=0)
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [11, 12, 13]])

Si lo que queremos añadir es una nueva columna el método funciona exactamente igual. Los elementos a agregar deberán estar en una matriz con el mismo número de filas que la matriz original.

Como nuestra matriz original tiene 2 filas si queremos añadir una nueva columna deberemos añadir una matriz con  2 elementos y que tenga la misma dimensión.

>>> np.append(mat, [[11], [12]], axis=1)
array([[ 1,  2,  3, 11],
       [ 4,  5,  6, 12]])


Y con esto hemos visto lo básico del método np.append() de Numpy, sigamos repasando lo visto en el capítulo con un nuevo ejercicio.


EJERCICIO


Creemos una matriz con una dimensión de 4*4 con valores aleatorios. 

Luego extraigamos todos los valores de la segunda fila.

A continuación extraeremos todos los valores de la tercera columna.

Asignaremos el valor 0.31 al subarreglo  2x2 superior izquierdo.

Para finalizar crearemos una matriz de 8x8 con un patrón de tablero de damas, es decir, alternando ceros y unos :

1 0 1

0 1 0

1 0 1

...


SOLUCIÓN.


>>> import numpy as np


"Creamos una matriz de 4x4 con valores arbitrarios enteros del 0,1"
>>> a=np.random.rand(4,4) # o números enteros con np.random.randint(0,11,(4,4))
>>> print(a)
[[0.86244938 0.10086289 0.49685669 0.89849421]
 [0.63274446 0.90953088 0.87053284 0.32822434]
 [0.94201367 0.60204052 0.72596334 0.36410397]
 [0.68208797 0.46102898 0.59214942 0.89901397]]


"Extraer cada elemento de la segunda fila."
>>> print(a[1])
[0.63274446 0.90953088 0.87053284 0.32822434]

"Extraer cada elemento de la tercera columna"
print(a[:,2])
[0.49685669 0.87053284 0.72596334 0.59214942]

"Asignar un valor de .31 a la matriz de 2*2 de arriba a la izquierda"
a[:2,:2]=0.31
print(a)
[[0.31       0.31       0.49685669 0.89849421]
 [0.31       0.31       0.87053284 0.32822434]
 [0.94201367 0.60204052 0.72596334 0.36410397]
 [0.68208797 0.46102898 0.59214942 0.89901397]]

"Crear una matriz de 8x8 alternando 0 y 1"
#b = np.array([[(i+j+1) % 2 for i in range(8)] for j in range(8)]) solución alternativa
b=np.zeros((8,8),dtype="int")
b[::2,::2]=1
b[1::2,1::2]=1
print(b)
[[1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]]



Próximo Post. Matemáticas con Numpy.