Librería Numpy

La librería numpy ofrece funciones eficientes para la manipulación y el procesamiento numérico en arrays.

Este tipo de estructuras de almacenamiento númerico no son exactamente listas aunque puedan aparentar ese comportamiento (duck-style). Sus elementos son homogéneos e incluyen operaciones básicas y operaciones más complejas como álgebra lineal.

Numpy forma parte del core de otras librerías como Pandas.

https://numpy.org/doc/stable/index.html

Mediante el siguiente comando en la terminal podemos comprovar si tenemos la librería numpy.

[ ]:
uv pip freeze | grep numpy

Si no está instalada podemos hacerlo mediante el siguiente comando en la terminal.

[ ]:
uv pip install numpy

Una vez comprobado que disponemos de la librería podemos empezar a usarla. Para usar la libreria siempre debemos importarla primero como en la siguiente celda.

[ ]:
import numpy as np

El resultado de la importación no tiene salida, pero ya podemos usar todas las funcionalidades de la librería.

[ ]:
data = np.array([1,0])
print(data[0])
print(type(data[0]))

data = np.array([[1,0],[2,0],[3,0]])
print(data[0][:0])

[ ]:
print(data.shape)
print(data.size)
print(data.ndim)
print(data.dtype)

Tipos de datos soportados:

  • int: int8, int16, int32, int64

  • uint: uint8, uint16, uint32, uint64

  • bool: Bool

  • float: float16, float32, float64, float128

  • complex: complex64, complex128, complex256

[ ]:
data = np.array([[1,0],[2,0]],dtype=np.int32)
data = np.array([[1,0],[2,0]],dtype=np.complex64)
data = np.array([[1,0],[2,0]],dtype=np.float16)
# Conversión
data = np.array(data,dtype=np.uint)
data = data.astype(np.bool_)
print(data)

Generación

No todo es cargar valores. A veces es necesario generar una muestra de puntos aleartoria, un vector neutro, o escala de valores.

[ ]:
data = np.array(range(10))
print(data)

data = np.arange(10)
print(data)

data = np.zeros((10,5))
# https://numpy.org/doc/stable/reference/generated/numpy.zeros.html
print(data)

data = np.ones(10)
print(data)
[ ]:
shape = (10,80) # 10 rows x 80 cols
data = np.zeros(shape)
print(data)
[ ]:
data = np.linspace(0,1,10)
print(data)
[ ]:
data = np.logspace(0,2,10) # 10puntos entre 2**0 y 2**2
print(data)
[ ]:
data = np.identity(3)
print(data)
print("-"*10)

data = np.eye(3,k=1) # https://numpy.org/doc/stable/reference/generated/numpy.eye.html
print(data)
print("-"*10)

data = np.diag(range(1,4))
print(data)

Generación o sampling aleatorio

Numpy incluye un módulo específico para generación de números aleatorios: numpy.random. Este módulo incluye funciones para generar números aleatorios siguiendo diferentes distribuciones estadísticas, como uniforme, normal, binomial, entre otras.

[ ]:
data = np.random.rand(3,3)
print(data)
print("-"*20)

data = np.random.randint(1,10,size=(2,2))
print(data)
print("-"*20)

data = np.random.randint(1,10,20).reshape(10,2)
print(data)
print("-"*20)

data = np.array(np.random.rand(20)*10,dtype=int).reshape(10,2)
print(data)

Actividades

Actividad 1

Imaginad la siguiente actividad donde tenemos que realizar la siguiente matriz:

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

tip: https://numpy.org/doc/stable/reference/generated/numpy.repeat.html

[ ]:
# TODO ACTIVITY

Actividad 2

Generar la siguiente estructura a partir del array([1, 2, 3])

array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3])

tip: https://numpy.org/doc/stable/reference/generated/numpy.tile.html

[ ]:
# TODO Activity

Carga y volcado de datos

Entrada y salida

¿Cómo cargar datos de un fichero y cómo salvar resultados?

Siempre hay que considerar el tipo de formato con el que se han guardado los datos. El formato influye en el rendimiento de las operaciones (R/W) y la capacidad de almacenamiento utilizada.

