En este notebook veremos como las principales características de los arrays de NumPy y cómo mejoran la eficiencia de nuestro código.
El objeto tipo array que proporciona NumPy (Python ya dispone de un tipo array que sirve para almacenar elementos de igual tipo pero no proporciona toda la artillería matemática necesaria como para hacer operaciones de manera rápida y eficiente) se caracteriza por:
Comencemos viendo que ocurre con las listas:
In [1]:
import numpy as np
In [2]:
lista = [ 1, 1+2j, True, 'aerodinamica', [1, 2, 3] ]
lista
Out[2]:
En el caso de los arrays:
In [3]:
array = np.array([ 1, 1+2j, True, 'aerodinamica'])
array
Out[3]:
¿Todo bien? Pues no. Mientras que en la lista cada elemento conserva su tipo, en el array, todos han de tener el mismo y NumPy ha considerado que todos van a ser string.
¡Tranquilo! los allocate son automáticos...
Igual que en el caso anterior, comencemos con la lista:
In [4]:
print(id(lista))
lista.append('fluidos')
print(lista)
print(id(lista))
In [5]:
print(id(array))
array = np.append(array, 'fluidos')
print(array)
print(id(array))
Si consultamos la ayuda de la función np.append
escribiendo en una celda help(np.append)
podemos leer:
Returns
-------
append : ndarray
A copy of `arr` with `values` appended to `axis`. Note that `append` does not occur in-place: a new array is allocated and filled. If `axis` is None, `out` is a flattened array.
Hasta el momento los arrays han demostrado ser bastante menos flexibles que las listas, luego olvidemos estos últimos 10 minutos y manejemos siempre listas... ¿no? ¡Pues no! Los arrays realizan una gestión de la memoria mucho más eficiente que mejora el rendimiento.
Prestemos atención ahora a la velocidad de ejecución gracias a la función mágica %%timeit
, que colocada al inicio de una celda nos indicará el tiempo que tarda en ejecutarse.
In [6]:
lista = list(range(0,100000))
type(lista)
Out[6]:
In [7]:
%%timeit
sum(lista)
In [8]:
array = np.arange(0, 100000)
In [9]:
%%timeit
np.sum(array)
Como ves, las mejoras en este caso son de 2 órdenes de magnitud. NumPy nos ofrece funciones que se ejecutan prácticamente en tiempos de lenguaje compilado (Fortran, C, C++) y optimizado, pero escribiendo mucho menos código y con un nivel de abstracción mayor. Conociendo una serie de buenas prácticas, podremos competir en velocidad con nuestros códigos en Python. Para casos en los que no sea posible, existen herramientas que nos permiten ejecutar desde Python nuestros códigos en otros lengujes como f2py. Este tema puede resultarte algo avanzado a estas alturas, pero bastante útil; puedes consultar este artículo de pybonacci si lo necesitas.
Para recordar los primeras lecciones vamos a implementar nuestra propia función linspace
usando un bucle (estilo FORTRAN) y usando una list comprehension (estilo pythonico). Después compararemos el rendimiento comparado con la de NumPy.
In [10]:
def my_linspace_FORTRAN(start, stop, number=50):
x = np.empty(number)
step = (stop - start) / (number - 1)
for ii in range(number):
x[ii] = ii * step
x += start
return x
In [11]:
def my_linspace_PYTHONIC(start, stop, number=50):
step = (stop - start) / (number - 1)
x = np.array([ii * step for ii in range(number)]) #esto es una list comprehension
x += start
return x
In [12]:
%%timeit
np.linspace(0,100,1000000)
In [13]:
%%timeit
my_linspace_FORTRAN(0,100,1000000)
In [14]:
%%timeit
my_linspace_PYTHONIC(0,100,1000000)
Hemos aprendido:
En definitiva:
¡Quiero más!Algunos enlaces:
Algunos enlaces en Pybonacci:
Algunos enlaces en otros sitios: