Decorando gráficos en Matplotlib

Estándar

En la última entrada, dimos una breve introducción a Matplotlib dibujando tres tipos básicos de gráfica (una gráfica de líneas, una de barras y una de sectores). No obstante, los resultados podrían ser visualmente muy mejorables.

En el escenario moderno en el que Big Data cobra cada vez más importancia y popularidad fuera de la comunidad de analistas de datos, la presentación juega a menudo un papel central en la publicación de resultados. Por tanto, la decoración de las gráficas no debería tomarse como un aspecto secundario. En esta entrada, redibujaremos las gráficas de la última entrada y las decoraremos adecuadamente.

Comenzamos con la gráfica de líneas. No vamos a revisar el código completo, sólo lo nuevo. Debido al funcionamiento interno de Jupyter, debemos escribir todo el código en una única celda para que el resultado se muestre al final. De otro modo, Jupyter dibujará el resultado parcial y no podremos cambiarlo. Una manera de cambiar este comportamiento es usando el comando mágico %matplotlib notebook, que creará un gráfico interactivo que responde a cambios manuales o cambios hechos en otras celdas. Esto será muy útil para el trabajo exploratorio, pero aquí no lo vamos a usar ahora.

In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0,2*np.pi,0.01)
y = np.sin(x)
plt.figure()
p = plt.plot(x,y, linewidth='2', color='red')
plt.legend(['sin(x)'])
plt.axis([0,2*np.pi,-1.5,1.5])
plt.xlabel('x (radianes)')

ax = plt.axes()

ax.spines['bottom'].set_linewidth(2)
ax.spines['left'].set_linewidth(2)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi])
ax.xaxis.set_ticklabels(['0', '$\pi$/2', '$\pi$', '3$\pi$/2', '2$\pi$'])
ax.yaxis.set_ticks([-1,-0.5,0,0.5,1])
ax.tick_params(labelsize=14)

ax.yaxis.grid(True, which='major')
ax.xaxis.grid(True, which='major')

plt.show()

En primer lugar, hemos modificado la llamada a plt.plot() para decorar la línea. Hemos especificado la anchura y el color de la línea siguiendo la documentación de Matplotlib.
Además, el eje debe cortarse en 2*pi, ya que no hay gráfico para valores más grandes. Incrementamos también el rango vertical para que el máximo y el mínimo de la gráfica no estén en el borde del área de representación. Hacemos esto llamando a plt.axis(limites) en la línea 10. En la línea 11 añadimos una etiqueta para el eje x. A continuación obtenemos una variable que nos da acceso a los ejes (mediante la función axes (línea 13) para cambiar las anchuras de línea de los ejes inferior e izquierdo, y ocultar los ejes superior y derecho (líneas 15 a 18). Para ello usamos llamadas a spine (cada "eje" es un "spine"). En las líneas 20 y 21 modificamos cada eje individual habilitando las marcas en los ejes visibles, colocándolas en los valores significativos (líneas 22 a 24) y cambiando el tamaño de fuente (25). Cualquier texto que se escriba entre caracteres $ se interpreta como una fórmula de Latex, lo cual nos permite incluir símbolos matemáticos. Finalmente, para facilitar la interpretación de los valores de la gráfica, añadimos una cuadrícula (líneas 27 y 28). Con este código complicado e incómodo, hemos logrado un resultado mucho más agrdable a la vista que el original:

In [2]:
plt.figure()
plt.plot(x,y)
plt.legend(['sin(x)'])
plt.show()

A continuación, cambiamos la gráfica de barras. Veamos cómo era originalmente:

In [3]:
top_5 = ['EEUU','URSS','UK','Francia','China']
medals = [976, 395, 236, 202, 201]

plt.figure()
plt.bar(range(5),medals, align='center')
ax = plt.axes()
ax.set_xticks(range(5))
ax.set_xticklabels(top_5)
plt.show()

Este código ya incluye un cierto grado de retoque, dado que de otro modo el gráfico sería prácticamente ilegible. En este caso, vamos a pintar cada columna de un color distinto, añadir líneas horizontales en las marcas del eje vertical y quitar las marcas del eje horizontal. Dado que el número de medallas no es muy legible sólo observando las alturas (como se puede ver para diferenciar Francia de China), añadiremos una etiqueta encima de cada barra con el número. Además, eliminaremos los bordes laterales y superior del área de representación.

In [4]:
plt.figure()
plt.bar(range(5),medals, align='center', color=['blue','red','green','white','brown'], edgecolor='none')
ax = plt.axes()
ax.set_xticks(range(5))
ax.set_xticklabels(top_5)
ax.set_yticklabels([])

ax.set_axis_bgcolor('gold')

for i,n in enumerate(medals):
    ax.text( i , n + 10, n, ha='center', va='bottom')

ax.spines['bottom'].set_linewidth(2)
ax.spines['left'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('none')

ax.yaxis.grid(True, which='major')    
plt.axis([-1,5,0,1200])

plt.ylabel('Numero de medallas de oro\npara los 5 paises mas premiados')

plt.show()

Revisando rápidamente el código, vemos que en la línea 2 hemos modificado el color de cada columna. Si hay más columnas que colores, se repetirá el patrón una y otra vez. En la línea 6, eliminamos las etiquetas de las marcas del eje y. En la línea 8, cambiamos el color de fondo del área de representación. En las líneas 10 y 11 iteramos sobre cada valor usando enumerate() (para mejorar la claridad del código) y colocamos una etiqueta de texto en la posición (i, n+10) donde i es el índice de la columna (la coordenada x) y n es el número de medallas (la coordenada y). El resto de las líneas son similares a las usadas en el código de la gráfica anterior.

Finalmente, modificamos la gráfica de sectores, que originalmente tiene este aspecto:

In [5]:
navegadores = ['Chrome', 'Firefox', 'IE', 'Safari', 'Opera']
usuarios = [67.4, 19.2, 6.8, 3.9, 1.5]
navegadores.append('Otros')
usuarios.append(round(100-sum(usuarios),2)) # Usamos round para evitar un problema de precision

plt.figure()
plt.pie(usuarios,labels=navegadores)
plt.show()

Por norma general, el público asocia cierto color con cada navegador. Usar colores distintos será fuente de confusión dado el Efecto Stroop, que incrementa el tiempo de reacción cuando el color y el nombre asociado al mismo (en este caso, el nombre del navegador) no coinciden. Alguien que viera el gráfico anterior por medio segundo pensaría que está muy desfasado, ya que el navegador más usado parece ser IE (tradicionalmente asociado al azul). Sólo con una segunda visualización vería que en realidad es Chrome.
Por tanto, el primer paso será cambiar los colores. Normalmente una gráfica de sectores es redonda, no la forma ovalada que se ve en este caso. Arreglaremos eso también. Finalmente, pondremos las etiquetas en una leyenda que además represente los porcentajes de uso de cada navegador.

In [6]:
colores = ['yellow', 'orange', 'blue', 'cyan', 'red', 'gray']
leyenda = []
for navegador, mercado in zip(navegadores, usuarios):
    leyenda.append(navegador + ' (' + str(mercado) + ' %)')
    
plt.figure()
plt.pie(usuarios,colors=colores)
plt.axis('equal')
plt.title('Cuota de mercado de los navegadores en Nov. 2015', y=1.1, fontdict={'fontsize':20})
plt.legend(leyenda,bbox_to_anchor=(0, 1))
plt.show()

En las líneas 3 y 4 creamos cada etiqueta de la leyenda combinando cada entrada de navegadores y usuarios en un bucle for combinado con la función zip(). Eliminamos el parámetro labels de plt.pie() en la línea 7 para que no se muestren en la gráfica. En la línea 9 añadimos el título, especificando el tamaño y la altura (de modo que haya margen entre el título y la gráfica). Finalmente, pintamos la leyenda especificando su posición como se muestra aquí.

¡Y eso es todo! Matplotlib tiene muchas opciones para personalizar las gráficas; aquí tan sólo hemos raspado la superficie. Pero la verdad es que para obtener gráficos aceptables, tenemos que buscar muchas funciones raras y ajustes poco comunes (incluso para tareas que deberían ser muy comunes). Se podría escribir un libro acerca de esta librería. En todo caso, el flujo de trabajo con Matplotlib implica un uso intensivo de Google, y dado que es una librería muy usada, hay documentación al respecto de casi cualquier pregunta que se pueda plantear.

2 comentarios en “Decorando gráficos en Matplotlib

  1. Agustín Cabaña

    Hola, cómo se podría hacer para que se asigne un color a cada barra del gráfico de barras desconociendo el número de ellas de antemano?

    • Emil

      Hola Agustín, en primer lugar, perdón por la tardanza 🙂
      En segundo lugar, que yo sepa, no hay una forma concreta de hacer lo que dices, con lo cual hay que generar la lista de colores de antemano. Es decir, en una línea anterior, creas un array con tantos colores como sea la dimensión de tus datos. Aunque puedes hacer esto a mano, Matplotlib te ofrece un método para hacerlo de forma automática: plt.cm.get_cmap(name, lut). Puedes ver la documentación aquí. Esta función te dará un mapa de colores, el cual tendrás que traducir a una lista de colores. El código de ejemplo quedaría así:

      data_length = len(medals)
      cmap = plt.cm.get_cmap('hsv',data_length)
      cmap_array = [cmap(i) for i in range(data_length)] # Creamos un array mediante una list comprehension
      plt.bar(range(data_length),medals, align='center', color=cmap_array, edgecolor='none')

      Date cuenta de que en la primera línea del código ya estamos dando por sentado que, a priori medals puede tener cualquier longitud, por lo que todo lo demás se hace teniendo en cuenta su longitud. El resto del código se mantendría igual, excepto la línea que ajusta los ejes, que quedaría así:

      plt.axis([-1,data_length,0,1200])

      Espero que te haya ayudado.
      Un saludo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *