numpy: Manipulación de Vectores y Matrices

La biblioteca numpy se importa


In [2]:
import numpy as np

se acostumbra llamarla np para facilitar la escritura de referencias a los objetos que contiene la librería.

Vectores y matrices

El objeto más importante en numpy es el arreglo n-dimensional (ndarray) que permite representar facilmente vectores, matrices y tensores.

Se puede crear un ndarray a partir de listas, por ejemplo:


In [64]:
# Una matriz fila (matriz 1x3)
a = np.array( [ [3,2,1] ] )
type(a), a.size, a.ndim, a.shape, a.dtype, a.data


Out[64]:
(numpy.ndarray, 3, 2, (1, 3), dtype('int64'), <memory at 0x7f53d807dea0>)

In [65]:
a


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

In [66]:
# Para un vector se excluyen los cochetes externos
a2 = np.array([3,2,1])
type(a2), a2.size, a2.ndim, a2.shape, a2.dtype


Out[66]:
(numpy.ndarray, 3, 1, (3,), dtype('int64'))

In [67]:
a2


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

In [69]:
# Agregando un eje a un vector shape=(3,) se convierte en una matriz con shape=(3,1)
a2[:, np.newaxis]


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

In [7]:
# Una matriz 2x3
b = np.array( [ [4.,5,6], 
                 [7,8,9] ] )
type(b), b.size, b.ndim, b.shape, b.dtype


Out[7]:
(numpy.ndarray, 6, 2, (2, 3), dtype('float64'))

In [8]:
# Tambien se puede utilizar una lista de tuplas
b2 = np.array( [ (4.,5,6), (7,8,9) ] )
type(b2), b2.size, b2.ndim, b2.shape, b2.dtype


Out[8]:
(numpy.ndarray, 6, 2, (2, 3), dtype('float64'))

In [58]:
# Un vector a partir de un rango de valores
c = np.arange(10)
type(c), c.size, c.ndim, c.shape, c.dtype


Out[58]:
(numpy.ndarray, 10, 1, (10,), dtype('int64'))

In [59]:
c


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

In [61]:
# El metodo tile permite crear una matriz replicando los elementos
np.tile(c, (2,1))


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

In [11]:
# Un vector a partir de un rango indicando inicio, fin y paso
d = np.arange(10,50,4)
d


Out[11]:
array([10, 14, 18, 22, 26, 30, 34, 38, 42, 46])

La función linspace permite crear vectores dividiendo un intervalo uniformemente


In [12]:
# Observar que se incluyen los extremos
e = np.linspace(10,50,12)
e


Out[12]:
array([ 10.        ,  13.63636364,  17.27272727,  20.90909091,
        24.54545455,  28.18181818,  31.81818182,  35.45454545,
        39.09090909,  42.72727273,  46.36363636,  50.        ])

In [13]:
# O en caso de necesitar construir una escala logaritmica
f = np.logspace(1,3,10) # desde 10**1 hasta 10**3
f


Out[13]:
array([   10.        ,    16.68100537,    27.82559402,    46.41588834,
          77.42636827,   129.1549665 ,   215.443469  ,   359.38136638,
         599.48425032,  1000.        ])

Un ndarray se puede reorganizar preservando los datos utilizando el método reshape


In [14]:
d.reshape((2,5))


Out[14]:
array([[10, 14, 18, 22, 26],
       [30, 34, 38, 42, 46]])

In [15]:
b.reshape((6,1))


Out[15]:
array([[ 4.],
       [ 5.],
       [ 6.],
       [ 7.],
       [ 8.],
       [ 9.]])

Existen métodos para instanciar estructuras de uso frecuente


In [16]:
# La matriz de ceros
np.zeros( (3,4) )


Out[16]:
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])

In [17]:
# La matriz de unos
np.ones((2,6))


Out[17]:
array([[ 1.,  1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.,  1.]])

In [18]:
# Una matriz sin inicializar
np.empty((2,3))


Out[18]:
array([[ 4.,  5.,  6.],
       [ 7.,  8.,  9.]])

In [19]:
# La matriz identidad
np.eye(5)


Out[19]:
array([[ 1.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  1.]])

Las operaciones de apilamiento (stacking) permiten unir estructuas verticalmente u horizontalmente


In [20]:
p = np.empty((2,3))
q = np.ones((2,3))

In [21]:
# Apilamiento vertical
np.vstack((p,q))


Out[21]:
array([[ 4.,  5.,  6.],
       [ 7.,  8.,  9.],
       [ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

In [22]:
# Apilamiento horizontal
np.hstack((p,q))


Out[22]:
array([[ 4.,  5.,  6.,  1.,  1.,  1.],
       [ 7.,  8.,  9.,  1.,  1.,  1.]])

Tipo de datos

NumPy asume un tipo por defecto, basado en los datos con los que se inicializa el arreglo. Se puede especificar el tipo de dato en caso de desear un tipo particular.


In [23]:
# Tipo por defecto: int64
a = np.array( [ [3,2,1] ] )
a.dtype


Out[23]:
dtype('int64')

In [24]:
# Usar otros tipos
a2 = np.array( [ [3,2,1] ], np.int32 )
a2.dtype


Out[24]:
dtype('int32')

In [25]:
a3 = np.array( [ [3,2,1] ], np.float )
a3.dtype


Out[25]:
dtype('float64')

In [26]:
a4 = np.array( [ [3,2,1] ], np.float32 )
a4.dtype


Out[26]:
dtype('float32')

In [27]:
b = np.linspace(1,10,20)
b.dtype


Out[27]:
dtype('float64')

In [28]:
b


Out[28]:
array([  1.        ,   1.47368421,   1.94736842,   2.42105263,
         2.89473684,   3.36842105,   3.84210526,   4.31578947,
         4.78947368,   5.26315789,   5.73684211,   6.21052632,
         6.68421053,   7.15789474,   7.63157895,   8.10526316,
         8.57894737,   9.05263158,   9.52631579,  10.        ])

In [29]:
b2 = np.linspace(1,10,20, dtype=np.int64)
b2.dtype


Out[29]:
dtype('int64')

In [30]:
# Observar que los valores se redondean hacia abajo
b2


Out[30]:
array([ 1,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,  7,  7,  8,  8,
        9,  9, 10])

In [31]:
# Los tipos string por defecto utilizan formato unicode. Observar que los numeros se toman como strings
c = np.array( [ ['hola',12], ['adios',21] ] )
c


Out[31]:
array([['hola', '12'],
       ['adios', '21']],
      dtype='<U5')

El tipo datetime64 permite representar fechas y horas. También se puede utilizar en los métodos arange()


In [32]:
hoy = np.datetime64('2017-09-26')
manana = np.datetime64('2017-09-27')
hoy, manana


Out[32]:
(numpy.datetime64('2017-09-26'), numpy.datetime64('2017-09-27'))

In [33]:
dosDias = np.array( [hoy, manana] )
dosDias


Out[33]:
array(['2017-09-26', '2017-09-27'], dtype='datetime64[D]')

In [34]:
unaSemana = np.arange('2017-09-24', '2017-10-01', dtype='datetime64')
unaSemana


Out[34]:
array(['2017-09-24', '2017-09-25', '2017-09-26', '2017-09-27',
       '2017-09-28', '2017-09-29', '2017-09-30'], dtype='datetime64[D]')

In [35]:
# Fecha y hora
ya = np.datetime64('2017-09-26T15:21:01')
ya


Out[35]:
numpy.datetime64('2017-09-26T15:21:01')

In [36]:
# Rango de fechas con pasos de 1 hora
horas = np.arange('2017-09-26T00:00', '2017-09-26T23:59', dtype='datetime64[h]' )
horas


Out[36]:
array(['2017-09-26T00', '2017-09-26T01', '2017-09-26T02', '2017-09-26T03',
       '2017-09-26T04', '2017-09-26T05', '2017-09-26T06', '2017-09-26T07',
       '2017-09-26T08', '2017-09-26T09', '2017-09-26T10', '2017-09-26T11',
       '2017-09-26T12', '2017-09-26T13', '2017-09-26T14', '2017-09-26T15',
       '2017-09-26T16', '2017-09-26T17', '2017-09-26T18', '2017-09-26T19',
       '2017-09-26T20', '2017-09-26T21', '2017-09-26T22'], dtype='datetime64[h]')

In [37]:
# Un rango de dias
dias = np.arange('2017-02', '2017-03', dtype='datetime64[D]')
dias


Out[37]:
array(['2017-02-01', '2017-02-02', '2017-02-03', '2017-02-04',
       '2017-02-05', '2017-02-06', '2017-02-07', '2017-02-08',
       '2017-02-09', '2017-02-10', '2017-02-11', '2017-02-12',
       '2017-02-13', '2017-02-14', '2017-02-15', '2017-02-16',
       '2017-02-17', '2017-02-18', '2017-02-19', '2017-02-20',
       '2017-02-21', '2017-02-22', '2017-02-23', '2017-02-24',
       '2017-02-25', '2017-02-26', '2017-02-27', '2017-02-28'], dtype='datetime64[D]')

In [38]:
# Datos tipo complejo
complejos = np.array([ 1+2j, -1-2j, 3., -7j ])
complejos.dtype


Out[38]:
dtype('complex128')

In [39]:
# Datos tipo booleano
booleanos = np.array([ True, False ])
booleanos.dtype


Out[39]:
dtype('bool')

In [40]:
cadenas = np.array(['one','two','three'])
cadenas.dtype


Out[40]:
dtype('<U5')

Indexación & Slicing

Para indicar un rango de indices se utiliza la notación inicio:fin:paso es cada una de las dimensiones. Si se omite el paso se asume que es 1, si se omite el inicio se asume que es desde el indice 0 y si se omite el fin se asume que es hasta la longitud de la correspondiente dimensión. Un índice negativo se asume que se resta de la longitud de la dimensión correspondiente.

Como un primer ejemplo, considerese el arreglo unidimensional dias el ejemplo anterior:


In [41]:
dias[0]


Out[41]:
numpy.datetime64('2017-02-01')

In [42]:
dias[-1]


Out[42]:
numpy.datetime64('2017-02-28')

In [43]:
dias[0:3]


Out[43]:
array(['2017-02-01', '2017-02-02', '2017-02-03'], dtype='datetime64[D]')

In [44]:
dias[-3:-1]


Out[44]:
array(['2017-02-26', '2017-02-27'], dtype='datetime64[D]')

In [45]:
dias[:4]


Out[45]:
array(['2017-02-01', '2017-02-02', '2017-02-03', '2017-02-04'], dtype='datetime64[D]')

In [46]:
dias[-3:]


Out[46]:
array(['2017-02-26', '2017-02-27', '2017-02-28'], dtype='datetime64[D]')

In [47]:
dias[1:10:2]


Out[47]:
array(['2017-02-02', '2017-02-04', '2017-02-06', '2017-02-08', '2017-02-10'], dtype='datetime64[D]')

Considerese ahora una matriz bidimensional 4x5


In [48]:
m = np.arange(1,21).reshape((4,5))
m


Out[48]:
array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15],
       [16, 17, 18, 19, 20]])

In [49]:
# Para seleccionar una de las columnas
m[:,2]


Out[49]:
array([ 3,  8, 13, 18])

In [50]:
# Para seleccionar una de las filas
m[2,:]


Out[50]:
array([11, 12, 13, 14, 15])

In [51]:
# Un rango dentro de la columna
m[1:3,2]


Out[51]:
array([ 8, 13])

In [52]:
# Un rango dentro de la fila
m[2,3:5]


Out[52]:
array([14, 15])

In [53]:
# El último elemento de la última fila
m[-1,-1]


Out[53]:
20

In [54]:
# Una submatriz
m[2:, 2:4]


Out[54]:
array([[13, 14],
       [18, 19]])

In [92]:
# Otra forma de dar la misma submatriz
m[-2:, -3:-1]


Out[92]:
array([[13, 14],
       [18, 19]])

Se puede indicar la lista de elementos a tomar de una fila o una columna


In [94]:
m[:,[1,3]]


Out[94]:
array([[ 2,  4],
       [ 7,  9],
       [12, 14],
       [17, 19]])

Manipulando la forma de un ndarray


In [70]:
m


Out[70]:
array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15],
       [16, 17, 18, 19, 20]])

In [76]:
# Convertir una matriz en un vector .flatten o .ravel
m.flatten()


Out[76]:
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
       18, 19, 20])

In [79]:
m.T.ravel()


Out[79]:
array([ 1,  6, 11, 16,  2,  7, 12, 17,  3,  8, 13, 18,  4,  9, 14, 19,  5,
       10, 15, 20])

In [80]:
m.shape


Out[80]:
(4, 5)

In [87]:
m.reshape((2,10))


Out[87]:
array([[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]])

In [89]:
# NumPy puede inferir la dimensión faltante
m.reshape((2,-1))


Out[89]:
array([[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]])

In [90]:
m.T.reshape((10,2))


Out[90]:
array([[ 1,  6],
       [11, 16],
       [ 2,  7],
       [12, 17],
       [ 3,  8],
       [13, 18],
       [ 4,  9],
       [14, 19],
       [ 5, 10],
       [15, 20]])

In [ ]: