Numpy

Numpy es una paquete para realizar cálculos sobre vectores y matrices.

Que provee?

En esencia, numpy provee dos cosas:

  • arrays multidimensionales que nos permiten almacenar datos estructurados (ndarray)
  • funciones para trabajar sobre estos arreglos en forma eficiente (ufunc)

Está implementado en C y Fortran, de modo que cuando los cálculos son vectorizados (formulados con vectores y matrices), el rendimiento es muy bueno.

Para trabajar con el paquete numpy debemos importarlo de la siguiente forma:


In [1]:
import numpy

Para acceder a las funciones del paquete


In [2]:
numpy.random.rand(10, 5)


Out[2]:
array([[ 0.5424842 ,  0.13813701,  0.6994033 ,  0.21405845,  0.58901978],
       [ 0.2805877 ,  0.57793562,  0.17096716,  0.91641758,  0.84479003],
       [ 0.65315897,  0.1504322 ,  0.00731068,  0.69912492,  0.66813079],
       [ 0.21170659,  0.25825005,  0.34104027,  0.70819363,  0.47065153],
       [ 0.0788757 ,  0.74192603,  0.4052298 ,  0.34477964,  0.98791865],
       [ 0.72538941,  0.47119788,  0.90701196,  0.61514621,  0.38005603],
       [ 0.81066646,  0.75255216,  0.55839316,  0.47228244,  0.82248458],
       [ 0.86346393,  0.77157786,  0.16696978,  0.10390833,  0.12372258],
       [ 0.13606412,  0.06042132,  0.12315949,  0.76657293,  0.21536561],
       [ 0.91427064,  0.75144119,  0.99099881,  0.70773668,  0.51109604]])

La funcion rand está dentro del modulo random del paquete numpy

Para simplificar el código podemos usar


In [3]:
import numpy as np

In [4]:
np.random?

Lo que hacemos es crear un alias al paquete NumPy de nombre np. Es simplemente una forma de abreviar el código. Esta forma de separar las funciones en paquetes (que se llaman espacios de nombres o namespaces) conduce a una mayor legibilidad del código y a la supresión de ambigüedades.

Para buscar ayuda sobre ciertos temas podemos usar la funcion lookfor.


In [5]:
np.lookfor("fourier")


Search results for 'fourier'
----------------------------
numpy.fft.fft
    Compute the one-dimensional discrete Fourier Transform.
numpy.fft.fft2
    Compute the 2-dimensional discrete Fourier Transform
numpy.fft.fftn
    Compute the N-dimensional discrete Fourier Transform.
numpy.fft.ifft
    Compute the one-dimensional inverse discrete Fourier Transform.
numpy.fft.rfft
    Compute the one-dimensional discrete Fourier Transform for real input.
numpy.fft.ifft2
    Compute the 2-dimensional inverse discrete Fourier Transform.
numpy.fft.ifftn
    Compute the N-dimensional inverse discrete Fourier Transform.
numpy.fft.rfftn
    Compute the N-dimensional discrete Fourier Transform for real input.
numpy.fft.fftfreq
    Return the Discrete Fourier Transform sample frequencies.
numpy.fft.rfftfreq
    Return the Discrete Fourier Transform sample frequencies
numpy.bartlett
    Return the Bartlett window.
numpy.convolve
    Returns the discrete, linear convolution of two one-dimensional sequences.
numpy.fft.irfft
    Compute the inverse of the n-point DFT for real input.
numpy.fft.rfft2
    Compute the 2-dimensional FFT of a real array.
numpy.fft.irfftn
    Compute the inverse of the N-dimensional FFT of real input.

Arrays

Un array multidimensional de numpy es un conjunto de elementos estructurados de cierta forma.

Creación de arrays

Podemos crear arreglos de diferentes maneras:

  • Pasandole una secuencia a la funcion array
  • generándolos a partir de funciones (arange, zeros, ones, linspace, rand)
  • cargándolos desde un archivo

In [6]:
np.array((1,2,3))


Out[6]:
array([1, 2, 3])

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


Out[7]:
array([[1, 2, 3],
       [4, 5, 6]])

In [8]:
type(a)


Out[8]:
numpy.ndarray

Podemos obtener la cantidad de elementos de un arreglo accediendo al atributo size (cuidado no usar el operador len)


In [9]:
a.size


Out[9]:
6

Podemos obtener las dimensiones del arreglo accedediendo al atributo shape


In [10]:
a.shape


Out[10]:
(2, 3)

Podemos cambiar la forma del arreglo usando la función reshape


In [11]:
a.reshape(6)


Out[11]:
array([1, 2, 3, 4, 5, 6])

In [12]:
a.reshape(6).shape


Out[12]:
(6,)

In [13]:
b = a.reshape((1,6))
print b.shape
b


(1, 6)
Out[13]:
array([[1, 2, 3, 4, 5, 6]])

