
NumPy#
¿Qué es NumPy?#
NumPy es una librería externa de Python diseñada específicamente para el cálculo científico que facilita el trabajo eficiente con vectores, matrices, números aleatorios, cálculos matemáticos y operaciones de álgebra lineal entre otras cosas.
Habíamos mencionado en la introducción a Python que los lenguages interpretados no son computacionalmente eficientes, ni son rápidos ni gestionan optimamente la memoria. Para superar este inconveniente se pueden programar librerías que trabajan como envoltorios de Python para módulos compilados en otros lenguages. Este es el caso de NumPy cuyo nucleo está programado, y por lo tanto compilado, con C. Además NumPy puede servirnos como interpretador de librerías en C o Fortran para poder invocarlas desde nuestro script en Python.
Puede que NumPy sea la librería de Python más popular. La mayoría de librerías que puedes encontrar tiene a NumPy como dependencia.

¿Cómo se instala?#
NumPy suele instalarse automáticamente como dependencia con cualquier librería. No obstante, si no tienes NumPy ya instalado en tu entorno de conda:
conda install numpy
¿Cómo se usa?#
Importando NumPy#
Existe el convenio de importar NumPy con el alias np
. Esto mismo sucede con otras librerías, que por su frecuente uso recomiendan un alias de pocos caracteres para invocarlo tecleando poco.
import numpy as np
Vectores#
El objeto más sencillo y popular de NumPy es su vector multidimensional (ndarray). Puedes pensar que un ndarray, del inglés n-dimensional array, es como una lista o una tupla, pero es mucho más que eso. Es una de las maneras más eficientes de manejar datos en memoria y operar con ellos.
Veamos primero como convertir una lista a un ndarray:
una_lista = [2,4,6,8,10]
un_vector = np.array(una_lista)
type(un_vector)
numpy.ndarray
un_vector
array([ 2, 4, 6, 8, 10])
print(un_vector)
[ 2 4 6 8 10]
A diferencia de las listas y tuplas, los ndarrays no son “expandibles”, pero esto los hace eficientes en memoria y de rápida lectura.
# Esto quizá es lo único que puedes echar de menos de trabajar con listas.
# Ya que nose puede hacer con ndarrays.
una_lista.append(12)
print(una_lista)
[2, 4, 6, 8, 10, 12]
Los ndarrays
tiene una forma fija:
un_vector.shape
(5,)
Podemos inicializar vectores de forma o dimensión deseada sin necesidad de recurrir a una lista:
vec_1 = np.empty(10)
vec_1.shape
(10,)
vec_1
array([4.65672448e-310, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000,
0.00000000e+000, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000,
0.00000000e+000, 6.93660889e-310])
El método np.empty()
reserva el espacio en memoria que aloja el vector, pero no se ocupa de inicializarlo con ningún valor. Es por eso que si lo leemos, antes de haber asignado valores, su contenido es meramente ruido.
Para inicializar algo directamente con valores cero podemos usar np.zeros()
, y para hacerlo con unos podemos usar np.ones()
.
vec_1 = np.zeros(6)
vec_2 = np.ones(8)
vec_1
array([0., 0., 0., 0., 0., 0.])
vec_2
array([1., 1., 1., 1., 1., 1., 1., 1.])
Al igual que las variables de Python, los ndarrays pueden ser de números enteros, de coma flotante, de doble precisión, de carácteres, variables lógicas, etc. Puedes checar aquí la lista de posibles tipos.
vec_1.dtype
dtype('float64')
vector_auxiliar = np.zeros(4,dtype=bool) # En lógica 0 ~ False y 1 ~ True
print(vector_auxiliar)
vector_auxiliar = np.ones(3,dtype=bool)
print(vector_auxiliar)
[False False False False]
[ True True True]
vector_auxiliar = np.zeros(4,dtype=int)
print(vector_auxiliar)
[0 0 0 0]
vector_auxiliar = np.zeros(4,dtype=complex)
print(vector_auxiliar)
[0.+0.j 0.+0.j 0.+0.j 0.+0.j]
vector_auxiliar = np.zeros(4,dtype='float64')
print(vector_auxiliar)
[0. 0. 0. 0.]
vector_auxiliar = np.zeros(4,dtype='S3') # para str
print(vector_auxiliar)
[b'' b'' b'' b'']
Estos objetos tienen también métodos y atributos muy útiles. Hemos visto shape
, pero hay muchos más.
dir(vec_1)
['T',
'__abs__',
'__add__',
'__and__',
'__array__',
'__array_finalize__',
'__array_function__',
'__array_interface__',
'__array_prepare__',
'__array_priority__',
'__array_struct__',
'__array_ufunc__',
'__array_wrap__',
'__bool__',
'__class__',
'__class_getitem__',
'__complex__',
'__contains__',
'__copy__',
'__deepcopy__',
'__delattr__',
'__delitem__',
'__dir__',
'__divmod__',
'__dlpack__',
'__dlpack_device__',
'__doc__',
'__eq__',
'__float__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__iand__',
'__ifloordiv__',
'__ilshift__',
'__imatmul__',
'__imod__',
'__imul__',
'__index__',
'__init__',
'__init_subclass__',
'__int__',
'__invert__',
'__ior__',
'__ipow__',
'__irshift__',
'__isub__',
'__iter__',
'__itruediv__',
'__ixor__',
'__le__',
'__len__',
'__lshift__',
'__lt__',
'__matmul__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__',
'__rmatmul__',
'__rmod__',
'__rmul__',
'__ror__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setattr__',
'__setitem__',
'__setstate__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__xor__',
'all',
'any',
'argmax',
'argmin',
'argpartition',
'argsort',
'astype',
'base',
'byteswap',
'choose',
'clip',
'compress',
'conj',
'conjugate',
'copy',
'ctypes',
'cumprod',
'cumsum',
'data',
'diagonal',
'dot',
'dtype',
'dump',
'dumps',
'fill',
'flags',
'flat',
'flatten',
'getfield',
'imag',
'item',
'itemset',
'itemsize',
'max',
'mean',
'min',
'nbytes',
'ndim',
'newbyteorder',
'nonzero',
'partition',
'prod',
'ptp',
'put',
'ravel',
'real',
'repeat',
'reshape',
'resize',
'round',
'searchsorted',
'setfield',
'setflags',
'shape',
'size',
'sort',
'squeeze',
'std',
'strides',
'sum',
'swapaxes',
'take',
'tobytes',
'tofile',
'tolist',
'tostring',
'trace',
'transpose',
'var',
'view']
Por ejemplo vamos a ver max
, argmax
,mean
o std
:
vec_1 = np.array([6.0, 10.0, 2.0, 5.0, 7.0, 5.0], dtype=float)
print(vec_1)
[ 6. 10. 2. 5. 7. 5.]
help(vec_1.max)
Help on built-in function max:
max(...) method of numpy.ndarray instance
a.max(axis=None, out=None, keepdims=False, initial=<no value>, where=True)
Return the maximum along a given axis.
Refer to `numpy.amax` for full documentation.
See Also
--------
numpy.amax : equivalent function
vec_1.max() # devuelve el máximo valor tomado por el vector
10.0
help(vec_1.argmax)
Help on built-in function argmax:
argmax(...) method of numpy.ndarray instance
a.argmax(axis=None, out=None, *, keepdims=False)
Return indices of the maximum values along the given axis.
Refer to `numpy.argmax` for full documentation.
See Also
--------
numpy.argmax : equivalent function
vec_1.argmax() # devuelve la posición en la que se encuentra el valor máximo
1
help(vec_1.mean)
Help on built-in function mean:
mean(...) method of numpy.ndarray instance
a.mean(axis=None, dtype=None, out=None, keepdims=False, *, where=True)
Returns the average of the array elements along given axis.
Refer to `numpy.mean` for full documentation.
See Also
--------
numpy.mean : equivalent function
vec_1.mean() # devuelve la media aritmética
5.833333333333333
help(vec_1.std)
Help on built-in function std:
std(...) method of numpy.ndarray instance
a.std(axis=None, dtype=None, out=None, ddof=0, keepdims=False, *, where=True)
Returns the standard deviation of the array elements along given axis.
Refer to `numpy.std` for full documentation.
See Also
--------
numpy.std : equivalent function
vec_1.std() # devuelve la desviación estandard
2.4094720491334933
Matrices y vectores multidimensionales#
Al comienzo de la sección pasada hemos revelado que ndarray viene del inglés n-dimensional array (vector n-dimensional, en español). Probablemente sospeches que en realidad podíamos haber definido un objeto ndarray de cualquier forma y dimensiones.
matriz = np.zeros((3,3),dtype=int)
matriz.shape
(3, 3)
matriz
array([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
otra_matriz = np.ones((2,4))
otra_matriz.shape
(2, 4)
otra_matriz
array([[1., 1., 1., 1.],
[1., 1., 1., 1.]])
la_matriz_transpuesta = otra_matriz.T
la_matriz_transpuesta.shape
(4, 2)
la_matriz_transpuesta
array([[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]])
un_tensor_de_rango_3 = np.zeros((2,3,5),dtype=bool)
un_tensor_de_rango_3.shape
(2, 3, 5)
un_tensor_de_rango_3
array([[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]],
[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]]])
un_tensor_de_rango_5 = np.zeros((2,3,2,4,3))
un_tensor_de_rango_5.shape
(2, 3, 2, 4, 3)
un_tensor_de_rango_5
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., 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., 0.],
[0., 0., 0.]],
[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]]]]])
Indexado y cortes de un ndarray#
Veamos como podemos acceder a los elementos de un ndarray:
vector = np.array([4,2,6,1,8,9,3])
vector[0]
4
vector[3]
1
vector[-1]
3
vector[-4]
1
vector[[1,2,4,5]] # podemos también usar listas de índices
array([2, 6, 8, 9])
matriz = np.zeros((3,3),dtype=int)
matriz[0,0] = 5
matriz[1,2] = 2
matriz [2,[0,1]] = 1
matriz
array([[5, 0, 0],
[0, 0, 2],
[1, 1, 0]])
Puedo delimitar cortes y fragmentar definiendo regiones. Para esto usamos el símbolo “:”.
vector = np.array([10, 9, 8, 7, 6, 5, 4, 3])
vector[:] # sólo con ':' no estamos estableciendo límites en esta dimensión
array([10, 9, 8, 7, 6, 5, 4, 3])
vector[2:]
array([8, 7, 6, 5, 4, 3])
vector[:4]
array([10, 9, 8, 7])
vector[2:4]
array([8, 7])
vector[-2:]
array([4, 3])
vector[-4:-1]
array([6, 5, 4])
Podemos también decidir cada cuento saco valores en el corte con un segundo símbolo “:”.
vector[::]
array([10, 9, 8, 7, 6, 5, 4, 3])
vector[::2]
array([10, 8, 6, 4])
vector[::3]
array([10, 7, 4])
vector[1:6:]
array([9, 8, 7, 6, 5])
vector[1:6:2]
array([9, 7, 5])
vector[-1:-6:-2]
array([3, 5, 7])
Y esto es extensible a ndarrays de veras multidimensionales.
un_tensor = np.zeros((2,4,7))
un_tensor
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.]]])
un_tensor[0,1,3] = 8.0
un_tensor[1,2,:] = -10.0
un_tensor[0,2:3,1:7:2] = 1.0
un_tensor[:,0,6] = 4
un_tensor
array([[[ 0., 0., 0., 0., 0., 0., 4.],
[ 0., 0., 0., 8., 0., 0., 0.],
[ 0., 1., 0., 1., 0., 1., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0., 0., 4.],
[ 0., 0., 0., 0., 0., 0., 0.],
[-10., -10., -10., -10., -10., -10., -10.],
[ 0., 0., 0., 0., 0., 0., 0.]]])
un_tensor[0]
array([[0., 0., 0., 0., 0., 0., 4.],
[0., 0., 0., 8., 0., 0., 0.],
[0., 1., 0., 1., 0., 1., 0.],
[0., 0., 0., 0., 0., 0., 0.]])
un_tensor[0,1,:]
array([0., 0., 0., 8., 0., 0., 0.])
un_tensor[:,0,2:4]
array([[0., 0.],
[0., 0.]])
El resultado de indexar o cortar es un nuevo ndarray. Así que podemos también aplicar los métodos y atributos que vimos en la sección anterior:
un_tensor[0].max()
8.0
un_tensor[:,0,2:4].mean()
0.0
Copias o vistas de un ndarray
.#
Este punto es muy relevante y no tenerlo presente puede crear desastrosos errores de dificil detección en tu código. No se trata de algo inherente a NumPy, más bien es inherente a Python. Mejor que comenzar explicándo la diferencia entre una copia y vista (un objeto que apunta o refiere a otro), vamos a dejar que tu solo o sola comiences a inferirlo en el siguiente ejemplo con listas -no son objetos propios de NumPy, ¿cierto?-:
a = [0,1,2]
b = a
b[1] = -7
print(a)
print(b)
[0, -7, 2]
[0, -7, 2]
a = [0,1,2]
b = a.copy()
b[1] = -7
print(a)
print(b)
[0, 1, 2]
[0, -7, 2]
¿Entiendes la diferencia? Un objeto está compuesto por el nombre de la variable (a
o b
), su valor almacenado en un segmento de la memoria física de la computadora ([0,1,2] en el caso inicial de a
), y la dirección o referencia de dicho segmento. Es decir, cuando invocamos a
la computadora mira cuál es la dirección del segmento físico de memoria y comienza a leer su contenido (el valor de la variable).
En Python podemos acceder a la dirección o referencia de una variable y sacarlo por pantalla:
id(a)
140398455852928
Vamos a ayudarnos del método id
que nos ofrece la dirección del objeto para darle un segundo vistazo a los ejemplos anteriores:
a = [0,1,2]
b = a
print('La dirección de a es:', id(a))
print('La dirección de b es:', id(b))
print(' ')
b[1] = -7
print('El valor de a es:', a)
print('La valor de b es:', b)
La dirección de a es: 140398332700480
La dirección de b es: 140398332700480
El valor de a es: [0, -7, 2]
La valor de b es: [0, -7, 2]
a = [0,1,2]
b = a.copy()
print('La dirección de a es:', id(a))
print('La dirección de b es:', id(b))
print(' ')
b[1] = -7
print('El valor de a es:', a)
print('La valor de b es:', b)
La dirección de a es: 140398332961216
La dirección de b es: 140398332799104
El valor de a es: [0, 1, 2]
La valor de b es: [0, -7, 2]
En el primer caso, el símbolo “=” crea un objeto b
que apunta al mismo segmento de memoria. A esto lo podemos llamar “vista” o “referencia”, ya que sólo se diferencia de a
en el nombre.
En el segundo caso, a.copy()
está generando una copia real física: un duplicado del segmento de memoria de a
en otra región del espacio memoria y por lo tanto con otra dirección. Al hacer b = a.copy()
, le estamos asignando al nombre de variable b
, ese nuevo espacio de memoria. Esto se llama “copia”.
Con un ndarray sucede exactamente lo mismo, con un pequeño matiz que veremos más adelante y que aunque te parezca molesto, resulta muy util.
a = np.array([2,4,6])
b = a
print('La dirección de a es:', id(a))
print('La dirección de b es:', id(b))
print(' ')
b[2] = 0
print('El valor de a es:', a)
print('La valor de b es:', b)
La dirección de a es: 140398332992496
La dirección de b es: 140398332992496
El valor de a es: [2 4 0]
La valor de b es: [2 4 0]
a = np.array([2,4,6])
b = a.copy()
print('La dirección de a es:', id(a))
print('La dirección de b es:', id(b))
print(' ')
b[2] = 0
print('El valor de a es:', a)
print('La valor de b es:', b)
La dirección de a es: 140398332993072
La dirección de b es: 140398332993168
El valor de a es: [2 4 6]
La valor de b es: [2 4 0]
El matiz que comentabamos anteriormente es que los ndarray nos permiten generar nuevas “vistas” que apuntan a regiones definidas por cortes:
a = np.array([[2,2,2],[4,4,4],[6,6,6]],dtype=int)
a
array([[2, 2, 2],
[4, 4, 4],
[6, 6, 6]])
a.shape
(3, 3)
b = a[1:,1:]
b
array([[4, 4],
[6, 6]])
b.shape
(2, 2)
b[:,:] = 0
b
array([[0, 0],
[0, 0]])
a
array([[2, 2, 2],
[4, 0, 0],
[6, 0, 0]])
b[0,0] = 1
b
array([[1, 0],
[0, 0]])
a
array([[2, 2, 2],
[4, 1, 0],
[6, 0, 0]])
a[2,:] = -1
a
array([[ 2, 2, 2],
[ 4, 1, 0],
[-1, -1, -1]])
b
array([[ 1, 0],
[-1, -1]])
Cuando se quiera duplicar un objeto ndarray generando una copia independiente, hay que recordar que se debe recurrir al método ndarray.copy()
.
Manipulación de ndarrays#
Podemos cambiar la forma de los ndarrays, combinarlos, etc.
matrix_1 = np.array([[1, 3, 5, 7], [2, 4, 6, 8]])
matrix_2 = matrix_1.T # La transpuesta intercambia las filas y las columnas
print(matrix_2)
[[1 2]
[3 4]
[5 6]
[7 8]]
matrix_2 = matrix_1.ravel() # Función de aplanado
print(matrix_2)
[1 3 5 7 2 4 6 8]
matrix_2 = np.array([1, 3, 5, 7, 2, 4, 6, 8]).reshape((2,4)) # Cambia la forma con los mismos elementos
print(matrix_2)
[[1 3 5 7]
[2 4 6 8]]
matrix_2 = np.array([1, 3, 5, 7, 2, 4, 6, 8]).reshape((4,2)) # Y si no es la misma cantidad de elementos?
print(matrix_2)
[[1 3]
[5 7]
[2 4]
[6 8]]
matrix_1.resize((3,5)) # Cambia el tamaño del `ndarray` completando con ceros
print(matrix_1)
[[1 3 5 7 2]
[4 6 8 0 0]
[0 0 0 0 0]]
matrix_1 = np.array([[1, 2], [3, 4]])
np.tile(matrix_1, (2,3)) # Repite como mosaico
array([[1, 2, 1, 2, 1, 2],
[3, 4, 3, 4, 3, 4],
[1, 2, 1, 2, 1, 2],
[3, 4, 3, 4, 3, 4]])
matrix_1 = np.array([[1, 2], [3, 4]]) # repite
np.repeat(matrix_1, 3)
array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])
Álgebra básica para operar con ndarrays#
Veamos la sintaxis de operaciones sencillas con ndarrays que transforman o devuelven otros ndarrays
vec_1 = np.array([0,1,2],dtype=int)
vec_2 = np.array([10,9,8],dtype=int)
print(vec_1,vec_2)
[0 1 2] [10 9 8]
vec_3 = vec_1 + 1
print(vec_3)
[1 2 3]
vec_3 = vec_1*2
print(vec_3)
[0 2 4]
vec_3 = vec_1**2
print(vec_3)
[0 1 4]
vec_3 = np.log2(vec_1+1) # Numpy tiene además una gran colección de operaciones matemáticas
print(vec_3)
[0. 1. 1.5849625]
vec_3 = np.cos(vec_1)
print(vec_3)
[ 1. 0.54030231 -0.41614684]
vec_3 = vec_1+vec_2
print(vec_3)
[10 10 10]
vec_3 = vec_1*vec_2 # Esta multiplicación es elemento a elemento
print(vec_3)
[ 0 9 16]
vec_3 = np.dot(vec_1,vec_2) # Esta es la multiplicación vectorial conocida como 'dot product'
print(vec_3)
25
vec_3 = np.matmul(vec_1,vec_2) # Esta es la multiplicación matricial, 'dot product' si son vectores
print(vec_3)
25
vec_3 = np.cross(vec_1.T,vec_2) # Esta es el producto vectorial, o 'cross product' en inglés.
print(vec_3)
[-10 20 -10]
vec_3 = (vec_1>1)
print(vec_3)
[False False True]
vec_3 = (vec_2>vec_1)
print(vec_3)
[ True True True]
mat_1 = np.array([[0,1,2],[0,1,2],[0,1,2]],dtype=int)
mat_2 = np.ones((3,3),dtype=int)*2
print(mat_1)
print(mat_2)
[[0 1 2]
[0 1 2]
[0 1 2]]
[[2 2 2]
[2 2 2]
[2 2 2]]
mat_3 = mat_1+10
print(mat_3)
[[10 11 12]
[10 11 12]
[10 11 12]]
mat_3 = mat_1*2
print(mat_3)
[[0 2 4]
[0 2 4]
[0 2 4]]
mat_3 = mat_1+mat_2
print(mat_3)
[[2 3 4]
[2 3 4]
[2 3 4]]
mat_3 = mat_1*mat_2 # Esta operación es elemento a elemento: aij*bij
print(mat_3)
[[0 2 4]
[0 2 4]
[0 2 4]]
mat_3 = np.dot(mat_1,mat_2) # esta operación es el producto matricial
print(mat_3)
[[6 6 6]
[6 6 6]
[6 6 6]]
mat_3 = np.matmul(mat_1,mat_2) # esta operación es el producto matricial
print(mat_3)
[[6 6 6]
[6 6 6]
[6 6 6]]
mat_3 = np.matmul(mat_1,vec_1) # esta operación es el producto matricial
print(mat_3)
[5 5 5]
mat_3 = (mat_1<1)
print(mat_3)
[[ True False False]
[ True False False]
[ True False False]]
mat_3 = (mat_1!=mat_2)
print(mat_3)
[[ True True False]
[ True True False]
[ True True False]]
logic_1 = np.zeros((4),dtype=bool)
logic_2 = np.zeros((4),dtype=bool)
logic_1[[1,2]]=True
logic_2[[0,2]]=True
print(logic_1)
print(logic_2)
[False True True False]
[ True False True False]
logic_3 = (logic_1 | logic_2) # "|" es la operación lógica "Or"
print(logic_3)
[ True True True False]
logic_3 = (logic_1 & logic_2) # "&" es la operación lógica "And"
print(logic_3)
[False False True False]
logic_3 = (logic_1 ^ logic_2) # "^" es la operación lógica "Xor"
print(logic_3)
[ True True False False]
logic_3 = ~logic_1 # "~" es la operación lógica "Not"
print(logic_3)
[ True False False True]
Algebra lineal#
NumPy tiene una colección de operaciones de algebra lineal muy completa. Acudir a NumPy para realizar ciertas operaciones, así como acudir a funciones de Scipy o Scikit-learn (como veremos en los siguientes notebooks), resulta muy conveniente por su eficiencia.
Veamos algún ejemplo:
vec_1 = np.array([2.0, 2.0, 4.0])
mat_1 = np.array([[1.0,0.0,0.0],[0.0,0.0,1.0],[0.0,1.0,0.0]])
print(vec_1)
print(mat_1)
[2. 2. 4.]
[[1. 0. 0.]
[0. 0. 1.]
[0. 1. 0.]]
np.linalg.norm(vec_1) # norma del vector
4.898979485566356
np.linalg.det(mat_1) # determinante de la matriz
-1.0
np.trace(mat_1) # traza de la matriz
1.0
np.transpose(mat_1) # matriz transpuesta
array([[1., 0., 0.],
[0., 0., 1.],
[0., 1., 0.]])
eigenvals, eigenvecs = np.linalg.eig(mat_1) # autovalores y autovectores
print('Autovalores:',eigenvals)
print('Autovectores:',eigenvecs)
print('')
for ii in range(eigenvals.shape[0]):
print('Autovalor y Autovector ',str(ii),':')
print('')
print('\t',eigenvals[ii],eigenvecs[:,ii])
print('')
Autovalores: [ 1. -1. 1.]
Autovectores: [[ 0. 0. 1. ]
[ 0.70710678 0.70710678 0. ]
[ 0.70710678 -0.70710678 0. ]]
Autovalor y Autovector 0 :
1.0 [0. 0.70710678 0.70710678]
Autovalor y Autovector 1 :
-1.0 [ 0. 0.70710678 -0.70710678]
Autovalor y Autovector 2 :
1.0 [1. 0. 0.]
eigenvals[1]*eigenvecs[:,1] == np.dot(mat_1,eigenvecs[:,1])
array([ True, True, True])
Generadores y otras funciones útiles#
Por último veamos unos generadores de números junto con otras funciones útiles de NumPy que has de saber que existen.
print(list(np.arange(10))) # NumPy tiene un iterador para secuencias
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(np.arange(5,15,2))) # NumPy tiene un iterador para secuencias
[5, 7, 9, 11, 13]
np.random.random() # Podemos encontrar varios generadores de números aleatorios
0.11559273386646429
random_gaussian_numbers = np.random.normal(0.0,1.0,5000) # 5000 números aleatorios en distribución normal
print('Primeros 5 números aleatorios:',random_gaussian_numbers[:5])
print('Valor promedio:', random_gaussian_numbers.mean())
print('Desviación estandard:', random_gaussian_numbers.std())
print('')
# Encontramos funciones para hacer histogramas
print('Histograma a 5 bins:')
frecuencias, limite_bins = np.histogram(random_gaussian_numbers,bins=5)
print('')
for ii in range(5):
print('de',limite_bins[ii],'a',limite_bins[ii+1],':',frecuencias[ii])
Primeros 5 números aleatorios: [ 1.85761051 -0.59102258 -1.49991714 -1.48619082 -2.01335101]
Valor promedio: -0.02049935541977901
Desviación estandard: 1.0064227358662432
Histograma a 5 bins:
de -3.4758496082297876 a -2.126870178327348 : 92
de -2.126870178327348 a -0.7778907484249085 : 1035
de -0.7778907484249085 a 0.5710886814775309 : 2479
de 0.5710886814775309 a 1.9200681113799707 : 1262
de 1.9200681113799707 a 3.26904754128241 : 132
np.linspace(-10.0,10.0,12) # Generador de puntos equi-espaciados, 12 aquí entre -10.0 y 10.0
array([-10. , -8.18181818, -6.36363636, -4.54545455,
-2.72727273, -0.90909091, 0.90909091, 2.72727273,
4.54545455, 6.36363636, 8.18181818, 10. ])
np.logspace(1, 10, 5, base=np.e) # Generador de puntos espaciados en escala logarítmica
array([2.71828183e+00, 2.57903399e+01, 2.44691932e+02, 2.32157241e+03,
2.20264658e+04])
x, y = np.mgrid[-2:2, 0:5] # Generador de enrejados (grids)
print(x)
print(y)
[[-2 -2 -2 -2 -2]
[-1 -1 -1 -1 -1]
[ 0 0 0 0 0]
[ 1 1 1 1 1]]
[[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]]
Escribiendo y leyendo ndarrays en ficheros#
NumPy cuenta con su propio formato de archivo para escritura y lectura de ndarrays
de manera eficiente:
vec_1 = np.array([0, 2, 4, 6])
np.save("fichero_numpy.npy", vec_1)
vec_2 = np.load("fichero_numpy.npy")
print(vec_2)
[0 2 4 6]
import os
os.remove("fichero_numpy.npy")
del(vec_2)
Además NumPy está preparado para manejar una multitud de otro tipo de ficheros para la entrada y salida de datos. Por ejemplo csv:
np.savetxt("fichero_numpy.csv", vec_1)
!cat fichero_numpy.csv
0.000000000000000000e+00
2.000000000000000000e+00
4.000000000000000000e+00
6.000000000000000000e+00
vec_2 = np.loadtxt("fichero_numpy.csv")
print(vec_2)
[0. 2. 4. 6.]
os.remove("fichero_numpy.csv")
del(vec_2)
Dudas, problemas técnicos y soluciones. #
Para centralizar esas dudas técnicas sobre el tema de este notebook o proponer soluciones o sugerencias más técnicas que queremos encontrar en el futuro comentadas y visibles para todos, haz uso del siguiente canal:
Más recursos útiles #
El propósito de este notebook es ser un documento únicamente introductorio. Puedes encontrar -o contribuir añadiendo- más información útil en el siguiente listado:
Documentación #
http://www.numpy.org/
https://docs.scipy.org/doc/numpy/
https://www.iaa.csic.es/python/cientifico/numpy.pdf
http://pcmap.unizar.es/~pilar/python_short.pdf
Tutoriales, Webinars y cursos gratuitos #
http://www.learnpython.org/es/Numpy%20Arrays
http://www.iac.es/sieinvens/python-course/source/numpy.html
https://geekytheory.com/pylab-parte-2-datos-basicos-numpy
http://damianavila.github.io/Python-Cientifico-HCC/3_NumPy.html
https://scipy-cookbook.readthedocs.io/items/idx_numpy.html
https://jakevdp.github.io/PythonDataScienceHandbook/02.00-introduction-to-numpy.html