PyConES 2014

Taller de Python científico

@CAChemE & @Pybonacci

1. El notebook de IPython

Será nuestra herramienta de trabajo durante el curso. Esto que estás leyendo ahora no es más que un notebook de IPython, que como diremos luego además de código puede contener texto e imágenes. Pero veamos primero cómo funciona.

Al iniciar el notebook de IPython, en la pantalla principal podemos ver una ruta y una lista de notebooks. Cada notebook es un archivo que está almacenado en el ordenador en la ruta que aparece. Si en esa carpeta no hay notebooks, veremos un mensaje indicando que la lista de notebooks está vacía.

Al crear un notebook o al abrir uno nuevo se abre la interfaz de IPython propiamente dicha donde ya podemos empezar a trabajar. Es similar a un intérprete, pero está dividida en celdas. Las celdas pueden contener, código, texto, imágenes...

Cada celda de código está marcada por la palabra In [<n>] y están numeradas. Tan solo tenemos que escribir el código en ella y hacer click arriba en Cell -> Run, el triángulo ("Run cell") o usar el atajo shift + Enter. El resultado de la celda se muestra en el campo Out [<n>], también numerado y coincidiendo con la celda que acabamos de ejecutar. Esto es importante, como ya veremos luego.

Si en la barra superior seleccionas Markdown (o usas el atajo Shift-M) en lugar de Code puedes escribir texto. Y si quieres incluso ecuaciones, ¿a quién no le gustan las ecuaciones?

$$g\!: \mathbb{R}^2 \rightarrow \mathbb{R} \\ g(x, y) = \cos{x} + \sin^2{y}$$

Algunas características interesantes de IPython:

  • Resultados de celdas en _<n>
  • Editor modal, atajos de teclado
  • Ayuda en línea y modos de autocompletado
In [10]:
%%timeit
# Ejemplo de medida de tiempo de ejecución
import time
def foo(x, y):
    time.sleep(0.01)
    return x ** y

for ii in range(10):
    for jj in range(10):
        kk = foo(ii, jj)
1 loops, best of 3: 1.01 s per loop
In [13]:
%%prun -s cumtime
# Ejemplo de profiling
import time
def foo(x, y):
    time.sleep(0.01)
    return x ** y

for ii in range(10):
    for jj in range(10):
        kk = foo(ii, jj)
 
In [12]:
# Ejemplo de depuración interactiva
%pdb on
def bar(t):
    ret = 1 / t
    return ret

bar(0)
Automatic pdb calling has been turned ON
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-12-e588d3341910> in <module>()
      5     return ret
      6 
----> 7 bar(0)

<ipython-input-12-e588d3341910> in bar(t)
      2 get_ipython().magic('pdb on')
      3 def bar(t):
----> 4     ret = 1 / t
      5     return ret
      6 

ZeroDivisionError: division by zero
> <ipython-input-12-e588d3341910>(4)bar()
      3 def bar(t):
----> 4     ret = 1 / t
      5     return ret

ipdb> c

También se pueden incrustar objetos como imágenes, vídeo... ¿conocéis ya a las PyLadies?

In [17]:
from IPython.display import Image
Image('pyladies_square.png')
Out[17]:

2. Arrays con NumPy

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.

Un array es un bloque de memoria que contiene elementos del mismo tipo. Básicamente:

  • nos recuerdan a los vectores, matrices, tensores...
  • podemos almacenar el array con un nombre y acceder a sus elementos mediante sus índices.
  • ayudan a gestionar de manera eficiente la memoria y a acelerar los cálculos.
Í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?

  • Vectores y matrices.
  • Datos de experimentos:
    • En distintos instantes discretos.
    • En distintos puntos del espacio.
  • Resultado de evaluar funciones con los datos anteriores.
  • Discretizaciones para usar algoritmos de: integración, derivación, interpolación...
In [18]:
%pdb off
Automatic pdb calling has been turned OFF

Creación de arrays a partir de listas

In [20]:
import numpy as np
In [21]:
mi_primer_array = np.array([1, 2, 3, 4]) 
mi_primer_array
Out[21]:
array([1, 2, 3, 4])
In [23]:
mi_segundo_array = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]) 

Funciones de creación de arrays

In [24]:
np.zeros(10)
Out[24]:
array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.])
In [25]:
np.zeros([10, 10])
Out[25]:
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.]])
In [26]:
np.ones([3, 2])
Out[26]:
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])
In [27]:
np.identity(4)
Out[27]:
array([[ 1.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  1.]])

Rangos

In [28]:
np.arange(0, 5)
Out[28]:
array([0, 1, 2, 3, 4])
In [29]:
np.arange(0,11,3)
Out[29]:
array([0, 3, 6, 9])
In [31]:
np.linspace(0, 1, 21)
Out[31]:
array([ 0.  ,  0.05,  0.1 ,  0.15,  0.2 ,  0.25,  0.3 ,  0.35,  0.4 ,
        0.45,  0.5 ,  0.55,  0.6 ,  0.65,  0.7 ,  0.75,  0.8 ,  0.85,
        0.9 ,  0.95,  1.  ])

Operaciones vectorizadas

In [39]:
arr = np.arange(11)
arr
Out[39]:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
In [40]:
arr + 10
Out[40]:
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
In [41]:
arr * 2
Out[41]:
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20])
In [42]:
np.sin(arr)
Out[42]:
array([ 0.        ,  0.84147098,  0.90929743,  0.14112001, -0.7568025 ,
       -0.95892427, -0.2794155 ,  0.6569866 ,  0.98935825,  0.41211849,
       -0.54402111])

Entrada/Salida

In [36]:
datos = np.loadtxt("temperaturas.dat")
In [44]:
datos = datos / 10
datos[:10]
Out[44]:
array([[ 0.44, -0.33],
       [ 0.06, -0.56],
       [ 0.  , -0.44],
       [ 0.28, -0.11],
       [ 0.56,  0.  ],
       [ 0.78,  0.11],
       [ 0.72,  0.28],
       [ 0.89,  0.17],
       [ 0.94,  0.39],
       [ 0.83,  0.44]])

3. Visualización con matplotlib

matplotlib es, hoy por hoy, el estándar de facto de visualización con Python en dos dimensiones. Existen dos maneras diferentes de usarla: el paquete pyplot (inspirado en MATLAB y que pretende ser lo más similar posible) y la interfaz orientada a objetos.

La biblioteca matplotlib es gigantesca y es difícil hacerse una idea global de todas sus posibilidades en una primera toma de contacto. Es recomendable tener a mano la documentación y la galería (http://matplotlib.org/gallery.html#pylab_examples):

In [10]:
from IPython.display import HTML
HTML('<iframe src="http://matplotlib.org/gallery.html#pylab_examples" width="800" height="600"></iframe>')
Out[10]:

TODO

  • Perfil de Yukovski con widget al final [x]
In [45]:
%matplotlib inline
import matplotlib.pyplot as plt
In [5]:
plt.plot([0.0, 0.1, 0.2, 0.7, 0.9], [1, -2, 3, 4, 1])
Out[5]:
[<matplotlib.lines.Line2D at 0x7fe6ce424748>]
In [6]:
def f(x):
    return np.exp(-x ** 2)

x = np.linspace(-1, 3, 100)

plt.plot(x, f(x), label="Función f(x)")
plt.xlabel("Eje $x$")
plt.ylabel("$f(x)$")
plt.legend()
plt.title("Función $f(x)$")
Out[6]:
<matplotlib.text.Text at 0x7fe6ce329e10>
In [7]:
with plt.xkcd():
    plt.plot(x, f(x))
    plt.plot(x, 1 - f(x))
    plt.xlabel("Eje x")
/home/juanlu/.local/lib/python3.4/site-packages/matplotlib/font_manager.py:1279: UserWarning: findfont: Font family ['Humor Sans', 'Comic Sans MS'] not found. Falling back to Bitstream Vera Sans
  (prop.get_family(), self.defaultFamily[fontext]))
In [8]:
N = 100
a = np.random.randn(N)
b = np.random.randn(N)

plt.scatter(a, b)
Out[8]:
<matplotlib.collections.PathCollection at 0x7fe6ce17fd68>
In [10]:
s = np.abs(50 + 50 * np.random.randn(N))
c = np.random.randn(N)

plt.scatter(a, b, s=s, c=c, cmap=plt.cm.Blues)
plt.colorbar()
Out[10]:
<matplotlib.colorbar.Colorbar at 0x7fe6ce1ec438>

Ejercicio

Representar las curvas de nivel de esta función:

$$g(x, y) = \cos{x} + \sin^2{y}$$

Para obtener este resultado:

Ejercicio

In [46]:
def g(x, y):
    pass

# Necesitamos muchos puntos en la malla, para que cuando se
# crucen las líneas no se vean irregularidades
#x = ?
#y = ?

xx, yy = np.meshgrid(x, y)

zz = g(xx, yy)

# Podemos ajustar el tamaño de la figura con figsize
fig = plt.figure(figsize=(6, 6))

# Ajustamos para que tenga 13 niveles y que use el colormap Spectral
# Tenemos que asignar la salida a la variable cs para luego crear el colorbar
#cs = ?

# Creamos la barra de colores
#plt.?

# Con `colors='k'` dibujamos todas las líneas negras
# Asignamos la salida a la variable cs2 para crear las etiquetas
#cs = ?

# Creamos las etiquetas sobre las líneas
plt.clabel(cs)

# Ponemos las etiquetas de los ejes
Out[46]:
<matplotlib.text.Text at 0x7fdb99d13a90>
In [47]:
def frecuencias(f1=10.0, f2=100.0):
    max_time = 0.5
    times = np.linspace(0, max_time, 1000)
    signal = np.sin(2 * np.pi * f1 * times) + np.sin(2 * np.pi * f2 * times)
    with plt.style.context("ggplot"):
        plt.plot(signal, label="Señal")
        plt.xlabel("Tiempo ($t$)")
        plt.title("Dos frecuencias")
        plt.legend()

frecuencias()
In [48]:
from IPython.html.widgets import interactive

interactive(frecuencias, f1=(10.0,200.0), f2=(10.0,200.0))

Este notebook está basado en el curso de AeroPython, Juan Luis Cano (@Pybonacci) y Álex Sáez (@Alex__S12)

Licencia Creative Commons