import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib_inline.backend_inline import set_matplotlib_formats
set_matplotlib_formats('pdf', 'svg')
%matplotlib inline8 Visualización de datos con Matplotlib y Seaborn
Objetivo
El objetivo de esta clase es enseñar a los estudiantes a crear visualizaciones de datos utilizando matplotlib y seaborn en Python, permitiéndoles representar gráficamente la información para explorar y analizar patrones y relaciones en los datos de manera efectiva.
8.1 Consejos generales sobre Matplotlib
Antes de profundizar en los detalles de la creación de visualizaciones con Matplotlib, hay algunas cosas útiles que debe saber sobre el uso del paquete.
8.1.1 Importación de Matplotlib
Así como usamos la abreviatura np para NumPy y la abreviatura pd para Pandas, usaremos algunas abreviaturas de uso común para las importaciones de Matplotlib:
La interfaz plt es la que utilizaremos con más frecuencia, como veremos a lo largo de este capítulo.
8.1.2 Estilos de configuración
Utilizaremos la directiva plt.style para elegir estilos estéticos apropiados para nuestras figuras. Aquí configuraremos el estilo clásico, que garantiza que los gráficos que creamos utilicen el estilo clásico de Matplotlib:
plt.style.use('classic')A lo largo de esta sección, ajustaremos este estilo según sea necesario.
import numpy as np
x = np.linspace(0, 10, 100)
fig = plt.figure()
plt.plot(x, np.sin(x), '-')
plt.plot(x, np.cos(x), '--')
plt.show();En el ejemplo anterior, el comando plt.show() desencadena una serie de eventos que busca los objetos de figura activos y muestra las gráficas pendientes como imagen. Como por ejemplo en las siguientes dos celdas.
fig = plt.figure()
plt.plot(x, np.sin(x), '-')
plt.plot(x, np.cos(x), '--');x = np.linspace(-3, 3, 100)
fig = plt.figure()
plt.plot(x, np.cos(x), '-')
plt.plot(x, np.cosh(x), '--')
plt.show();8.1.3 Guardar figuras en archivo
Una característica interesante de Matplotlib es la capacidad de guardar figuras en una amplia variedad de formatos. Se puede guardar una figura utilizando el comando savefig(). Por ejemplo, para guardar la figura anterior como un archivo PNG o SVG, puede ejecutar esto:
fig.savefig('my_figure.png')
fig.savefig('my_figure.svg')Para confirmar que contiene lo que creemos que contiene, usemos el objeto Image de IPython para mostrar el contenido de este archivo:
from IPython.display import Image
Image('my_figure.png')En savefig(), el formato de archivo se infiere de la extensión del nombre de archivo dado. Dependiendo de los backends que tenga instalados, hay muchos formatos de archivos diferentes disponibles. La lista de tipos de archivos compatibles con su sistema se puede encontrar utilizando el siguiente método del objeto figura:
fig.canvas.get_supported_filetypes()Tenga en cuenta que al guardar su figura, no es necesario utilizar plt.show().
8.2 Dos interfaces por el precio de una
Una característica de Matplotlib que puede resultar confusa es su interfaz dual: una interfaz basada en estados, al estilo de MATLAB, y una interfaz orientada a objetos más potente. Destacaremos rápidamente las diferencias entre ambas.
8.2.1 Interfaz estilo MATLAB
Matplotlib se escribió originalmente como una alternativa de Python para los usuarios de MATLAB y gran parte de su sintaxis refleja ese hecho. Las herramientas de estilo MATLAB están contenidas en la interfaz pyplot (plt). Por ejemplo, el siguiente código probablemente resultará bastante familiar para los usuarios de MATLAB:
plt.figure() # crear una figura de trama
# Crea el primero de dos paneles y establece el eje actual.
plt.subplot(2, 1, 1) # (filas, columnas, número de panel)
plt.plot(x, np.sin(x))
# Crea el segundo panel y establece el eje actual.
plt.subplot(2, 1, 2)
plt.plot(x, np.cos(x))
plt.show()Es importante tener en cuenta que esta interfaz es con estado: realiza un seguimiento de la figura y los ejes ‘actuales’, que es donde se aplican todos los comandos plt. Puede obtener una referencia a estos utilizando las rutinas plt.gcf() (obtener la figura actual) y plt.gca() (obtener los ejes actuales).
Si bien esta interfaz con estado es rápida y conveniente para gráficos simples, es fácil tener problemas. Por ejemplo, una vez creado el segundo panel, ¿cómo podemos volver atrás y agregar algo al primero? Esto es posible dentro de la interfaz de estilo MATLAB, pero es un poco complicado. Afortunadamente, existe una manera más pythonica.
8.2.2 Interfaz orientada a objetos
La interfaz orientada a objetos está disponible para estas situaciones más complicadas y para cuando quieras tener más control sobre tu figura. En lugar de depender de alguna noción de una figura o ejes ‘activos’, en la interfaz orientada a objetos las funciones de trazado son métodos de objetos explícitos figure y axis. Para recrear el gráfico anterior utilizando este estilo de trazado, puedes hacer lo siguiente:
# Primero crea una cuadrícula de gráficas
# ax será una matriz de dos objetos Axes
fig, ax = plt.subplots(2)
# Llamar al método plot() en el objeto apropiado
ax[0].plot(x, np.sin(x))
ax[1].plot(x, np.cos(x))
plt.show()Para gráficas más simples, la elección del estilo a utilizar es en gran medida una cuestión de preferencia, pero el enfoque orientado a objetos puede volverse una necesidad a medida que las gráficas se vuelven más complicadas. A lo largo de este capítulo, alternaremos entre las interfaces de estilo MATLAB y las orientadas a objetos, dependiendo de lo que sea más conveniente. En la mayoría de los casos, la diferencia es tan pequeña como cambiar plt.plot() a ax.plot(), pero hay algunos problemas que destacaremos a medida que surjan en las siguientes secciones.
8.3 Gráficos de líneas simples
Quizás el más simple de todos los gráficos es la visualización de una única función \(y = f(x)\). Aquí daremos un primer vistazo a la creación de un gráfico sencillo de este tipo. Comenzaremos configurando el notebook para graficar. Primero, examinemos los estilos de gráficos que tiene disponible Matplotlib:
mpl.style.available%matplotlib inline
plt.style.use('ggplot')Para todos los gráficos de Matplotlib, comenzamos creando una figura y un eje. En su forma más simple, una figura y ejes se pueden crear de la siguiente manera:
fig = plt.figure()
ax = plt.axes()
plt.show()En Matplotlib, la figura (una instancia de la clase plt.Figure) puede considerarse como un contenedor único que contiene todos los objetos que representan ejes, gráficos, texto y etiquetas. Los ejes (una instancia de la clase plt.Axes) son lo que vemos arriba: un cuadro delimitador con marcas y etiquetas, que eventualmente contendrá los elementos del gráfico que componen nuestra visualización. A lo largo de este Notebook, utilizaremos comúnmente el nombre de variable ‘fig’ para referirnos a una instancia de figura, y ‘ax’ para referirnos a una instancia de ejes o un grupo de instancias de ejes.
Una vez que hemos creado los ejes, podemos utilizar la función ax.plot para representar gráficamente algunos datos. Empecemos con una sinusoide simple:
fig = plt.figure()
ax = plt.axes()
x = np.linspace(0, 10, 1000)
ax.plot(x, np.sin(x));
plt.show()Si queremos crear una sola figura con varias líneas, simplemente podemos llamar a la función plot varias veces:
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x));
plt.show()¡Eso es todo lo que hay que saber para trazar funciones simples en Matplotlib! Ahora profundizaremos en algunos detalles más sobre cómo controlar la apariencia de los ejes y las líneas.
8.3.1 Ajuste de colores y estilos de línea
El primer ajuste que quizás desees realizar en un gráfico es controlar los colores y estilos de línea. La función plt.plot() toma argumentos adicionales que pueden usarse para especificarlos. Para ajustar el color, puede utilizar la palabra clave color, que acepta un argumento de cadena que representa prácticamente cualquier color imaginable. El color se puede especificar de varias maneras:
plt.plot(x, np.sin(x - 0), color='blue') # especificar color por nombre
plt.plot(x, np.sin(x - 1), color='g') # código de color corto (rgbcmyk)
plt.plot(x, np.sin(x - 2), color='0.35') # Escala de grises entre 0 y 1
plt.plot(x, np.sin(x - 3), color='#E15759') # Código hexadecimal (RRGGBB de 00 a FF)
plt.plot(x, np.sin(x - 4), color=(1.0,0.5,0.3)) # Tupla RGB, valores 0 a 1
plt.plot(x, np.sin(x - 5), color='cyan'); # Se admiten todos los nombres de colores HTML
plt.legend(['Línea 1', 'Línea 2','Línea 3','Línea 4', 'Línea 5','Línea 6'])
plt.show()Para una lista de los nombres de colores disponibles consulte la documentación.
Si no se especifica ningún color, Matplotlib recorrerá automáticamente un conjunto de colores predeterminados para varias líneas.
De manera similar, el estilo de línea se puede ajustar utilizando la palabra clave linestyle:
plt.plot(x, x + 0, linestyle='solid')
plt.plot(x, x + 1, linestyle='dashed')
plt.plot(x, x + 2, linestyle='dashdot')
plt.plot(x, x + 3, linestyle='dotted');
# Para abreviar, puedes utilizar los siguientes códigos:
plt.plot(x, x + 4, linestyle='-') # sólido
plt.plot(x, x + 5, linestyle='--') # discontinuo
plt.plot(x, x + 6, linestyle='-.') # raya y punto
plt.plot(x, x + 7, linestyle=':'); # punteado
plt.show()Si desea ser extremadamente conciso, estos códigos linestyle y color se pueden combinar en un solo argumento que no sea una palabra clave para la función plt.plot():
plt.plot(x, x + 0, '-g') # verde sólido
plt.plot(x, x + 1, '--c') # cian discontinuo
plt.plot(x, x + 2, '-.k') # raya negra
plt.plot(x, x + 3, ':r'); # punteado rojo
plt.show()Estos códigos de color de un solo carácter reflejan las abreviaturas estándar de los sistemas de color RGB (rojo/verde/azul) y CMYK (cian/magenta/amarillo/negro), comúnmente utilizados para gráficos en color digitales.
Hay muchos otros argumentos de palabras clave que se pueden usar para ajustar la apariencia del gráfico; para obtener más detalles, sugiero revisar la documentación de la función plt.plot().
8.3.2 Ajuste de límites de los ejes
Matplotlib hace un buen trabajo al elegir los límites de ejes predeterminados para su gráfico, pero a veces es bueno tener un control más preciso. La forma más básica de ajustar los límites de los ejes es utilizar los métodos plt.xlim() y plt.ylim():
plt.plot(x, np.sin(x))
plt.xlim(-1, 11)
plt.ylim(-1.5, 1.5)
plt.show()Si por alguna razón desea que alguno de los ejes se muestre al revés, puede simplemente invertir el orden de los argumentos:
plt.plot(x, np.sin(x))
plt.xlim(10, 0)
plt.ylim(1.2, -1.2)
plt.show()Un método relacionado útil es plt.axis() (note aquí la posible confusión entre axes con una e y axis con una i). El método plt.axis() le permite establecer los límites x y y con una sola llamada, pasando una lista que especifica [xmin, xmax, ymin, ymax]:
plt.plot(x, np.sin(x))
plt.axis([-1, 11, -1.5, 1.5])
plt.show()El método plt.axis() va incluso más allá de esto, permitiéndole hacer cosas como ajustar automáticamente los límites alrededor del gráfico actual:
plt.plot(x, np.sin(x))
plt.axis('tight')
plt.show()Permite especificaciones de nivel aún más alto, como garantizar una relación de aspecto igual para que en la pantalla, una unidad en «x» sea igual a una unidad en «y»:
plt.plot(x, np.sin(x))
plt.axis('equal')
plt.show()Para obtener más información sobre los límites de los ejes y otras capacidades del método plt.axis, consulte su documentación.
8.3.4 Etiquetas
Como última parte de esta sección, veremos brevemente el etiquetado de los gráficos: títulos, etiquetas de ejes y leyendas simples.
Los títulos y las etiquetas de los ejes son las etiquetas más simples; existen métodos que se pueden usar para configurarlas rápidamente:
plt.plot(x, np.sin(x))
plt.title('Curva seno')
plt.xlabel('x')
plt.ylabel('sen(x)')
plt.show()La posición, el tamaño y el estilo de estas etiquetas se pueden ajustar utilizando argumentos opcionales para la función. Para obtener más información, consulte la documentación de Matplotlib .
Cuando se muestran varias líneas dentro de un solo eje, puede ser útil crear una leyenda de gráfico que etiquete cada tipo de línea. Nuevamente, Matplotlib tiene una forma incorporada de crear rápidamente dicha leyenda. Esto se hace a través del método (lo adivinaste) plt.legend(). Si bien existen varias formas válidas de utilizar esto, me resulta más fácil especificar la etiqueta de cada línea utilizando la palabra clave label de la función plot:
plt.plot(x, np.sin(x), '-g', label='sin(x)')
plt.plot(x, np.cos(x), ':b', label='cos(x)')
plt.axis('equal')
plt.legend()
plt.show()Como puede ver, la función plt.legend() realiza un seguimiento del estilo y el color de la línea y los combina con la etiqueta correcta. Puede encontrar más información sobre cómo especificar y formatear leyendas de gráficos en la documentación plt.legend.
Adicionalmente, Matplotlib permite establecer etiquetas y leyendas usando intérprete de LateX:
plt.plot(x, np.sin(x), '--g', label=r'$\sin(x)$')
plt.plot(x, np.cos(x), '-.b', label=r'$\cos(x)$')
plt.axis('equal')
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.legend(fontsize=20)
plt.show()8.3.5Problemas con Matplotlib
Si bien la mayoría de las funciones plt se traducen directamente a métodos ax (como plt.plot() → ax.plot(), plt.legend() → ax.legend(), etc.), este no es el caso de todos los comandos. En particular, se modifican ligeramente las funciones para establecer límites, etiquetas y títulos. Para realizar la transición entre funciones de estilo MATLAB y métodos orientados a objetos, realice los siguientes cambios:
plt.xlabel()→ax.set_xlabel()plt.ylabel()→ax.set_ylabel()plt.xlim()→ax.set_xlim()plt.ylim()→ax.set_ylim()plt.title()→ax.set_title()
En la interfaz orientada a objetos para trazar gráficos, en lugar de llamar a estas funciones individualmente, a menudo es más conveniente utilizar el método ax.set() para configurar todas estas propiedades a la vez:
ax = plt.axes()
ax.plot(x, np.sin(x))
ax.set(xlim=(0, 10), ylim=(-2, 2),
xlabel='$x$', ylabel=r'$\sin(x)$',
title='Gráfica simple')
plt.show()8.4 Diagramas de dispersión simples
Otro tipo de gráfico comúnmente utilizado es el gráfico de dispersión simple, un primo cercano del gráfico lineal. En lugar de unir los puntos mediante segmentos de línea, aquí los puntos se representan individualmente con un punto, un círculo u otra forma.
8.4.1 Diagramas de dispersión con plt.plot
En la sección anterior, analizamos plt.plot/ax.plot para producir gráficos lineales. Resulta que esta misma función también puede producir gráficos de dispersión:
x = np.linspace(0, 10, 30)
y = np.sin(x)
plt.plot(x, y, 'ok')
plt.show()El tercer argumento en la llamada de función es un carácter que representa el tipo de símbolo utilizado para el gráfico. Así como se pueden especificar opciones como '-', '--' para controlar el estilo de línea, el estilo de marcador tiene su propio conjunto de códigos de cadena cortos. La lista completa de símbolos disponibles se puede ver en la documentación de plt.plot, o en la documentación en línea de Matplotlib. La mayoría de las posibilidades son bastante intuitivas, y aquí mostraremos algunas de las más comunes:
rng = np.random.RandomState(0)
plt.figure(figsize=(12,6))
for marker in ['o', '.', ',', 'x', '+', 'v', '^', '<', '>', 's', 'd']:
plt.plot(rng.rand(5), rng.rand(5), marker,
label=f'marker={marker}')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5),numpoints=1)
plt.xlim(0, 1.1)
plt.show()Para obtener aún más posibilidades, estos códigos de caracteres se pueden utilizar junto con códigos de línea y color para trazar puntos junto con una línea que los conecta:
plt.plot(x, y, '-ok')
plt.show()Los argumentos de palabras clave adicionales a plt.plot especifican una amplia gama de propiedades de las líneas y marcadores:
plt.plot(x, y, '-p', color='gray',
markersize=12, linewidth=4,
markerfacecolor='cyan',
markeredgecolor='gray',
markeredgewidth=2)
plt.ylim(-1.2, 1.2)
plt.show()Este tipo de flexibilidad en la función plt.plot permite una amplia variedad de posibles opciones de visualización. Para obtener una descripción completa de las opciones disponibles, consulte la documentación de plt.plot.
8.4.2 Diagramas de dispersión con plt.scatter
Un segundo método más potente para crear gráficos de dispersión es la función plt.scatter, que se puede utilizar de forma muy similar a la función plt.plot:
plt.scatter(x, y, marker='o')
plt.show()La principal diferencia entre plt.scatter y plt.plot es que se puede utilizar para crear gráficos de dispersión donde las propiedades de cada punto individual (tamaño, color de la cara, color del borde, etc.) se pueden controlar individualmente o asignar a los datos.
Demostremos esto creando un gráfico de dispersión aleatorio con puntos de muchos colores y tamaños. Para ver mejor los resultados superpuestos, también usaremos la palabra clave alpha para ajustar el nivel de transparencia:
rng = np.random.RandomState(0)
x = rng.randn(100)
y = rng.randn(100)
colors = rng.rand(100)
sizes = 1000 * rng.rand(100)
plt.figure(figsize=(12,12))
plt.scatter(x, y, c=colors, s=sizes, alpha=0.5,
cmap='inferno')
plt.colorbar()
plt.show()Tenga en cuenta que el argumento de color se asigna automáticamente a una escala de color (mostrada aquí por el comando colorbar()) y que el argumento de tamaño se da en píxeles. De esta manera, el color y el tamaño de los puntos se pueden utilizar para transmitir información en la visualización, con el fin de visualizar datos multidimensionales.
Por ejemplo, podríamos utilizar los datos de Iris de Scikit-Learn, donde cada muestra es uno de los tres tipos de flores a las que se les ha medido cuidadosamente el tamaño de sus pétalos y sépalos:
from sklearn.datasets import load_iris
iris = load_iris()
features = iris.data.T
plt.scatter(features[0], features[1], alpha=0.6,
s=200*features[2], c=iris.target, cmap='magma')
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
plt.show()Podemos ver que este diagrama de dispersión nos ha dado la capacidad de explorar simultáneamente cuatro dimensiones diferentes de los datos: La ubicación \((x, y)\) de cada punto corresponde a la longitud y el ancho del sépalo, el tamaño del punto está relacionado con el largo del pétalo y el color está relacionado con la especie particular de flor. Los gráficos de dispersión multicolores y de múltiples características como éste pueden ser útiles tanto para la exploración como para la presentación de datos.
8.4.3 plot versus scatter: una nota sobre la eficiencia
Además de las diferentes funciones disponibles en plt.plot y plt.scatter, ¿por qué elegiría utilizar uno en lugar del otro? Si bien no es tan importante para pequeñas cantidades de datos, a medida que los conjuntos de datos se hacen más grandes que unos pocos miles de puntos, plt.plot puede ser notablemente más eficiente que plt.scatter. La razón es que plt.scatter tiene la capacidad de renderizar un tamaño y/o color diferente para cada punto, por lo que el renderizador debe hacer el trabajo adicional de construir cada punto individualmente. En plt.plot, por otro lado, los puntos son siempre esencialmente clones unos de otros, por lo que el trabajo de determinar la apariencia de los puntos se realiza solo una vez para todo el conjunto de datos. Para conjuntos de datos grandes, la diferencia entre estos dos puede generar un rendimiento muy diferente y, por este motivo, se debería preferir plt.plot sobre plt.scatter para conjuntos de datos grandes.
8.5 Visualizando errores
En cualquier medición científica, la contabilización precisa de los errores es casi tan importante, si no más, que la información precisa del número en sí. Por ejemplo, imaginemos que tenemos algunas observaciones astrofísicas para estimar la Constante de Hubble, la medida local de la tasa de expansión del Universo.
Sé que la literatura actual sugiere un valor de alrededor de 71 (km/s)/Mpc, y mido un valor de 74 (km/s)/Mpc con mi método. ¿Son consistentes los valores? La única respuesta correcta, dada esta información, es esta: no hay forma de saberlo.
Supongamos que amplío esta información con las incertidumbres reportadas: la literatura actual sugiere un valor de alrededor de 71 \(\pm\) 2.5 (km/s)/Mpc, y mi método ha medido un valor de 74 \(\pm\) 5 (km/s)/Mpc. Ahora bien, ¿son consistentes los valores? Esa es una pregunta que puede responderse cuantitativamente.
En la visualización de datos y resultados, mostrar estos errores de manera efectiva puede hacer que un gráfico transmita información mucho más completa.
Se puede crear una barra de error básica con una sola llamada a la función Matplotlib:
x = np.linspace(0, 10, 50)
dy = 0.1
y = np.sin(x) + dy * np.random.randn(50)
plt.errorbar(x, y, yerr=dy, fmt='.k')
plt.show()Aquí, fmt es un código de formato que controla la apariencia de líneas y puntos, y tiene la misma sintaxis que la abreviatura utilizada en plt.plot.
Además de estas opciones básicas, la función errorbar tiene muchas opciones para ajustar las salidas. Usando estas opciones adicionales puedes personalizar fácilmente la estética de tu gráfico con barras de error.
plt.errorbar(x, y, yerr=dy, fmt='o', color='red',
ecolor='darkgray', elinewidth=3, capsize=5)
plt.show()Además de estas opciones, también puede especificar barras de error horizontales (xerr), barras de error unilaterales y muchas otras variantes. Para obtener más información sobre las opciones disponibles, consulte la documentación de plt.errorbar.
8.6 Gráficas de densidad y contorno
A veces es útil mostrar datos tridimensionales en dos dimensiones utilizando contornos o regiones codificadas por colores. Hay tres funciones de Matplotlib que pueden ser útiles para esta tarea: plt.contour para gráficos de contorno, plt.contourf para gráficos de contorno rellenos y plt.imshow para mostrar imágenes.
Comenzaremos demostrando un gráfico de contorno usando una función \(z = f(x, y)\), usando la siguiente opción particular para \(f\):
def f(x, y):
return np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)Se puede crear un gráfico de contorno con la función plt.contour. Toma tres argumentos: una cuadrícula de valores x, una cuadrícula de valores y y una cuadrícula de valores z. Los valores x e y representan posiciones en el gráfico, y los valores z estarán representados por los niveles de contorno. Quizás la forma más sencilla de preparar dichos datos es utilizar la función np.meshgrid, que construye cuadrículas bidimensionales a partir de matrices unidimensionales:
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 40)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)Ahora veamos esto con un gráfico de contorno de solo líneas estándar:
plt.contour(X, Y, Z, colors='black')
plt.show()Tenga en cuenta que, de forma predeterminada, cuando se utiliza un solo color, los valores negativos se representan mediante líneas discontinuas y los valores positivos, mediante líneas continuas. Alternativamente, las líneas pueden codificarse por colores especificando un mapa de colores con el argumento cmap. Aquí también especificaremos que queremos que se dibujen más líneas: 20 intervalos igualmente espaciados dentro del rango de datos:
plt.contour(X, Y, Z, 20, cmap='RdGy')
plt.show()Aquí elegimos el mapa de colores «RdGy» (abreviatura de Rojo-Gris), que es una buena opción para datos centrados. Matplotlib tiene una amplia gama de mapas de colores disponibles, que puedes explorar fácilmente en IPython completando una tabulación en el módulo plt.cm:
plt.cm.<TAB>
Nuestra gráfica se ve mejor, pero los espacios entre las líneas pueden distraer un poco. Podemos cambiar esto cambiando a un gráfico de contorno relleno usando la función plt.contourf() (observe la f al final), que utiliza en gran medida la misma sintaxis que plt.contour().
Además, agregaremos un comando plt.colorbar(), que crea automáticamente un eje adicional con información de color etiquetada para el gráfico:
plt.contourf(X, Y, Z, 20, cmap='RdGy')
plt.colorbar()
plt.show()La barra de colores deja claro que las regiones negras son ‘picos’, mientras que las regiones rojas son ‘valles’.
Un problema potencial con este gráfico es que es un poco irregular, es decir, los pasos de color son discretos en lugar de continuos, lo que no siempre es lo deseado. Esto se podría remediar estableciendo el número de contornos en un número muy alto, pero el resultado es un gráfico bastante ineficiente: Matplotlib debe representar un nuevo polígono para cada paso del nivel. Una mejor manera de manejar esto es usar la función plt.imshow(), que interpreta una cuadrícula bidimensional de datos como una imagen.
El siguiente código lo muestra:
plt.imshow(Z, extent=[0, 5, 0, 5], origin='lower',cmap='RdGy')
plt.colorbar()
plt.axis()
plt.show()Sin embargo, existen algunos problemas potenciales con imshow():
plt.imshow()no acepta una cuadrícula x e y, por lo que debe especificar manualmente la extensión [xmin, xmax, ymin, ymax] de la imagen en el gráfico.plt.imshow()sigue de manera predeterminada la definición de matriz de imágenes estándar, donde el origen está en la esquina superior izquierda, no en la esquina inferior izquierda como en la mayoría de los gráficos de contorno. Esto se debe cambiar cuando se muestran datos en cuadrícula.
Por último, a veces puede resultar útil combinar gráficos de contorno y gráficos de imágenes. Por ejemplo, aquí utilizaremos una imagen de fondo parcialmente transparente (con la transparencia establecida a través del parámetro alpha) y superpondremos los contornos con etiquetas en los contornos mismos (utilizando la función plt.clabel()):
contours = plt.contour(X, Y, Z, 3, colors='black')
plt.clabel(contours, inline=True, fontsize=10)
plt.imshow(Z, extent=[0, 5, 0, 5], origin='lower',
cmap='RdGy', alpha=0.5)
plt.colorbar()
plt.show()La combinación de estas tres funciones (plt.contour, plt.contourf y plt.imshow) ofrece posibilidades casi ilimitadas para mostrar este tipo de datos tridimensionales dentro de un gráfico bidimensional.
8.7 Histogramas, binnings y densidad
Un histograma simple puede ser un gran primer paso para comprender un conjunto de datos.
data = np.random.randn(1000)
plt.hist(data)
plt.show()La función hist() tiene muchas opciones para ajustar tanto el cálculo como la visualización; He aquí un ejemplo de un histograma más personalizado:
plt.hist(data, bins=30, density=True, alpha=0.5,
histtype='stepfilled', color='steelblue',
edgecolor='none')
plt.show()La cadena de documentación plt.hist tiene más información sobre otras opciones de personalización disponibles.
x1 = np.random.normal(0, 0.8, 1000)
x2 = np.random.normal(-2, 1, 1000)
x3 = np.random.normal(3, 2, 1000)
kwargs = dict(histtype='stepfilled', alpha=0.3, density=False, bins=40)
plt.hist(x1, **kwargs)
plt.hist(x2, **kwargs)
plt.hist(x3, **kwargs)
plt.show()8.7.1 Histogramas y binnings bidimensionales
Así como creamos histogramas en una dimensión dividiendo la línea numérica en contenedores, también podemos crear histogramas en dos dimensiones dividiendo puntos entre contenedores bidimensionales. Echaremos un vistazo breve a varias formas de hacer esto aquí. Comenzaremos definiendo algunos datos: una matriz extraída de una distribución gaussiana multivariada:
mean = [0, 0]
cov = [[1, 1], [1, 2]]
x, y = np.random.multivariate_normal(mean, cov, 10000).Tplt.hist2d: Histograma bidimensional
Una forma sencilla de trazar un histograma bidimensional es utilizar la función plt.hist2d de Matplotlib:
plt.hist2d(x, y, bins=30, cmap='Blues')
cb = plt.colorbar()
cb.set_label('Frecuencia')
plt.show()Al igual que con plt.hist, plt.hist2d tiene una serie de opciones adicionales para ajustar el gráfico y el binning, que están muy bien delineadas en la cadena de documentación de la función.
plt.hexbin: Agrupamientos hexagonales
El histograma bidimensional crea una teselación de cuadrados a lo largo de los ejes. Otra forma natural para tal teselación es el hexágono regular. Para este propósito, Matplotlib proporciona la rutina plt.hexbin, que representará un conjunto de datos bidimensionales agrupados dentro de una cuadrícula de hexágonos:
plt.hexbin(x, y, gridsize=30, cmap='Blues')
cb = plt.colorbar(label='Frecuencia')
plt.show()plt.hexbin tiene varias opciones interesantes, incluida la capacidad de especificar pesos para cada punto y cambiar la salida en cada contenedor a cualquier agregado NumPy (media de pesos, desviación estándar de pesos, etc.).
8.8 Personalización de leyendas
Las leyendas de la gráfica dan significado a una visualización, asignando significado a los diversos elementos de la trama.
Anteriormente vimos cómo crear una leyenda simple; aquí veremos cómo personalizar la ubicación y la estética de la leyenda en Matplotlib.
La leyenda más simple se puede crear con el comando plt.legend(), que crea automáticamente una leyenda para cualquier elemento gráfico etiquetado:
x = np.linspace(0, 10, 1000)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), '-b', label='Seno')
ax.plot(x, np.cos(x), '--r', label='Coseno')
ax.axis('equal')
leg = ax.legend()
plt.show()Pero hay muchas formas en las que podríamos personalizar dicha leyenda. Por ejemplo, podemos especificar la ubicación y desactivar el marco:
ax.legend(loc='upper left', frameon=False)
figPodemos usar el comando ncol para especificar el número de columnas en la leyenda:
ax.legend(frameon=False, loc='lower center', ncol=2)
figPodemos utilizar un cuadro redondeado (fancybox) o agregar una sombra, cambiar la transparencia (valor alfa) del marco o cambiar el relleno alrededor del texto:
ax.legend(fancybox=True, framealpha=1, shadow=True, borderpad=1)
figPara obtener más información sobre las opciones de leyenda disponibles, consulte la cadena de documentación plt.legend.
8.8.1 Elección de elementos para la leyenda
Como ya hemos visto, la leyenda incluye todos los elementos etiquetados por defecto. Si esto no es lo deseado, podemos ajustar qué elementos y etiquetas aparecen en la leyenda utilizando los objetos devueltos por los comandos de trazado. El comando plt.plot() puede crear varias líneas a la vez y devuelve una lista de instancias de líneas creadas. Al pasar cualquiera de estos a plt.legend() le indicaremos cuál identificar, junto con las etiquetas que nos gustaría especificar:
y = np.sin(x[:, np.newaxis] + np.pi * np.arange(0, 2, 0.5))
lines = plt.plot(x, y)
plt.legend(lines[:2], ['1°', '2°'])
plt.show()Generalmente, en la práctica encuentro que es más claro utilizar el primer método, aplicando etiquetas a los elementos del gráfico que desea mostrar en la leyenda:
plt.plot(x, y[:, 0], label='1°')
plt.plot(x, y[:, 1], label='2°')
plt.plot(x, y[:, 2:])
plt.legend(framealpha=1, frameon=True)
plt.show()Tenga en cuenta que, de forma predeterminada, la leyenda ignora todos los elementos sin un atributo label establecido y aquellas etiquetas que inician con _
8.8.2 Leyenda del tamaño de los puntos
A veces, los valores predeterminados de la leyenda no son suficientes para la visualización dada. Por ejemplo, quizás esté utilizando el tamaño de los puntos para marcar ciertas características de los datos y desee crear una leyenda que refleje esto. A continuación se muestra un ejemplo en el que utilizaremos el tamaño de los puntos para indicar las poblaciones de las ciudades de California. Nos gustaría una leyenda que especifique la escala de los tamaños de los puntos, y lograremos esto trazando algunos datos etiquetados sin entradas:
import pandas as pd
try:
cities = pd.read_csv('california_cities.csv')
except:
!curl -O https://raw.githubusercontent.com/jakevdp/PythonDataScienceHandbook/master/notebooks_v1/data/california_cities.csv
cities = pd.read_csv('california_cities.csv')
# Extraer los datos que nos interesan
lat, lon = cities['latd'], cities['longd']
population, area = cities['population_total'], cities['area_total_km2']
# Distribuye los puntos, usando tamaño y color pero sin etiqueta
plt.scatter(lon, lat, label=None,
c=np.log10(population), cmap='viridis',
s=area, linewidth=0, alpha=0.5)
plt.axis('equal')
plt.xlabel('longitude')
plt.ylabel('latitude')
plt.colorbar(label='log$_{10}$(population)')
plt.clim(3, 7)
# Aquí creamos una leyenda:
# Trazaremos listas vacías con el tamaño y la etiqueta deseados
for area in [100, 300, 500]:
plt.scatter([], [], c='k', alpha=0.3, s=area,
label=str(area) + ' km$^2$')
plt.legend(scatterpoints=1, frameon=False, labelspacing=1, title='Área de la ciudad')
plt.title('Ciudades de California: Área y Población')
plt.show()La leyenda siempre hará referencia a algún objeto que esté en el gráfico, por lo que si queremos mostrar una forma particular, necesitaremos trazarla. En este caso, los objetos que queremos (círculos grises) no están en el gráfico, por lo que los falsificamos trazando listas vacías. Tenga en cuenta también que la leyenda solo enumera los elementos de la trama que tienen una etiqueta especificada.
Al trazar listas vacías, creamos objetos de trazado etiquetados que son recogidos por la leyenda, y ahora nuestra leyenda nos brinda información útil. Esta estrategia puede ser útil para crear visualizaciones más sofisticadas.
8.8.3 Leyendas múltiples
A veces, al diseñar un gráfico, es posible que desees agregar varias leyendas a los mismos ejes. Desafortunadamente, Matplotlib no lo hace fácil: a través de la interfaz estándar de «leyenda», solo es posible crear una única leyenda para todo el gráfico. Si intenta crear una segunda leyenda usando plt.legend() o ax.legend(), simplemente anulará la primera. Podemos solucionar esto creando un nuevo artista de leyendas desde cero y luego usando el método de nivel inferior ax.add_artist() para agregar manualmente el segundo artista a la gráfica:
fig, ax = plt.subplots()
lines = []
styles = ['-', '--', '-.', ':']
x = np.linspace(0, 10, 1000)
for i in range(4):
lines += ax.plot(x, np.sin(x - i * np.pi / 2),
styles[i], color='black')
ax.axis('equal')
# Especifica las líneas y etiquetas de la primera leyenda.
ax.legend(lines[:2], ['line A', 'line B'],
loc='upper right', frameon=False)
# Crea la segunda leyenda y agrega el artista manualmente.
from matplotlib.legend import Legend
leg = Legend(ax, lines[2:], ['line C', 'line D'],
loc='lower right', frameon=False)
ax.add_artist(leg)
plt.show()Este es un vistazo a los objetos de artista de bajo nivel que componen cualquier gráfico de Matplotlib. Si examina el código fuente de ax.legend() (usando ax.legend??) verá que la función simplemente consiste en cierta lógica para crear un artista Legend adecuado, que luego se guarda en el atributo legend_ y se agrega a la figura cuando se dibuja la trama.
8.9 Varias subgráficas
A veces es útil comparar diferentes vistas de datos una al lado de la otra. Para este fin, Matplotlib tiene el concepto de subgráficas: grupos de ejes más pequeños que pueden existir juntos dentro de una sola figura. Estas subgráficas pueden ser inserciones, cuadrículas de gráficas u otros diseños más complicados. En esta sección exploraremos cuatro rutinas para crearlas en Matplotlib.
8.9.1 plt.axes
El método más básico para crear ejes es utilizar la función plt.axes. Como hemos visto anteriormente, de forma predeterminada esto crea un objeto de ejes estándar que llena toda la figura. plt.axes también toma un argumento opcional que es una lista de cuatro números en el sistema de coordenadas de la figura. Estos números representan [izquierda, abajo, ancho, alto] en el sistema de coordenadas de la figura, que va desde 0 en la parte inferior izquierda de la figura hasta 1 en la parte superior derecha de la figura.
Por ejemplo, podríamos crear un eje insertado en la esquina superior derecha de otro eje estableciendo la posición x e y en 0.65 (es decir, comenzando en el 65 % del ancho y el 65 % de la altura de la figura) y las extensiones x e y en 0.2 (es decir, el tamaño de los ejes es el 20 % del ancho y el 20 % de la altura de la figura):
ax1 = plt.axes() # ejes estándar
ax2 = plt.axes([0.65, 0.65, 0.2, 0.2])
plt.show()El equivalente de este comando dentro de la interfaz orientada a objetos es fig.add_axes(). Usémoslo para crear dos ejes apilados verticalmente:
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4],
xticklabels=[], ylim=(-1.2, 1.2))
ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4],
ylim=(-1.2, 1.2))
x = np.linspace(0, 10)
ax1.plot(np.sin(x))
ax2.plot(np.cos(x))
plt.show()8.9.2 plt.subplot
Las columnas o filas alineadas de subgráficas son una necesidad bastante común, por lo que Matplotlib tiene varias rutinas convenientes que facilitan su creación. El nivel más bajo de estos es plt.subplot(), que crea una única subgráfica dentro de una cuadrícula. Como puede ver, este comando toma tres argumentos enteros: el número de filas, el número de columnas y el índice del gráfico que se creará en este esquema, que va desde la esquina superior izquierda hasta la esquina inferior derecha:
for i in range(1, 7):
plt.subplot(2, 3, i)
plt.text(0.5, 0.5, str((2, 3, i)),
fontsize=18, ha='center')
plt.show()El comando plt.subplots_adjust se puede utilizar para ajustar el espaciado entre estos gráficos. El siguiente código utiliza el comando orientado a objetos equivalente, fig.add_subplot():
fig = plt.figure()
fig.subplots_adjust(hspace=0.4, wspace=0.4)
for i in range(1, 7):
ax = fig.add_subplot(2, 3, i)
ax.text(0.5, 0.5, str((2, 3, i)),
fontsize=18, ha='center')
plt.show()Hemos utilizado los argumentos hspace y wspace de plt.subplots_adjust, que especifican el espaciado a lo largo de la altura y el ancho de la figura, en unidades del tamaño de la subgráfica (en este caso, el espacio es el 40% de la altura y el ancho de la subgráfica).
8.9.3 plt.subplots
El enfoque que acabamos de describir puede resultar bastante tedioso al crear una cuadrícula grande de subgráficas, especialmente si desea ocultar las etiquetas de los ejes x e y en las gráficas internas. Para este propósito, plt.subplots() es la herramienta más fácil de usar (observe la s al final de subplots). En lugar de crear un solo subplot, esta función crea una cuadrícula completa de subplots en una sola línea y los devuelve en una matriz NumPy. Los argumentos son el número de filas y el número de columnas, junto con las palabras clave opcionales sharex y sharey, que le permiten especificar las relaciones entre diferentes ejes.
Aquí crearemos una cuadrícula de \(2 \times 3\) subgráficas, donde todos los ejes en la misma fila comparten su escala de eje y, y todos los ejes en la misma columna comparten su escala de eje x:
fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
plt.show()Tenga en cuenta que al especificar sharex y sharey, eliminamos automáticamente las etiquetas internas de la cuadrícula para que el gráfico sea más limpio. La cuadrícula de instancias de ejes resultante se devuelve dentro de una matriz NumPy, lo que permite una especificación conveniente de los ejes deseados utilizando la notación de indexación de matriz estándar:
# Los ejes están en una matriz bidimensional, indexados por [fila, columna]
for i in range(2):
for j in range(3):
ax[i, j].text(0.5, 0.5, str((i, j)),
fontsize=18, ha='center')
figEn comparación con plt.subplot(), plt.subplots() es más consistente con la indexación convencional basada en 0 de Python.
8.9.4 plt.GridSpec
Para ir más allá de una cuadrícula regular a subgráficas que abarcan múltiples filas y columnas, plt.GridSpec() es la mejor herramienta. El objeto plt.GridSpec() no crea un gráfico por sí mismo; es simplemente una interfaz conveniente que es reconocida por el comando plt.subplot(). Por ejemplo, una especificación de cuadrícula para una cuadrícula de dos filas y tres columnas con un espacio de ancho y alto especificado se ve así:
grid = plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)A partir de esto, podemos especificar las ubicaciones y extensiones de las subgráficas utilizando la sintaxis de corte habitual de Python:
plt.subplot(grid[0, 0])
plt.subplot(grid[0, 1:])
plt.subplot(grid[1, :2])
plt.subplot(grid[1, 2])
plt.show()Este tipo de alineación de cuadrícula flexible tiene una amplia gama de usos.
# Crear algunos datos distribuidos normalmente
mean = [0, 0]
cov = [[1, 1], [1, 2]]
x, y = np.random.multivariate_normal(mean, cov, 3000).T
# Configurar los ejes con gridspec
fig = plt.figure(figsize=(6, 6))
grid = plt.GridSpec(4, 4, hspace=0.3, wspace=0.3)
main_ax = fig.add_subplot(grid[:-1, 1:])
y_hist = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=main_ax)
x_hist = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=main_ax)
# puntos de dispersión en los ejes principales
main_ax.plot(x, y, 'ok', markersize=3, alpha=0.2)
# histograma en los ejes adjuntos
x_hist.hist(x, 40, histtype='stepfilled',
orientation='vertical', color='gray')
x_hist.invert_yaxis()
y_hist.hist(y, 40, histtype='stepfilled',
orientation='horizontal', color='gray')
y_hist.invert_xaxis()
plt.show()8.10 Texto y anotaciones
Crear una buena visualización implica guiar al lector para que la figura cuente una historia. En algunos casos, esta historia se puede contar de una manera totalmente visual, sin necesidad de texto añadido, pero en otros son necesarias pequeñas señales textuales y etiquetas. Quizás los tipos de anotaciones más básicos que utilizará sean las etiquetas de ejes y los títulos, pero las opciones van más allá de esto.
8.10.1 Ejemplo: Efecto de los días festivos en los nacimientos en Estados Unidos
try:
births = pd.read_csv('births.csv')
except:
!curl -O https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv
births = pd.read_csv('births.csv')
quartiles = np.percentile(births['births'], [25, 50, 75])
mu, sig = quartiles[1], 0.74 * (quartiles[2] - quartiles[0])
births = births.query('(births > @mu - 5 * @sig) & (births < @mu + 5 * @sig)')
births['day'] = births['day'].astype(int)
births.index = pd.to_datetime(10000 * births.year +
100 * births.month +
births.day, format='%Y%m%d')
births_by_date = births.pivot_table('births',
[births.index.month, births.index.day])
births_by_date.index = [pd.Timestamp(2012, month, day) for (month, day) in births_by_date.index]
births_by_datefig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax)
plt.show()Cuando comunicamos datos como estos, a menudo es útil anotar ciertas características de la gráfica para llamar la atención del lector. Esto se puede hacer manualmente con el comando plt.text/ax.text, que colocará el texto en un valor x/y particular:
fig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax)
# Añadir etiquetas al gráfico
style = dict(size=10, color='gray')
ax.text('2012-1-1', 3950, "New Year's Day", **style)
ax.text('2012-7-4', 4250, 'Independence Day', ha='center', **style)
ax.text('2012-9-4', 4850, 'Labor Day', ha='center', **style)
ax.text('2012-10-31', 4600, 'Halloween', ha='right', **style)
ax.text('2012-11-25', 4450, 'Thanksgiving', ha='center', **style)
ax.text('2012-12-25', 3850, 'Christmas ', ha='right', **style)
# Etiquetar los ejes
ax.set(title='USA births by day of year (1969-1988)',
ylabel='average daily births')
# Formatear el eje x con etiquetas de meses centradas
ax.xaxis.set_major_locator(mpl.dates.MonthLocator())
ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15))
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'))
plt.show()El método ax.text toma una posición x, una posición y, una cadena y luego palabras clave opcionales que especifican el color, el tamaño, el estilo, la alineación y otras propiedades del texto. Aquí usamos ha='right' y ha='center', donde ha es la abreviatura de alineación horizontal. Consulte la cadena de documentación de plt.text() y de mpl.text.Text() para obtener más información sobre las opciones disponibles.
8.10.2 Transformaciones y posición del texto
En el ejemplo anterior, hemos anclado nuestras anotaciones de texto a ubicaciones de datos. A veces es preferible anclar el texto a una posición en los ejes o la figura, independientemente de los datos. En Matplotlib, esto se hace modificando la transformación.
Cualquier marco de visualización de gráficos necesita algún esquema para traducir entre sistemas de coordenadas. Por ejemplo, un punto de datos en \((x, y) = (1, 1)\) necesita estar representado de alguna manera en una ubicación determinada en la figura, que a su vez necesita estar representada en píxeles en la pantalla. Matemáticamente, estas transformaciones de coordenadas son relativamente sencillas y Matplotlib tiene un conjunto de herramientas bien desarrollado que utiliza internamente para realizarlas (estas herramientas se pueden explorar en el submódulo matplotlib.transforms).
El usuario medio rara vez necesita preocuparse por los detalles de estas transformaciones, pero es un conocimiento útil para considerar la ubicación del texto en una figura. Hay tres transformaciones predefinidas que pueden resultar útiles en esta situación:
ax.transData: Transformación asociada a las coordenadas de los datosax.transAxes: Transformación asociada a los ejes (en unidades de dimensiones de los ejes)fig.transFigure: Transformación asociada a la figura (en unidades de dimensiones de la figura)
Veamos aquí un ejemplo de cómo dibujar texto en varias ubicaciones utilizando estas transformaciones:
fig, ax = plt.subplots(facecolor='lightgray')
ax.axis([0, 10, 0, 10])
# transform=ax.transData es el valor predeterminado, pero lo especificaremos de todos modos
ax.text(1, 5, '. Data: (1, 5)', transform=ax.transData) # Coordenadas absolutas
ax.text(0.5, 0.1, '. Axes: (0.5, 0.1)', transform=ax.transAxes) # Porcentajes del eje
ax.text(0.5, 0.5, '. Figure: (0.5, 0.5)', transform=fig.transFigure) # Porcentajes de la figura
plt.show()Tenga en cuenta que, de forma predeterminada, el texto se alinea arriba y a la izquierda de las coordenadas especificadas: aquí, el ‘.’ al comienzo de cada cadena marcará aproximadamente la ubicación de las coordenadas dadas.
Las coordenadas transData proporcionan las coordenadas de datos habituales asociadas con las etiquetas de los ejes x e y. Las coordenadas ‘transAxes’ dan la ubicación desde la esquina inferior izquierda de los ejes (aquí el cuadro blanco), como una fracción del tamaño de los ejes. Las coordenadas ‘transFigure’ son similares, pero especifican la posición desde la parte inferior izquierda de la figura (aquí el cuadro gris), como una fracción del tamaño de la figura.
Observe ahora que si cambiamos los límites de los ejes, solo se verán afectadas las coordenadas transData, mientras que las demás permanecerán estacionarias:
ax.set_xlim(0, 2)
ax.set_ylim(-6, 6)
figEste comportamiento se puede ver más claramente al cambiar los límites de los ejes de forma interactiva: si está ejecutando este código en un cuaderno, puede hacer que esto suceda cambiando %matplotlib inline a %matplotlib notebook y usando el menú de cada gráfico para interactuar con el gráfico.
8.10.3 Flechas y anotaciones
Dibujar flechas en Matplotlib suele ser mucho más difícil de lo que uno imagina. Si bien hay una función plt.arrow() disponible, no recomendaría usarla: las flechas que crea son objetos SVG que estarán sujetos a la relación de aspecto variable de sus gráficos, y el resultado rara vez es el que el usuario pretendía. En su lugar, sugeriría utilizar la función plt.annotate(). Esta función crea un texto y una flecha, y las flechas se pueden especificar de forma muy flexible.
Aquí usaremos annotate con varias de sus opciones:
plt.style.use('classic')
fig, ax = plt.subplots()
x = np.linspace(0, 20, 1000)
ax.plot(x, np.cos(x))
ax.axis('equal')
ax.annotate('local maximum', xy=(2 * np.pi, 1), xytext=(10, 4),
arrowprops=dict(facecolor='black', shrink=0.05))
ax.annotate('local minimum', xy=(np.pi, -1), xytext=(2*np.pi, -2),
arrowprops=dict(arrowstyle='->', facecolor='red',
connectionstyle='angle3,angleA=45,angleB=-90'))
plt.show()El estilo de la flecha se controla a través del diccionario arrowprops, que tiene numerosas opciones disponibles. Estas opciones están bastante bien documentadas en la documentación en línea de Matplotlib, por lo que en lugar de repetirlas aquí probablemente sea más útil mostrar rápidamente algunas de las posibilidades. Demostremos varias de las posibles opciones utilizando el gráfico de tasa de natalidad de antes:
fig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax)
# Añadir etiquetas al gráfico
ax.annotate("New Year's Day", xy=('2012-1-1', 4100), xycoords='data',
xytext=(50, -30), textcoords='offset points',
arrowprops=dict(arrowstyle='->',
connectionstyle='arc3,rad=-0.2'))
ax.annotate('Independence Day', xy=('2012-7-4', 4250), xycoords='data',
bbox=dict(boxstyle='round', fc='none', ec='gray'),
xytext=(10, -40), textcoords='offset points', ha='center',
arrowprops=dict(arrowstyle='->'))
ax.annotate('Labor Day', xy=('2012-9-4', 4850), xycoords='data', ha='center',
xytext=(0, -20), textcoords='offset points')
ax.annotate('', xy=('2012-9-1', 4850), xytext=('2012-9-7', 4850),
xycoords='data', textcoords='data',
arrowprops={'arrowstyle': '|-|,widthA=0.2,widthB=0.2', })
ax.annotate('Halloween', xy=('2012-10-31', 4600), xycoords='data',
xytext=(-80, -40), textcoords='offset points',
arrowprops=dict(arrowstyle='fancy',
fc='0.6', ec='none',
connectionstyle='angle3,angleA=0,angleB=-90'))
ax.annotate('Thanksgiving', xy=('2012-11-25', 4500), xycoords='data',
xytext=(-120, -60), textcoords='offset points',
bbox=dict(boxstyle='round4,pad=.5', fc='0.9'),
arrowprops=dict(arrowstyle='->',
connectionstyle='angle,angleA=0,angleB=80,rad=20'))
ax.annotate('Christmas', xy=('2012-12-25', 3850), xycoords='data',
xytext=(-30, 0), textcoords='offset points',
size=13, ha='right', va='center',
bbox=dict(boxstyle='round', alpha=0.1),
arrowprops=dict(arrowstyle='wedge,tail_width=0.5', alpha=0.1));
# Etiquetar los ejes
ax.set(title='USA births by day of year (1969-1988)',
ylabel='average daily births')
# Formatear el eje x con etiquetas de meses centradas
ax.xaxis.set_major_locator(mpl.dates.MonthLocator())
ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15))
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'));
ax.set_ylim(3600, 5400)
plt.show()Notarás que las especificaciones de las flechas y los cuadros de texto son muy detalladas: esto te da el poder de crear casi cualquier estilo de flecha que desees. Lamentablemente, esto también significa que este tipo de características a menudo deben ajustarse manualmente, un proceso que puede consumir mucho tiempo cuando se producen gráficos con calidad de publicación. Por último, quiero señalar que la combinación de estilos anterior no es de ninguna manera la mejor práctica para presentar datos, sino que se incluye como una demostración de algunas de las opciones disponibles.
8.11 Personalización de ticks
Los localizadores y formateadores de marcas predeterminados de Matplotlib están diseñados para ser generalmente suficientes en muchas situaciones comunes, pero de ninguna manera son óptimos para todos los gráficos. En esta sección se brindarán varios ejemplos de cómo ajustar las ubicaciones y el formato de las marcas para el tipo de gráfico en particular que le interesa.
8.11.1 Marcas mayores y menores
Dentro de cada eje, existe el concepto de una marca de graduación principal y una marca de graduación secundaria. Como lo implican los nombres, las marcas de graduación principales suelen ser más grandes o más pronunciadas, mientras que las marcas de graduación secundarias suelen ser más pequeñas. De forma predeterminada, Matplotlib rara vez utiliza marcas de graduación secundarias, pero un lugar donde se pueden ver es dentro de los gráficos logarítmicos:
plt.style.use('bmh')
ax = plt.axes(xscale='log', yscale='log')
ax.grid()
plt.show()Vemos aquí que cada marca principal muestra una marca grande y una etiqueta, mientras que cada marca secundaria muestra una marca más pequeña sin etiqueta.
Estas propiedades de las marcas de verificación (ubicaciones y etiquetas) se pueden personalizar configurando los objetos formateador y localizador de cada eje. Examinémoslos para el eje x del gráfico que se acaba de mostrar:
print(ax.xaxis.get_major_locator())
print(ax.xaxis.get_minor_locator())print(ax.xaxis.get_major_formatter())
print(ax.xaxis.get_minor_formatter())8.11.2 Ocultar marcas o etiquetas
Quizás la operación de formato de marca o etiqueta más común es el acto de ocultar marcas o etiquetas. Esto se puede hacer usando plt.NullLocator() y plt.NullFormatter(), como se muestra aquí:
ax = plt.axes()
ax.plot(np.random.rand(50))
plt.show()ax = plt.axes()
ax.plot(np.random.rand(50))
ax.yaxis.set_major_locator(plt.NullLocator())
ax.xaxis.set_major_formatter(plt.NullFormatter())
plt.show()Tenga en cuenta que hemos eliminado las etiquetas (pero conservamos las marcas/líneas de cuadrícula) del eje x, y eliminamos las marcas (y, por lo tanto, también las etiquetas) del eje y. No tener ninguna marca de verificación puede ser útil en muchas situaciones; por ejemplo, cuando desea mostrar una cuadrícula de imágenes. Por ejemplo, considere la siguiente figura, que incluye imágenes de diferentes caras, un ejemplo que se utiliza a menudo en problemas de aprendizaje automático supervisado.
fig, ax = plt.subplots(5, 5, figsize=(5, 5))
fig.subplots_adjust(hspace=0, wspace=0)
# Obtenga algunos datos faciales de scikit-learn
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces().images
for i in range(5):
for j in range(5):
ax[i, j].imshow(faces[10 * i + j], cmap='bone')
plt.show()fig, ax = plt.subplots(5, 5, figsize=(5, 5))
fig.subplots_adjust(hspace=0, wspace=0)
# Obtenga algunos datos faciales de scikit-learn
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces().images
for i in range(5):
for j in range(5):
ax[i, j].xaxis.set_major_locator(plt.NullLocator())
ax[i, j].yaxis.set_major_locator(plt.NullLocator())
ax[i, j].imshow(faces[10 * i + j], cmap='bone')
plt.show()Tenga en cuenta que cada imagen tiene sus propios ejes y hemos establecido los localizadores en nulo porque los valores de las marcas (número de píxel en este caso) no transmiten información relevante para esta visualización en particular.
8.11.3 Reducir o aumentar el número de ticks
Un problema común con las configuraciones predeterminadas es que las subgráficas más pequeñas pueden terminar con etiquetas abarrotadas. Podemos ver esto en la cuadrícula de la gráficaa que se muestra aquí:
fig, ax = plt.subplots(4, 4, sharex=True, sharey=True)
plt.show()En particular, en el caso de las marcas x, los números casi se superponen y hacen que sea bastante difícil descifrarlas. Podemos solucionar esto con plt.MaxNLocator(), que nos permite especificar el número máximo de ticks que se mostrarán. Dado este número máximo, Matplotlib utilizará lógica interna para elegir las ubicaciones de las marcas particulares:
# Para cada eje, establezca el localizador principal x e y
for axi in ax.flat:
axi.xaxis.set_major_locator(plt.MaxNLocator(3))
axi.yaxis.set_major_locator(plt.MaxNLocator(3))
figEsto hace que todo sea mucho más claro.
8.11.4 Formatos de ticks elegantes
El formato de marcas predeterminado de Matplotlib puede dejar mucho que desear: funciona bien como un valor predeterminado amplio, pero a veces desearía hacer algo más. Considere esta gráfica de un seno y un coseno:
# Graficar una curva de seno y coseno
fig, ax = plt.subplots()
x = np.linspace(0, 3 * np.pi, 1000)
ax.plot(x, np.sin(x), lw=3, label='Sine')
ax.plot(x, np.cos(x), lw=3, label='Cosine')
# Configurar cuadrícula, leyenda y límites
ax.grid(True)
ax.legend(frameon=False)
ax.axis('equal')
ax.set_xlim(0, 3 * np.pi)
plt.show()Hay un par de cambios que nos gustaría hacer. Primero, es más natural que estos datos espacien las marcas y las líneas de la cuadrícula en múltiplos de \(\pi\). Podemos hacer esto estableciendo un MultipleLocator, que ubica las marcas en un múltiplo del número que proporciones. Por si acaso, agregaremos marcas mayores y menores en múltiplos de \(\pi/4\):
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 4))
figPero ahora estas etiquetas de verificación parecen un poco tontas: podemos ver que son múltiplos de \(\pi\), pero la representación decimal no lo transmite inmediatamente. Para solucionar esto, podemos cambiar el formateador de ticks. No hay un formateador incorporado para lo que queremos hacer, por lo que en su lugar usaremos plt.FuncFormatter, que acepta una función definida por el usuario que brinda un control detallado sobre las salidas de ticks:
def format_func(value, tick_number):
# Encuentra el número de múltiplos de pi/2
N = int(np.round(2 * value / np.pi))
if N == 0:
return '0'
elif N == 1:
return r'$\pi/2$'
elif N == 2:
return r'$\pi$'
elif N % 2 > 0:
return r'${0}\pi/2$'.format(N)
else:
return r'${0}\pi$'.format(N // 2)
ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
fig¡Esto es mucho mejor! Observe que hemos hecho uso del soporte LaTeX de Matplotlib, especificado al encerrar la cadena entre signos de dólar. Esto es muy conveniente para la visualización de símbolos y fórmulas matemáticas: en este caso, '$\pi$' se representa como el carácter griego \(\pi\).
plt.FuncFormatter() ofrece un control extremadamente detallado sobre la apariencia de las marcas de su gráfico y resulta muy útil al preparar gráficos para su presentación o publicación.
8.12 Personalización de Matplotlib
8.12.1 Personalización de la gráfica a mano
Empecemos por una gráfica de un histograma, con las configuraciones por defecto de Matplotlib.
x = np.random.randn(1000)
plt.hist(x)
plt.show()Podemos ajustar esto a mano para que sea una gráfica mucho más agradable a la vista:
# utiliza un fondo gris
ax = plt.axes(facecolor='#F0F0F0')
ax.set_axisbelow(True)
# Dibujar líneas de cuadrícula blancas sólidas
plt.grid(color='w', linestyle='solid')
# ocultar las espinas del eje
for spine in ax.spines.values():
spine.set_visible(False)
# Ocultar los ticks superiores y derechos
ax.xaxis.tick_bottom()
ax.yaxis.tick_left()
# Aclarar las marcas y las etiquetas
ax.tick_params(colors='gray', direction='out')
for tick in ax.get_xticklabels():
tick.set_color('gray')
for tick in ax.get_yticklabels():
tick.set_color('gray')
# Controlar el color de la cara y del borde del histograma
ax.hist(x, edgecolor='#E15759', color='#FF9D9A');
plt.show()8.12.2 Cambiar los valores predeterminados: rcParams
Cada vez que se carga Matplotlib, define una configuración de tiempo de ejecución (rc) que contiene los estilos predeterminados para cada elemento del gráfico que cree. Esta configuración se puede ajustar en cualquier momento utilizando la rutina de conveniencia plt.rc. Veamos cómo se ve modificar los parámetros rc para que nuestro gráfico predeterminado se vea similar a lo que hicimos antes.
Comenzaremos guardando una copia del diccionario rcParams actual, para que podamos restablecer fácilmente estos cambios en la sesión actual:
IPython_default = plt.rcParams.copy()Ahora podemos usar la función plt.rc para cambiar algunas de estas configuraciones:
from matplotlib import cycler
colors = cycler('color',
['#FF9D9A', '#A0CBE8', '#8CD17D',
'#FFBE7D', '#D4A6C8', '#9C755F'])
plt.rc('axes', facecolor='#F0F0F0', edgecolor='none',
axisbelow=True, grid=True, prop_cycle=colors)
plt.rc('grid', color='w', linestyle='solid')
plt.rc('xtick', direction='out', color='gray')
plt.rc('ytick', direction='out', color='gray')
plt.rc('patch', edgecolor='#E15759', facecolor='#FF9D9A')
plt.rc('lines', linewidth=3)Con estos ajustes definidos, ahora podemos crear un gráfico y ver nuestros ajustes en acción:
plt.hist(x)
plt.show()Veamos cómo se ven los gráficos de líneas simples con estos parámetros rc:
for i in range(4):
plt.plot(np.random.rand(10))
plt.show()8.12.3 Hojas de estilo
Matplotlib cuenta con un módulo style muy práctico, que incluye varias hojas de estilo predeterminadas nuevas, así como la capacidad de crear y empaquetar sus propios estilos. Estas hojas de estilo tienen un formato similar al de los archivos .matplotlibrc mencionados anteriormente, pero deben tener una extensión .mplstyle.
Incluso si no creas tu propio estilo, las hojas de estilo incluidas por defecto son extremadamente útiles. Los estilos disponibles se enumeran en plt.style.available; aquí enumeraré solo los primeros cinco para abreviar:
plt.style.available[:5]La forma básica de cambiar a una hoja de estilo es llamar
plt.style.use('stylename')¡Pero ten en cuenta que esto cambiará el estilo para el resto de la sesión! Alternativamente, puede utilizar el administrador de contexto de estilo, que establece un estilo temporalmente:
with plt.style.context('stylename'):
make_a_plot()Vamos a crear una función que creará dos tipos básicos de gráficos:
def hist_and_lines():
np.random.seed(0)
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].hist(np.random.randn(1000))
for i in range(3):
ax[1].plot(np.random.rand(10))
ax[1].legend(['a', 'b', 'c'], loc='lower left')Usaremos esto para explorar cómo se ven estos gráficos usando los distintos estilos incorporados.
Estilo predeterminado
El estilo predeterminado es el que hemos estado viendo hasta ahora a lo largo del libro; comenzaremos con eso. Primero, restablezcamos nuestra configuración de tiempo de ejecución a la configuración predeterminada del notebook:
# restablecer rcParams
plt.rcParams.update(IPython_default);Ahora veamos cómo se ve:
hist_and_lines()
plt.show()Estilo FiveThiryEight
El estilo ‘fivethirtyeight’ imita los gráficos que se encuentran en el popular FiveThirtyEight website. Como se puede ver aquí, se caracteriza por colores llamativos, líneas gruesas y ejes transparentes:
with plt.style.context('fivethirtyeight'):
hist_and_lines()
plt.show()Estilo ggplots
El paquete ggplot en el lenguaje R es una herramienta de visualización muy popular. El estilo ggplot de Matplotlib imita los estilos predeterminados de ese paquete:
with plt.style.context('ggplot'):
hist_and_lines()
plt.show()Estilo Métodos bayesianos para hackers
Hay un libro en línea muy lindo y breve llamado Probabilistic Programming and Bayesian Methods for Hackers; presenta figuras creadas con Matplotlib y utiliza un lindo conjunto de parámetros rc para crear un estilo consistente y visualmente atractivo en todo el libro. Este estilo se reproduce en la hoja de estilos bmh:
with plt.style.context('bmh'):
hist_and_lines()
plt.show()Fondo oscuro
Para las figuras utilizadas en presentaciones, a menudo es útil tener un fondo oscuro en lugar de claro. El estilo dark_background proporciona esto:
with plt.style.context('dark_background'):
hist_and_lines()
plt.show()Escala de grises
A veces es posible que te encuentres preparando figuras para una publicación impresa que no acepta figuras en color. Para ello, el estilo «escala de grises», que se muestra aquí, puede ser muy útil:
with plt.style.context('grayscale'):
hist_and_lines()
plt.show()Con todas estas opciones integradas para varios estilos de gráficos, Matplotlib se vuelve mucho más útil tanto para la visualización interactiva como para la creación de figuras para publicación. A lo largo de este libro, generalmente utilizaré una o más de estas convenciones de estilo al crear gráficos.
8.13 Visualización con Seaborn
Matplotlib ha demostrado ser una herramienta de visualización increíblemente útil y popular, pero incluso los usuarios más ávidos admitirán que a menudo deja mucho que desear. Una respuesta a estos problemas es Seaborn. Seaborn proporciona una API sobre Matplotlib que ofrece opciones sensatas para el estilo de gráfico y los valores predeterminados de color, define funciones simples de alto nivel para tipos de gráficos estadísticos comunes y se integra con la funcionalidad proporcionada por los ‘DataFrame’ de Pandas.
8.13.1 Seaborn contra Matplotlib
A continuación se muestra un ejemplo de un gráfico de caminata aleatoria simple en Matplotlib, utilizando su formato de gráfico y colores clásicos.
plt.style.use('classic')Ahora creamos algunos datos de caminata aleatoria:
# Crear algunos datos
rng = np.random.RandomState(0)
x = np.linspace(0, 10, 500)
y = np.cumsum(rng.randn(500, 6), 0)Y haz un gráfico sencillo:
# Grafique los datos con los valores predeterminados de Matplotlib
plt.plot(x, y)
plt.legend('ABCDEF', ncol=2, loc='upper left')
plt.show()Aunque el resultado contiene toda la información que nos gustaría transmitir, lo hace de una manera que no es del todo agradable estéticamente, e incluso parece un poco anticuado en el contexto de la visualización de datos del siglo XXI.
Ahora veamos cómo funciona con Seaborn. Como veremos, Seaborn tiene muchas de sus propias rutinas de gráficos de alto nivel, pero también puede sobrescribir los parámetros predeterminados de Matplotlib y, a su vez, lograr que incluso los scripts simples de Matplotlib produzcan resultados muy superiores. Podemos establecer el estilo llamando al método set() de Seaborn. Por convención, Seaborn se importa como sns:
import seaborn as sns
sns.set()Ahora volvamos a ejecutar las mismas líneas que antes:
# ¡El mismo código de graficación que arriba!
plt.plot(x, y)
plt.legend('ABCDEF', ncol=2, loc='upper left')
plt.show()8.13.2 Explorando las gráficas de Seaborn
La idea principal de Seaborn es que proporciona comandos de alto nivel para crear una variedad de tipos de gráficos útiles para la exploración de datos estadísticos e incluso algunos ajustes de modelos estadísticos.
Echemos un vistazo a algunos de los conjuntos de datos y tipos de gráficos disponibles en Seaborn. Tenga en cuenta que todo lo siguiente podría realizarse utilizando comandos Matplotlib sin formato (esto es, de hecho, lo que hace Seaborn en segundo plano), pero la API de Seaborn es mucho más conveniente.
Histogramas, KDE y densidades
A menudo, en la visualización de datos estadísticos, lo único que se desea es trazar histogramas y distribuciones conjuntas de variables. Hemos visto que esto es relativamente sencillo en Matplotlib:
data = np.random.multivariate_normal([0, 0], [[5, 2], [2, 2]], size=2000)
data = pd.DataFrame(data, columns=['x', 'y'])
for col in 'xy':
plt.hist(data[col], density=True, alpha=0.5)
plt.show()En lugar de un histograma, podemos obtener una estimación suave de la distribución utilizando una estimación de densidad de kernel, que Seaborn hace con sns.kdeplot:
for col in 'xy':
sns.kdeplot(data[col], fill=True)
plt.show()Los histogramas y KDE se pueden combinar usando displot:
sns.displot(data['x'], kde=True)
sns.displot(data['y'], kde=True)
plt.show()Si pasamos el conjunto de datos bidimensionales completo a kdeplot, obtendremos una visualización bidimensional de los datos:
sns.kdeplot(data,x='x',y='y')
plt.show()Podemos ver la distribución conjunta y las distribuciones marginales juntas usando sns.jointplot. Para este gráfico, estableceremos el estilo en un fondo blanco:
with sns.axes_style('white'):
sns.jointplot(data, x='x', y='y', kind='kde')
plt.show()Hay otros parámetros que se pueden pasar a jointplot; por ejemplo, podemos utilizar un histograma de base hexagonal en su lugar:
with sns.axes_style('white'):
sns.jointplot(data, x='x', y='y', kind='hex')
plt.show()Gráficas de pares
Cuando se generalizan gráficos conjuntos a conjuntos de datos de dimensiones mayores, se obtienen gráficos de pares. Esto resulta muy útil para explorar correlaciones entre datos multidimensionales, cuando se desea representar gráficamente todos los pares de valores entre sí.
Demostraremos esto con el conocido conjunto de datos Iris, que enumera las medidas de pétalos y sépalos de tres especies de iris:
iris = sns.load_dataset('iris')
iris.head()Visualizar las relaciones multidimensionales entre las muestras es tan fácil como llamar a sns.pairplot:
sns.pairplot(iris, hue='species', height=2.5)
plt.show()Histogramas facetados
A veces, la mejor forma de visualizar los datos es mediante histogramas de subconjuntos. ‘FacetGrid’ de Seaborn hace que esto sea extremadamente sencillo. Echemos un vistazo a algunos datos que muestran la cantidad que el personal del restaurante recibe en propinas en función de varios datos indicadores:
tips = sns.load_dataset('tips')
tips.head()tips['tip_pct'] = 100 * tips['tip'] / tips['total_bill']
grid = sns.FacetGrid(tips, row='sex', col='time', margin_titles=True)
grid.map(plt.hist, 'tip_pct', bins=np.linspace(0, 40, 15))
plt.show()Distribuciones conjuntas
De manera similar al diagrama de pares que vimos anteriormente, podemos usar sns.jointplot para mostrar la distribución conjunta entre diferentes conjuntos de datos, junto con las distribuciones marginales asociadas:
with sns.axes_style('white'):
sns.jointplot(tips, x='total_bill', y='tip', kind='hex')
plt.show()El gráfico conjunto puede incluso realizar algunas estimaciones y regresiones automáticas de la densidad del kernel:
sns.jointplot(tips, x='total_bill', y='tip', kind='reg')
plt.show()Gráficos de barras
Las series temporales se pueden representar gráficamente utilizando sns.factorplot.
planets = sns.load_dataset('planets')
planets.head()with sns.axes_style('white'):
g = sns.catplot(planets, x ='year', aspect=2,
kind='count', color='steelblue')
g.set_xticklabels(step=5)
plt.show()Podemos aprender más observando el método de descubrimiento de cada uno de estos planetas:
with sns.axes_style('white'):
g = sns.catplot(planets, x='year', aspect=4.0, kind='count',
hue='method', order=range(2001, 2015))
g.set_ylabels('Number of Planets Discovered')
plt.show()8.14 Ejercicios prácticos
- Cree un nuevo Notebook.
- Guarde el archivo como Ejercicios_practicos_clase_8.ipynb.
- Asigne un título H1 con su nombre.
8.14.1 Ejercicio práctico 1
El archivo titanic.csv contiene información sobre los pasajeros del Titanic. Crear un dataframe con Pandas y a partir de él generar los siguientes diagramas.
- Diagrama de sectores con los fallecidos y supervivientes.
- Histograma con las edades.
- Diagrama de barras con el número de personas en cada clase.
- Diagrama de barras con el número de personas fallecidas y supervivientes en cada clase, discriminados por sexo.
8.14.2 Ejercicio práctico 2
El archivo bancos.csv contiene las cotizaciones de los principales bancos de España, en el mes de abril de 2021. Contiene las columnas: Empresa (nombre de la empresa), Apertura (precio de la acción a la apertura de bolsa), Máximo (precio máximo de la acción durante la jornada), Mínimo (precio mínimo de la acción durante la jornada), Cierre (precio de la acción al cierre de bolsa), Volumen (volumen al cierre de bolsa). Construir una función reciba el archivo bancos.csv y cree un diagrama de líneas con las series temporales de las cotizaciones de cierre de cada banco, usando el mismo eje y colores diferentes por cada banco.