Numpy

../../../../_images/numpy.jpeg

NumPy es el paquete fundamental para la computación científica con Python. Contiene entre otras cosas:

  • un poderoso objeto de matriz N-dimensional

  • funciones sofisticadas (de transmisión)

  • herramientas para integrar código C / C ++ y Fortran

  • Álgebra lineal útil, transformada de Fourier y capacidades de números aleatorios

Además de sus obvios usos científicos, NumPy también se puede utilizar como un eficiente contenedor multidimensional de datos genéricos. Se pueden definir tipos de datos arbitrarios. Esto permite que NumPy se integre sin problemas y rápidamente con una amplia variedad de bases de datos.

NumPy tiene licencia bajo la licencia BSD, lo que permite su reutilización con pocas restricciones.

¿ Por qué usar Numpy ?

Las razones por las que debería usar NumPy en lugar de cualquier otro objeto _iterable en Python son:

  • NumPy proporciona una estructura de ndarray para almacenar datos numéricos de manera contigua.

  • También implementa operaciones matemáticas rápidas en ndarrays, que explotan esta contigüidad.

  • Brevedad de la sintaxis para las operaciones de matriz.

Un lenguaje como C o Java requeriría que escribamos un bucle para una operación matricial tan simple como C = A + B.

Operaciones básicas de NumPy

Las razones por las que debería usar NumPy en lugar de cualquier otro objeto _iterable en Python son:

  • NumPy proporciona una estructura de ndarray para almacenar datos numéricos de manera contigua.

  • También implementa operaciones matemáticas rápidas en ndarrays, que explotan esta contigüidad.

  • Brevedad de la sintaxis para las operaciones de matriz.

Un lenguaje como C o Java requeriría que escribamos un bucle para una operación matricial tan simple como C = A + B.

# importar libreria: numpy
import numpy as np
import time
import sys
Copy to clipboard
# Arreglo de ceros: np.zeros(shape)
print("Zeros:")
print( np.zeros((3,3)) )
Copy to clipboard
Zeros:
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
Copy to clipboard
# Arreglos de uno: np.ones(shape)
print("\nOnes:")
print( np.ones((3,3)) )
Copy to clipboard
Ones:
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
Copy to clipboard
# Arreglo vacio: np.empty(shape)
print("\nEmpty:")
print( np.empty([2, 2]) )
Copy to clipboard
Empty:
[[4.68745428e-310 0.00000000e+000]
 [1.58101007e-322 6.92198063e-310]]
Copy to clipboard
# Rango de valores: np.range(start, stop, step)
print("\nRange:")
np.arange(0., 10., 1.) 
Copy to clipboard
Range:
Copy to clipboard
array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
Copy to clipboard
# Grilla de valores: np.linspace(start, end, n_values)
print("\nRegular grid:")
print( np.linspace(0., 1., 9) )
Copy to clipboard
Regular grid:
[0.    0.125 0.25  0.375 0.5   0.625 0.75  0.875 1.   ]
Copy to clipboard
# fijar semilla
np.random.seed(42)

# Sequencia aleatoria: np.random
print("\nRandom sequences:")
print( np.random.uniform(10, size=6) )
Copy to clipboard
Random sequences:
[6.62913893 1.44357124 3.41205452 4.61207364 8.59583224 8.59604932]
Copy to clipboard
# Construccion de arreglos: np.array( python_iterable )
print("\nArray constructor")
print( np.array([2, 3, 5, 10, -1]) )
Copy to clipboard
Array constructor
[ 2  3  5 10 -1]
Copy to clipboard

Manipulación de datos

En esta sección se presentan operaciones básicas de los arreglos de numpy.

# matrix
matriz_34 = np.array([
    [1,2,3,4], 
    [5,6,7,8],
    [9,10,11,12]
])
matriz_34
Copy to clipboard
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])
Copy to clipboard
### tipo
type(matriz_34)
Copy to clipboard
numpy.ndarray
Copy to clipboard
### dimensiones
size = matriz_34.shape

print(f"rows: {size[0]}")
print(f"columns: {size[1]}")
Copy to clipboard
rows: 3
columns: 4
Copy to clipboard
### Escoger filas y columnas
print(f"first row: \n {matriz_34[0]} \n")
print(f"first col: \n {matriz_34[:,0]} \n")
print(f"first and second rows: \n {matriz_34[:2]} \n")
print(f"first and second cols: \n {matriz_34[:,:2]} \n")
Copy to clipboard
first row: 
 [1 2 3 4] 

first col: 
 [1 5 9] 

first and second rows: 
 [[1 2 3 4]
 [5 6 7 8]] 

first and second cols: 
 [[ 1  2]
 [ 5  6]
 [ 9 10]] 
Copy to clipboard

Añadir filas y columnas

Para añadir una columna, se debe asegurar que se este agregando un arreglo de tamaño: (1,n)

A1 = np.array([[0,0,0,0]]) #  [[]]
A1.shape
Copy to clipboard
(1, 4)
Copy to clipboard
# add row
add_row = np.r_[matriz_34, A1]
add_row
Copy to clipboard
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12],
       [ 0,  0,  0,  0]])
Copy to clipboard

Para añadir una columna, se debe asegurar que se este agregando un arreglo de tamaño (n,)

A2= np.array([0,0,0]) # []
A2.shape
Copy to clipboard
(3,)
Copy to clipboard
# add column
add_column = np.c_[matriz_34, A2]
add_column
Copy to clipboard
array([[ 1,  2,  3,  4,  0],
       [ 5,  6,  7,  8,  0],
       [ 9, 10, 11, 12,  0]])
Copy to clipboard

Observación:

  • Se debe tener cuidado al momento de operar vectores con dimensión (1,n) o (n,).

  • Para añadir filas o columnas a una matriz o juntar arreglos de numpy, se puede utilizar la función np.concatenate().

Operaciones matemáticas básicas de matrices

La mayoría de las operaciones realizadas en NumPy se manejan por elementos, es decir, calcular C = A + B se traducirá en C[i,j]=A[i,j]+B[i,j]. (La excepción es la transmisión y se explicará pronto).

A continuación hay una lista con las operaciones matemáticas más comunes.

# fijar semilla
np.random.seed(42)

# crear dos arreglos
A = np.array([
    [1,2,3],
    [4,5,6],
    [7,8,9]
])

B = np.array([[9,8,7],[6,5,4],[3,2,1]])

print(f"Matrix A: \n {A} \n")
print(f"Matrix B: \n {B}")
Copy to clipboard
Matrix A: 
 [[1 2 3]
 [4 5 6]
 [7 8 9]] 

Matrix B: 
 [[9 8 7]
 [6 5 4]
 [3 2 1]]
Copy to clipboard
# suma
print("Sum:")
print( A+B )
Copy to clipboard
Sum:
[[10 10 10]
 [10 10 10]
 [10 10 10]]
Copy to clipboard
# resta
print("\nSubtraction")
print( A-B )
Copy to clipboard
Subtraction
[[-8 -6 -4]
 [-2  0  2]
 [ 4  6  8]]
Copy to clipboard
# producto uno a uno
print("\nProduct")
print( A*B )
Copy to clipboard
Product
[[ 9 16 21]
 [24 25 24]
 [21 16  9]]
Copy to clipboard
# producto matricial
print("\nMatricial Product")
print( np.dot(A,B) )
Copy to clipboard
Matricial Product
[[ 30  24  18]
 [ 84  69  54]
 [138 114  90]]
Copy to clipboard
# potencia
print("\n Power")
print( A**2 )
Copy to clipboard
 Power
[[ 1  4  9]
 [16 25 36]
 [49 64 81]]
Copy to clipboard
# algunas funciones comunes 
print("\n np.exp()")
print( np.exp(A) )
print("\n np.sin()")
print( np.sin(A) )
print("\n np.cos()")
print( np.cos(A))
print("\n np.tan()")
print( np.tan(A) )
Copy to clipboard
 np.exp()
[[2.71828183e+00 7.38905610e+00 2.00855369e+01]
 [5.45981500e+01 1.48413159e+02 4.03428793e+02]
 [1.09663316e+03 2.98095799e+03 8.10308393e+03]]

 np.sin()
[[ 0.84147098  0.90929743  0.14112001]
 [-0.7568025  -0.95892427 -0.2794155 ]
 [ 0.6569866   0.98935825  0.41211849]]

 np.cos()
[[ 0.54030231 -0.41614684 -0.9899925 ]
 [-0.65364362  0.28366219  0.96017029]
 [ 0.75390225 -0.14550003 -0.91113026]]

 np.tan()
[[ 1.55740772 -2.18503986 -0.14254654]
 [ 1.15782128 -3.38051501 -0.29100619]
 [ 0.87144798 -6.79971146 -0.45231566]]
Copy to clipboard

Álgebra Lineal

En esta sección se presentan algunas propiedades comunmente ocupadas en álgebra lineal de matrices.

# crear un arreglo

A = np.array([[1,2],
              [3,4]])
Copy to clipboard
# transpuesta
print("Transpose: ")
print( A.T )
Copy to clipboard
Transpose: 
[[1 3]
 [2 4]]
Copy to clipboard
# determinante
print("determinant")
print( round(np.linalg.det(A) ,2))
Copy to clipboard
determinant
-2.0
Copy to clipboard
# Inversa
print("Inverse")
print( np.linalg.inv(A) )
Copy to clipboard
Inverse
[[-2.   1. ]
 [ 1.5 -0.5]]
Copy to clipboard
# traza
print("Trace")
print( np.trace(A))
Copy to clipboard
Trace
5
Copy to clipboard
# sistemas lineales: Ax = b
b = np.array([[5.], [7.]])

print("linear system: Ax=b")
print("\nx = ")
print( np.linalg.solve(A, b) )
Copy to clipboard
linear system: Ax=b

