Python por ejemplo
Tugurium/Python

Python, por ejemplo

Personalizar los gráficos

1.1.2 Personalizar los gráficos

La biblioteca de matplotlib proporciona una cantidad enorme de opciones de personalización. En esta sección nos centraremos en algunos de los elementos clave, suficientes para que los gráficos tengan una apariencia profesional.

En los ejemplos introductorios hemos visto que basta con pasar a la función plot() listas de valores con los que crea de forma automática el espacio para el dibujo, el tamaño de la figura, los ejes, las marcas para la escala, el color y grosor de la línea, etc.

La firma básica de la función plot() es:

matplotlib.pyplot.plot(*args, scalex=True, scaley=True, data=None, **kwargs)

Que puede recibir valores para el eje Y o valores para los ejes X e Y, para uno o más gráficos, estableciendo un formato independiente para cada uno de ellos.

plot([x], y, [fmt], [[x], y, [fmt],] **kwargs)

El parámetro opcional fmt es una forma abreviada de definir el formato básico de las líneas, su estilo, el marcador y el color.

1.1.2.1 Formato del gráfico

El formato fmt es una cadena de códigos para el marcador, el tipo de línea y el color.

fmt = '[marca][línea][color]'

Cada una de las partes es opcional. Si no se proporcionan se utiliza el valor del estilo por defecto. Salvo que se proporcione el estilo de línea, pero no la marca, con lo que tendremos una línea sin marcas.

También se admiten otras combinaciones como [color][marcador][línea], pero el análisis sintáctico puede ser ambiguo.

Los marcadores se definen con los códigos:

MarcadorDescripción
.punto
,píxel
ocírculo
vtriángulo abajo
^triángulo arriba
<triangulo izquierda
>triangulo derecha
1tri_down
2tri_up
3tri_left
4tri_right
scuadrado
ppentágono
*estrella
hhexágono1
Hhexágono2
+más
xx
Ddiamante
ddiamante fino
|línea vertical
_línea horizontal

El estilo de línea se establece con los códigos:

EstiloDescripción
-línea continua
--línea discontinua
-.línea discontinua punteada
:línea punteada

El color se describe con los códigos:

ColorDescripción
bAzul
gVerde
rRojo
cCian
mMagenta
yAmarillo
kNegro
wBlanco

Para un mayor detalle de los colores ver los formatos de color en matplotlib.

Vamos a darle una vuelta al ejemplo de seno y coseno dibujando el gráfico con diferentes formatos. El seno con cuadrados azules, que representan los valores discretos de los datos. Después el coseno con una línea con trazos rojos discontinuos y sobre ella los valores discretos con círculos de color cian.

matplotlib_01_00_formatos.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

# dibujar tres gráficos en la misma área
# con diferentes formatos de gráfico
plt.plot(grados, seno, 'sb')        # cuadrados azules
plt.plot(grados, coseno, '--r')     # linea discontínua roja
plt.plot(grados, coseno, 'oc')      # circulos cian

# mostar el gráfico
plt.show()

El resultado queda de la forma:

Gráfico con formato personalizado

Hemos utilizado tres llamadas a la función plot() en el código anterior, que bien podemos reducir a una única llamada, ya que la función admite más de un grupo de valores para X e Y con su formato. Así, el script se simplifica y el resultado es el mismo.

matplotlib_01_01_formatos.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

# dibujar tres gráficos en la misma área
# con una única llamada a plot()
# con diferentes formatos de gráfico
plt.plot(grados, seno, 'sb',
         grados, coseno, '--r',
         grados, coseno, 'oc')

# mostar el gráfico
plt.show()

1.1.2.2 Ejes, bordes y marcas

En matplotlib los bordes (spines) son las líneas que conectan las marcas (ticks) de los ejes (axis) y señalan los límites del área de datos.

Ejes, bordes y marcas

Los gráficos de matplotlib presentan un sistema de coordenadas cartesianas, con un eje X y un eje Y, que se unen en el origen, el punto (0, 0).

Los bordes son las líneas que unen las marcas de graduación de los ejes. La representación por defecto nos muestra solo un cuadrante, pero podemos disponer los bordes en posiciones arbitrarias haciendo uso del contenedor spines de los bordes del gráfico para adaptar la visualización a nuestras necesidades.

Con spines podemos referenciar los bordes como un diccionario mediante las claves 'left', 'right', 'bottom', 'top'.

En el ejemplo vamos a invisibilizar los bordes superior y derecho y a desplazar los bordes inferior e izquierdo de acuerdo a los datos.

matplotlib_01_02_cuadrantes.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# invisibilizar bordes superior y derecho 
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')

# mover borde inferior a la posición y=0
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))

# mover borde izquierdo a la posición x=0
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# dibujar el gráfico
ax.plot(grados, seno)

# mostar el gráfico
plt.show()

Esto nos ofrece como resultado un gráfico con los cuatro cuadrantes.

Cuatro cuadrantes

Las marcas (ticks) se utilizan para señalar los puntos de datos en los ejes. Hemos visto como matplotlib se encarga de espaciar las marcas sobre los ejes y de asignarles etiquetas. Las marcas y sus valores por defecto son suficientes en la mayoría de los casos, pero es posible establecerlos explícitamente con las funciones set_xticks() y set_yticks(), que tienen como firma:

Axes.set_xticks(ticks, labels=None, *, minor=False, **kwargs)
Axes.set_yticks(ticks, labels=None, *, minor=False, **kwargs)
Parámetros

ticks: Lista de las posiciones de las marcas.