Carga de datos con Numpy

Vamos a utilizar cualquier fichero csv (i.e. altura de gabilos) para tener en una estructura de numpy dichos valores de altura. Podeis descargar este fichero des de este enlace.

Recomendaciones No pongais nombres de ficheros con espacios ni con acentos!

[ ]:
# https://numpy.org/doc/stable/reference/generated/numpy.loadtxt.html
data = np.loadtxt("data/GALIBOS TUNELES MADRID.csv", delimiter=";", usecols = 1, skiprows=1, converters={1: lambda s:float(str(s.decode()).replace(",","."))})
print(data)
print(data.shape)

Volviendo a trabajar con datos numéricos

[ ]:
data = np.random.uniform(0.01,20.0,size=100000)
print(data[:5])
[ ]:
f = open("data/tmp.npy","wb") # Writing file, in Binary mode
np.save(f,data)
f.close()

with open("data/tmp2.csv","w") as f2: # Writing but in txt
    for n in data:
        f2.write(str(n)+",")

[ ]:
!ls -lih data/tmp*
[ ]:
data = np.random.uniform(0.01,20.0,size=100000)
data2 = np.random.normal(0.3,10,100000)
print(data[:5])
print(data2[:5])

f = open("data/tmp.npy","wb") # Writing file, in Binary mode
np.save(f,data)
np.save(f,data2)
f.close()

print("saved")


with open('data/tmp.npy', 'rb') as f:
    a = np.load(f)
    b = np.load(f)

print(a[:5])
print(b[:5])

[ ]:
import pickle

with open("data/tmp3.npy",'wb') as f:
    pickle.dump(a, f)
    pickle.dump(b, f)

[ ]:
!ls -lih data/tmp*
[ ]:
f = "data/tmp3.npy"
ca = pickle.load(open(f,"rb"))
print(ca)
cb = pickle.load(open(f,"rb"))
print(cb)

# Qué paso en este punto?
# ¿Cómo podriamos haber guardado ambas variables dentro del mismo fichero?

Operaciones con series numpy

Las operaciones básicas entre arrays de numpy son element-wise, es decir, se aplican elemento a elemento. Por ellos podemos hacer operaciones aritméticas básicas como suma, resta, multiplicación, división y potencias.

[ ]:
import numpy as np
a = np.array([.0,0.1])
b = np.array([1,1])
print(a+b)
print(a-b)
print(a/b)
print(a*b)
print(2**a)
[ ]:
c = np.array([1,1,1])
print(a+c) #Alerta
[ ]:
c = np.array([1,1,1,1]).reshape(2,2)
print(a*c)
print("-"*10)
print(a.dot(c)) #https://numpy.org/doc/stable/reference/generated/numpy.dot.html

[ ]:
# Tensor dot
#https://numpy.org/doc/stable/reference/generated/numpy.tensordot.html#numpy.tensordot
a = np.arange(60.).reshape(3,4,5)
b = np.arange(24.).reshape(4,3,2)
c = np.tensordot(a,b, axes=([1,0],[0,1]))
print(c)
print(c.shape)
[ ]:
# Einseum
# https://numpy.org/doc/stable/reference/generated/numpy.einsum.html

a = np.arange(25).reshape(5,5)
np.einsum("ii",a)
\[ \begin{align}\begin{aligned}a \otimes b\\En matemáticas, se llama producto de Kronecker, denotado con ⊗, a una operación sobre dos matrices de tamaño arbitrario que da como resultado una matriz bloque. Es un caso especial del producto tensorial. El producto de Kronecker no debería confundirse con el producto de matrices habitual, que es una operación totalmente diferente. Debe su nombre al matemático alemán Leopold Kronecker.\end{aligned}\end{align} \]

Definición extraida de Wikipedia (Enlace).

[ ]:
a = np.arange(1,5).reshape(2,2)
print(a)
b = np.array([0,5,6,7]).reshape(2,2)
print(b)
print("-"*10)
k = np.kron(a,b)
print(k)

Actividad

Implementa con operaciones básicas de numpy la multiplicación de Kron. Compara tiempos de ejecución entre tú versión y la ya implementada.