x = 
[[-3.]
 [ 4.]]
Copy to clipboard
# Valores y vectores propios
eigenvalues, eigenvectors = np.linalg.eig(A) 

print("eigenvalues")
print( eigenvalues )
print("\neigenvectors")
print( eigenvectors )
Copy to clipboard
eigenvalues
[-0.37228132  5.37228132]

eigenvectors
[[-0.82456484 -0.41597356]
 [ 0.56576746 -0.90937671]]
Copy to clipboard
# descomposicion QR
Q,R = np.linalg.qr(A)

print("QR decomposition:")
print("\nQ")
print( Q )
print("\nR")
print( R )
Copy to clipboard
QR decomposition:

Q
[[-0.31622777 -0.9486833 ]
 [-0.9486833   0.31622777]]

R
[[-3.16227766 -4.42718872]
 [ 0.         -0.63245553]]
Copy to clipboard

Broadcasting

Unas de las ventajas de numpy es que podemos hacer broadcasting, es to significa que numpy permite realizar operaciones binarias con arreglos de distintos tamaños.

../../../../_images/broad.png
# example 01
a = np.arange(3)+ 5
print(f"np.arange(3)+ 5:\n{a}" )
Copy to clipboard
np.arange(3)+ 5:
[5 6 7]
Copy to clipboard
# example 02
b = np.ones((3,3))+np.arange(3)
print(f"np.ones((3,3))+np.arange(3):\n{b}" )
Copy to clipboard
np.ones((3,3))+np.arange(3):
[[1. 2. 3.]
 [1. 2. 3.]
 [1. 2. 3.]]
Copy to clipboard
# example 03
c = np.arange(3).reshape((3,1)) +  np.arange(3)
print(f"np.arange(3).reshape((3,1)) +  np.arange(3):\n{c }" )
Copy to clipboard
np.arange(3).reshape((3,1)) +  np.arange(3):
[[0 1 2]
 [1 2 3]
 [2 3 4]]
Copy to clipboard

Python Lists vs. Numpy Arrays

La librería principal de Python son las listas. Una lista es el equivalente a Python de una matriz, pero es redimensionable y puede contener elementos de diferentes tipos.

Una pregunta común para principiantes es cuál es la verdadera diferencia aquí. La respuesta es el rendimiento. Las estructuras de datos de Numpy funcionan mejor en:

  • Tamaño: las estructuras de datos de Numpy ocupan menos espacio

  • Rendimiento: necesitan velocidad y son más rápidos que las listas

  • Funcionalidad: SciPy y NumPy tienen funciones optimizadas, como las operaciones de álgebra lineal integradas.

Memoria

Los principales beneficios del uso de matrices NumPy deberían ser un menor consumo de memoria y un mejor comportamiento en tiempo de ejecución.

Para las listas de Python: podemos concluir de esto que para cada elemento nuevo, necesitamos otros ocho bytes para la referencia al nuevo objeto. El nuevo objeto entero en sí consume 28 bytes. El tamaño de una lista lst sin el tamaño de los elementos se puede calcular con:

                    64 + 8 * len (lst) + + len (lst) * 28
Copy to clipboard
../../../../_images/list_structure.png

NumPy ocupa menos espacio. Esto significa que una matriz entera arbitraria de longitud n en necesidades numpy se calcula por:

                    96 + n * 8 bytes
Copy to clipboard
../../../../_images/array_structure.png

Para convensernos de esto, ejecutemos un ejemplo:

# example

# class: array
class Array:
    """
    Clase array que da como rsultado el tiempo y espacio en 
    memoria de los objetos list y numpy array
    """
    def __init__(self, size_of_vec):
        self.size_of_vec = size_of_vec

    def pure_python_version(self):
        """
        Tiempo y espacio en memoria para objeto list
        """
        t1 = time.time()
        X = range(self.size_of_vec)
        Y = range(self.size_of_vec)
        Z = [X[i] + Y[i] for i in range(len(X)) ]
        return (time.time() - t1,sys.getsizeof(Z) )
        
    def numpy_version(self):
        """
        Tiempo y espacio en memoria para objeto numpy array
        """
        t1 = time.time()
        X = np.arange(self.size_of_vec)
        Y = np.arange(self.size_of_vec)
        Z = X + Y
        return (time.time() - t1,sys.getsizeof(Z) )
Copy to clipboard
# parameters
size_of_vec = 1000000
class_array = Array(size_of_vec)

t1, size1 = class_array.pure_python_version()
t2, size2 = class_array.numpy_version()
Copy to clipboard
# results
print(f"python list -- time: {round(t1,8)} seg, size: {size1} bytes")
print(f"numpy array -- time: {round(t2,8)} seg, size: {size2} bytes")
Copy to clipboard
python list -- time: 0.36786675 seg, size: 8697456 bytes
numpy array -- time: 0.01345587 seg, size: 8000104 bytes
Copy to clipboard