NUMPY - Vectores y Matrices

Por Jose R. Zapata

Ultima actualizacion: 11/Nov/2023

Invitame a un Cafe

NumPy es una libreria de Algebra Lineal para Python, la razón por la cual es tan importante para Data Science con Python es que casi todas las librerias del ecosistema de PyData confían en NumPy como uno de sus principales componentes.

Numpy también es increíblemente rápido, ya que tiene enlaces a librerias en C. Para obtener más información sobre por qué usar Arrays en lugar de listas, mira esto StackOverflow post.

Instrucciones de instalacion

Si esta usando google colab ya tiene instalado NumPy, de ser necesario puede instalar NumPy en el terminal escribiendo:

pip install numpy

Importando NumPy

Luego de instalar Numpy la libreria se importa de la siguiente manera:

# esta es la forma estandar de importar numpy
import numpy as np
np.__version__ # para ver la version de numpy
'1.26.1'

Numpy tiene muchas funciones integradas. No los cubriremos todos, sino que nos centraremos en algunos de los aspectos más importantes de Numpy: vectores, arreglos, matrices y generación de números.

Arreglos en Numpy (Arrays)

Los arreglos en NumPy son la principal forma de usar Numpy. Los arreglos de NumPy esencialmente vienen en dos tipos: vectores y matrices. Los vectores son estrictamente matrices de 1-d y las matrices son 2-d (pero debe tener en cuenta que una matriz aún puede tener solo una fila o una columna).

Crear Arreglos desde Listas de Python

Podemos crear un arreglo mediante la conversión directa de una lista o lista de listas:

my_list = [1,2,3] #lista en python
my_list
[1, 2, 3]
np.array(my_list) #conversion de una lista a arreglo mediante el metodo 'array'
array([1, 2, 3])
a = np.array(my_list) #se puede asignar a una variable
type(a) #tipo de dato
numpy.ndarray
a.dtype  #tipo de datos que estan en el arreglo
dtype('int64')
 # Una lista de listas
my_matrix = [[1,2,3],[4,5,6],[7,8,9]]
my_matrix
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
np.array(my_matrix) # conversion de una lista de listas a Matriz
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
b = np.array(my_matrix) # aasginar la conversion a una variable
type(b) # tipo de dato
numpy.ndarray

Metodos integrados (Built-in Methods)

Existen muchas funciones para de generar arreglos

arange

Devuelve valores espaciados uniformemente dentro de un intervalo dado. Es muy parecida a la funcion range de las basicas de python.

# generar un arreglo de enteros desde 0 hasta 9,
# recordar que el valor final no se incluye
# arange(valor_inicial,valor_final-1)
np.arange(0,10)  
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
## arange(valor_inicial,valor_final-1,paso)
np.arange(0,11,2) 
array([ 0,  2,  4,  6,  8, 10])

zeros y ones

Generar arreglos de Ceros o Unos

# Generar un arreglo de ceros que contenga 3 elementos
np.zeros(3) 
array([0., 0., 0.])
# Generar una matriz de ceros de 5 x 5 elementos
np.zeros((5,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.]])
np.ones(3) # Generar un arreglo de unos que contenga 3 elementos
array([1., 1., 1.])
np.ones((3,3)) # Generar una matriz de unos de 5 x 5 elementos
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])

linspace

Devuelve números espaciados uniformemente durante un intervalo especificado.

# Generar 3 valores iniciando en 0 y terminando en 10 (incluyendolo)
# linspace(valor_inicial, valor_final, numero_de_elementos)
 
np.linspace(0,10,3)
array([ 0.,  5., 10.])
# Generar 50 valores iniciando en 0 y terminando en 10 (incluyendolo)
np.linspace(0,10,50)
array([ 0.        ,  0.20408163,  0.40816327,  0.6122449 ,  0.81632653,
        1.02040816,  1.2244898 ,  1.42857143,  1.63265306,  1.83673469,
        2.04081633,  2.24489796,  2.44897959,  2.65306122,  2.85714286,
        3.06122449,  3.26530612,  3.46938776,  3.67346939,  3.87755102,
        4.08163265,  4.28571429,  4.48979592,  4.69387755,  4.89795918,
        5.10204082,  5.30612245,  5.51020408,  5.71428571,  5.91836735,
        6.12244898,  6.32653061,  6.53061224,  6.73469388,  6.93877551,
        7.14285714,  7.34693878,  7.55102041,  7.75510204,  7.95918367,
        8.16326531,  8.36734694,  8.57142857,  8.7755102 ,  8.97959184,
        9.18367347,  9.3877551 ,  9.59183673,  9.79591837, 10.        ])

eye

Crea la matriz identidad

# crea la matriz identidad de 4x4 elementos
np.eye(4) 
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])

Numeros Aleatorios (Random)

Numpy tiene diferentes formas de crear arrelgos de numeros aleatorios, el modulo para realizar esto se llama Random:

rand

Crea un arreglo de la forma dada y rellenela con muestras aleatorias de una distribución uniforme sobre [0, 1).

# creacion de un arreglo de 2 elementos 1 una dimension
 #Los numeros aleatorios seran de una distribucion uniforme
np.random.rand(2)
array([0.55630335, 0.54640311])
# creacion de un arreglo de 5x5
#Los numeros aleatorios seran de una distribucion uniforme
np.random.rand(5,5)
array([[0.90562981, 0.81329168, 0.80394423, 0.44098396, 0.86245014],
       [0.99072343, 0.82319432, 0.89504292, 0.56765543, 0.15067085],
       [0.8358101 , 0.1118367 , 0.40969933, 0.38922484, 0.58263261],
       [0.67242108, 0.20178568, 0.385257  , 0.14725517, 0.68733183],
       [0.21113437, 0.17065523, 0.13928985, 0.14802795, 0.75117634]])

randn

Devuelve una muestra (o muestras) de la distribución “estándar normal”. A diferencia del rand que es uniforme:

# creacion de un arreglo de 2 elementos 1 una dimension
#Los numeros aleatorios seran de una distribucion "estándar normal"
np.random.randn(2) 
array([ 1.47681411, -0.35820491])
# creacion de un arreglo de 5x5
 #Los numeros aleatorios seran de una distribucion "estándar normal"
np.random.randn(5,5)
array([[-0.02161729, -0.94424517, -0.21972354, -1.18095898, -0.82122845],
       [ 0.74180186, -0.03329197,  1.23188616,  0.84041974,  0.79392027],
       [ 0.58985611,  0.78576512, -1.5216466 , -0.21546246, -0.11700756],
       [ 0.24025012, -0.54683943,  1.25113531,  0.90113039,  2.00725429],
       [ 0.35403177, -0.85534654, -1.47542499, -0.19743709,  0.24366922]])

randint

Entrega numeros enteros aleatorios desde inicio (inclusivo) hasta final (exclusivo).

# Genera un numero aleatorio entre 1 y 99
np.random.randint(1,100) 
28
# Genera un arreglo de 10 elementos entre 1 y 99
np.random.randint(1,100,(10,2)) 
array([[52, 71],
       [45, 79],
       [56, 68],
       [19, 62],
       [15, 93],
       [60, 18],
       [55, 12],
       [ 4, 85],
       [85, 99],
       [63, 66]])

Atributos y Metodos de los arreglos

import numpy as np
 #Genera un arreglo de numeros enteros del 0 al 24
arr = np.arange(25)
 #Genera un arreglo de 10 elementosdel 0 al 49
ranarr = np.random.randint(0,50,10)
arr
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23, 24])
ranarr
array([33, 11, 15, 13, 40, 28, 32, 15, 13,  5])

Reshape

Devuelve una matriz que contiene los mismos datos con una nueva distribucion

# arr es un vector de 25 elementos 
# y se convertira en una matriz de 5x5
arr.reshape(5,5)
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

max,min,argmax,argmin

Estos son métodos útiles para encontrar valores máximos o mínimos. O para encontrar el indice de su ubicacione usando argmin o argmax

ranarr
array([33, 11, 15, 13, 40, 28, 32, 15, 13,  5])
ranarr.max() # Valor maximo del arreglo
40
# Posicion del valor maximo del arreglo (recordar que empieza en cero)
ranarr.argmax() 
4
ranarr.min() # Valor minimo del arreglo
5
# Posicion del valor maximo del arreglo (recordar que empieza en cero)
ranarr.argmin()
9

Shape

Shape es un attribute que los arreglos tienen para definir sus dimensiones (No es metodo):

# Vector
arr.shape
(25,)
# Cambiando las dimensiones del arreglo para que sea una matriz
# de una sola dimension horizontal
arr.reshape(1,25)
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24]])
# Cambiando las dimensiones del arreglo para que sea una matriz
# de una sola dimension vertical
arr.reshape(25,1)
array([[ 0],
       [ 1],
       [ 2],
       [ 3],
       [ 4],
       [ 5],
       [ 6],
       [ 7],
       [ 8],
       [ 9],
       [10],
       [11],
       [12],
       [13],
       [14],
       [15],
       [16],
       [17],
       [18],
       [19],
       [20],
       [21],
       [22],
       [23],
       [24]])
arr.reshape(25,1).shape
(25, 1)

dtype

Para obtener los tipos de datos dentro del arreglo:

arr.dtype
dtype('int64')

size

Numero de elementos en un arreglo

arr_2d = np.array(([5,10,15],[20,25,30],[35,40,45]))
arr_2d.size
9

ndim

Numero de dimensiones del arreglo o matriz

arr_2d.ndim
2

Indexacion y Seleccion en NumPy

Como indexar y seleccionar elementos o grupos de elementos de un arreglo (array)

#Creando un arreglo de ejemplo
arr = np.arange(0,11) # Generar un arreglo de enteros del 0 hasta el 10
arr
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
arr.dtype # tipos de datos dentro del arreglo
dtype('int64')

Indexación y Selección con corchetes

La forma más sencilla de elegir uno o algunos elementos de una matriz es similar a las listas de Python:

# Obtener un valor conociendo su indice (index)
arr[8]
8
#Obtener los valores en un rango [valor_inicial, valor_final -1]
arr[1:5]
array([1, 2, 3, 4])
#Obtener los valores en un rango [valor_inicial, valor_final -1]
arr[0:5]
array([0, 1, 2, 3, 4])

Broadcasting (Difusion)

Los arreglos de Numpy difieren de una lista normal de Python en su capacidad de Broadcasting, que es asignar un valor a un rango de posiciones:

# definiendo un valor para todo un rango de posiciones (Broadcasting)
# Asignar el numero 100 a las pocisiones desde el 0 hasta el 4
arr[0:5]=100 
arr
array([100, 100, 100, 100, 100,   5,   6,   7,   8,   9,  10])
#crear nuevamente el arreglo con el que estabamos trabajando
arr = np.arange(0,11)
arr
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
#NOTA importante en la seleccion de rangos (sclicing)
# los arreglos son mutables
slice_of_arr = arr[0:6]
slice_of_arr
array([0, 1, 2, 3, 4, 5])
#Cambiar todos los valores a 99
slice_of_arr[:]=99
slice_of_arr
array([99, 99, 99, 99, 99, 99])

Observe que los cambios tambien ocurren en el arreglo original

arr
array([99, 99, 99, 99, 99, 99,  6,  7,  8,  9, 10])

Los datos no se copian, ¡es un puntero a el arreglo original! ¡Esto evita problemas de memoria!

# Para obtener una copia, se debe hacer explicito
arr = np.arange(0,11)
arr_copy = arr.copy()
arr_copy[:] = 99
arr_copy
array([99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99])
# Observe que el arreglo original no se modifico
arr
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10])

Indexacion de arreglos 2D (matrices)

El formato general es arr_2d[fila][col] o arr_2d[fila,col]. Se recomienda usar la notacion con la coma por claridad.

# Creacion de una matriz de 3x3
arr_2d = np.array(([5,10,15],[20,25,30],[35,40,45]))

arr_2d
array([[ 5, 10, 15],
       [20, 25, 30],
       [35, 40, 45]])
#Indexando por filas
# Obtener la fila 1 (en python empieza en 0)
arr_2d[1] 
array([20, 25, 30])
# El formato es **arr_2d[fila][col]** o **arr_2d[fila,col]**
# Obteniendo un elemento en especifico
arr_2d[1][0] # elemento de la fila 1 columna 0
20
# Obteniendo un elemento en especifico
arr_2d[1,0] # elemento de la fila 1 columna 0
20
# seleccion de rangos en arreglos 2D (slicing)

#dimensiones (2,2) desde la esquina superior derecha
arr_2d[:2,1:] # filas [0,1] y columnas 1 hasta el final
array([[10, 15],
       [25, 30]])
# fila de indice 2
arr_2d[2]
array([35, 40, 45])
# todos los elementos de las columnas que estan en la fila de posicion 2
arr_2d[2,:]
array([35, 40, 45])

Indexacion especial

La indexación especial permite seleccionar filas o columnas enteras desordenadas

#Creando una matriz de zeros
arr2d = np.zeros((10,10))
arr2d
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.]])
#Tamaño del arreglo
arr_length = arr2d.shape[1]
arr_length
10
# Creando una matriz con elementos que contienen el valor correspondiente a la posicion de la fila
for i in range(arr_length):
    arr2d[i] = i
    