TIP: Para comparar tiempos de ejecución mira esta información.

[ ]:
#TODO Activity

Funciones sobre series

Hay muchas funciones ya implementadas en numpy para trabajar con arrays. Vamos a ver algunas de las más comunes:

  • Trigonometricas: np.cos, np.sin, np.tan

  • Exponenciales y logaritmos: np.exp, np.log

  • Estadísticas: np.sum, np.cumsum, np.mean, np.cumprod, np.min, np.argmax. Estas funciones también están implementadas como métodos de los arrays, a.mean().

[ ]:
a = np.array(range(10))
print(np.cos(a))
print(np.exp(a))
print(np.log(a))
[ ]:
print(np.sum(a))
print(np.cumsum(a))
print(np.mean(a))

print("\n",np.cumprod(a))
print(np.min(a))
print(np.argmax(a))

[ ]:
print(a.mean())
print(a.min())
print(a.argmax())
[ ]:
a = a.reshape(2,5)
print(a)
print("-"*10)
print(np.sum(a,axis=1))
print(np.sum(a,axis=0))

Actividades

Actividad. 1

¿Cómo calcular la distancia euclidea entre dos vectores?

\[d{v_1,v_2}=\sqrt{\sum_{k=1}^n(x_{1,k}-x_{2,k})^2}\]
[ ]:
v1 = np.arange(1,4)
v2 = np.arange(4,7)
#TODO Activity
# Solucion == 5.196152422706632

Actividad. 2

¿Y calcular la distancia de Manhattan?

\[d{v_1,v_2}= \sum_{k=1}^n \mid x_{1,k} - x_{2,k} \mid\]
[ ]:
v1 = np.arange(1,4)
v2 = np.arange(4,7)
#TODO Activity
# Solucion == 9

Restructurando la dimensión de una serie

[ ]:
a = np.arange(10)
print(a.shape)
print(a.reshape(2,5))
print(a)

[ ]:
a = a.reshape(2,5)
print(a.T)
print("-"*10)
print(np.hstack(a))  # https://numpy.org/doc/stable/reference/generated/numpy.hstack.html

[ ]:
b = np.arange(10,20).reshape(2,5)
print(b)
print("-"*10)
print(np.hstack((a,b))) # axis-1
print(np.vstack((a,b))) # axis-0

[ ]:
c = np.dstack((a,b)) # axis-2  https://numpy.org/doc/stable/reference/generated/numpy.dstack.html
print(c)
print(c.shape)
[ ]:
print(a)
print(np.ravel(a)) # https://numpy.org/doc/stable/reference/generated/numpy.ravel.html

print(np.ravel(a,order="F")) # ‘F’ means to index the elements in column-major,
[ ]:
print(a)
print(np.split(a,2))
c1,c2 = np.split(a,2)

print("-"*10)

print(c1)
print(c1.shape)
print(np.ravel(c1))
print(c2)
[ ]:
print(np.concatenate((a,b)))
print("-"*10)
print(np.concatenate((a,b),axis=1))
[ ]:
# https://pillow.readthedocs.io/en/stable/
[ ]:
%pip install pillow
[ ]:
from PIL import Image

image = Image.open('images/gatito.jpeg')
# summarize some details about the image
print(image.format)
print(image.size)
print(image.mode)

display(image)

[ ]:
data = np.asarray(image)
print(len(data[0]))
print(data.size)
print(data.shape)
w,h,_ = data.shape
[ ]:
print(data[w//2,h//2])
data2 = data.copy()
data2[w//2,h//2] = np.array([255,0,0]) # a red point

img = Image.fromarray(data2, 'RGB')

display(img)

[ ]:
center_mask = w//2,h//2
rectangle_size = int(w*0.1)
mask = np.ones(rectangle_size*rectangle_size).reshape(rectangle_size,rectangle_size)
print(mask.shape)

for x in range(w//2,w//2+rectangle_size):
    for y in range(h//2,h//2+rectangle_size):
        data2[x,y] =  np.array([255,0,0])


img = Image.fromarray(data2, 'RGB')
display(img)

# REALMENTE, el cuadrado está en el centro?

Actividad

Transforma la imagen en tonos grises. Solo con numpy!!!

[ ]:
#WAY 1:
#
data = np.asarray(image)
data2 = data.copy()
for x in range(data2.shape[0]): #no eficiente
     for y in range(data2.shape[1]):
         data2[x,y] = np.repeat(data[x,y].mean(),3)

img = Image.fromarray(data2, 'RGB')
display(img)

Operaciones de Slicing

[ ]:
a = np.arange(300).reshape(10,10,3)
print(a[:1])
print("-"*10)

print(a[0][0])
print("-"*10)

print(a[:,0])
print("-"*10)

print(a[:,2:4])
print("-"*10)
[ ]:
a = np.arange(300).reshape(10,10,3)
print(a[:1])
print("-"*10)

print(a[:,:,0])

print("-"*10)

r = 0.2126
print(a[:,:,0]*r)

[ ]:
# WAY2
# https://e2eml.school/convert_rgb_to_grayscale.html


# TODO

print(data.shape)
img = Image.fromarray(np.uint8(data))
display(img)


[ ]:
# Way 3

rgbcorrection = np.array([0.2989, 0.5870, 0.1140])

data = np.asarray(image)
print(data.shape)
data2 = np.dot(data,rgbcorrection)

print(data2.shape)
img = Image.fromarray(np.uint8(data2))
display(img)


# as a plot
import matplotlib.pyplot as plt
plt.imshow(data2, cmap = plt.get_cmap(name = 'gray'))
plt.show()

Funciones propias vectorizadas

Las funciones vectorizadas de NumPy son una de las características más potentes de esta biblioteca. Permiten realizar operaciones matemáticas y lógicas sobre arrays completos sin necesidad de usar bucles explícitos en Python. En lugar de iterar elemento por elemento, NumPy aplica la operación de forma simultánea a todos los elementos del array, aprovechando la eficiencia de la librería.

Esto no solo hace que el código sea más compacto y legible, sino también mucho más rápido. Por ejemplo, operaciones como la suma, multiplicación o funciones matemáticas (como np.sin, np.exp o np.sqrt) pueden aplicarse directamente sobre arrays enteros. En resumen, las funciones vectorizadas son clave para trabajar con grandes volúmenes de datos numéricos de forma eficiente y elegante.

En este apartado veremos como crear nuestras propias funciones vectorizadas. Como ejemplo, vamos a definir una función que determine si una temperatura es “caliente” o no. Definiremos “caliente” como cualquier temperatura superior a 33 grados Celsius.

Primero definimos una serie de temperaturas aleatorias para probar nuestra función usando np.random.randint.

[ ]:
temperatura = np.random.randint(-10,43,1000)

En la array temperatura tenemos 1000 valores. Para saber si cada uno de esos valores es “caliente” o no, podemos definir una función is_hot que tome un valor de temperatura y devuelva True si es mayor a 33 grados y False en caso contrario.

[ ]:
def is_hot(grados):
    if 33 < grados <= 40: # Equivalente a grados>33 and grados<=40
        return True
    else:
        return False

def is_hot(grados):
    return 33 < grados <= 40

En la celda anterior podemos observar dos versiones de la misma función. Se trata de una funció normal de python. Si la aplicamos directamente sobre la serie temperatura, obtendremos un error, ya que la función no está diseñada para manejar arrays de `NumPyì directamente.

[ ]:
is_hot(temperatura)

Para utilizar estas funciones con una array de NumPy, podemos vectorizar la función usando np.vectorize. Esto crea una versión vectorizada de la función que puede aplicarse a cada elemento del array de manera eficiente.

[ ]:
f_hot = np.vectorize(is_hot)
print(f_hot(temperatura))

Existen otras formas de aplicar condiciones lógicas sobre arrays de NumPy. Por ejemplo, podemos usar operaciones lógicas directamente sobre la array para crear una máscara booleana que indique qué elementos cumplen la condición de ser “calientes”. Otra opción es usar la función map de Python para aplicar la función is_hot a cada elemento de la array.

Se recomienda usar las funciones vectorizadas de NumPy por su versatilidad.

[ ]:
# https://numpy.org/doc/stable/reference/routines.logic.html
np.logical_and(temperatura>30,temperatura<=40)
[ ]:
isHot = lambda x: (x>30 and x<=40)
index = list(map(isHot,temperatura))
temperatura[index]

Más funciones lógicas

[ ]:
np.random.seed(2022)
a = np.random.randint(-90,0,100)

index = np.where(a<-80) # Alerta: Son índices
print(index)
print(a[index])
[ ]:
print(np.logical_or(a<-80,a<-90))

[ ]:
print(np.logical_not(np.logical_or(a<-40,a<-90)))

Actividades

¿Cómo podemos conseguir esta transformación?

De

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

a

array([ 0, -1,  2, -1,  4, -1,  6, -1,  8, -1])
[ ]:

Operaciones con Grupos

[ ]:
np.random.seed(2022)
a = np.random.randint(-30,45,100)
print(a)
[ ]:
9 in a
[ ]:
if -14 in a and not -6 in a:
    print("Something strange")
elif 45 in a:
    print("No 11")
else:
    print("Pues está el -14 y el -6, y no el 45")
[ ]:
#https://numpy.org/doc/stable/reference/generated/numpy.unique.html?highlight=unique#numpy.unique

unique_a = np.unique(a)  # sort but
print(unique_a)
[ ]:
unique_a, freq_a = np.unique(a,return_counts=True)
print(a)
print(len(a))
print("-"*10)
print(freq_a)
print(len(freq_a))
print("-"*10)
print(unique_a)
print(len(unique_a))

# ¿Cuántos elementos repetidos hay?
[ ]:
unique_a,index_a,freq_a = np.unique(a,return_counts=True,return_index=True)
print(freq_a)
print(index_a)
print("-"*10)
print(np.where(freq_a==4))
print(unique_a[17]) # se repite cuatro veces
print(unique_a[33]) # se repite cuatro veces
print(unique_a[50]) # se repite cuatro veces

print(a[np.where(a==-7)])
[ ]:
np.sort(freq_a)
[ ]:

print(np.array([0,4,1,2,5,7,9])[::-1]) print(np.argsort(np.array([0,4,1,2,5,7,9]))) print(np.argsort(np.array([0,4,1,2,5,7,9]))[::-1]) print("-"*10) index_sorted = np.argsort(freq_a)[::-1] #https://numpy.org/doc/stable/reference/generated/numpy.argsort.html print(index_sorted)

[ ]:
unique_a[index_sorted]

Actividades

Actividad 1

  • ¿Cuál es el color más frecuente en la imagen del gatito?

  • Sustituye esos pixeles por un color azul: rgb=(0,0,255)

[ ]:
from PIL import Image

image = Image.open('images/gatito.jpeg')
#TODO

Unión y diferencia de conjuntos

La intersección, unión y diferencia de conjuntos es una operación común en álgebra de conjuntos y análisis de datos. Numpy proporciona funciones eficientes para realizar estas operaciones en arrays:

  • np.isin: Comprueba si los elementos de un array están presentes en otro array.

  • np.intersect1d: Encuentra los elementos comunes entre dos arrays.

  • np.setdiff1d: Encuentra los elementos que están en un array pero no en otro.

  • np.union1d: Encuentra todos los elementos únicos presentes en ambos arrays.

[ ]:
a = np.arange(10)
b = np.arange(5,15)
print(a)
print(b)
print("-"*10)
print(np.isin(a,b))
print(np.intersect1d(a,b))
print("-"*10)
print(np.setdiff1d(a,b))
print(np.setdiff1d(b,a))
print("-"*10)
print(np.union1d(a,b))

Actividad 2

¿Cómo podemos conseguir encontrar valores pico, valores mayores sobre sus vecinos?

array([0, 1, 2, 3, 4, 54, 6, 7, 80, 9])
array([5, 8])

Un par de pistas:

[ ]:
import numpy as np

a = np.array([0, 2, 2, 3, 4, 54, 6, 7, 80, 9])

print(np.diff(a))
print(np.sign(np.diff(a)))
print(np.diff(np.sign(np.diff(a))))
# TODO

Actividad 3

Existe alguna columna o fila que sólo tenga una única incógnita?

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

ceros_por_fila = np.count_nonzero(sudoku == 0, axis=1)
ceros_por_col = np.count_nonzero(sudoku == 0, axis=0)

print(ceros_por_fila, ceros_por_col)

Funciones de estadística

La librería NumPy ofrece una amplia gama de funciones estadísticas que permiten analizar y resumir datos de manera eficiente. Algunas de las funciones estadísticas más comunes en NumPy incluyen:

  • np.mean(): Calcula la media aritmética de los datos.

  • np.median(): Encuentra la mediana de los datos.

  • np.std(): Calcula la desviación estándar, que mide la dispersión de los datos alrededor de la media.

  • np.var(): Calcula la varianza, que es el cuadrado de la desviación estándar.

  • np.min() y np.max(): Encuentran los valores mínimo y máximo en un conjunto de datos.

  • np.percentile(): Calcula percentiles específicos, que dividen los datos en partes iguales.

  • np.histogram(): Crea un histograma que muestra la distribución de los datos.

[ ]:
np.random.seed(2022)
temperatures= np.random.normal(loc=17,scale=20,size=1000000)


# https://numpy.org/doc/stable/reference/routines.statistics.html

print(temperatures.mean())

[ ]:
import matplotlib.pyplot as plot

fig, ax = plot.subplots()
ax.plot(np.sort(temperatures))
[ ]:
np.quantile(temperatures,0.5)
[ ]:
np.percentile(a,90) #https://numpy.org/doc/stable/reference/generated/numpy.percentile.html#numpy.percentile
[ ]:
# https://numpy.org/doc/stable/reference/generated/numpy.histogram.html#numpy.histogram
hist, bin_edges = np.histogram(temperatures)
print(hist)
print(bin_edges)

print(np.sum(hist))

[ ]:
import matplotlib.pyplot as plt
_ = plt.hist(temperatures, bins='auto')  # arguments are passed to np.histogram
plt.title("Histogram with 'auto' bins")
plt.show()
[ ]:
# https://numpy.org/doc/stable/reference/generated/numpy.linspace.html

space = np.linspace(0,1,len(temperatures))
print(space[:10])
[ ]:
import matplotlib.pylab as plt

temperatures_sorted = np.sort(temperatures)
fig, ax = plt.subplots()
ax.plot(temperatures_sorted,space)
#CDF?

Gestión de alertas y errores

En computación, una excepción es un evento que ocurre durante la ejecución de un programa que interrumpe el flujo normal de las instrucciones. Cuando se produce una excepción, el programa puede dejar de funcionar correctamente o incluso finalizar abruptamente. Las excepciones pueden ser causadas por diversos factores, como errores de programación, condiciones inesperadas en los datos de entrada, problemas de hardware, entre otros.

Normalmente , cuando se produce una excepción, el programa genera un mensaje de error que describe la naturaleza del problema. Este mensaje puede incluir información sobre el tipo de excepción, la ubicación en el código donde ocurrió y detalles adicionales que pueden ayudar a diagnosticar y solucionar el problema.

[ ]:
a , b = 0, 3
c = b/a
[ ]:
a , b = 0, 3
try:
    c = b/a
except:
    print("error")
finally:
    print("Intento realizar una linea alternativa de ejecucion")

print("Esto sigue")
[ ]:
a , b = 0, 3
try:
    c = b/a
except ZeroDivisionError:
    print("Error")
finally:
    print("Intento realizar una linea alternativa de ejecución")
[ ]:
a , b = 0, 3
d={"a":0,"b":-1}
try:
    print(d["c"])
except ZeroDivisionError:
    print("error")
finally:
    print("Intento realizar una linea alternativa de ejecucion")
[ ]:
a , b = 0, 3
d={"a":0,"b":-1}
try:
    print(d["c"])
except ZeroDivisionError:
    print("hay un cero")
except KeyError:
    print("Key no existente")
finally:
    print("Intento realizar una linea alternativa de ejecucion")

Docs : https://docs.python.org/3/tutorial/errors.html

[ ]:
a , b = np.arange(10), np.arange(10,20)

c = b/a
print(c)
[ ]:
import math
print(math.inf in c)
print(c * np.random.rand(10))
[ ]:
try:
    c = b/a
except RuntimeWarning:
    print("Capturo warning ? ") # No
[ ]:
# Puedo ignorarlos
import warnings
warnings.filterwarnings("ignore")
c = b/a
print(c)
[ ]:
# Puedo gestionarlos como una excepción
np.seterr(all='raise')
c = b/a
print(c)
[ ]:
try:
    c = b/a
except FloatingPointError:
    print("Capturo warning ? ") # Yes

Reflexiones sobre el rendimiento computacional y algorítmico

El rendimiento de un sistema informático se puede medir mediante diversas métricas que reflejan tanto la experiencia del usuario como la eficiencia del sistema en sí.

Principales métricas percibidas por el usuario:

  • tiempo de respuesta, tiempo de servicio y tiempo de espera

Principales métricas para el sistema:

  • Productividad (trabajos/tiempo)

El rendimiento está influido por:

  • Hardware: tecnología, arquitectura,

  • Software: sistemas operativos, lenguaje de programación, aplicaciones

  • Vuestra manera de programar!

[ ]:
import time

start = time.time()
# do something
print("Response time: %s seconds"%(time.time()-start))
[ ]:
import numpy as np
import time
serie = np.random.random(10000000)

start = time.time()
b = []
for value in serie:
    try:
        b.append(math.sqrt(value))
    except:
        b.append(0)
end1 = time.time()-start
print("Response time: %s seconds"%(end1))
[ ]:
start = time.time()
b = np.sqrt(serie)
end2 = time.time()-start
print("Response time: %s seconds"%(end2))
[ ]:
speedup = end1/end2
print(speedup)
print("El programa 2 es %0.2f veces más rápido que el programa 1"%speedup)

Si necesitáis 1 hora de ejecución del programa 1, con el segundo solo, 3.8199 minutos. Si necesitáis 24 horas de ejecución del programa 1, con el segundo solo, 1.527 horas.

Atención las métricas de rendimiento suelen seguir una distribución exponencial. NO SON LINEALES!!!!

[ ]:
times1 = []
for size in range(10,1000000,1000):
    serie = np.random.random(size)
    start = time.time()
    b = []
    for value in serie:
        try:
            b.append(math.sqrt(value))
        except:
            b.append(0)
    end1 = time.time()-start
    times1.append(end1)

print(".")
times2 = []
for size in range(10,1000000,1000):
    serie = np.random.random(size)
    start = time.time()
    b = np.sqrt(serie)
    end2 = time.time()-start
    times2.append(end2)

[ ]:
import matplotlib.pyplot as plt
x = list(range(len(times1)))
fig, ax = plt.subplots()
ax.plot(x, times1, label = "programa 1")
ax.plot(x, times2, label = "programa 2")
ax.legend()
plt.show()

# No es suficientemente complejo (o sí -depende de arquitectura) para crear la curva

Librería Numba

Numba es una librería de código abierto para Python que permite acelerar el rendimiento de código numérico mediante compilación Just-In-Time (JIT). Está especialmente diseñada para trabajar con NumPy, aunque también puede usarse con código Python estándar que haga uso intensivo de cálculos matemáticos.

¿Qué hace Numba?

Numba traduce funciones de Python a código máquina optimizado. Esto permite que las funciones numéricas se ejecuten a velocidades mucho mas altas, sin necesidad de reescribirlas en otro lenguaje.

Instalación

Puedes instalar Numba fácilmente con pip o conda:

uv pip install numba
[ ]:
import multiprocessing

multiprocessing.cpu_count()
[ ]:
from numba import njit
import random


def monte_carlo_pi_sinParalelizar(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

@njit
def monte_carlo_pi(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples
[ ]:
%timeit monte_carlo_pi_sinParalelizar(100)
[ ]:
%timeit monte_carlo_pi(100)