Antes de comenzar...
%matplotlib inline
Esta API está diseñada para ser compatible con la funciones de "plotting" de MATLAB, de manera tal que la transición desde dicha plataforma sea lo más amena posible.
Para utilizar esta API, se encesita importar el módulo pylab
:
from pylab import *
x = linspace(0, 5, 10)
y = x ** 2
figure()
plot(x, y, 'r')
xlabel('x')
ylabel('y')
title('titulo')
show()
subplot(1,2,1)
plot(x, y, 'r--')
subplot(1,2,2)
plot(y, x, 'g*-');
Recomendación: Usen esta API sólo para la transición desde MATLAB o para el trabajo sucio, para todo lo demás...
Esta API está diseñada para tener un complto control en cada uno de los objetos que forman parte de la figura.
Las ventajas de esta API se evidencian a medida que las figuras se complejizan.
import matplotlib.pyplot as plt
fig = plt.figure()
axes = fig.add_axes([0.1, 0.1, 0.8, 0.8])
axes.plot(x, y, 'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title('title');
Entonces, a la hora de complejizar la figura:
fig = plt.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 sel inserto
# figura principal
axes1.plot(x, y, 'r')
axes1.set_xlabel('x')
axes1.set_ylabel('y')
axes1.set_title('titulo')
# inserto
axes2.plot(y, x, 'g')
axes2.set_xlabel('y')
axes2.set_ylabel('x')
axes2.set_title('titulo del inserto');
Aunque para trabajar con figuras complejas es recomendable utilizar el sistema de "layouts" que posee matplotlib a traves de subplots
:
fig, axes = plt.subplots()
axes.plot(x, y, 'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title('titulo');
fig, axes = plt.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('titulo');
Para lidiar con el "overlapping", se puede utilizar el método tight_layout
que hace los ajustes necesarios automáticamente.
fig, axes = plt.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('title')
fig.tight_layout()
Cuando creamos la figura, podemos especificar el ratio y el DPI, a través de los argumentos figsize
(tupla: (ancho,alto) en pulgadas) y dpi
(pixel por pulgada).
Para crear una figura de 800 x 400 pixeles:
fig = plt.figure(figsize=(8,4), dpi=100)
Incluso podemos pasar estos mismos argumentos a subplots
.
fig, axes = plt.subplots(figsize=(12,3))
axes.plot(x, y, 'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title('titulo');
fig.savefig("filename.png")
Opcionalmente se puede especificar las resolusción DPI o elegir distintos formatos de archivo:
fig.savefig("filename.png", dpi=200)
fig.savefig("filename.svg")
¿Qué formatos puedo elegir?
ax.set_title("title")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.legend(["curve1", "curve2", "curve3"]) # MATLAB API
ax.plot(x, x**2, label="curve1") # OO API
ax.plot(x, x**3, label="curve2")
ax.legend();
La ventaja del último método es que se actualiza automáticamente si adicionamos o quitamos curvas.
La función legend
puede tomar un argumento opcional loc
para localizar la leyenda en diferentes posiciones. Más info: http://matplotlib.org/users/legend_guide.html#legend-location
ax.legend(loc=0) # let matplotlib decide the optimal location
ax.legend(loc=1) # upper right corner
ax.legend(loc=2) # upper left corner
ax.legend(loc=3) # lower left corner
ax.legend(loc=4) # lower right corner
# y más opciones disponibles
Un ejemplo de figura configurando lo que hemos descripto:
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('titulo')
ax.legend(loc=2); # esquina superior izquierda
<matplotlib.legend.Legend at 0x36c3c90>
Matplotlib tiene soporte para Latex. Solo debemos usar signos de dolar $
encapsulando el texto que nos interesa, por ejemplo "$y=x^3$"
.
Para evitar problemas con Python a la hora de utilizar la barra invertida, debemos usar texto precedido por la letra "r
" (por "raw"), por ejemplo r"\alpha"
o r'\alpha'
en lugar de "\alpha"
o '\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('titulo')
ax.legend(loc=2); # esquina superior izquierda
<matplotlib.legend.Legend at 0x30dead0>
Incluso podemos cambiar globalmente el tamaño y el tipo de fuente:
# Actualizar parámetros de configuración:
matplotlib.rcParams.update({'font.size': 12, '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('titulo')
ax.legend(loc=2); # upper left corner
<matplotlib.legend.Legend at 0x3a74f50>
ax.plot(x, x**2, 'b.-') # linea azul con puntos (blue line with dots)
ax.plot(x, x**3, 'g--') # linea verde entrecordata (green dashed line)
Podemos definir colores por su nombre o códido RGB con el argumento color
y transparencia con el argumento alpha
:
fig, ax = subplots()
ax.plot(x, x+1, color="red", alpha=0.5) # rojo, transparencia a la mitad
ax.plot(x, x+2, color="#1155dd") # código RGB para el UN azul
ax.plot(x, x+3, color="#15cc55") # código RGB para el UN verde
[<matplotlib.lines.Line2D at 0x3789ed0>]
4.2.6. Ancho y estilo de lineas.
Para cambiar el ancho de las lineas usamos el arguemnto linewidth
o lw
, y podemos seleccionar el estilo de linea usando el argumento 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)
# posible 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=':')
# interlineado personalizado
line, = ax.plot(x, x+8, color="black", lw=1.50)
line.set_dashes([5, 10, 15, 10]) # formato: longitud de linea, longitud de espacios, ...
# símbolod marcadores: marker = '+', '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");
Podemos modificar el rango de los ejes usando los métodos set_ylim
y set_xlim
, o axis('tight')
para obtener ejes auto-ajustados:
fig, axes = subplots(1, 3, figsize=(12, 4))
axes[0].plot(x, x**2, x, x**3)
axes[0].set_title("rango default")
axes[1].plot(x, x**2, x, x**3)
axes[1].axis('tight')
axes[1].set_title("ranjo auto-ajustado")
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 personalizado");
Podemos setear "ticks" en el eje horizontal y vertical, usando los métodos set_xticks
y set_yticks
, los cuales toman una lista de valores para determinar dónde se ubican.
También podemos usar los métodos set_xticklabels
y set_yticklabels
para usar una lista personalizada de etiquetas 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) # etiquetas formateadas por LaTeX
yticks = [0, 50, 100, 150]
ax.set_yticks(yticks)
ax.set_yticklabels(["$%.1f$" % y for y in yticks], fontsize=18); # etiquetas formateadas por LaTeX
[<matplotlib.text.Text at 0x2a5cf90>, <matplotlib.text.Text at 0x42ce250>, <matplotlib.text.Text at 0x2a794d0>, <matplotlib.text.Text at 0x2e07090>]
Podemos activar o desactivar la grilla usando el método grid
.
También podemos personalizar la apariencia de la lineas de la grilla, usando los mismos argumentos que previamente utilzamos para la función plot
:
fig, axes = subplots(1, 2, figsize=(10,3))
# grilla default
axes[0].plot(x, x**2, x, x**3, lw=2)
axes[0].grid(True)
# grilla personalizada
axes[1].plot(x, x**2, x, x**3, lw=2)
axes[1].grid(color='b', alpha=0.5, linestyle='dashed', linewidth=0.5)
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)
# ekiminar el spine del eje de la derecha
ax.spines['right'].set_color("none")
ax.yaxis.tick_left() # sólo tick en la izquierda
fig, ax1 = subplots()
ax1.plot(x, x**2, lw=2, color="blue")
ax1.set_ylabel(r"area $(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"volume $(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)) # setear la posición del eje x en x = 0
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) # setear la posición del eje y en y = 0
xx = np.linspace(-0.75, 1., 100)
ax.plot(xx, xx**3);
Para ver la multiplicidad de figuras que pueden obtenerse, pueden visitar http://matplotlib.org/gallery.html.
n = array([0,1,2,3,4,5])
fig, axes = subplots(1, 4, figsize=(12,3))
axes[0].scatter(xx, xx + 0.25*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);
# figura polar
fig = plt.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);
Puede anotarse texto usando la función text
, que soporta 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 nuevos ejes a una figura usando el método make_axes
o usando un layout manager como subplots
o subplot2grid
o gridspec
:
subplots
fig, ax = subplots(2, 3)
fig.tight_layout()
subplot2grid
fig = plt.figure()
ax1 = plt.subplot2grid((3,3), (0,0), colspan=3)
ax2 = plt.subplot2grid((3,3), (1,0), colspan=2)
ax3 = plt.subplot2grid((3,3), (1,2), rowspan=2)
ax4 = plt.subplot2grid((3,3), (2,0))
ax5 = plt.subplot2grid((3,3), (2,1))
fig.tight_layout()
gridspec
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()
add_axes
fig, ax = subplots()
ax.plot(xx, xx**2, xx, xx**3)
fig.tight_layout()
# inserto
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')
# set axis range
inset_ax.set_xlim(-.2, .2)
inset_ax.set_ylim(-.005, .01)
# set axis tick locations
inset_ax.set_yticks([0, 0.005, 0.01])
inset_ax.set_xticks([-0.1,0,.1]);
Lista de mapas de colores predefinidos: 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
pcolor
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)
imshow
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)
contour
fig, ax = subplots()
cnt = contour(Z, cmap=cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, 0, 1])
Una forma conveniente de crear ejes en una tercera dimensión es usar el argumento projection='3d'
en las funciones add_axes
o add_subplot
.
from mpl_toolkits.mplot3d.axes3d import Axes3D
fig = plt.figure(figsize=(14,6))
ax = fig.add_subplot(1, 2, 1, projection='3d')
p = ax.plot_surface(X, Y, Z, rstride=4, cstride=4, linewidth=0)
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) # gradiente de color y barra
cb = fig.colorbar(p, shrink=0.5)
fig = plt.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 = plt.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);
fig = plt.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) # elevación y rotación
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()
Usando FuncAnimation
podemos generar un video a partir de una secuencia de imágenes. La función toma los siguientes argumentos: fig
(canvas), func
(updates de la figura), init_func
(setup de la figura), frame
(número de frames a generar), blit
(update sólo las partes del frame que cambian, lo que resulta en animaciones mas fluídas):
def init():
# setup de la figura
def update(frame_counter):
# update de la figura para nuevos frames
anim = animation.FuncAnimation(fig, update, init_func=init, frames=200, blit=True)
anim.save('animation.mp4', fps=30) # fps = frames per second
Para usar las animaciones necesitamos importar el módulo matplotlib.animation
:
from matplotlib import animation
# doble péndulo
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] # estado inicial
t = linspace(0, 10, 250) # coordenadas de tiempo
x = odeint(dx, x0, t) # resolución de la ODE
fig, ax = plt.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 frames
# calcula la posición de los pédulos
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])
# update de los datos
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);
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-49-b959fdf447cd> in <module>() 25 anim = animation.FuncAnimation(fig, update, init_func=init, frames=len(t), blit=True) 26 ---> 27 anim.save('animation.mp4', fps=20); /home/damian/.virtualenvs/blog/local/lib/python2.7/site-packages/matplotlib/animation.pyc in save(self, filename, writer, fps, dpi, codec, bitrate, extra_args, metadata, extra_anim, savefig_kwargs) 698 writer = writers.list()[0] 699 except IndexError: --> 700 raise ValueError("Cannot save animation: no writers are " 701 "available. Please install mencoder or " 702 "ffmpeg to save animations.") ValueError: Cannot save animation: no writers are available. Please install mencoder or ffmpeg to save animations.
/home/damian/.virtualenvs/blog/local/lib/python2.7/site-packages/matplotlib/animation.py:695: UserWarning: MovieWriter ffmpeg unavailable warnings.warn("MovieWriter %s unavailable" % writer)
Nota: Para generqar el video debemos tener installado ffmpeg
:
$ sudo apt-get install ffmpeg
Lista de "backends" disponibles:
print matplotlib.rcsetup.all_backends
A veces es necesario cambiar el "backend" según las necesidades, por ejemplo, si queremos generar gráficos vectoriales de alta resolución:
#
# RESETEAR EL NOTEBOOK: el "backend" sólo puede seleccionarse antes de importar pylab!
#
import matplotlib
matplotlib.use('svg')
import matplotlib.pylab as plt
import numpy
from IPython.display import Image, SVG
/home/damian/.virtualenvs/blog/local/lib/python2.7/site-packages/matplotlib/__init__.py:1141: UserWarning: This call to matplotlib.use() has no effect because the 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)
fig, ax = plt.subplots()
t = numpy.linspace(0, 10, 100)
ax.plot(t, numpy.cos(t)*numpy.sin(t))
plt.savefig("test.svg")
SVG(filename="test.svg") # el gráfico vectorial que queríamos