Versión original en inglés de J.R. Johansson (robert@riken.jp) http://dml.riken.jp/~rob/
Traducido/Adaptado por G.F. Rubilar.
La última versión de estos notebooks de IPython está disponible en http://github.com/gfrubi/clases-python-cientifico.
La última versión del original (en inglés) de estos notebooks de IPython está disponible en http://github.com/jrjohansson/scientific-python-lectures.
Los otros notebooks de esta serie están listados en http://jrjohansson.github.com.
# Esta línea configura matplotlib para que muestre figuras en el cuaderno IPython,
# en lugar de en una ventana nueva. Más detalles abajo.
%matplotlib inline
Matplotlib es una excelente librería gráfica 2D y 3D. Algunas de las muchas ventajas de esta librería son:
Una de las características claves de Matplotlib y que hace a Matplotlib altamente adecuado para generar figuras para publicaciones científicas es que todos los aspectos de la figura pueden ser controlados en programando. Esto es imporante para la reproducibilidad y conveniente cuando se necesita regenerar la figura con datos actualizados o cambios en su apariencia.
Más información en la página de Matplotlib: http://matplotlib.org/
Una forma sencilla para comenzar a graficar usando Matplotlib es usando la API tipo MATLAB que Matplotlib suministra, a través del módulo 'matplotlib.pyplot'.
Esta API está diseñada para que sea compatible con las funciones para graficar de MATLAB, de modo que resulta fácil para aquellos acostumbrados a usar MATLAB.
Importamos entonces el módulo pyplot
:
from matplotlib.pyplot import *
Una figura simple usando la API tipo MATLAB:
from numpy import *
x = linspace(0,5,50)
y = x**2
plot(x,y,'r')
xlabel('x')
ylabel('y')
title(u'Título') # el caracter `u` es necesario para incluir acentos en el texto
show() # muestra el gráfico en una ventana, si no se ha usado %matplotlib inline
La mayoría de las funciones gráficas de MATLAB están includas en el módulo pyplot
. Por ejemplo, las funciones subplot y color/symbol:
subplot(1,2,1)
plot(x, y, 'r--')
subplot(1,2,2)
plot(y, x, 'g*-');
Lo positivo de la API tipo MATLAB Pyplot es que es fácil comenzar si ya se sabe usar MATLAB, y que requiere un mínimo de código para producir gráficos simples.
Sin embargo, esta API no es la más adecuada cuando se requiere producir gráficos complejos y/o se requiere controlar cada aspecto del gráfico. En este caso, es recomendable aprender y usar la API gráfica orientada al objeto de Matplotlib. Es muy poderosa y agradable de usar en caso de requerir figuras avanzadas, con sub-gráficos, objetos insertados y otros componentes.
La idea principal de la programación orientada al objeto is que se tienen objetos sobre los cuales se pueden aplicar funciones y acciones, y que ningún estado de un objeto o programa debe ser global (tal como en la API tipo MATLAB). La ventaja de esta forma de trabajo se manifiesta cuando se requiere crear más de una figura, o cuando una figura contiene sub-figuras.
Para usar la API orientada al objeto comenzamos similarmente al ejemplo anterior, pero en lugar de crear una instancia de figura global almacenamos una referencia a la figura recién creada en la variable fig
, y a partir de ella creamos nuevos ejes axes
usando el método add_axes
en la instancia fig
de la clase Figure
.
fig = figure()
ejes = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # izquierda, abajo, ancho, altura (rango 0 a 1)
ejes.plot(x,y,'r')
ejes.set_xlabel('x')
ejes.set_ylabel('y')
ejes.set_title(u'Título');
Aunque se requiere algo más de código, la ventaja es que ahora tenemos control completo sobre dónde se ubican los ejes, y además podemos agregar fácilmente más de un eje a la figura.
fig = figure()
axes1 = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # ejes principales
axes2 = fig.add_axes([0.2, 0.5, 0.4, 0.3]) # ejes del gráfico insertado
# figura principal
axes1.plot(x,y,'r')
axes1.set_xlabel('x')
axes1.set_ylabel('y')
axes1.set_title(u'Título principal')
# figura insertada
axes2.plot(y,x,'g')
axes2.set_xlabel('y')
axes2.set_ylabel('x')
axes2.set_title(u'Título secundario');
Si no nos importa ser explícitos sobre dónde estarán ubicados los ejes en el marco de nuestra figura, podemos usar uno de los muchos administradores de la distribución de los ejes que tiene Matplotlib. Un favorito es subplots
, que puede ser usado de la forma siguiente:
fig, axes = subplots() # crea una nueva figura y un set de ejes
axes.plot(x,y,'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title(u'Título');
# crea una figura, con dos set de ejes, almacenados en el array axes
fig, axes = subplots(nrows=1, ncols=2)
for ax in axes:
ax.plot(x, y, 'r')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(u'Título');
Fácil, pero no tan bonito debido a la superposición de los ejes y etiquetas en la parte central, cierto?
Podemos mejorar esto usando el método fig.tight_layout
, que ajusta automáticamente la posición de los ejes en el marco de la figura de modo que no exista contenido que se superponga:
fig, axes = subplots(nrows=1, ncols=2)
for ax in axes:
ax.plot(x, y, 'r')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(u'Título')
fig.tight_layout()
Matplotlib permite especificar la proporción de los ejes, la densidad de puntos (DPI) y el tamaño de la figura cuando el objeto Figure
es creado, usando los argumentos figsize
y dpi
. figsize
es una tupla con el ancho y la altura de la figura en pulgadas, y dpi
es el número de puntos (pixels) por pulgada. Para crear una figura de 800 por 400 pixels podemos usar:
fig = figure(figsize=(8,4), dpi=100)
<matplotlib.figure.Figure at 0x7fe8d7afc410>
Los mismos argumentos pueden ser usados en los administradores de distribución de ejes, como subplots
.
fig, axes = subplots(figsize=(12,3))
axes.plot(x, y, 'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title(u'Título');
Para guardar una figura en un archivo podemos usar el método savefig
en la clase Figure
.
fig.savefig("archivo.png")
Podemos, en forma opcional, especificar los DPI, además de elegir entre varios formatos.
fig.savefig("archivo.png", dpi=200)
fig.savefig("archivo.svg")
Matplotlib puede crear gráficos de alta calidad en varios formatos, incluyendo PNG, JPG, EPS, SVG y PDF. Para publicaciones científicas, se recomienda usar PDF donde sea posible (y compilar documentos LaTeX con pdflatex
, que puede incluir las figuras en PDF usando el comando includegraphics
.
Ahora que hemos cubierto lo básico de cómo generar un marco de figura y agregar ejes al marco, veamos cómo decorar una figura con títulos, etiquetas de ejes y leyendas:
Títulos de figura
Se puede agregar un título a cada instancia de ejes de una figura. Para definir el título usamos el método set_title
en la instancia de ejes:
ax.set_title(u"Título")
<matplotlib.text.Text at 0x7fe8d7b293d0>
Etiquetas de ejes
Similarmente, usandos los métodos set_xlabel
y set_ylabel
podemos definir las etiquetas de los ejes X e Y:
ax.set_xlabel("x")
ax.set_ylabel("y")
<matplotlib.text.Text at 0x7fe8d7b1e750>
Leyendas
Las leyendas de las curvas (o puntos, etc.) de una figura pueden ser agregados de dos maneras. Podemos usar el método legend
de un eje y pasarle una lista/tupla de textos de lygendas para las curvas que ya han sido creadas previamente:
ax.legend(["curva1", "curva2", "curva3"]);
Este método sigue la API de MATLAB. Tiende a inducir a error y es poco flexible si se agregan o eliminan curvas de una figura (y como resultado se obtienen leyendas erroneamente asociadas a las curvas).
Es mejor usar los argumentos label="texto"
cuando se agregan gráficos u otros objetos a la figura, y luego usar el método legend
sin argumentos para agregar las leyendas:
ax.plot(x, x**2, label="curva1")
ax.plot(x, x**3, label="curva2")
ax.legend();
La ventaja de este método es que si se agregan o eliminan curvas a una figura, las leyendas se actualizan automáticamente.
La función legend
acepta el argumento adicional loc
que puede ser usado para especificar dónde se ubicará la leyenda en la figura. Los valores permitidos para loc
son números que codifican los distintos lugares donde puede ser ubicada la leyenda. Ver http://matplotlib.org/users/legend_guide.html#legend-location para más detalles. Las alternativas más comunes son:
ax.legend(loc=0) # deja que Matplotlib decida la ubicación óptima
ax.legend(loc=1) # esquina superior derecha
ax.legend(loc=2) # esquina superior izquierda
ax.legend(loc=3) # esquina inferior izquierda
ax.legend(loc=4) # esquina inferior derecha
# .. muchas otras opciones disponiles
<matplotlib.legend.Legend at 0x7fe8d7c2ac90>
La siguiente figura muestra cómo usar los títulos, etiquetas de ejes y leyendas descritas anteriormente:
fig, ax = subplots()
ax.plot(x, x**2, label="y = x**2")
ax.plot(x, x**3, label="y = x**3")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(u'Título')
ax.legend(loc=2); # esquina superior izquierda
<matplotlib.legend.Legend at 0x7fe8d7503310>
La figura anterior es funcional, pero (aún) no satisface los criterior para una figura usada en una publicación. Primero, necesitamos tener texto formateado en LaTeX, y además necesitamos poder ajustar el tamaño de las letras que aparecen en la publicación.
Matplotlib tiene un muy buen soporte de LaTeX. Lo único que se requiere hacer es usar el signo $ para encapsular cualquier texto (leyenda, título, etiqueta, etc.). Por ejemplo `"$y=x^3$"`.
Aquí, sin embargo, podemos encontrarnos con un sutil problema con el código LaTeX y las cadenas de texto de Python. En LaTeX frecuentemente usamos el backslash \ en los comandos, por ejemplo \alpha
para producir el símbolo $\alpha$. Pero \ ya tiene un significado en las cadenas Python (el caracter de escape). Para evitar que Python malinterprete nuestro código LaTeX necesitamos usar cadenas de textos "crudas" ("raw"). Las cadenas de texto crudas son precedidas por 'r', por ejemplo r"\alpha"
o r'\alpha'
en lugar de "\alpha"
y '\alpha'
.
fig, ax = subplots()
ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.set_xlabel(r'$\alpha$', fontsize=18)
ax.set_ylabel(r'$y$', fontsize=18)
ax.set_title(u'Título')
ax.legend(loc=2); # esquina superior izquierda
<matplotlib.legend.Legend at 0x7fe8d7448710>
Podemos también cambiar el tamaño y el tipo de letra globalmente, de modo que se aplique a todos los textos de una figura (etiquedas numéricas, etiquetas de ejes, títulos, leyendas, etc.):
# Actualiza los parámetros de configuración de Matplotlib:
matplotlib.rcParams.update({'font.size': 18, 'font.family': 'serif'})
fig, ax = subplots()
ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.set_xlabel(r'$\alpha$')
ax.set_ylabel(r'$y$')
ax.set_title(u'Título')
ax.legend(loc=2); # esquina superior izquierda
<matplotlib.legend.Legend at 0x7fe8d73bff10>
# vuelve a la configuración original
matplotlib.rcParams.update({'font.size': 12, 'font.family': 'sans'})
En Matplotlib podemos definir los colores de las líneas y otrs elementos gráficos de distintas maneras. Primero, podemos usar la sintaxis tipo MATLAB donde 'b'
signifuca azul (blue) 'g'
significa verde (green), etc. La API MATLAB para para seleccionar tipos de línea también está disponible: por ejemplo 'b.-' significa línas con puntso azules.
# Selección de color y estilo de línea tipo MATLAB
ax.plot(x, x**2, 'b.-') # línea con puntos azul
ax.plot(x, x**3, 'g--') # línea a trazos verde
[<matplotlib.lines.Line2D at 0x7fe8d7b4f7d0>]
En Matplotlib también podemos definir colores por su nombre o sus códigos RGB, y opcionalmente suministrar un valor alfa, usando los argumentos color
y alpha
:
fig, ax = subplots()
ax.plot(x, x+1, color="red", alpha=0.5) # rojo semi-transparente
ax.plot(x, x+2, color="#1155dd") # código RGB para un color azulado
ax.plot(x, x+3, color="#15cc55") # código RGB para un color verdoso
[<matplotlib.lines.Line2D at 0x7fe8d715bf50>]
Para cambiar el ancho de una línea podemos usar el argumento linewidth
o lw
, y el estilo de línea puede ser seleccioando usando los argumentos linestyle
o ls
:
fig, ax = subplots(figsize=(12,6))
ax.plot(x, x+1, color="blue", linewidth=0.25)
ax.plot(x, x+2, color="blue", linewidth=0.50)
ax.plot(x, x+3, color="blue", linewidth=1.00)
ax.plot(x, x+4, color="blue", linewidth=2.00)
# posibles opciones de linestype: ‘-‘, ‘–’, ‘-.’, ‘:’, ‘steps’
ax.plot(x, x+5, color="red", lw=2, linestyle='-')
ax.plot(x, x+6, color="red", lw=2, ls='-.')
ax.plot(x, x+7, color="red", lw=2, ls=':')
# línea a trazos personalizada
line, = ax.plot(x, x+8, color="black", lw=1.50)
line.set_dashes([5, 10, 15, 10]) # formato: longitud de línea, longitud de espacio, ...
# posibles símbolos de marcadores: '+', 'o', '*', 's', ',', '.', '1', '2', '3', '4', ...
ax.plot(x, x+ 9, color="green", lw=2, ls='*', marker='+')
ax.plot(x, x+10, color="green", lw=2, ls='*', marker='o')
ax.plot(x, x+11, color="green", lw=2, ls='*', marker='s')
ax.plot(x, x+12, color="green", lw=2, ls='*', marker='1')
# Tamaño y color del marcador
ax.plot(x, x+13, color="purple", lw=1, ls='-', marker='o', markersize=2)
ax.plot(x, x+14, color="purple", lw=1, ls='-', marker='o', markersize=4)
ax.plot(x, x+15, color="purple", lw=1, ls='-', marker='o', markersize=8, markerfacecolor="red")
ax.plot(x, x+16, color="purple", lw=1, ls='-', marker='s', markersize=8,
markerfacecolor="yellow", markeredgewidth=2, markeredgecolor="blue");
La apariencia de los ejes es un aspecto importante de una figura que comúnmente se requiere modificar para confeccionar un gráfico con calidad de publicacións. Necesitamos ser capaces de controlar dónde están ubicados los ticks y las etiquetas, modificar el tamaño de letra y posiblemente las etiquetas usadas en los ejes. En esta sección veremos cómo controlar estas propiedades en una figura de matplotlib.
Lo primero que quisieramos configurar es el rango de los ejes. Podemos hacer esto usando los métodos set_ylim
y set_xlim
en un objeto ejes, o axis('tight')
para obtener automáticamente rangos de ejes "apretados".
fig, axes = subplots(1, 3, figsize=(12, 4))
axes[0].plot(x, x**2, x, x**3)
axes[0].set_title("rango de ejes por defecto")
axes[1].plot(x, x**2, x, x**3)
axes[1].axis('tight')
axes[1].set_title("ejes apretados")
axes[2].plot(x, x**2, x, x**3)
axes[2].set_ylim([0, 60])
axes[2].set_xlim([2, 5])
axes[2].set_title("rango de ejes personalizado");
Podemos determinar explícitamente dónde queremos que aparezcan los ticks de los ejes usando set_xticks
y set_yticks
, donde ambos métodos toman una lista de valores que determinan dónde se localizarán los ticks sobre el eje. Podemos también usar las funciones set_xticklabels
y set_yticklabels
para ingresar una lista de etiquetas personalizadas para cada tick:
fig, ax = subplots(figsize=(10, 4))
ax.plot(x, x**2, x, x**3, lw=2)
ax.set_xticks([1, 2, 3, 4, 5])
ax.set_xticklabels([r'$\alpha$', r'$\beta$', r'$\gamma$', r'$\delta$', r'$\epsilon$'], fontsize=18)
yticks = [0, 50, 100, 150]
ax.set_yticks(yticks)
ax.set_yticklabels(["$%.1f$" % y for y in yticks], fontsize=18); # usa etiquetas con formato LaTeX
[<matplotlib.text.Text at 0x7fe8d6fe7810>, <matplotlib.text.Text at 0x7fe8d7c7aa90>, <matplotlib.text.Text at 0x7fe8dc0bf290>, <matplotlib.text.Text at 0x7fe8d7c41e90>]
En Matplotlib existen muchos métodos más avanzados para controlar la ubicación de los ticks mayores y menores, como por ejemplo la ubicación automática de acuerdo a distintos criterios. Ver http://matplotlib.org/api/ticker_api.html para más detalles.
Usando el método grid
en un eje podemos activar y desactivar las líneas de malla (grid). También podemos personalizar la apariencia de las líneas de malla, usando los mismos argumentos que usamos previamente con la función plot
.
fig, axes = subplots(1, 2, figsize=(10,3))
# apariencia por defecto de la malla
axes[0].plot(x, x**2, x, x**3, lw=2)
axes[0].grid(True)
# apariencia personalizada de la malla
axes[1].plot(x, x**2, x, x**3, lw=2)
axes[1].grid(color='b', alpha=0.5, linestyle='dashed', linewidth=0.5)
También podemos cambiar las propiedades de las líneas de los ejes (las "espinas", spines):
fig, ax = subplots(figsize=(6,2))
ax.spines['bottom'].set_color('blue')
ax.spines['top'].set_color('blue')
ax.spines['left'].set_color('red')
ax.spines['left'].set_linewidth(2)
# desactiva la espina del eje derecho
ax.spines['right'].set_color("none")
ax.yaxis.tick_left() # sólo ticks a la izquierda
Algunas veces es útil tener ejes x o y duales en una figura, por ejemplo cuando se grafican juntas curvas con diferentes unidades. Matplotlib incorpora esta opción usando las funciones twinx
y twiny
:
fig, ax1 = subplots()
ax1.plot(x, x**2, lw=2, color="blue")
ax1.set_ylabel(u"área $(m^2)$", fontsize=18, color="blue")
for label in ax1.get_yticklabels():
label.set_color("blue")
ax2 = ax1.twinx()
ax2.plot(x, x**3, lw=2, color="red")
ax2.set_ylabel(r"volumen $(m^3)$", fontsize=18, color="red")
for label in ax2.get_yticklabels():
label.set_color("red")
fig, ax = subplots()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # define posición de la espina de x en x=0
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) # define posición de la espina de y en y=0
xx = np.linspace(-0.75, 1., 100)
ax.plot(xx, xx**3);
Además de la función plot
, existen varias otras funciones para generar distintos tipos de gráficos. Ver la galería de Matplotlib para una lista completa de los tipos disponibles: http://matplotlib.org/gallery.html. Algunos de los más útiles se muestra a continuación:
n = array([0,1,2,3,4,5])
fig, axes = subplots(1, 4, figsize=(12,3))
axes[0].scatter(xx, xx + 0.25*random.randn(len(xx)))
axes[1].step(n, n**2, lw=2)
axes[2].bar(n, n**2, align="center", width=0.5, alpha=0.5)
axes[3].fill_between(x, x**2, x**3, color="green", alpha=0.5);
# Gráfico polar usando add_axes y proyección polar
fig = figure()
ax = fig.add_axes([0.0, 0.0, .6, .6], polar=True)
t = linspace(0, 2 * pi, 100)
ax.plot(t, t, color='blue', lw=3);
Se pueden incluir notas de texto en figuras de Matplotlib usando la función text
. Tal como los textos en las etiquetas, leyendas títulos, incluye soporte para texto en formato LaTeX:
fig, ax = subplots()
ax.plot(xx, xx**2, xx, xx**3)
ax.text(0.15, 0.2, r"$y=x^2$", fontsize=20, color="blue")
ax.text(0.65, 0.1, r"$y=x^3$", fontsize=20, color="green");
Se pueden agregar ejes a un marce de figura de Matplotlib usando make_axes
o bien un administrador de distribución de sub-figuras tal como subplots
, subplot2grid
o gridspec
:
fig, ax = subplots(2, 3)
fig.tight_layout()
fig = figure()
ax1 = subplot2grid((3,3), (0,0), colspan=3)
ax2 = subplot2grid((3,3), (1,0), colspan=2)
ax3 = subplot2grid((3,3), (1,2), rowspan=2)
ax4 = subplot2grid((3,3), (2,0))
ax5 = subplot2grid((3,3), (2,1))
fig.tight_layout()
import matplotlib.gridspec as gridspec
fig = figure()
gs = gridspec.GridSpec(2, 3, height_ratios=[2,1], width_ratios=[1,2,1])
for g in gs:
ax = fig.add_subplot(g)
fig.tight_layout()
El agregar ejes manualmente con add_axes
es útil para incluir figuras en el interior de otras:
fig, ax = subplots()
ax.plot(xx, xx**2, xx, xx**3)
fig.tight_layout()
# figura interna
inset_ax = fig.add_axes([0.2, 0.55, 0.35, 0.35]) # X, Y, ancho, alto
inset_ax.plot(xx, xx**2, xx, xx**3)
inset_ax.set_title('zoom cerca del origen')
# define el rango de los ejes
inset_ax.set_xlim(-.2, .2)
inset_ax.set_ylim(-.005, .01)
# define la posición de los tick de los ejes
inset_ax.set_yticks([0, 0.005, 0.01])
inset_ax.set_xticks([-0.1,0,.1]);
Los mapas de colores y las líneas de contorno son útles para graficar funciones de dos variables. En la mayoría de los casos usaremos un mapa de colores para codificar una dimensión de los datosa. Existen varios mapas de colores predefinidos, y además definir mapas de colores personalizados es relativamente directo. Para ver una lista de los mapas de colores predefinidos, ver: http://www.scipy.org/Cookbook/Matplotlib/Show_colormaps.
alpha = 0.7
phi_ext = 2 * pi * 0.5
def flux_qubit_potential(phi_m, phi_p):
return 2 + alpha - 2 * cos(phi_p)*cos(phi_m) - alpha * cos(phi_ext - 2*phi_p)
phi_m = linspace(0, 2*pi, 100)
phi_p = linspace(0, 2*pi, 100)
X,Y = meshgrid(phi_p, phi_m)
Z = flux_qubit_potential(X, Y).T
fig, ax = subplots()
p = ax.pcolor(X/(2*pi), Y/(2*pi), Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max())
cb = fig.colorbar(p)
fig, ax = subplots()
im = imshow(Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, 0, 1])
im.set_interpolation('bilinear')
cb = fig.colorbar(im)
fig, ax = subplots()
cnt = contour(Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, 0, 1])
Para producir gráficos 3D con Matplotlib, primero debemos crear una instancia de ejes de la clase Axes3D
. Ejes 3D pueden ser agregados al marco de una figura de Matplotlib de la misma forma que para ejes 2D axes. Sin embargo, una forma conveniente para crear una instancia de eje 3D es usar el argumento projection='3d'
en las funciones add_axes
o `add_subplot.
from mpl_toolkits.mplot3d.axes3d import Axes3D
fig = figure(figsize=(14,6))
# `ax` es una instancia de ejes 3D, ya que se usó el argumento projection='3d' en add_subplot
ax = fig.add_subplot(1, 2, 1, projection='3d')
p = ax.plot_surface(X, Y, Z, rstride=4, cstride=4, linewidth=0)
# Gráfico de superficie con gradación de color y barra de color
ax = fig.add_subplot(1, 2, 2, projection='3d')
p = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=0, antialiased=False)
cb = fig.colorbar(p, shrink=0.5)
fig = figure(figsize=(8,6))
ax = fig.add_subplot(1, 1, 1, projection='3d')
p = ax.plot_wireframe(X, Y, Z, rstride=4, cstride=4)
fig = figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
cset = ax.contour(X, Y, Z, zdir='z', offset=-pi, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-pi, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=3*pi, cmap=cm.coolwarm)
ax.set_xlim3d(-pi, 2*pi);
ax.set_ylim3d(0, 3*pi);
ax.set_zlim3d(-pi, 2*pi);
Podemos cambiar la perspectiva en un gráfico 3D usando la función view_init
, que toma dos argumentos: la elevación y el ángulo azimutal (en grado):
fig = figure(figsize=(12,6))
ax = fig.add_subplot(1,2,1, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
ax.view_init(30, 45)
ax = fig.add_subplot(1,2,2, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
ax.view_init(70, 30)
fig.tight_layout()
Matplotlib también incluye una API simple para generar animaciones a partir de secuencias de figuras. Usando la función FuncAnimation
podemos generar un archivo de video a partir de una secuencia de figuras. La función usa los siguientes argumentos: fig
un marco de figura, func
es una función que definimos y que actualiza la figura, init_func
es una función que definimos para configurar la figura, frame
es el número de cuadros a ser generados, blit
le dice a la función que sólo actualize las partes del cuadro que han cambiado (lo que produce animaciones más suaves):
def init():
# define figura
def update(frame_counter):
# actualiza figura con nuevo cuadro
anim = animation.FuncAnimation(fig, update, init_func=init, frames=200, blit=True)
anim.save('animation.mp4', fps=30) # fps = cuadros por segundo (frames per second)
Para usar estas opciones de animación debemos importar primero el módulo matplotlib.animation
:
from matplotlib import animation
# resuelve la EDO del péndulo doble
from scipy.integrate import odeint
g = 9.82; L = 0.5; m = 0.1
def dx(x, t):
x1, x2, x3, x4 = x[0], x[1], x[2], x[3]
dx1 = 6.0/(m*L**2) * (2 * x3 - 3 * cos(x1-x2) * x4)/(16 - 9 * cos(x1-x2)**2)
dx2 = 6.0/(m*L**2) * (8 * x4 - 3 * cos(x1-x2) * x3)/(16 - 9 * cos(x1-x2)**2)
dx3 = -0.5 * m * L**2 * ( dx1 * dx2 * sin(x1-x2) + 3 * (g/L) * sin(x1))
dx4 = -0.5 * m * L**2 * (-dx1 * dx2 * sin(x1-x2) + (g/L) * sin(x2))
return [dx1, dx2, dx3, dx4]
x0 = [pi/2, pi/2, 0, 0] # valores iniciales
t = linspace(0, 10, 250) # tiempos
x = odeint(dx, x0, t) # resuelve la EDO
Genera una animación que muestra las posiciones de los péndulos como función del tiempo:
fig, ax = subplots(figsize=(5,5))
ax.set_ylim([-1.5, 0.5])
ax.set_xlim([1, -1])
pendulum1, = ax.plot([], [], color="red", lw=2)
pendulum2, = ax.plot([], [], color="blue", lw=2)
def init():
pendulum1.set_data([], [])
pendulum2.set_data([], [])
def update(n):
# n = contador de cuadros
# calcula las posiciones de los péndulos
x1 = + L * sin(x[n, 0])
y1 = - L * cos(x[n, 0])
x2 = x1 + L * sin(x[n, 1])
y2 = y1 - L * cos(x[n, 1])
# actualiza los dato
pendulum1.set_data([0 ,x1], [0 ,y1])
pendulum2.set_data([x1,x2], [y1,y2])
anim = animation.FuncAnimation(fig, update, init_func=init, frames=len(t), blit=True)
anim.save('animation.mp4', fps=20, extra_args=['-vcodec', 'libx264'])
close(fig)
/usr/lib/pymodules/python2.7/matplotlib/animation.py:695: UserWarning: MovieWriter ffmpeg unavailable warnings.warn("MovieWriter %s unavailable" % writer)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-56-cedefe3c5431> in <module>() 25 anim = animation.FuncAnimation(fig, update, init_func=init, frames=len(t), blit=True) 26 ---> 27 anim.save('animation.mp4', fps=20, extra_args=['-vcodec', 'libx264']) 28 29 close(fig) /usr/lib/pymodules/python2.7/matplotlib/animation.pyc in save(self, filename, writer, fps, dpi, codec, bitrate, extra_args, metadata, extra_anim, savefig_kwargs) 710 # since GUI widgets are gone. Either need to remove extra code to 711 # allow for this non-existant use case or find a way to make it work. --> 712 with writer.saving(self._fig, filename, dpi): 713 for data in itertools.izip(*[a.new_saved_frame_seq() 714 for a in all_anim]): AttributeError: 'str' object has no attribute 'saving'
Nota: Para generar el archivo con la animación se requiere tener instalado el programa ffmpeg
. Puede instalarlo (Mint/Ubuntu/Debian) usando:
$ sudo apt-get install ffmpeg
o bien
$ sudo port install ffmpeg
from IPython.display import HTML
video = open("animation.mp4", "rb").read()
video_encoded = video.encode("base64")
video_tag = '<video controls alt="test" src="data:video/x-m4v;base64,{0}">'.format(video_encoded)
HTML(video_tag)
--------------------------------------------------------------------------- IOError Traceback (most recent call last) <ipython-input-57-dee50753e05a> in <module>() 1 from IPython.display import HTML ----> 2 video = open("animation.mp4", "rb").read() 3 video_encoded = video.encode("base64") 4 video_tag = '<video controls alt="test" src="data:video/x-m4v;base64,{0}">'.format(video_encoded) 5 HTML(video_tag) IOError: [Errno 2] No such file or directory: 'animation.mp4'
Matplotlib tiene varios "backends", que son responsables de desplegar gráficos. Los diferentes backends son capaces de generar gráficos con diferentes formatos o usando distintas tecnologías de display. Existe una distinción entre los backends no-interactivos (tales como 'agg', 'svg', 'pdf', etc.) que sólo son usados para generar archivos de imágenes (por ejemplo, con la función savefig
), y backends interactivos (tales como Qt4Agg, GTK, MaxOSX) que pueden desplegar una ventana GUI para la exploración interactiva de figuras:
Una lista de los backends disponibles es la siguiente:
print matplotlib.rcsetup.all_backends
['GTK', 'GTKAgg', 'GTKCairo', 'MacOSX', 'Qt4Agg', 'TkAgg', 'WX', 'WXAgg', 'CocoaAgg', 'GTK3Cairo', 'GTK3Agg', 'WebAgg', 'agg', 'cairo', 'emf', 'gdk', 'pdf', 'pgf', 'ps', 'svg', 'template']
El backend estándar es llamado agg
, y está basado en una librería para gráficos de mapas de bits y es muy bueno para generar gráficos en formaos de mapa de bits tales como PNG.
Normalmente, no necesitamos preocuparnos de cambiar el backend seleccionado por defecto, pero algunas veces puede ser útil para cambiar, por ejemplo, al formato PDF o GTKCairo (si está usando Linux), para producir gráficos vectoriales de alta calidad en lugar de gráficos basados en mapas de bits.
#
# Reiniciar el cuaderno IPython: el backend matplotlib sólo puede ser selecccionado antes que se importe pylab!
import matplotlib
matplotlib.use('svg')
from matplotlib.pyplot import *
import numpy
from IPython.display import Image, SVG
/usr/lib/pymodules/python2.7/matplotlib/__init__.py:1173: UserWarning: This call to matplotlib.use() has no effect because the backend has already been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot, or matplotlib.backends is imported for the first time. warnings.warn(_use_error_msg)
#
# Ahora estamos usando el backend svg para producir gráficos vectoriales SVG
#
fig, ax = subplots()
t = numpy.linspace(0, 10, 100)
ax.plot(t, numpy.cos(t)*numpy.sin(t))
savefig("test.svg")
#
# Muestra el archivo SVG producido.
#
SVG(filename="test.svg")
Cuando usamos un cuaderno IPython es conveniente usar un backend de Matplotlib que genera gráficos incrustados en el archivo del cuaderno. Para activar este backend agregamos:
%pylab inline
en algún punto al comienzo del cuaderno.
#
# Reiniciar el cuaderno IPython: el backend matplotlib sólo puede ser selecccionado antes que se importe pylab!
#
import matplotlib
matplotlib.use('Qt4Agg') # o bien, por ejemplo, Qt4Agg
from matplotlib.pyplot import *
from numpy import *
# Ahora estamos usando el backend Qt4Agg, que abre una ventana interactiva
fig, ax = subplots()
t = linspace(0, 10, 100)
ax.plot(t, cos(t)*sin(t))
show()
Note que cuando usamos el backend interactivo, necesitamos show()
para desplegar la figura en la pantalla.