junio 8, 2020
~ 10 MIN
Introducción a NumPy
< Blog RSSNumPy
NumPy
(Numerical Python) es uno de los módulos
más importantes y, probablemente, el más utilizado en el campo del cálculo numérico en el ecosistema de Python
. En posts anteriores hemos visto como utilizar diferentes estructuras de datos en Python
y otros módulos
que nos ofrecen funciones matemáticas, cómo el módulo math
. NumPy
extiende esta funcionalidad en el campo del cálculo numérico de la siguiente manera:
- Ofrece el objeto
ndarray
, similar a una lista dePython
pero optimizada para el cálculo numérico. Nos referiremos a este objeto comoarray
deNumPy
, o simplementearray
. - Implementa funciones matemáticas que pueden trabajar directamente sobre
arrays
sin tener que implementar bucles. - Proporciona funciones para leer/escribir datos a archivos de manera optimizada.
- Permite aplicaciones de álgebra lineal, generación de números aleatorios y transformadas de Fourier.
El núcleo de Numpy
está implementado en C, ofreciendo bindings en Python
para interactuar con él. Esto se traduce en que NumPy
es más rápido que el equivalente en puro Python
. Muchas otras librerías para el análisis de datos están construidas sobre NumPy
, utilizando los ndarrays
como la estructura de datos básica debido a su eficiencia. Para ilustrar esta propiedad vamos a calcular el tiempo necesario para llevar a cabo la misma operación en puro Python
en comparación con arrays
de NumPy
.
l = [i for i in range(10000000)]
%time l2 = [2*i for i in l]
Wall time: 655 ms
import numpy as np
a = np.array(l)
%time a2 = 2*a
Wall time: 13.1 ms
Podemos ver que Numpy
es 50 veces más rápido que Python
llevando a cabo la misma operación. No te preocupes por el resto de detalles, en las siguientes secciones aprenderás lo necesario para crear arrays
, hacer operaciones, etc. Lo más importante es destacar que en Python
necesitamos iterar por cada valor en la lista aplicando la operación en concreto (lo cual es lento) mientras que en NumPy
podemos simplemente aplicar la operación al array
entero confiando en la implementación para llevar a cabo la operación de la manera más eficiente posible.
⚠️
NumPy
es unmódulo
externo aPython
. Para poder usarlo primero hay que instalarlo. Puedes hacerlo abriendo un terminal y escribiendoconda install numpy
si instalastePython
usandoconda
. Alternativamente, puedes hacerlo conpip install numpy
. Si necesitas ayuda en este paso te recomiendo leer el post en el que instalamosPython
y vimos como instalar librerías.
El objeto ndarray
Como hemos comentado en la sección anterior, NumPy
está basado en el objeto ndarray
. Puedes ver este objeto como una lista de Python
con súperpoderes. El objeto ndarray
es multidimensional, lo que implica que nos permite representar tanto valores escalares como vectores, matrices y matrices multidimensionales (lo que llamamos tensores
).
Para poder trabajar con NumPy
, primero tenemos que importarlo. Es común importarlo con el nombre np
.
import numpy as np
Tenemos varias maneras de crear un array
. Una de ellas es utilizar funciones implementadas en NumPy
para la creación de arrays
, indicando el número de elementos en cada dimensión.
# crear un vector de ceros
np.zeros(5)
array([0., 0., 0., 0., 0.])
# crear una matriz de ceros
np.zeros((3, 4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
De la misma manera podemos crear arrays
de 1s, con un valor determinado o sin inicializar con las funciones np.one()
, np.full()
o np.empty()
respectivamente. Estas son algunas de las propiedades de un array
.
# tensor de unos
a = np.ones((3, 4, 2))
a
array([[[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]]])
# numero elementos en cada dimension
a.shape
(3, 4, 2)
# longitud del array
a.ndim
3
# elementos totales en el array
a.size
24
Otra manera muy común de inicializar arrays
de Numpy
es mediante listas de Python
. Para ello usamos la función np.array()
.
np.array([[1, 2, 3],[4, 5, 6]])
array([[1, 2, 3],
[4, 5, 6]])
Por último, también podemos crear arrays
mediante funciones secuenciales o con valores aleatorios de la siguiente manera.
# vector de `int` en rango
np.arange(1, 5)
array([1, 2, 3, 4])
# vector de `float` en rango
np.linspace(0, 1, 10)
array([0. , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
0.55555556, 0.66666667, 0.77777778, 0.88888889, 1. ])
# matriz de números aleatorios
np.random.rand(3,4)
array([[0.91866741, 0.285973 , 0.18087869, 0.32169549],
[0.54680139, 0.91715266, 0.37301333, 0.22500604],
[0.34965872, 0.48719109, 0.74743635, 0.03647023]])
Tipos de datos
Un motivo por el que los arrays
de NumPy
son tan eficientes es que todos los elementos en el array
deben tener el mismo tipo.
a = np.arange(1, 5)
a
array([1, 2, 3, 4])
# acceder al tipo de datos
a.dtype
dtype('int32')
Podemos indicarle a NumPy
el tipo de dato con el que queremos trabajar al crear nuestro array
.
a = np.arange(1, 5, dtype=np.uint8)
a
array([1, 2, 3, 4], dtype=uint8)
Los tipos disponibles son int8
, int16
, int32
, int64
, uint8
|16
|32
|64
, float16
|32
|64
y complex64
|128
. Puedes encontrar una lista completa en la documentación.
Cambiando la forma
Es muy común cambiar la forma de un array
para acomodarlo a ciertas operaciones.
# vector
a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# convertir vector en matriz
a2 = a.reshape(2,5)
a2
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
Obviamente, para poder cambiar la forma del array
el número de elementos tiene que encajar en el número de nuevas dimensiones.
a.reshape(2,4)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-63-5fcd0a049a15> in <module>
----> 1 a.reshape(2,4)
ValueError: cannot reshape array of size 10 into shape (2,4)
# convertir en vector
a2.ravel()
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Operaciones aritméticas
Una de las aplicaciones en las que los arrays
de NumPy
brillan es en la facilidad de usar operaciones artiméticas de manera optimizada y sin tener que implementar bucles como hacemos en Python
. Esta propiedad se conoce como vectorización
, algo de lo que hablaremos en más detalle en un futuro post. Podemos usar los operadores que ya conocemos de Python
directamente con nuestros arrays
.
a = np.array([14, 23, 32, 41])
b = np.array([5, 4, 3, 2])
a + b
array([19, 27, 35, 43])
a - b
array([ 9, 19, 29, 39])
a*b
array([70, 92, 96, 82])
a / b
array([ 2.8 , 5.75 , 10.66666667, 20.5 ])
⚠️ Estas operaciones son elementwise, se aplican elemento a elemento. Para llevar a cabo otras operaciones como por ejemplo el producto escalar de dos vectores usaremos las funciones apropiadas que veremos en un futuro post.
Podremos aplicar estas operaciones siempre que las dimensiones de los arrays
coincidan. De no ser así, es posible que NumPy
siga dándonos resultados. Esto es debido a una propiedad conocida como broadcasting, algo que veremos en más detalle en un próximo post.
Indexado y Troceado
NumPy
adopta la misma lógica de indexado y troceado que Python
, algo que ya conocemos y que puedes refrescar en este post.
a = np.array([1, 5, 3, 19, 13, 7, 3])
a
array([ 1, 5, 3, 19, 13, 7, 3])
# acceder a un valor por su índice
a[3]
19
⚠️ Igual que en
Python
el primer valor de unarray
tiene el índice 0.
# troceado
a[2:5]
array([ 3, 19, 13])
# usamos índices negativos para indexar desde el final
a[2:-1]
array([ 3, 19, 13, 7])
Podemos indexar arrys
multidimensionales con diferentes índices para cada dimensión, separados por comas.
b = np.arange(48).reshape(4, 12)
b
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]])
# valor en segunda fila, tercera columna
b[1, 2]
14
# segunda fila
b[1, :]
array([12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23])
# última columna
b[:, -1]
array([11, 23, 35, 47])
Indexado fancy
El indexado fancy nos permite indexar un array
mediante una lista con los índices de interés.
# primera y tercera fila, desde la tercera columna a la cuarta
b[(0,2),2:4]
array([[ 2, 3],
[26, 27]])
Indexado booleano
El indexado booleano es muy útil para trabajar con máscaras.
a = np.array([1, 2, 3, 4])
mask = np.array([True, False, True, False])
a[mask]
array([1, 3])
Iterado
Podemos iterar sobre un array
de NumPy
de la misma manera que iteramos cualquier otra estructura de datos en Python
.
a = np.arange(5)
a
array([0, 1, 2, 3, 4])
for i in a:
print(i)
0
1
2
3
4
Al trabajar con arrays
multidimensionales, necesitaremos un loop para cada dimensión.
a = np.arange(9).reshape((3,3))
a
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
for fila in a:
for i in fila:
print(i)
0
1
2
3
4
5
6
7
8
Guardar y Cargar
Podemos guardar nuestros arrays
en archivos que más tarde podemos cargar de nuevo.
a = np.random.rand(2,3)
a
array([[0.14663265, 0.98325048, 0.36281673],
[0.33008445, 0.31005347, 0.634345 ]])
# guardar array en archivo
np.save("mi_array", a)
Por defecto un array
se guarda con la extensión .npy
. Para cargar de nuevo el array
b = np.load("mi_array.npy")
b
array([[0.14663265, 0.98325048, 0.36281673],
[0.33008445, 0.31005347, 0.634345 ]])
Las funciones que hemos visto guardan los arrays
en formato binario para maximizar la velocidad de lectura. Sin embargo, podemos guardar nuestros arrays
en formato texto para utilizarlos en otras aplicaciones.
# guardar array en formato csv
np.savetxt("mi_array.csv", a, delimiter=",")
También podemos guardar varios arrays
en un solo archivo comprimido en formato .npz
.
b = np.arange(24, dtype=np.uint8).reshape(2, 3, 4)
b
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]], dtype=uint8)
a
array([[0.14663265, 0.98325048, 0.36281673],
[0.33008445, 0.31005347, 0.634345 ]])
# guardar arrays
np.savez("mis_arrays", a=a, b=b)
# cargar arrays
mis_arrays = np.load("mis_arrays.npz")
mis_arrays
Podemos extraer cada uno de los arrays mediante su nombre, al estilo dict
.
mis_arrays["a"]
array([[0.14663265, 0.98325048, 0.36281673],
[0.33008445, 0.31005347, 0.634345 ]])
mis_arrays["b"]
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]], dtype=uint8)
Resumen
En este post hemos introducido NumPy
la librería de Python
por defecto para cálculo numérico. Hemos hablado de sus propiedades principales y del objeto básico con el que trabajamos: el ndarray
. Esta estructura de datos es similar a la lista de Python
, pero implementada de manera eficiente para su aplicación en cálculo numérico. Con el ndarray
, o simplemente array
, podemos definir y operar con valores escalares, vectores, matrices y tensores
de muchos tipos (numéricos). En el proceso del análisis de datos utilizaremos el array
como estructura de datos básica tanto para representar nuestros datos (texto, imágenes, vídeos, datos tabulares, etc) como los distintos modelos y algoritmos de Machine Learning y Deep Learning que hagamos. En próximos posts hablaremos en más detalle sobre algunas características importantes que hay que tener en cuenta a la hora de trabajar con NumPy
para sacarle el máximo provecho y empezaremos a utilizarlo para asentar las bases fundamentales que nos encaminarán hacia el desarrollo e implementación de algoritmos de Inteligencia Artificial.