labels: Lista de etiquetas. Si no se establece, las etiquetas se generan con el formateador de marcas del eje.

minor: Si es False, establece solo las marcas mayores; si es True, también las menores. Por defecto False.

Vamos a hacer uso de los métodos set_xticks() y set_yticks() para establecer los espaciados y los valores más adecuados al gráfico anterior.

matplotlib_01_03_marcas.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# invisibilizar bordes superior y derecho 
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')

# mover borde inferior a la posición y=0
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))

# mover borde izquierdo a la posición x=0
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# establecer marcas en los ejes
ax.set_xticks([-360, -270, -180, -90, 0, 90, 180, 270, 360])
ax.set_yticks([-1, -0.5, 0, +0.5, 1])

# dibujar el gráfico
ax.plot(grados, seno)

# mostar el gráfico
plt.show()

De esta forma modificamos los valores de las marcas en los ejes, así como el espaciado, adaptándolo mejor a los valores de nuestro gráfico, como podemos ver.

Marcas y valores adaptados

Si no es factible establecer previamente los valores del eje X, o puede ser engorroso tener que cambiarlos frecuentemente, podemos calcularlos en el programa mediante la función MultipleLocator(), que establece una marca en cada múltiplo entero del parámetro proporcionado dentro del intervalo de la vista.

matplotlib_01_04_locator.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# invisibilizar bordes superior y derecho 
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')

# mover borde inferior a la posición y=0
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))

# mover borde izquierdo a la posición x=0
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# calcular marcas en el eje X
ax.xaxis.set_major_locator(plt.MultipleLocator(360 / 6))

# dibujar el gráfico
ax.plot(grados, seno)

# mostar el gráfico
plt.show()

En el gráfico vemos como se han distribuido seis marcas en el intervalo con los valores calculados.

Marcas con valores calculados

Una vez definidas las posiciones de las marcas podemos establecer etiquetas distintas de los valores numéricos para esas marcas utilizando las funciones set_xticklables() y set_yticklabels(). Ambas funciones toman como argumento un iterable cuyos elementos son los valores que se mostrarán en las posiciones correspondientes a las marcas.

Volviendo a nuestro ejemplo, donde establecíamos las marcas para el eje X de -360 a 360 grados en múltiplos de 90, resulta mejor etiquetar las marcas con los valores en radianes, haciendo uso de la facilidad que nos ofrece matplotlib para escribir expresiones matemáticas, al estilo LaTeX.

matplotlib_01_05_etiquetas.py
import matplotlib.pyplot as plt
import math

# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# invisibilizar bordes superior y derecho 
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')

# mover borde inferior a la posición y=0
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))

# mover borde izquierdo a la posición x=0
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# establecer marcas en los ejes
ax.set_xticks([-360, -270, -180, -90, 0, 90, 180, 270, 360])
ax.set_yticks([-1, -0.5, 0, +0.5, 1])

# proporcionar etiquetas para las marcas del eje X
# en formato LaTeX
ax.set_xticklabels([r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', 
                    r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', 
                    r'$+\pi$', r'$\frac{3\pi}{2}$', r'$+2\pi$'])

# dibujar el gráfico
ax.plot(grados, seno)

# mostar el gráfico
plt.show()

El resultado queda mucho mejor así para nuestra función trigonométrica.

Marcas con expresiones matemáticas

1.1.2.3 Ajustar límites

Hemos visto como matplotlib adapta las marcas en los ejes automáticamente de forma que los cubran según los datos a visualizar, lo que es suficiente en la mayoría de los casos. Y también la forma de especificar un conjunto de valores que sean los más adecuados al gráfico.

Pero, supongamos que no nos interesa mostrar más que un detalle del gráfico limitado a un rango de valores. Para esto matplotlib nos ofrece la posibilidad de ajustar los limites de visualización del gráfico con los métodos xlim(), para el eje X, e ylim() para el eje Y, que tienen las firmas:

matplotlib.pyplot.xlim(*args, **kwargs)
matplotlib.pyplot.ylim(*args, **kwargs)
Parámetros

En el caso de xlim():

left: Establece el límite izquierdo del rango de valores.

right: Establece el límite derecho del rango de valores.

En el caso de ylim():

bottom: Establece el límite inferior del rango de valores.

top: Establece el límite superior del rango de valores.

**kwargs: Este parámetro admite propiedades de texto que se utilizan para controlar la apariencia de las etiquetas.

Los dos métodos devuelven los nuevos límites del gráfico con una tupla.

En ambos casos les proporcionamos los valores mínimo y máximo con los que vamos a truncar la vista del gráfico.

Volvemos sobre nuestro ejemplo de las funciones seno y coseno que hemos visto cuando hemos tratado Múltiples gráficos en un cuadro.

Hemos proporcionado al gráfico unas coordenadas para el eje X entre 0 y 360, mientras que las funciones nos ofrecen valores para el eje Y entre -1.00 y 1.00.

Una vez dibujado el gráfico queremos centrarnos en un área limitada por los valores del eje X entre 150 y 300 y para el eje Y entre -1.00 y 0. Nos basta con ajustar los límites de visualización pasando estos valores a las funciones xlim() e ylim() para obtener el gráfico del área indicada.

matplotlib_01_05_1_limites.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

# dibujar el gráfico
plt.plot(grados, seno)
plt.plot(grados, coseno)

# ajustar límites de visualización
plt.xlim(150, 300)
plt.ylim(-1.0, 0)

# mostar el gráfico
plt.show()

En el gráfico tenemos la zona correspondiente a los valores de X e Y según la limitación establecida.

Gráfico detallado

1.1.2.4 Títulos y etiquetas

Crear una buena visualización implica que la figura sea capaz de comunicar la idea que contienen los datos subyacentes, pero en algunos casos el complemento proporcionado por textos o etiquetas facilitan la comprensión del gráfico. Para comunicar mejor lo que representan los datos añadir un título (title) a cada instancia de una figura, e incluso añadir etiquetas (label) a los ejes X e Y, ofrecen pistas para entender mejor la imagen del gráfico.

Para establecer el título, disponemos del método set_title(), que sitúa un texto descriptivo sobre los ejes, además de controlar la posición, el estilo y el tamaño de la fuente. Su firma es:

pyplot.title(label, fontdict=None, loc=None, pad=None, *, y=None, **kwargs)
Parámetros

label: Texto a utilizar para el título.

fontdict: Diccionario que controla la apariencia del texto del título.

loc: Posición que ocupa el texto {'center', 'left', 'right'}. Por defecto 'center'.

pad: Desplazamiento del título desde la parte superior de los ejes, en puntos. Por defecto 6.0.

Muchas funciones que contienen etiquetas nos permiten controlar la apariencia del texto mediante el uso del parámetro fontdict, que es un diccionario de opciones que, además, facilita el compartir parámetros entre muchos objetos de texto y etiquetas. El formato es:

{'family': ['serif'|'sans-serif'|'cursive'|'fantasy'|'monospace'],
 ['size'|'fontsize']: ,
 ['weight'|'fontweight']: ['normal'|'bold'|'heavy'|'light'|'ultrabold'|'ultralight'],
 'style': ['normal'|'italic'|'oblique'],
 'color': ,
 ['verticalalignment'|'va']: ['center'|'top'|'bottom'|'baseline'],
 ['horizontalalignment'|'ha']: ['center'|'right'|'left'],
 'rotation': [|'vertical'|'horizontal']
}

En la documentación de matplotlib sobre texto en las figuras está la relación completa de atributos y valores.

En ocasiones resulta interesante precisar más añadiendo un subtítulo al gráfico. Realmente matplotlib no ofrece ningún método para poner un subtítulo, aunque con el método suptitle(), que actúa como un título de nivel superior, podemos utilizar el título normal como subtítulo. La firma de suptitle() es:

pyplot.suptitle(text, fontdict=None , **kwargs)
Parámetros

text: Texto a utilizar para el título.

fontdict: Diccionario que controla la apariencia del texto del título.

A parte de los títulos para el gráfico, también podemos asignar etiquetas (label) a los ejes X e Y, para hacer aún más descriptivo el gráfico.

Las etiquetas de los ejes se establecen con los métodos xlabel() e ylabel(), de la misma manera que con los títulos. Las firmas de xlabel() e ylabel() son:

pyplot.xlabel(label, fontdict=None, labelpad=None, *, loc=None, **kwargs)
pyplot.ylabel(label, fontdict=None, labelpad=None, *, loc=None, **kwargs)
Parámetros

label: Texto a utilizar para la etiqueta.

fontdict: Diccionario que controla la apariencia del texto del título.

labelpad: Espacio en puntos desde la caja delimitadora de los ejes incluyendo marcas y etiquetas de las marcas.

loc: Posición que ocupa el texto {'center', 'left', 'right'}. Por defecto 'center'.

Vamos a ilustrar nuestro ejemplo ya clásico con título, subtítulo y etiquetas en ambos ejes. Estableceremos los tipos de letra y colores de las diversas formas que nos permite matplotlib: con un diccionario o directamente haciendo uso de parámetros con nombre en las funciones.

El título y subtítulo los estableceremos con suptitle() y title() respectivamente, mientras que para las etiqueta utilizaremos xlabel() e ylabel().

matplotlib_01_06_titulo.py
import matplotlib.pyplot as plt
import math


# estilo de texto
font = {'family': 'arial',
        'color':  'darkblue',
        'weight': 'medium',
        'size': 10,
        }

# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

# establecer titulo y subtitulo del gráfico
plt.suptitle('Función Seno', fontweight='bold')
plt.title('De -360 a +360 grados',
          fontdict={'style':'italic', 'color':'red'})

# etiquetas para los ejes
plt.xlabel('Valores de ángulos', labelpad=10)
plt.ylabel('Valores de la función', fontdict=font)

# dibujar el gráfico
plt.plot(grados, seno)

# mostar el gráfico
plt.show()

El resultado ya empieza a parecer un gráfico serio.

Títulos y etiquetas

Todos los parámetros relativos al texto tienen un método equivalente en programación OO. Estos métodos se aplican a la variable que represente la figura (figure) o los ejes (axes).

Modificamos el ejemplo anterior en este sentido, para obtener el mismo resultado.

matplotlib_01_07_titulo_OO.py
import matplotlib.pyplot as plt
import math


# estilo de texto
font = {'family': 'arial',
        'color':  'darkblue',
        'weight': 'medium',
        'size': 10,
        }

# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# establecer titulo y subtitulo del gráfico
titulo = fig.suptitle('Función Seno')
titulo.set_fontweight('bold')
subtitulo = ax.set_title('De -360 a +360 grados')
subtitulo.set_color('red')
subtitulo.set_style('italic')

# etiquetas para los ejes
ax.set_xlabel('Valores de ángulos')
ax.xaxis.labelpad = 10
ax.set_ylabel('Valores de la función', fontdict=font)

# dibujar el gráfico
ax.plot(grados, seno)

# mostar el gráfico
plt.show()

1.1.2.5 Ejes duales

Cuando se traza más de una curva en el mismo gráfico, y sobre todo si es con diferentes unidades, resulta práctico disponer de ejes X o Y duales. Esta generación de ejes se realiza con las funciones twinx() y twiny(). Mediante twinx() podemos crear un segundo eje Y gemelo en el gráfico, compartiendo el eje X. Con twiny() compartimos el eje Y, creando un segundo eje X.

Una vez creado el eje dual es posible utilizar diferentes escalas y marcas, ya que los dos ejes son independientes.

Damos otra vuelta a nuestro ejemplo con dos curvas, seno y seno hiperbólico, ambas en el mismo gráfico para los valores de -360 a 360 grados. Dispondremos los valores del seno en el eje Y, mientras que los del seno hiperbólico los situaremos en un eje Y gemelo, cada uno con sus marcas y etiquetas. Después de dibujar el primer gráfico obtenemos un eje gemelo sobre el que ponemos las marcas y etiquetas y con el que trazamos la curva.

matplotlib_01_08_twin.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
sinh = [math.sinh(math.radians(i)) for i in grados]

fig, ax1 = plt.subplots()

# establecer titulo del gráfico
ax1.set_title('Seno y Seno hiperbólico')

# primer gráfico
# etiquetas para los ejes
ax1.set_xlabel('Ángulos en grados')
ax1.set_ylabel('Valores seno', color='blue')
# marcas para eje eje Y
ax1.tick_params(axis='y', labelcolor='blue') 
# dibujar el gráfico
ax1.plot(grados, seno, color='blue')

# obtener eje Y gemelo
ax2 = ax1.twinx()

# etiquetas para el eje gemelo
ax2.set_ylabel('Valores seno hiperbólico', color='magenta')
# marcas para eje Y gemelo
ax2.tick_params(axis='y', labelcolor='magenta') 
# dibujar el gráfico
ax2.plot(grados, sinh, color='magenta')

# mostar el gráfico
plt.show()

El gráfico resultante queda:

Ejes duales

1.1.2.6 Leyendas

Cuando tenemos una única línea en el gráfico no existe ningún equivoco, pero si tenemos más de una es difícil distinguir que trazado se corresponde con qué función o datos. Para identificar correctamente los distintos trazados empleamos diferentes códigos de color y etiquetas.

Al igual que las leyendas en los planos proporcionan información sobre su simbología, las leyendas en los gráficos nos proporcionan información sobre las distintas líneas en el gráfico.

Una leyenda presenta la información en una caja rectangular con una o más entradas correspondientes a las líneas trazadas, cada entrada consta de una clave que identifica el trazo y un texto descriptivo. La información de la leyenda puede mostrarse en cualquier zona del gráfico.

Para visualizar las leyendas tenemos la función legend(), cuya firma es:

pyplot.legend (parent, handles, labels, *, loc=None, alignment='center', ncol=1, **kwargs)
Parámetros

parent: Axes o Figure que contiene la leyenda.

handles: Lista de Artist que se añadirán a la leyenda.

labels: Lista de etiquetas a mostrar. La longitud de los manejadores y las etiquetas debe ser la misma, si no lo son, se truncan a la longitud de la lista más corta.

loc: Posibles ubicaciones de la leyenda.

ncols: Número de columnas que tiene la leyenda.

El método más flexible es añadir la etiqueta cuando se traza el gráfico con la función plot(), utilizando la palabra clave label con el texto de la etiqueta. Los elementos que se añadirán a la leyenda se determinan automáticamente cuando no se pasa ningún argumento extra. En este caso, las etiquetas se toman del dibujo.

Otra forma para crear una leyenda, para las líneas que ya existen en los ejes, consiste en llamar a la función legend() con una tupla de cadenas, una para cada elemento de la leyenda. Este sistema es algo propenso a errores y poco flexible si se añaden o eliminan curvas de la figura, lo que terminaría ofreciendo una etiqueta errónea en la leyenda.

Las posibles valores para la ubicación de la leyenda vienen dados por las cadenas siguientes:

UbicaciónDescripción
'best'Mejor. (Valor por defecto)

Sitúa la leyenda en la ubicación con el mínimo solapamiento con las imágenes dibujadas. Esta opción puede ser bastante lenta para trazados con grandes cantidades de datos, en cuyo caso es mejor proporcionar una ubicación específica.

'upper right'Superior derecha.
'upper left'Superior izquierda.
'lower left'Inferior izquierda.
'lower right'Inferior derecha.
'right'Derecha.
'center left'Centro izquierda.
'center right'Centro derecha.
'lower center'Centro inferior.
'upper center'Centro superior.
'center'Centro.

Vamos a establecer la leyenda implícitamente en la función plot(), pasando con el parámetro de palabra clave label el valor que aparecerá como elemento en el cuadro.

matplotlib_01_09_leyenda.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

# dibujar dos gráficos en la misma área de dibujo
plt.plot(grados, seno, label='Seno')
plt.plot(grados, coseno, '--r', label='Coseno')

# detección automática de los elementos en la leyenda
# las etiquetas se toman del dibujo
# posicionar la leyenda
plt.legend(loc='best')

# mostar el gráfico
plt.show()

La leyenda aparece en la parte inferior derecha, ya que la hemos situado con la opción 'best', lo que evita solapamientos con las líneas del gráfico

Gráfico con leyenda

Hemos comentado que también podemos pasar un listado explícito de gráficas y etiquetas en legend(). Pasaríamos al método un iterable de los manejadores de los gráficos, seguido de un iterable de etiquetas para la leyenda.

