ndarray
+ ufunc
ndarray
) nos permiten almacenar datos de manera estructuradaufunc
) nos permiten operar con esos datos de manera eficientePython está organizado en módulos, que son archivos con extensión .py
que contienen funciones, variables y otros objetos, y paquetes, que son conjuntos de módulos. Cuando queremos utilizar objetos que están definidos en un módulo tenemos que importarlo, y una vez que lo hemos hecho podemos usar el operador .
para ir descendiendo en la jerarquía de paquetes y acceder al objeto que necesitamos. Por ejemplo, de esta manera importamos NumPy:
import numpy
Y de esta manera accedemos a la función norm
, que calcula la norma (o módulo) de un array:
numpy.linalg.norm
<function numpy.linalg.linalg.norm>
La función norm
está dentro del paquete linalg
, que a su vez está dentro del paquete NumPy.
La convención para importar NumPy siempre es esta:
import numpy as np
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 encontrar ayuda sobre cierto tema podemos usar la función lookfor
:
np.lookfor("solve")
Search results for 'solve' -------------------------- numpy.linalg.solve Solve a linear matrix equation, or system of linear scalar equations. numpy.linalg.lstsq Return the least-squares solution to a linear matrix equation. numpy.linalg.tensorsolve Solve the tensor equation ``a x = b`` for x. numpy.linalg._umath_linalg.solve solve the system a x = b, on the last two dimensions, broadcast to the rest. numpy.linalg._umath_linalg.solve1 solve the system a x = b, for b being a vector, broadcast in the outer dimensions. numpy.distutils.misc_util.njoin Join two or more pathname components + numpy.distutils.misc_util.minrelpath Resolve `..` and '.' from path. numpy.distutils.system_info.UmfpackNotFoundError UMFPACK sparse solver (http://www.cise.ufl.edu/research/sparse/umfpack/) numpy.linalg.pinv Compute the (Moore-Penrose) pseudo-inverse of a matrix. numpy.linalg.cholesky Cholesky decomposition. numpy.linalg.tensorinv Compute the 'inverse' of an N-dimensional array. numpy.linalg.LinAlgError Generic Python-exception-derived object raised by linalg functions. numpy.polynomial.hermite.hermfit Least squares fit of Hermite series to data. numpy.polynomial.laguerre.lagfit Least squares fit of Laguerre series to data. numpy.polynomial.legendre.legfit Least squares fit of Legendre series to data. numpy.polynomial.chebyshev.chebfit Least squares fit of Chebyshev series to data. numpy.polynomial.hermite_e.hermefit Least squares fit of Hermite series to data. numpy.polynomial.polynomial.polyfit Least-squares fit of a polynomial to data.
Además de arrays, NumPy contiene también constantes y funciones matemáticas de uso cotidiano.
np.e
2.718281828459045
np.pi
3.141592653589793
np.log(2)
0.69314718055994529
Un array de NumPy es una colección de N
elementos, igual que una secuencia de Python (por ejemplo, una lista). Tiene las mismas propiedades que una secuencia y alguna más. Para crear un array, la forma más directa es pasarle una secuencia a la función np.array
.
np.array([1, 2, 3])
array([1, 2, 3])
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
.
a = np.array([1, 2, 3.0])
print(a.dtype)
float64
np.array([1, 2, "3"])
array(['1', '2', '3'], dtype='<U1')
np.array([1, 2, 3], dtype=float)
array([ 1., 2., 3.])
np.array([1, 2, 3], dtype=complex)
array([ 1.+0.j, 2.+0.j, 3.+0.j])
También podemos convertir un array de un tipo a otro utilizando el método .astype
.
a
array([ 1., 2., 3.])
a.astype(int)
array([1, 2, 3])
Ejemplo:
$$ a_{ij} = b_{ij} + c_{ij} $$N, M = 100, 100
a = np.empty(10000).reshape(N, M)
b = np.random.rand(10000).reshape(N, M)
c = np.random.rand(10000).reshape(N, M)
%%timeit
for i in range(N):
for j in range(M):
a[i, j] = b[i, j] + c[i, j]
100 loops, best of 3: 8.75 ms per loop
%%timeit
a = b + c
100000 loops, best of 3: 8.34 µs per loop
¡1000 veces más rápido! Se hace fundamental vectorizar las operaciones y aprovechar al máximo la velocidad de NumPy.
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.
a = np.array([
[1, 2, 3],
[4, 5, 6]
])
a
array([[1, 2, 3], [4, 5, 6]])
Los índices se indican entre corchetes justo después del array. Recuerda que en Python la indexación empieza en 0. Si recuperamos el primer elemento de un array de dos dimensiones, obtenemos la primera fila.
a[0]
array([1, 2, 3])
En vez de usar a[0][0]
para recuperar el primer elemento de la primera fila, podemos abreviar aún más la sintaxis:
a[0, 0]
1
No solo podemos recuperar un elemento aislado, sino también porciones del array, utilizando la sintaxis [<inicio>:<final>:<salto>]
.
a[0, 1:3]
array([2, 3])
a[0, ::2]
array([1, 3])
Muchos métodos y muy variados
array
, copy
empty
, eye
, ones
, zeros
, *_like
arange
, linspace
, logspace
, meshgrid
rand
, randn
empty(shape)
crea un array con «basura», equivalente a no inicializarlo, ligeramente más rápido que zeros
o ones
eye(N, M=None, k=0)
crea un array con unos en una diagonal y ceros en el restoidentity(n)
devuelve la matriz identidad*_like(a)
construyen arrays con el mismo tamaño que uno dadonp.identity(5).astype(int)
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]])
_.shape
(5, 5)
Si la función recibe como argumento shape
, debemos pasarle el número de filas y columnas como una tupla (es decir, encerrado entre paréntesis).
np.zeros((3, 4))
array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 0., 0., 0., 0.]])
np.zeros(3, 4)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-26-06beb765944a> in <module>() ----> 1 np.zeros(3, 4) TypeError: data type not understood
np.ones((3, 4))
array([[ 1., 1., 1., 1.], [ 1., 1., 1., 1.], [ 1., 1., 1., 1.]])
i3 = np.identity(3)
i3
array([[ 1., 0., 0.], [ 0., 1., 0.], [ 0., 0., 1.]])
i3.shape
(3, 3)
np.ones(i3.shape)
array([[ 1., 1., 1.], [ 1., 1., 1.], [ 1., 1., 1.]])
Si en lugar de pasar directamente la forma del array ya sabemos que queremos crear un array con la misma forma que otro, podemos usar las funciones *_like
, que reciben un array en vez de una tupla.
np.ones_like(i3)
array([[ 1., 1., 1.], [ 1., 1., 1.], [ 1., 1., 1.]])
linspace(start, stop, num=50)
devuelve números equiespaciados dentro de un intervalologspace(start, stop, num=50, base=10.0)
devuelve números equiespaciados según una escala logarítmicameshgrid(x1, x2, ...)
devuelve matrices de n-coordenadasnp.linspace(0, 1, num=10)
array([ 0. , 0.11111111, 0.22222222, 0.33333333, 0.44444444, 0.55555556, 0.66666667, 0.77777778, 0.88888889, 1. ])
np.logspace(0, 3)
array([ 1. , 1.1513954 , 1.32571137, 1.52641797, 1.75751062, 2.02358965, 2.32995181, 2.6826958 , 3.0888436 , 3.55648031, 4.09491506, 4.71486636, 5.42867544, 6.25055193, 7.19685673, 8.28642773, 9.54095476, 10.98541142, 12.64855217, 14.56348478, 16.76832937, 19.30697729, 22.22996483, 25.59547923, 29.47051703, 33.93221772, 39.06939937, 44.98432669, 51.79474679, 59.63623317, 68.6648845 , 79.06043211, 91.0298178 , 104.81131342, 120.67926406, 138.94954944, 159.98587196, 184.20699693, 212.09508879, 244.20530945, 281.1768698 , 323.74575428, 372.75937203, 429.19342601, 494.17133613, 568.9866029 , 655.12855686, 754.31200634, 868.51137375, 1000. ])
La función np.meshgrid
se utiliza mucho a la hora de representar funciones en dos dimensiones, y crea dos arrays: uno varía por filas y otro por columnas. Combinándolos, podemos evaluar la función en un cuadrado.
x = np.linspace(0, 1, num=5)
y = np.linspace(0, 1, num=5)
xx, yy = np.meshgrid(x, y)
xx, yy
(array([[ 0. , 0.25, 0.5 , 0.75, 1. ], [ 0. , 0.25, 0.5 , 0.75, 1. ], [ 0. , 0.25, 0.5 , 0.75, 1. ], [ 0. , 0.25, 0.5 , 0.75, 1. ], [ 0. , 0.25, 0.5 , 0.75, 1. ]]), array([[ 0. , 0. , 0. , 0. , 0. ], [ 0.25, 0.25, 0.25, 0.25, 0.25], [ 0.5 , 0.5 , 0.5 , 0.5 , 0.5 ], [ 0.75, 0.75, 0.75, 0.75, 0.75], [ 1. , 1. , 1. , 1. , 1. ]]))
xx + 1j * yy
array([[ 0.00+0.j , 0.25+0.j , 0.50+0.j , 0.75+0.j , 1.00+0.j ], [ 0.00+0.25j, 0.25+0.25j, 0.50+0.25j, 0.75+0.25j, 1.00+0.25j], [ 0.00+0.5j , 0.25+0.5j , 0.50+0.5j , 0.75+0.5j , 1.00+0.5j ], [ 0.00+0.75j, 0.25+0.75j, 0.50+0.75j, 0.75+0.75j, 1.00+0.75j], [ 0.00+1.j , 0.25+1.j , 0.50+1.j , 0.75+1.j , 1.00+1.j ]])
Las funciones universales (ufunc
) operan sobre arrays de NumPy elemento a elemento y siguiendo las reglas de broadcasting.
sin
, cos
, sqrt
, exp
, ...<
, ~
, ...all
, any
, isnan
, allclose
, ...a = np.arange(2 * 3).reshape(2, 3)
a
array([[0, 1, 2], [3, 4, 5]])
np.sqrt(a)
array([[ 0. , 1. , 1.41421356], [ 1.73205081, 2. , 2.23606798]])
np.sqrt(np.arange(-3, 3))
-c:1: RuntimeWarning: invalid value encountered in sqrt
array([ nan, nan, nan, 0. , 1. , 1.41421356])
np.arange(-3, 3).astype(complex)
array([-3.+0.j, -2.+0.j, -1.+0.j, 0.+0.j, 1.+0.j, 2.+0.j])
np.sqrt(_)
array([ 0.00000000+1.73205081j, 0.00000000+1.41421356j, 0.00000000+1.j , 0.00000000+0.j , 1.00000000+0.j , 1.41421356+0.j ])
Las comparaciones devuelven un array de booleanos:
a = np.arange(6)
b = np.ones(6).astype(int)
a, b
(array([0, 1, 2, 3, 4, 5]), array([1, 1, 1, 1, 1, 1]))
a < b
array([ True, False, False, False, False, False], dtype=bool)
np.any(a < b)
True
np.all(a < b)
False
a = np.arange(6).astype(float)
b = np.ones(6)
a, b
(array([ 0., 1., 2., 3., 4., 5.]), array([ 1., 1., 1., 1., 1., 1.]))
Las funciones isclose
y allclose
realizan comparaciones entre arrays especificando una tolerancia:
np.isclose(a, b, rtol=1e-6)
array([False, True, False, False, False, False], dtype=bool)
np.allclose(a, b, rtol=1e-6)
False
0.1 + 0.2 + 0.3
0.6000000000000001
0.3 + 0.2 + 0.1
0.6
0.1 + 0.2 + 0.3 == 0.3 + 0.2 + 0.1
False
z1
3x4 lleno de ceros de tipo entero.z2
3x4 lleno de ceros salvo la primera fila que serán todo unos.z3
3x4 lleno de ceros salvo la última fila que será el rango entre 5 y 8.a = np.zeros((3, 4))
a
array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 0., 0., 0., 0.]])
a[0, :] = 1
a
array([[ 1., 1., 1., 1.], [ 0., 0., 0., 0.], [ 0., 0., 0., 0.]])
b = np.zeros((3, 4))
b[-1] = np.arange(5, 9)
b
array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 5., 6., 7., 8.]])
v = np.ones(10)
v
array([ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
v[::2] = 2
v
array([ 2., 1., 2., 1., 2., 1., 2., 1., 2., 1.])
tablero = np.zeros((8, 8))
tablero
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.]])
tablero[1::2, ::2] = 1
tablero[::2, 1::2] = 1
tablero
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.]])