arr2d
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [2., 2., 2., 2., 2., 2., 2., 2., 2., 2.],
       [3., 3., 3., 3., 3., 3., 3., 3., 3., 3.],
       [4., 4., 4., 4., 4., 4., 4., 4., 4., 4.],
       [5., 5., 5., 5., 5., 5., 5., 5., 5., 5.],
       [6., 6., 6., 6., 6., 6., 6., 6., 6., 6.],
       [7., 7., 7., 7., 7., 7., 7., 7., 7., 7.],
       [8., 8., 8., 8., 8., 8., 8., 8., 8., 8.],
       [9., 9., 9., 9., 9., 9., 9., 9., 9., 9.]])

La indexacion especial permite:

# sacar las filas 2, 4 , 6, 8
arr2d[[2,4,6,8]] # observe el uso de los dobles corchetes
array([[2., 2., 2., 2., 2., 2., 2., 2., 2., 2.],
       [4., 4., 4., 4., 4., 4., 4., 4., 4., 4.],
       [6., 6., 6., 6., 6., 6., 6., 6., 6., 6.],
       [8., 8., 8., 8., 8., 8., 8., 8., 8., 8.]])
#Permite cualquier orden
arr2d[[6,4,2,7]]
array([[6., 6., 6., 6., 6., 6., 6., 6., 6., 6.],
       [4., 4., 4., 4., 4., 4., 4., 4., 4., 4.],
       [2., 2., 2., 2., 2., 2., 2., 2., 2., 2.],
       [7., 7., 7., 7., 7., 7., 7., 7., 7., 7.]])

Ayudas para indexacion

La indexación de una matriz 2d puede ser un poco confusa al principio, especialmente cuando comienza a agregar pasos en la seleccion. Pruebe buscar imagenes en google con la parala NumPy indexing y encontrara ejemplos utiles como:

Seleccion basados en operadores de comparacion

# creacion de un arreglo de enteros desde 0 hasta 10
# recordar que el ultimo valor no se incluye
arreglo = np.arange(1,11)
arreglo
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
# determinar cuales valores del arreglo son mayores que 4
arreglo > 4 
# el resultado es un arreglo de Booleans
array([False, False, False, False,  True,  True,  True,  True,  True,
        True])
bool_arr = arreglo>4 # creacion de arreglo de booleans
bool_arr
array([False, False, False, False,  True,  True,  True,  True,  True,
        True])
# Seleccion de elementos usando un arreglo de Booleans

# Solamente se retornan los elementos en los cuales las
# posiciones de bool_arr sean verdaderas
arreglo[bool_arr] 
array([ 5,  6,  7,  8,  9, 10])
# Se puede hacer esta seleccion mucho mas rapida
# realizando la comparacion dentro de los corchetes

# Obtener los valores del arreglo mayores que 2
arreglo[arreglo>2] 
array([ 3,  4,  5,  6,  7,  8,  9, 10])
x = 2 # se puede hacer usando una variable
arreglo[arreglo>x]
array([ 3,  4,  5,  6,  7,  8,  9, 10])

Concatenacion

# En una dimension
x = np.array([1, 2, 3]) # Vector de valores
y = np.array([3, 2, 1]) # Vector de valores
np.concatenate([x, y]) # Concatenacion
array([1, 2, 3, 3, 2, 1])
# En dos dimensiones de forma vertical
grid = np.array([[1, 2, 3],[4, 5, 6]])
np.concatenate([grid, grid], axis=0)
array([[1, 2, 3],
       [4, 5, 6],
       [1, 2, 3],
       [4, 5, 6]])
# En dos dimensiones de forma horizontal
grid = np.array([[1, 2, 3],[4, 5, 6]])
np.concatenate([grid, grid], axis=1)
array([[1, 2, 3, 1, 2, 3],
       [4, 5, 6, 4, 5, 6]])

Operaciones con NumPy

Con las listas de python no se pueden realizar operaciones elemento a elemento, pero con NumPy si se puede realizar.

Como es el comportamiento de las listas ante las siguientres operaciones:

# observar el comportamiento de las listas
lista = list(range(10))
lista
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
lista + lista # este procedimento lo que hace es concatenar las listas
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
x = 2
lista*x # este procedimento hace que se repita la lista x numero de veces
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Si quiero realizar operaciones vectoriales o con matrices se realizan con arreglos NumPy

Aritmetica

Puede realizar fácilmente aritmética de matriz a matriz o aritmética de escalar con matriz.

import numpy as np # importar la libreria de NumPy
arr = np.arange(0,10) # crear un arreglo de 10 elementos
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr + arr # Suma elemento a elemento de los arreglos
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
# Suma de valores uno a uno con arreglos
aa = np.arange(5)
bb = np.arange(10) # este arreglo es mas grande que el anterios
aa+bb[:5] # deben tener el mismo tamaño para realizar la suma
array([0, 2, 4, 6, 8])
arr * arr # Multiplicacion elemento a elemento
array([ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81])
arr - arr # Resta elemento a elemento
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
# Mensaje de advertencia si se presenta 
# una division por cero, pero no hay error!
# solo se reemplazo por el valor nan
arr/arr # division elemento a elemento  
/tmp/ipykernel_38414/3422641830.py:4: RuntimeWarning: invalid value encountered in divide
  arr/arr # division elemento a elemento





array([nan,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.])
# Tambien genera una advertencia, pero no hay error
# lo que se genera es un infinity
# Observar el primer elemento del arreglo
1/arr
/tmp/ipykernel_38414/3744608766.py:4: RuntimeWarning: divide by zero encountered in divide
  1/arr





array([       inf, 1.        , 0.5       , 0.33333333, 0.25      ,
       0.2       , 0.16666667, 0.14285714, 0.125     , 0.11111111])
# eleva al cubo cada elemento del arreglo
arr**3 
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])

Funciones Universales de los arreglos

NumPy tiene integrado muchas funciones universales, que son esencialmente solo operaciones matemáticas que se pueden usar para realizar la operación en todo el arreglo. Las mas importantes son:

Algunos ejemplos son:

#Calcular la raiz cuadrada de cada elemento
np.sqrt(arr)
array([0.        , 1.        , 1.41421356, 1.73205081, 2.        ,
       2.23606798, 2.44948974, 2.64575131, 2.82842712, 3.        ])
#Calcular el exponencial (e^) de cada elemento
np.exp(arr)
array([1.00000000e+00, 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])
# Obtener el valor maximo como una funcion
np.max(arr) #lo mismo arr.max()
9
# Calcular el Sin de cada elemento
np.sin(arr)
array([ 0.        ,  0.84147098,  0.90929743,  0.14112001, -0.7568025 ,
       -0.95892427, -0.2794155 ,  0.6569866 ,  0.98935825,  0.41211849])
# Calcular el Logaritmo de cada elemento
np.log(arr)
/tmp/ipykernel_38414/1102247883.py:2: RuntimeWarning: divide by zero encountered in log
  np.log(arr)





array([      -inf, 0.        , 0.69314718, 1.09861229, 1.38629436,
       1.60943791, 1.79175947, 1.94591015, 2.07944154, 2.19722458])
# Calcular el valor absoluto
np.abs(arr)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Estadistica

Mas funciones en:

https://docs.scipy.org/doc/numpy/reference/routines.statistics.html

# Desviacion estandar
np.std(arr)
2.8722813232690143
# Promedio de los valores
np.mean(arr) 
4.5
# Media
np.median(arr)
4.5
# Varianza
np.var(arr)
8.25

Operaciones de Matrices

#creacion de una matriz de ejemplo
arr_2d = np.array(([5,10,15],[20,25,30],[35,40,45]))
arr_2d
array([[ 5, 10, 15],
       [20, 25, 30],
       [35, 40, 45]])
# transpuesta de una matriz
arr_2d.T # tambien puede ser arr_2d.transpose()
array([[ 5, 20, 35],
       [10, 25, 40],
       [15, 30, 45]])
# Multiplicacion de matrices
a = np.array([1,4,3]) # vector = arreglo de 1 dimension
b = np.array([2,-1,5]) # vector = arreglo de 1 dimension
a@b
13
# Multiplicacion de matrices
# Producto Punto
a = np.array(([2,0,1],[3,0,0],[5,1,1]))
b = np.array(([1,0,1],[1,2,1],[1,1,0]))
a@b 
array([[3, 1, 2],
       [3, 0, 3],
       [7, 3, 6]])
#Tambien puede ser de esta forma
np.dot(a,b)
array([[3, 1, 2],
       [3, 0, 3],
       [7, 3, 6]])
# Producto Cruz
a = np.array([1,4,3]) # vector = arreglo de 1 dimension
b = np.array([2,-1,5]) # vector = arreglo de 1 dimensi
np.cross(a,b)
array([23,  1, -9])

Referencias

Phd. Jose R. Zapata