En el ejemplo le indicamos que la leyenda va a tener dos columnas y también la mejor posición para mostrarla.

matplotlib_01_10_leyenda.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# dibujar dos gráficos en la misma área de dibujo
linea_sen, = ax.plot(grados, seno)
linea_cos, = ax.plot(grados, coseno, '--r')

# indicar manejadores de líneas y leyendas
# con la mejor posición
ax.legend((linea_sen, linea_cos), ('Seno', 'Coseno') , ncols=2, loc='best')

# mostar el gráfico
plt.show()

El gráfico resultante es similar al anterior, pero con dos columnas en la leyenda.

Leyenda con dos columnas

Y otra forma, no tan recomendable, consiste en pasar tan solo las etiquetas. Como la relación entre los elementos del gráfico y las etiquetas sólo está implícita por su orden puede malinterpretarse.

En el ejemplo pasamos la relación de etiquetas, una por cada elemento de leyenda, y confiamos que la posición se trate correctamente.

matplotlib_01_11_leyenda.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

# dibujar dos gráficos en la misma área de dibujo
plt.plot(grados, seno,
         grados, coseno)

# la relación entre los elementos y las etiquetas
# sólo está implícita por su orden y puede malinterpretarse
# leyenda y posición
plt.legend(('Seno', 'Coseno'), loc='upper center')

# mostar el gráfico
plt.show()

En este caso hemos modificado la localización de la leyenda, como podemos observar.

Gráfico con leyenda

1.1.2.7 Anotaciones

Títulos, etiquetas y leyendas ofrecen una explicación del gráfico, pero en ocasiones una nota sobre un detalle sirve para transmitir información interesante sobre diferentes zonas importantes del gráfico.

Con el método annotate() podemos establecer anotaciones dentro del grafico. En una anotación hay tres elementos a tener en cuenta: el texto de la nota, la localización del punto al que se refiere la nota y la posición del texto de la nota. La firma del método es:

pyplot.annotate(text, xy, xytext=None, xycoords='data', textcoords=None, arrowprops=None, annotation_clip=None, **kwargs)
Parámetros

text : Texto de la anotación.

xy: Coordenadas (x, y) del punto a anotar.

xytext: Posición (x, y) en la que colocar el texto.

Los siguientes parámetros opcionales nos ofrecen un ajuste más fino:

xycoords: Representa el sistema de coordenadas en el que se da xy.

textcoords: Representa el sistema de coordenadas para xytext.

arrowprops: Diccionario con las propiedades utilizadas para dibujar una flecha entre las posiciones xy y xytext.

annotation_clip: Indica si no se dibuja la anotación cuando el punto xy está fuera del área de los ejes. Si es True, la anotación se recortará. Si es False, la anotación se dibujará siempre. Si es None, la anotación se recortará cuando xy esté fuera de los ejes y xycoords sea 'data'.

Disponemos de diferentes sistemas de coordenadas para los puntos xy y xytext, que podemos especificar en xycoords y textcoords respectivamente, dados por las cadenas:

CoordenadasDescripción
'figure points'Puntos desde la esquina inferior izquierda de la figura.
'figure pixels'Píxeles desde la esquina inferior izquierda de la figura.
'figure fraction'(0, 0) es la parte inferior izquierda de la figura y (1, 1) es la parte superior derecha.
'axes points'Puntos desde la esquina inferior izquierda de los ejes.
'axes pixels'Píxeles desde la esquina inferior izquierda de los ejes.
'axes fraction'(0, 0) es la parte inferior izquierda de los ejes y (1, 1) es la parte superior derecha.
'data'Utiliza el sistema de coordenadas de datos de los ejes. Es el valor por defecto.

Sobre nuestro ya conocido gráfico vamos a anotar el máximo y el mínimo de la función seno. En su forma más simple el método annotate() necesita dos argumentos, el texto a visualizar y las coordenadas del punto a anotar. Vamos a añadir colores distintos a cada texto como una forma de ilustrarlos.

En los ejemplos vamos a emplear el sistema de coordenadas por defecto.

matplotlib_01_12_anotaciones.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# dibujar el gráfico
ax.plot(grados, seno, label='Seno')

# anotar máximo y mínimo
maximo = (90, 1.0)      # coordenada del máximo
minimo = (270, -1.0)    # coordenada del mínimo
ax.annotate('máximo', maximo, color='green')
ax.annotate('mínimo', minimo, color='red')

# mostar el gráfico
plt.show()

Vemos que las notas quedan encima del punto que queremos anotar, y que, en el caso del mínimo, se monta sobre la curva.

Gráfico anotado

Una segunda tupla de coordenadas xytext nos sirve para situar el texto en otra posición. Pero lo que nos serviría para apartar el texto de la curva también complicaría la idea que se quiere transmitir con la anotación, ya que nos podemos encontrar con un texto perdido en el gráfico. La solución está en el empleo de líneas o flechas para dirigir la atención del usuario al punto sobre el que se realiza el comentario.

La misma función annotate() nos facilita la definición de flechas de una forma muy flexible. El estilo de la flecha se controla a través del diccionario arrowprops, que ofrece la posibilidad de crear casi cualquier estilo de flecha que se desee. Vamos a ver algunas de las opciones disponibles, ya que las posibilidades son muchas.

La flecha se dibuja entre las posiciones xy y xytext, y se fija al borde del cuadro de texto, la posición exacta depende de hacia dónde apunte. Si no indicamos nada, por defecto, no se dibuja ninguna flecha.

