_Hasta ahora hemos visto los tipos de datos más básicos que nos ofrece Python: integer, real, complex, boolean, list, tuple... Pero ¿no echas algo de menos? Efectivamente, los arrays. _
Durante esta nos adentraremos en el paquete NumPy: veremos como los arrays mejoran la eficiencia de nuestro código, aprenderemos a crearlos y a operar con ellos.
Un array es un bloque de memoria que contiene elementos del mismo tipo. Básicamente:
Índice | 0 | 1 | 2 | 3 | ... | n-1 | n |
---|---|---|---|---|---|---|---|
Valor | 2.1 | 3.6 | 7.8 | 1.5 | ... | 5.4 | 6.3 |
¿Qué solemos guardar en arrays?
NumPy es un paquete fundamental para la programación científica que proporciona un objeto tipo array para almacenar datos de forma eficiente y una serie de funciones para operar y manipular esos datos. Para usar NumPy lo primero que debemos hacer es importarlo:
import numpy as np
#para ver la versión que tenemos instalada:
np.__version__
'1.9.0'
¿No decíamos que Python era fácil? Pues creemos nuestros primeros arrays:
import numpy as np
# Array de una dimensión
mi_primer_array = np.array([1, 2, 3, 4])
mi_primer_array
array([1, 2, 3, 4])
# Podemos usar print
print(mi_primer_array)
[1 2 3 4]
# Comprobar el tipo de mi_primer_array
type(mi_primer_array)
numpy.ndarray
# Comprobar el tipo de datos que contiene
mi_primer_array.dtype
dtype('int64')
Los arrays de una dimensión se crean pasándole una lista como argumento a la función np.array
. Para crear un array de dos dimensiones le pasaremos una lista de listas:
# Array de dos dimensiones
mi_segundo_array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Esto sería una buena manera de definirlo, de acuerdo con el PEP 8 (indentation):
mi_segundo_array = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
Hemos dicho que NumPy también incorporá funciones. Un ejemplo sencillo:
# Suma
np.sum(mi_primer_array)
10
# Máximo
np.max(mi_primer_array)
4
# Seno
np.sin(mi_segundo_array)
array([[ 0.84147098, 0.90929743, 0.14112001], [-0.7568025 , -0.95892427, -0.2794155 ], [ 0.6569866 , 0.98935825, 0.41211849]])
Y algunas constantes que podemos neccesitar:
np.pi, np.e
(3.141592653589793, 2.718281828459045)
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:
lista = [ 1, 1+2j, True, 'aerodinamica', [1, 2, 3] ]
lista
[1, (1+2j), True, 'aerodinamica', [1, 2, 3]]
En el caso de los arrays:
array = np.array([ 1, 1+2j, True, 'aerodinamica'])
array
array(['1', '(1+2j)', 'True', 'aerodinamica'], dtype='<U64')
¿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:
print(id(lista))
lista.append('fluidos')
print(lista)
print(id(lista))
139998521305288 [1, (1+2j), True, 'aerodinamica', [1, 2, 3], 'fluidos'] 139998521305288
print(id(array))
array = np.append(array, 'fluidos')
print(array)
print(id(array))
139998351447504 ['1' '(1+2j)' 'True' 'aerodinamica' 'fluidos'] 139998351448304
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.
lista = list(range(0,100000))
type(lista)
list
%%timeit
sum(lista)
1000 loops, best of 3: 1.61 ms per loop
array = np.arange(0, 100000)
%%timeit
np.sum(array)
10000 loops, best of 3: 98.2 µs per loop
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.
¿Demasiada teoría? vayamos a la práctica. Ya hemos visto que la función np.array()
nos permite crear arrays con los valores que nosotros introduzcamos manualmente a través de listas. Más adelante, aprenderemos a leer ficheros y almacenarlos en arrays. Mientras tanto, ¿qué puede hacernos falta?
# En una dimensión
np.zeros(100)
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.])
# En dos dimensiones
np.zeros([10,10])
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.]])
np.empty(10)
array([ 6.91684881e-310, 2.03912117e-316, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000, 0.00000000e+000])
np.ones([3,2])
array([[ 1., 1.], [ 1., 1.], [ 1., 1.]])
np.identity(4)
array([[ 1., 0., 0., 0.], [ 0., 1., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 1.]])
NumPy, dame un array que vaya de 0 a 5:
a = np.arange(0, 5)
a
array([0, 1, 2, 3, 4])
Mira con atención el resultado anterior, ¿hay algo que deberías grabar en tu cabeza para simpre? El último elemento no es 5 sino 4
NumPy, dame un array que vaya de 0 a 10, de 3 en 3:
np.arange(0,11,3)
array([0, 3, 6, 9])
Si has tenido que usar MATLAB alguna vez, seguro que esto te suena:
np.linspace(0, 10, 21)
array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5, 10. ])
En este caso sí que se incluye el último elemento.
Con np.arange()
es posible crear "vectores" cuyos elementos tomen valores consecutivos o equiespaciados, como hemos visto anteriormente. ¿Podemos hacer lo mismo con "matrices"? Pues sí, pero no usando una sola función. Imagina que quieres crear algo como esto:
np.arange()
.np.reshape(array, (dim0, dim1))
.a = np.arange(1,10)
M = np.reshape(a, [3,3])
M
array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# También funciona como método
N = a.reshape([3,3])
N
array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Python es un lenguaje que está altamente modularizado: está dividido en bibliotecas que realizan tareas específicas. Para hacer uso de ellas debemos importarlas. Podemos importar cosas de la biblioteca estándar, de paquetes que hayamos descargado (o se enceuntren en nuestra distribución) o de módulos que nosotros mismos construyamos.
Existen varias formas de importar:
import numpy
Cada vez que queramos acceder a una función de numpy, deberemos escribir:
numpy.sin(5)
numpy.linspace(0,100,50)
Como esto puede resultar tedioso, suele utilizarse un namespace, el recomendado en la documentación oficial y que usaremos en el curso es:
import numpy as np
Ahora podremos llamar a funciones escribiendo:
np.sin(5)
np.linspace(0,100,50)
Si esto te sigue pareciendo demasido escribir puedes hacer (altamente no recomendado):
from numpy import *
El asterisco, quiere decir TODO. Esto genera varios problemas:
from numpy import *
a = [1,2,3,4,5]
sin(a)
array([ 0.84147098, 0.90929743, 0.14112001, -0.7568025 , -0.95892427])
from math import *
sin(a)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-37-bd5f2f081266> in <module>() 1 from math import * 2 ----> 3 sin(a) TypeError: a float is required
La función seno que incorporá math no es la misma que la de NumPy. Ambas proporcionarán el seno de un número, evidentemente, el mismo resultado para el mismo número, pero una acepta listas y la otra no. Al hacer la segunda importación, la función seno de NumPy se ha sustituido por la de math y la misma sentencia, da un error. Esto puede hacer que te vuelvas un poco loco si tu código es grande o acabes volviendo loco a alguien si usa tu código.
¿Suficiente? Ahora ya sabes por qué tendrás que escribir np.loquesea
siempre.
import numpy as np
Ahora que pocas cosas se nos escapan de los arrays, probemos a hacer algunas operaciones. El funcionamiento es el habitual en FORTRAN y MATLAB y poco hay que añadir:
#crear un arra y y sumarle un número
arr = np.arange(11)
arr + 55
array([55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65])
#multiplicarlo por un número
arr * 2
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20])
#elevarlo al cuadrado
arr ** 2
array([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100])
#calcular una función
np.tanh(arr)
array([ 0. , 0.76159416, 0.96402758, 0.99505475, 0.9993293 , 0.9999092 , 0.99998771, 0.99999834, 0.99999977, 0.99999997, 1. ])
Si las operaciones involucran dos arrays también se realizan elemento a elemento
#creamos dos arrays
arr1 = np.arange(0,11)
arr2 = np.arange(20,31)
#los sumamos
arr1 + arr2
array([20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40])
#multiplicamos
arr1 * arr2
array([ 0, 21, 44, 69, 96, 125, 156, 189, 224, 261, 300])
# >,<
arr1 > arr2
array([False, False, False, False, False, False, False, False, False, False, False], dtype=bool)
# ==
arr1 == arr2 # ¡ojo! los arrays son de integers, no de floats
array([False, False, False, False, False, False, False, False, False, False, False], dtype=bool)
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[::2] = 2
v
array([ 2., 1., 2., 1., 2., 1., 2., 1., 2., 1.])
tablero = np.zeros((8, 8))
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.]])
Extra:
%matplotlib inline
import matplotlib.pyplot as plt
plt.matshow(tablero, cmap=plt.cm.gray_r)
<matplotlib.image.AxesImage at 0x7fd334544358>
___Hemos aprendido:___
En definitiva:
El próximo día aprenderemos cómo acceder a elementos de un array slicing, cómo realizar algunas operaciones de álgebra lineal (determinantes, trazas, autovalores...) y practicaremos todo lo aprendido.
__¡Quiero más!__Algunos enlaces:
Algunos enlaces en Pybonacci:
Algunos enlaces en otros sitios:
Clase en vídeo, parte del Curso de Python para científicos e ingenieros grabado en la Escuela Politécnica Superior de la Universidad de Alicante.
from IPython.display import YouTubeVideo
YouTubeVideo("UltVlYCacD0", width=560, height=315, list="PLGBbVX_WvN7bMwYe7wWV5TZt1a58jTggB")
Las siguientes celdas contienen configuración del Notebook
Para visualizar y utlizar los enlaces a Twitter el notebook debe ejecutarse como seguro
File > Trusted Notebook
%%html
<a href="https://twitter.com/AeroPython" class="twitter-follow-button" data-show-count="false">Follow @AeroPython</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
# Esta celda da el estilo al notebook
from IPython.core.display import HTML
css_file = '../static/styles/style.css'
HTML(open(css_file, "r").read())