Tipos de datos

Los arrays de NumPy son homogéneos, es decir, todos sus elementos son del mismo tipo. Si le pasamos a np.array una secuencia con objetos de tipos diferentes, promocionará todos al tipo con más información. Para acceder al tipo del array, podemos usar el atributo dtype.


In [14]:
a = np.array([1, 2.0, 3])
a


Out[14]:
array([ 1.,  2.,  3.])

In [15]:
a.dtype


Out[15]:
dtype('float64')

La función array nos permite especificar el tipo de datos que queremos


In [16]:
a = np.array([1, 2, 3, 4, 5], dtype=float)
a


Out[16]:
array([ 1.,  2.,  3.,  4.,  5.])

O si queremos podemos obtener un array de otro tipo de datos usando la función astype


In [17]:
a.astype(complex)


Out[17]:
array([ 1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j,  5.+0.j])

In [18]:
a.astype(int)


Out[18]:
array([1, 2, 3, 4, 5])

Por que usar arrays de numpy?

Hasta el momento el arreglo numpy.ndarray luce como una lista Python (anidada). Entonces, ¿por qué simplemente no usar listas para hacer cálculos en lugar de crear un tipo nuevo de arreglo?

Existen varias razones:

Las listas Python son muy generales. No permiten usar funciones matemáticas tales como la multiplicación de matricies, el producto escalar, etc. El implementar tales funciones para las listas Python no sería muy eficiente debido a la asignación dinámica de su tipo.


In [19]:
[1,2,3] + [4,5,6]


Out[19]:
[1, 2, 3, 4, 5, 6]

In [20]:
a = np.array([1,2,3])
b = np.array([4,5,6])
a+b


Out[20]:
array([5, 7, 9])

In [21]:
min([[1,2, 3], [4,5,6]])


Out[21]:
[1, 2, 3]

In [22]:
np.min(np.array([[1,2, 3], [4,5,6]]))


Out[22]:
1

Las listas de Python pueden contener cualquier tipo de objeto.

Los arreglos Numpy tienen tipo estático y homogéneo. Esto los hace eficientes en el uso de memoria. Debido a su tipo estático, se pueden desarrollar implementaciones rápidas de funciones matemáticas tales como la multiplicación y la suma de arreglos numpy usando lenguajes compilados (se usan C y Fortran).

Ejemplo: crear dos mastrices b y c de 100x100 elementos y realizar sobre ellas la suma matricial definida como:

$$ a_{ij} = b_{ij} + c_{ij} $$

Guardar el resultado en la matriz a

Usando listas de python


In [23]:
a = [[None] * 100]*100
b = [range(100)] * 100
c = [range(100)] * 100

In [24]:
%%timeit
for i in range(100):
    for j in range(100):
        a[i][j] = b[i][j] + c[i][j]


100 loops, best of 3: 2.59 ms per loop

Iterando sobre arrays de numpy


In [25]:
a = np.empty(10000, dtype=int).reshape((100,100))
b = np.arange(10000, dtype=int).reshape((100,100))
c = np.arange(10000, dtype=int).reshape((100,100))

In [26]:
%%timeit
for i in range(100):
    for j in range(100):
        a[i,j] = b[i,j] + c[i,j]


10 loops, best of 3: 18.8 ms per loop

Vectorizando la función


In [27]:
def suma(a,b):
    return a + b

In [28]:
np.vectorize?

In [29]:
suma_vectorizada = np.vectorize(suma)

In [30]:
%%timeit
a = suma_vectorizada(b,c)


100 loops, best of 3: 2.86 ms per loop

Usando el operador + defindo en numpy


In [31]:
%%timeit
a = b + c


10000 loops, best of 3: 18.7 µs per loop

In [32]:
a


Out[32]:
array([[    0,     2,     4, ...,   194,   196,   198],
       [  200,   202,   204, ...,   394,   396,   398],
       [  400,   402,   404, ...,   594,   596,   598],
       ..., 
       [19400, 19402, 19404, ..., 19594, 19596, 19598],
       [19600, 19602, 19604, ..., 19794, 19796, 19798],
       [19800, 19802, 19804, ..., 19994, 19996, 19998]])
¡1000 veces más rápido! Se hace fundamental aprovechar al máximo la velocidad de las operaciones definidas en NumPy.

Indexación de arrays

Una de las herramientas más importantes a la hora de trabajar con arrays es el indexado. Consiste en seleccionar elementos aislados o secciones de un array. Nosotros vamos a ver la indexación básica, pero existen técnicas de indexación avanzada que convierten los arrays en herramientas potentísimas.


In [33]:
a = np.arange(10, dtype=float) # otra forma de generar un arreglo: a través de funciones específicas de numpy
a


Out[33]:
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

In [34]:
a[1]


Out[34]:
1.0
Cuidado!!!!! la indexación comienza en 0 (no como en Matlab que empieza en 1)

In [35]:
a[0], a[-1]


Out[35]:
(0.0, 9.0)

In [36]:
a[[0,2,4,6,8]]


Out[36]:
array([ 0.,  2.,  4.,  6.,  8.])

In [37]:
a[0:9:2]


Out[37]:
array([ 0.,  2.,  4.,  6.,  8.])

In [38]:
a[::2]


Out[38]:
array([ 0.,  2.,  4.,  6.,  8.])

In [39]:
a = np.array([
    [0,1,2,3,4,5],
    [10,11,12,13,14,15],
    [20,21,22,23,24,25],
    [30,31,32,33,34,35],
    [40,41,42,43,44,45],
    [50,51,52,53,54,55]
])
a


Out[39]:
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

In [40]:
a[0,3:5]


Out[40]:
array([3, 4])

In [41]:
a[4:, 4:]


Out[41]:
array([[44, 45],
       [54, 55]])

In [42]:
a[:,2]


Out[42]:
array([ 2, 12, 22, 32, 42, 52])

In [43]:
a[2::2, ::2]


Out[43]:
array([[20, 22, 24],
       [40, 42, 44]])

Operaciones Aritméticas

Operaciones con escalares


In [44]:
a = np.arange(5)
a


Out[44]:
array([0, 1, 2, 3, 4])

In [45]:
print a + 1
print a - 1
print a * 10
print a / 5
print a ** 2


[1 2 3 4 5]
[-1  0  1  2  3]
[ 0 10 20 30 40]
[0 0 0 0 0]
[ 0  1  4  9 16]

Operaciones elemento a elemento entre arreglos


In [46]:
a = np.arange(3)
a


Out[46]:
array([0, 1, 2])

In [47]:
b = np.ones(3, dtype=int) * 2
b


Out[47]:
array([2, 2, 2])

In [48]:
print a + b
print a * b
print a / b
print a ** b


[2 3 4]
[0 2 4]
[0 0 1]
[0 1 4]

Broadcasting


In [49]:
c = (np.arange(4) * 10).reshape((4,1))
c


Out[49]:
array([[ 0],
       [10],
       [20],
       [30]])

In [50]:
d = np.arange(3)
d


Out[50]:
array([0, 1, 2])

In [51]:
c + d


Out[51]:
array([[ 0,  1,  2],
       [10, 11, 12],
       [20, 21, 22],
       [30, 31, 32]])

Multplicacion matricial


In [52]:
a = np.array([1,2,3,4]).reshape((2,2))
a


Out[52]:
array([[1, 2],
       [3, 4]])

In [53]:
b = np.ones(4, dtype=int).reshape((2,2))
b


Out[53]:
array([[1, 1],
       [1, 1]])

In [54]:
a * b


Out[54]:
array([[1, 2],
       [3, 4]])

In [55]:
a.dot(b)


Out[55]:
array([[3, 3],
       [7, 7]])

In [56]:
b.dot(a)


Out[56]:
array([[4, 6],
       [4, 6]])

Funciones de comparación

Las comparaciones devuelven un array de booleanos:


In [57]:
a = np.arange(6)
b = np.ones(6).astype(int)
a, b


Out[57]:
(array([0, 1, 2, 3, 4, 5]), array([1, 1, 1, 1, 1, 1]))

In [58]:
a < b


Out[58]:
array([ True, False, False, False, False, False], dtype=bool)

In [59]:
np.any(a < b)


Out[59]:
True

In [60]:
np.all(a < b)


Out[60]:
False
**¡Importante!** Ni en Python ni en ningún otro lenguaje debemos hacer comparaciones exactas entre números de punto flotante.

In [61]:
0.1 +0.2 + 0.3


Out[61]:
0.6000000000000001

In [62]:
0.3 + 0.2 + 0.1


Out[62]:
0.6

In [63]:
np.array([0.1 +0.2 + 0.3]) == np.array([0.3 + 0.2 + 0.1])


Out[63]:
array([False], dtype=bool)

In [64]:
np.isclose(np.array([0.1 +0.2 + 0.3]), np.array([0.3 + 0.2 + 0.1]))


Out[64]:
array([ True], dtype=bool)

In [65]:
np.allclose(np.array([0.1 +0.2 + 0.3]), np.array([0.3 + 0.2 + 0.1]), atol=1e-10)


Out[65]:
True

Ejercicio

Crea un «tablero de ajedrez», con unos en las casillas negras y ceros en las blancas.


In [66]:
tablero = np.zeros((8, 8))
tablero


Out[66]:
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.,  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.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

In [67]:
tablero[1::2, ::2] = 1
tablero[::2, 1::2] = 1
tablero


Out[67]:
array([[ 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.],
       [ 1.,  0.,  1.,  0.,  1.,  0.,  1.,  0.]])