Existen dos formas diferentes de especificar flechas: simple y elegante.

En la forma simple, si arrowprops no contiene la clave arrowstyle, las claves permitidas son:

ClaveDescripción
widthAnchura de la flecha en puntos.
headwidthAnchura de la base de la flecha en puntos.
headlengthLongitud de la flecha en puntos.
shrinkPorcentaje de la longitud total a reducir desde ambos extremos (valores de 0 a 1).

Aumentando este valor se reduce el espacio ocupado por la flecha entre el texto y el punto.

facecolorColor interno de la flecha, sin incluir los bordes
edgecolorDefine el color de los bordes.
colorColor general (facecolor + edgecolor)
linewidthAncho del borde (usando 0 los bordes desaparecerán)
visibleSe puede utilizar para ocultar la flecha
alfaVa de 0 a 1. Establece la transparencia.
arrowstyleCambia el estilo de la punta de la flecha.
connectionstylePermite cambiar el estilo de la flecha (curva, en ángulo, etc.)

Vamos a modificar el ejemplo anterior desplazando el texto del comentario y dirigiendo la atención de la nota con una flecha simple.

matplotlib_01_13_flechas.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# dibujar gráfico
ax.plot(grados, seno, label='Seno')

# anotar máximo y mínimo
maximo = (90, 1.0)
minimo = (270, -1.0)
ax.annotate('máximo', maximo,
            xytext=(50, 0.60),
            arrowprops=dict(facecolor='cyan', width=2, headwidth=6))
ax.annotate('mínimo', minimo,
            xytext=(250, -0.65),
            arrowprops=dict(facecolor='red', width=2, headwidth=6))

# mostar el gráfico
plt.show()

El gráfico queda de esta forma más legible, ya que el texto no se monta sobre la curva y la flecha orienta el punto a comentar.

Comentario con flecha

La forma elegante de especificar las flechas requiere el uso de arrowstyle dentro del diccionario arrowprops.

Si hacemos uso de arrowstyle los atributos anteriores, correspondientes a la forma simple, son mutuamente excluyentes.

Las claves permitidas son:

ValorDescripción
arrowstyleEl estilo de la flecha. . Los posibles valores son:

'-', '->', '-[', '|-|', '-|>', '<-', '<->', '<|-', '<|-|>', 'fancy', 'simple' y 'wedge'.

connectionstyleEl estilo de la conexión.
relposLa posición del punto de inicio de la flecha respecto al texto de la anotación.

Es una tupla de coordenadas relativas a la caja de texto, donde (0, 0) es la esquina inferior izquierda y (1, 1) es la esquina superior derecha. Los valores <0 y >1 son compatibles y especifican puntos fuera de la caja de texto.

Por defecto es (0.5, 0.5).

patchALa forma del punto inicial de la flecha, por defecto es un cuadro de texto.
patchBLa forma del punto final de la flecha, vacía por defecto.
shrinkAEl punto de sangría del punto inicial de la flecha, por defecto es 2.
shrinkBEl número de puntos de sangría del punto final de la flecha, por defecto es 2.
mutation_scaleAtributo para escalar el estilo de flecha, por defecto es el tamaño del texto en puntos.
mutation_aspectLa altura del rectángulo antes del cambio será comprimida por este valor, y el rectángulo cambiado será estirado por su inverso, el valor por defecto es 1.

Aparte de la flecha podemos dibujar un cuadro delimitador (bounding box) alrededor del texto con la propiedad bbox, que es un diccionario con claves que definen el estilo de la caja.

La clave boxstyle dispone de los valores: 'square', 'circle', 'larrow', 'rarrow', 'darrow', 'round', 'round4', 'sawtooth' y 'roundtooth', que establecen el tipo de cuadro de la caja de texto.

Modificamos nuevamente nuestro ejemplo para adornarlo con flechas y cajas de texto.

matplotlib_01_14_flechas.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# dibujar gráfico
ax.plot(grados, seno, label='Seno')

# anotar máximo y mínimo
maximo = (90, 1.0)
minimo = (270, -1.0)
ax.annotate('máximo', maximo,
            xytext=(50, 0.60),
            arrowprops=dict(facecolor='cyan', width=2, headwidth=6))
ax.annotate('mínimo', minimo,
            xytext=(250, -0.65),
            arrowprops=dict(facecolor='red', width=2, headwidth=6))

# anotar ceros
ceros = [0, 180, 360]
for cero in ceros:
    ax.annotate('ceros', (cero, 0),
                xytext=(75, -0.25),
                arrowprops=dict(arrowstyle='->', color='magenta',
                                connectionstyle='angle3,angleA=0,angleB=-90'),
                bbox=dict(boxstyle='round4,pad=.3', fc='white', color='grey'))

# añadir texto sobre el gráfico
plt.text(250, 0.60, 'texto\nindependiente', size=10,
         bbox=dict(boxstyle='circle',
                   fc='wheat', alpha=0.3, color='black'))
         
# mostar el gráfico
plt.show()

El gráfico resultante presenta distintos tipos de flechas, y cajas de texto, bien con flechas asociadas o bien independientes.

Gráfico con flechas y cajas de texto

Notas, flechas y cuadros de texto ofrecen una variedad muy amplia de posibilidades, con el inconveniente de que deben ajustarse manualmente, lo que en un gráfico de cierta entidad requiere un trabajo fino, que puede llegar a ser tedioso. De todas formas no está de más conocer las opciones disponibles, ya que nos pueden ayudar en algún momento, pero sin excederse. Que los árboles dejen ver el bosque.

1.1.2.8 Cuadrícula

Un gráfico con el fondo en blanco no facilita la lectura de las marcas en los ejes. En ocasiones resulta conveniente mostrar sobre el gráfico un sistema de referencia mediante una trama, cuadrícula o rejilla, que sirva para facilitar la interpretación de los datos, relacionando los puntos del gráfico con los valores de la escala.

Por defecto, las líneas de cuadrícula están desactivadas, pero pueden activarse con la función grid(), que hace uso de las marcas visibles para trazar la rejilla. Si deseamos visualizar la rejilla, también, sobre las marcas secundarias deberemos mostrarlas previamente. La firma de grid() es:

matplotlib.pyplot.grid(visible=None, which='major', axis='both', **kwargs)
Parámetros

visible: Muestra (True) o no (False) las líneas de la rejilla. Si no se especifica, las líneas de la cuadrícula se activan por defecto..

which: Especifica qué líneas de cuadrícula mostrar: las marcas principales, las secundarias o a ambas. ('major', 'minor' o 'both').

axis: Indica sobre qué eje aplicar la configuración: ambos, X o Y. ('both', 'x' o 'y').

También disponemos de propiedades para controlar la apariencia de las líneas de la cuadrícula, como:

linewidth: Ancho de las líneas.

linestyle: Estilo de las líneas de la rejilla

color: Color de las líneas de la rejilla.

alpha: Grado de transparencia.

Volviendo sobre el gráfico que conocemos, vamos a dibujar una cuadrícula para las marcas principales en ambos ejes, con un estilo de línea de guiones y color gris. Todo esto lo podemos hacer con una única llamada a la función grid().

matplotlib_01_15_rejilla.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

# dibujar el gráfico
plt.plot(grados, seno)

# dibujar la rejilla
plt.grid(which='major', axis='both',
         linestyle='--', color='grey', alpha=0.7)

# mostar el gráfico
plt.show()

El resultado ofrece una mejor idea sobre la situación de máximo, mínimo o ceros.

Gráfico con cuadrícula

Como hemos comentado al principio, si deseamos visualizar la rejilla sobre las marcas secundarias deberemos mostrarlas previamente. Para ello disponemos de las funciones set_major_locator() y set_minor_locator(), tanto para los ejes X como Y, con las firmas:

set_major_locator(locator)
set_minor_locator(locator)

Donde locator determina las posiciones de las marcas.

Como no siempre es posible establecer los valores de las marcas, podemos calcularlos de forma dinámica con la clase MultipleLocator(), que establecerá una marca por cada múltiplo entero del valor que se le pase, dentro del intervalo de la vista.

Otra forma es hacer uso de la clase AutoMinorLocator(), que establecerá las posiciones de las marcas menores basándose en las posiciones de las marcas mayores de forma dinámica. Las marcas mayores deben estar espaciadas uniformemente junto con una escala lineal.

Con todos estos elementos podemos dibujar una cuadrícula tanto para las marcas mayores como menores, con los estilos que mejor faciliten la lectura del gráfico.

Una vez más vamos a modificar nuestro gráfico, estableciendo las marcas mayores de forma dinámica, y añadiremos marcas menores de forma dinámica para el eje X y automáticas para el eje Y. Mostraremos etiquetas en las marcas menores del eje X. Acabaremos dibujando una rejilla empleando las marcas mayores y otra más suave para las marcas menores.

matplotlib_01_16_rejilla.py
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import math


# creación de los valores a visualizar
grados = [i for i in range (-360, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# establecer marcas en los ejes
# marcas mayores
ax.xaxis.set_major_locator(plt.MultipleLocator(360 / 4))
ax.yaxis.set_major_locator(plt.MultipleLocator(1.0 / 4))
# marcas menores
ax.xaxis.set_minor_locator(plt.MultipleLocator(360 / 8))
ax.yaxis.set_minor_locator(ticker.AutoMinorLocator())

# mostrar etiquetas menores en el eje X
ax.xaxis.set_minor_formatter(ticker.FormatStrFormatter("%d"))
ax.tick_params(which='minor', labelsize=6, labelrotation = 50, labelcolor='red')

# dibujar la rejilla
# eje X
ax.grid(which='major', axis='x',
        linewidth=0.8, linestyle='-')
ax.grid(which='minor', axis='x',
        linewidth=0.5, linestyle='--', color= 'red')
# eje Y
ax.grid(which='major', axis='y',
        linewidth=1, linestyle='-', color='0.65')
ax.grid(which='minor', axis='y',
        linewidth=0.5, linestyle='-', color='0.75')

# dibujar el gráfico
ax.plot(grados, seno)

# mostar el gráfico
plt.show()

En el gráfico podemos ver líneas de la rejilla, entre las marcas mayores en color más oscuro y entre las menores en rojo o en un gris más pálido. Además, hemos rotado las etiquetas de las marcas menores para que no se monten con las mayores.

Rejillas para marcas principales y secundarias

1.1.2.9 Sombreado de zonas

Sombrear un área entre dos curvas en un gráfico es útil para visualizar la diferencia entre dos conjuntos de datos o para mostrar una región cubierta por una curva.

Esto se realiza con la función fill_between(), cuya firma es:

fill_between(x, y1, y2=0, where=None, interpolate=False, **kwargs)
Parámetros

x: Valores para eje X.

y1: Valores para el eje Y de la primera línea.

y2: Valores para eje Y de la segunda línea. Si no existe una segunda curva el valor es 0.

where: Condición que puede utilizarse para controlar qué puntos se incluyen en el relleno. Si es None (por defecto) se rellena entre todas las partes. Si no es None, es una expresión booleana y el relleno sólo se realizará en las regiones donde el resultado de la condición es True.

interpolate: especifica si se interpolan las curvas para rellenar la región. Si es True, interpola entre las dos líneas para encontrar el punto exacto de intersección. En caso contrario, los puntos inicial y final de la región rellenada sólo se producirán sobre los valores explícitos de X.

Vamos a rellenar la curva de nuestro ejemplo del seno delimitando con el eje y2 en 0. El sombreado en rojo tiene un valor alpha de 0.1, para suavizarlo.

matplotlib_01_17_sombreado.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# dibujar el gráfico
ax.plot(grados, seno, color='blue', alpha=1.0)

# rellenar la imágen
ax.fill_between(grados, seno, 0, color='red', alpha=0.1)

# mostar el gráfico
plt.show()

El resultado se ve de la forma:

Curva con áreas sombreadas

En el siguiente ejemplo crearemos un gráfico con dos curvas, y sombrearemos el área entre las dos. El sombreado, como en el caso anterior es un rojo pálido.

matplotlib_01_18_sombreado.py
import matplotlib.pyplot as plt
import math


# creación de los valores a visualizar
grados = [i for i in range (0, 361, 10)]
seno = [math.sin(math.radians(i)) for i in grados]
coseno = [math.cos(math.radians(i)) for i in grados]

fig, ax = plt.subplots()

# dibujar los dos gráficos
ax.plot(grados, seno, color='blue', alpha=1.0)
ax.plot(grados, coseno, color='red', alpha=1.0)

# rellenar el área entre las curvas
ax.fill_between(grados, seno, coseno, color='red', alpha=0.1)

# mostar el gráfico
plt.show()

En el gráfico vemos sombreada el área entre las dos curvas.

Sombreado entre dos curvas

1.1.2.10 Configuración en tiempo de ejecución

Todos los ajustes para la personalización de un gráfico se pueden cambiar no solo dinámicamente durante la ejecución, si no previamente modificando el rcParams de matplotlib.

El rcParams (runtime configuration parameters – parámetros de configuración en tiempo de ejecución) es un diccionario que permite establecer la apariencia de los gráficos al inicio de un trabajo y que se va a aplicar a todas las figuras que se tracen desde ese momento como valores por defecto.

Para visualizar todos los parámetros con su valor podemos emplear el script siguiente (ojo, en mi instalación obtuve 308 entradas)

from matplotlib import rcParams


for p in rcParams:
    print(f'{p}    {rcParams[p]}')

Como ejemplo presentaremos solo las entradas para la definición de líneas.

lines.antialiased    True
lines.color    C0
lines.dash_capstyle    butt
lines.dash_joinstyle    round
lines.dashdot_pattern    [6.4, 1.6, 1.0, 1.6]
lines.dashed_pattern    [3.7, 1.6]
lines.dotted_pattern    [1.0, 1.65]
lines.linestyle    -
lines.linewidth    1.5
lines.marker    None
lines.markeredgecolor    auto
lines.markeredgewidth    1.0
lines.markerfacecolor    auto
lines.markersize    6.0
lines.scale_dashes    True
lines.solid_capstyle    projecting
lines.solid_joinstyle    round

En la documentación de matplotlib sobre rcParams está toda la información sobre cada entrada del diccionario.

Para cambiar un valor basta con tratarlo como cualquier otro diccionario:

rcParams[<clave>] = <valor>

Vamos a obtener un valor y después cambiarlo.

matplotlib_01_19_rcparams.py
from matplotlib import rcParams


# obtener el valor de ancho de línea
print('Antes', rcParams['lines.linewidth'])

# modificar el valor de ancho de línea
rcParams['lines.linewidth'] = 3.5

# obtener el nuevo valor de ancho de línea
print('Después', rcParams['lines.linewidth'])

Como podemos ver hemos cambiado el valor del ancho de línea, que será con el que se van a trazar los gráficos a partir de ese momento.

Antes 1.5
Después 3.5

Volviendo al ejemplo inicial del capítulo, vamos a cambiar el estilo de trazado de la línea, modificando el parámetro correspondiente al ancho en el rcParams previo al trazado, y dinámicamente cambiaremos el tipo de línea y color.

matplotlib_01_20_rcparams.py
import matplotlib.pyplot as plt
from matplotlib import rcParams


# relación de valores a visualizar
# para los ejes X e Y
x = [1, 2, 3, 4, 5, 6, 7, 8]
y = [1, 1, 2, 3, 5, 8, 13, 24]

# establecer valores para el trazado
# en los parámetros de ejecución
# ancho de línea
rcParams['lines.linewidth'] = 3.5

# dibujar los valores
# modificando dinámicamente el trazado
# estilo de línea y color
plt.plot(x, y, '--r') 

# mostrar el gráfico
plt.show()

Vemos el resultado según los distintos cambios realizados. En rcParams hemos modificado el ancho de línea por defecto de matplotlib. Y en el momento de trazar el gráfico hemos establecido el tipo de línea y el color.

Gráfico modificado

Personalizar los gráficos

    • Personalizar los gráficos
      • Formato del gráfico
      • Ejes, bordes y marcas
      • Ajustar límites
      • Títulos y etiquetas
      • Ejes duales
      • Leyendas
      • Anotaciones
      • Cuadrícula
      • Sombreado de zonas
      • Configuración en tiempo de ejecución