Matplotlib¶
(1)
matplotlib
es el módulo Python más utilizado en el ámbito de ciencia de datos para representaciones gráficas.
Instalación¶
Modo de uso¶
La forma más habitual de importar esta librería es utilizar el alias plt
:
Si bien podemos utilizar matplotlib en el intérprete habitual de Python, suele ser muy frecuente trabajar con esta librería mediante entornos Jupyter, ya que facilitan la visualización de los gráficos en su interfaz de usuario.
Figura¶
La figura es el elemento base sobre el que se construyen todos los gráficos en matplotlib. Veamos cómo crearla:
>>> fig = plt.figure()
>>> type(fig)
matplotlib.figure.Figure
>>> fig
<Figure size 640x480 with 0 Axes>
Podemos observar que la resolución (por defecto) de la figura es de 640x480 píxeles y que no dispone de ningún eje («0 Axes»).
Ejes
El término «axes» hace referencia a un conjunto de ejes. Puede resultar confuso en español y he decidido asignar el nombre marco cuando haga referencia a «axes».
La resolución final de una figura viene determinada por su altura (height) y anchura (width) especificadas en pulgadas1 que, a su vez, se multiplican por los puntos por pulgada o DPI. Veamos el funcionamiento:
>>> fig
<Figure size 640x480 with 0 Axes>
>>> fig.get_figwidth()#(1)!
6.4
>>> fig.get_figheight()#(2)!
4.8
>>> fig.get_dpi()#(3)!
100.0
>>> fig.get_figwidth() * fig.dpi, fig.get_figheight() * fig.dpi
(640.0, 480.0)
- Resultado en pulgadas.
- Resultado en pulgadas.
- Puntos por pulgada (DPI).
Entorno Jupyter
Si utilizamos entornos de desarollo basados en Jupyter, los valores por defecto son distintos:
- Ancho de figura: 6 in
- Alto de figura: 4 in
- DPI: 75
- Resolución: 450x300 px
Por tanto, cuando creamos una figura podemos modificar los parámetros por defecto para obtener la resolución deseada:
>>> fig = plt.figure(figsize=(19.2, 10.8))#(1)!
>>> fig
<Figure size 1920x1080 with 0 Axes>
>>> fig = plt.figure(figsize=(19.2, 10.8), dpi=300)
>>> fig
<Figure size 5760x3240 with 0 Axes>
- 100 DPI
Si nos interesa que cualquier figura tome unos valores concretos de resolución, podemos modificar los valores por defecto del entorno. Para ello, matplotlib hace uso de un diccionario plt.rcParams que contiene los parámetros globales de configuración. Veamos cómo modificarlo:
>>> plt.rcParams['figure.figsize']
[6.4, 4.8]
>>> plt.rcParams['figure.dpi']
100.0
>>> plt.rcParams['figure.figsize'] = (10, 5)
>>> plt.rcParams['figure.dpi'] = 300#(1)!
>>> fig.get_figwidth()
10.0
>>> fig.get_figheight()
5.0
>>> fig.dpi
300.0
- Resolución final = \((300*10, 300*5) = (3000, 1500)\ px\)
Marco¶
Para poder empezar a graficar necesitamos tener, al menos, un marco. Utilizaremos la función add_subplot()
que requiere pasar como parámetros el número de filas, el número de columnas y el marco activo:
Para comenzar vamos a trabajar únicamente con un marco:
>>> fig = plt.figure()
>>> ax = fig.add_subplot(1, 1, 1)#(1)!
>>> ax#(2)!
<AxesSubplot:>
>>> fig
<Figure size 640x480 with 1 Axes>
- Equivalente a
fig.add_subplot(111)
. - Suele ser habitual encontrar
ax
como nombre de variable del «axes» devuelto por la funciónadd_subplot()
.
Escala
La escala por defecto de cada eje va de 0 a 1 con marcas cada 0.2
Ahora vamos a generar 4 marcos sobre los que fijaremos un título identificativo:
>>> fig = plt.figure()
>>> for i in range(1, 5):
... ax = fig.add_subplot(2, 2, i)
... ax.set_title(f'Subplot {i}')
>>> fig.tight_layout(pad=1)#(1)!
>>> fig
<Figure size 640x480 with 4 Axes>
- Sólo para que no se solapen los títulos.
Atajo para subgráficos¶
Matplotlib nos ofrece una forma compacta de crear a la vez tanto la figura como los marcos que necesitemos.
Para ello utilizaremos la función plt.subplots()
que recibe como parámetros el número de filas y el número de columnas para la disposición de los marcos, y devuelve una tupla con la figura y los marcos.
En el siguiente ejemplo creamos una figura con un único marco:
>>> fig, ax = plt.subplots(1, 1)#(1)!
>>> fig
<Figure size 640x480 with 1 Axes>
>>> ax
<AxesSubplot:>
- Si invocamos la función
plt.subplots()
sin parámetros, creará (por defecto) un único marco.
En el siguiente ejemplo creamos una figura con 6 marcos en disposición de 2 filas por 3 columnas:
>>> fig, ax = plt.subplots(2, 3)
>>> fig
<Figure size 640x480 with 6 Axes>
>>> ax
array([[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>],
[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>]], dtype=object)
>>> ax.shape
(2, 3)
Combinación
Se podría ver la función subplots() como una combinación de figure()
+ add_subplot()
.
Etiquetas¶
Dentro de un marco también es posible fijar las etiquetas de los ejes (X e Y). Veamos cómo hacerlo:
>>> fig, ax = plt.subplots()
>>> ax.set_title('Gráfico en blanco')
Text(0.5, 1.0, 'Gráfico en blanco')
>>> ax.set_xlabel('Etiqueta para el eje X')
Text(0.5, 0, 'Etiqueta para el eje X')
>>> ax.set_ylabel('Etiqueta para el eje Y')
Text(0, 0.5, 'Etiqueta para el eje Y')
>>> fig
<Figure size 640x480 with 1 Axes>
Ejes¶
Un marco (2D) está compuesto por dos ejes: eje X e eje Y. Podemos acceder a cada eje mediante sendos atributos:
>>> ax.xaxis
<matplotlib.axis.XAxis at 0x112b34100>
>>> ax.yaxis
<matplotlib.axis.YAxis at 0x112b34850>
Rejilla¶
En cada eje podemos activar o desactivar la rejilla, así como indicar su estilo.
En primer lugar vamos a activar la rejilla en ambos ejes:
Esto sería equivalente a ax.grid(True)
.
Y obtendríamos una figura con la rejilla (por defecto):
Interruptores
Las funciones de matplotlib que actúan como «interruptores» tienen por defecto el valor verdadero. En este sentido ax.grid()
invocada sin parámetros hace que se muestre la rejilla. Esto se puede aplicar a muchas otras funciones.
Supongamos ahora que queremos personalizar la rejilla2 con estilos diferentes en cada eje:
- Equivale a
color='red', linestyle='solid
- Equivale a
color='blue', linestyle='solid
Marcas¶
Por defecto, los ejes del marco tienen unas marcas3 equiespaciadas que constituyen las marcas mayores. Igualmente existen unas marcas menores que, a priori, no están activadas.
Ambos elementos son susceptibles de modificarse. Veamos un ejemplo en el que establecemos las marcas menores con distinto espaciado en cada eje y además le damos un estilo diferente a cada rejilla:
>>> from matplotlib.ticker import MultipleLocator
>>> ax.xaxis.set_minor_locator(MultipleLocator(0.1))#(1)!
>>> ax.yaxis.set_minor_locator(MultipleLocator(0.05))#(2)!
>>> ax.xaxis.grid(which='minor', linestyle='dashed', color='gray')
>>> ax.yaxis.grid(which='minor', linestyle='dashed', color='lightskyblue')
- Eje X: Separación cada 0.1 unidades.
- Eje Y: Separación cada 0.05 unidades.
También es posible asignar etiquetas a las marcas menores. En ese sentido, veremos un ejemplo en el que incorporamos los valores a los ejes con estilos propios:
- Marcas menores en el eje X: precisión de 1 decimal, tamaño de letra 8 y color gris.
- Marcas menores en el eje Y: precisión de 2 decimales, tamaño de letra 8 y color azul.
>>> # Eje X
>>> ax.xaxis.set_minor_formatter('{x:.1f}')
>>> ax.tick_params(axis='x', which='minor', labelsize=8, labelcolor='gray')
>>> # Eje Y
>>> ax.yaxis.set_minor_formatter('{x:.2f}')
>>> ax.tick_params(axis='y', which='minor', labelsize=8, labelcolor='lightskyblue')
Primeros pasos¶
Vamos a empezar por representar la función \(f(x) = sin(x)\) . Para ello crearemos una variable con valores flotantes equidistantes y una variable aplicando la función senoidal. Nos apoyamos en NumPy para ello.
A continuación usaremos la función plot()
del marco para representar la función creada:
>>> x = np.linspace(0, 2 * np.pi)
>>> y = np.sin(x)
>>> fig, ax = plt.subplots()
>>> ax.plot(x, y)
[<matplotlib.lines.Line2D at 0x120914040>]
Múltiples funciones¶
Partiendo de un mismo marco, es posible graficar todas las funciones que necesitemos. A continuación crearemos un marco con las funciones seno y coseno:
>>> x = np.linspace(0, 2 * np.pi)
>>> sin = np.sin(x)
>>> cos = np.cos(x)
>>> fig, ax = plt.subplots()
>>> ax.plot(x, sin)
[<matplotlib.lines.Line2D at 0x1247b6310>]
>>> ax.plot(x, cos)
[<matplotlib.lines.Line2D at 0x112b0d4c0>]
Colores
Los colores «auto» asignados a las funciones siguen un ciclo establecido por matplotlib que es igualmente personalizable.
Leyenda¶
En el caso de que tengamos múltiples gráficos en el mismo marco puede ser deseable mostrar una leyenda identificativa. Para usarla necesitamos asignar etiquetas a cada función. Veamos a continuación cómo incorporar una leyenda:
>>> ax.plot(x, sin, label='sin')
[<matplotlib.lines.Line2D at 0x124e07ac0>]
>>> ax.plot(x, cos, label='cos')
[<matplotlib.lines.Line2D at 0x123c58f10>]
>>> ax.legend()
<matplotlib.legend.Legend at 0x123c8f190>
Es posible incorporar sintaxis TeX en los distintos elementos textuales de matplotlib. En el siguiente ejemplo usaremos esta notación en las etiquetas de las funciones utilizando las marcas $ ... $
para ello:
>>> ax.plot(x, sin, label='$f_1(x) = sin(x)$')
[<matplotlib.lines.Line2D at 0x11682f3a0>]
>>> ax.plot(x, cos, label='$f_2(x) = cos(x)$')
[<matplotlib.lines.Line2D at 0x11682b3a0>]
Ubicación de la leyenda¶
Matplotlib intenta encontrar la mejor ubicación para la leyenda en el marco. Sin embargo, también es posible personalizar el lugar en el que queremos colocarla.
Si nos interesa situar la leyenda en la parte superior central del marco haríamos lo siguiente:
Aplicando estilos¶
Para cada función que incluimos en el marco es posible establecer un estilo personalizado con multitud de parámetros. Veamos la aplicación de algunos de estos parámetros a las funciones seno y coseno con las que hemos estado trabajando:
>>> sin_style = dict(linewidth=3, color='darkorange')
>>> cos_style = dict(marker='o', markerfacecolor='limegreen', color='darkgreen')
>>> ax.plot(x, sin, label='$f_1(x) = sin(x)$', **sin_style)
[<matplotlib.lines.Line2D at 0x1131e9fd0>]
>>> ax.plot(x, cos, label='$f_2(x) = cos(x)$', **cos_style)
[<matplotlib.lines.Line2D at 0x1226d76d0>]
Acotando ejes¶
Hay veces que nos interesa definir los límites de los ejes. En ese caso, podemos hacerlo de una manera muy sencilla:
- Sólo a efectos estéticos.
Límite completado
También es posible especificar únicamente límite inferior o superior en ambas funciones set_xlim()
y set_ylim()
. En ese caso, el otro valor sería ajustado automáticamente por matplotlib.
Anotaciones¶
En ocasiones necesitamos añadir ciertas anotaciones al gráfico que estamos diseñando. Esto permite destacar áreas o detalles que pueden ser relevantes.
Partiendo de las funciones seno y coseno con las que hemos estado trabajando, vamos a suponer que queremos obtener sus puntos de corte, es decir, resolver la siguiente ecuación:
Para el caso que nos ocupa haríamos \(n=0\) con lo que obtendríamos la siguiente solución:
>>> xsol = np.pi / 4 + np.pi * 0
>>> ysol = np.sin(xsol)
>>> xsol, ysol
(0.7853981633974483, 0.7071067811865475)
Vamos a insertar una serie de anotaciones en el gráfico:
- Flecha en el punto de corte con etiqueta de ecuación.
- Coordenadas de solución en el punto de corte.
- Proyección del punto de corte hacia ambos ejes.
>>> ax.annotate('$sin(x) = cos(x)$',
... xy=(xsol, ysol),
... xytext=(1.2, 0.8),
... arrowprops=dict(facecolor='black', shrink=0.05))
>>> ax.text(0.47, 0.72, f'({xsol:.2f}, {ysol:.2f})')
>>> ax.plot([xsol, xsol], [0, ysol], color='gray', linestyle='--')
>>> ax.plot([0, xsol], [ysol, ysol], color='gray', linestyle='--')
Ejercicio
Escribe el código Python necesario para recrear el siguiente gráfico:

Datos:
- \(x \in [0, 2\pi]\) (1000 puntos)
- \(y = e^{-\alpha x} sin(\beta x)\), donde \(\alpha = 0.7\) y \(\beta = 10\).
Tipos de gráficos¶
Mediante matplotlib podemos hacer prácticamente cualquier tipo de gráfico. En esta sección haremos un repaso por algunos de ellos.
Gráficos de barras¶
Características del «dataset» con el que vamos a trabajar:
- Resultados de los JJOO de Tokio 2020.
- Fichero:
medals.xlsx
- Fuente: Kaggle
En primer lugar cargaremos el fichero en un DataFrame y haremos una pequeña «limpieza»:
>>> df = pd.read_excel('medals.xlsx')#(1)!
>>> df.head()
Rank Team/NOC Gold Silver Bronze Total Rank by Total
0 1 United States of America 39 41 33 113 1
1 2 People's Republic of China 38 32 18 88 2
2 3 Japan 27 14 17 58 5
3 4 Great Britain 22 21 22 65 4
4 5 ROC 20 28 23 71 3
>>> df.rename(columns={'Team/NOC': 'Country'}, inplace=True)
>>> df.set_index('Country', inplace=True)
>>> df.head()
Rank Gold Silver Bronze Total Rank by Total
Country
United States of America 1 39 41 33 113 1
People's Republic of China 2 38 32 18 88 2
Japan 3 27 14 17 58 5
Great Britain 4 22 21 22 65 4
ROC 5 20 28 23 71 3
- Para la carga de ficheros Excel, es necesario instalar un paquete adicional denominado OpenPyXL.
Ahora ya podemos centrarnos en el diseño del gráfico de barras:
>>> fig, ax = plt.subplots(figsize=(8, 5), dpi=100)#(1)!
>>> bar_width = 0.30
>>> x = np.arange(df_best.index.size)
>>> golden_medals = ax.bar(x - bar_width, df_best['Gold'],
... bar_width, label='Oro', color='#ffd700')
>>> silver_medals = ax.bar(x, df_best['Silver'],
... bar_width, label='Plata', color='#aaa9ad')
>>> bronze_medals = ax.bar(x + bar_width, df_best['Bronze'],
... bar_width, label='Bronce', color='#cd7f32')
>>> ax.set_xticks(x)
>>> ax.set_xticklabels(df_best.index, rotation=90)
>>> ax.legend()
>>> # Etiquetas en barras
>>> ax.bar_label(golden_medals, padding=3)
>>> ax.bar_label(silver_medals, padding=3)
>>> ax.bar_label(bronze_medals, padding=3)
>>> ax.spines['right'].set_visible(False)#(2)!
>>> ax.spines['top'].set_visible(False)#(3)!
>>> fig.tight_layout()#(4)!
>>> fig
- 800x500 px
- Ocultar el borde derecho.
- Ocultar el border superior.
- Ajustar elementos al tamaño de la figura.
Ejercicio
Características del «dataset» con el que vamos a trabajar:
- Valoraciones de los lenguajes de programación más utilizados durante 2020.
- Fichero:
tiobe-2020-clean.csv
- Fuente: TIOBE
Recrea el siguiente gráfico:

Gráficos de dispersión¶
Características del «dataset» con el que vamos a trabajar:
- Estadísticas de jugadores de la NBA4 desde 1996 hasta 2019.
- Fichero:
nba-data.csv
- Fuente: Kaggle
En primer lugar cargamos los datos y nos quedamos con un subconjunto de las columnas:
>>> df = pd.read_csv('nba-data.csv', usecols=['pts', 'reb', 'ast'])
>>> df.head()
pts reb ast
0 4.8 4.5 0.5
1 0.3 0.8 0.0
2 4.5 1.6 0.9
3 7.8 4.4 1.4
4 3.7 1.6 0.5
>>> df.shape
(11700, 3)
El objetivo es crear un gráfico de dispersión en el relacionaremos los puntos anotados con los rebotes capturados, así como las asistencias dadas:
>>> fig, ax = plt.subplots(figsize=(8, 6), dpi=100)#(1)!
>>> x = df['pts']#(2)!
>>> y = df['reb']#(3)!
>>> colors = df['ast']
>>> p = ax.scatter(x, y,
... s=30,#(4)!
... c=colors, cmap='RdBu_r',#(5)!
... vmin=colors.min(), vmax=colors.max(),#(6)!
... alpha=0.7,
... edgecolors='none')
>>> cb = fig.colorbar(p, ax=ax, label='Asistencias', extend='max')#(7)!
>>> cb.outline.set_visible(False)
>>> ax.set_xlabel('Puntos')
>>> ax.set_ylabel('Rebotes')
>>> ax.spines['right'].set_visible(False)
>>> ax.spines['top'].set_visible(False)
>>> fig.tight_layout()
- 800x600 px
- Variable auxiliar \(x\).
- Variable auxiliar \(y\).
- Tamaño de puntos 30.
- Colores azul y rojo.
- Normalización de colores con
vmin
yvmax
. - Barra de colores.
Del gráfico anterior cabe destacar un par de aspectos:
- Normalización: Cuando aplicamos una estética de color al gráfico basada en los datos de una variable, debemos normalizar dicha variable en el mapa de color («colormap») que elijamos. Para ello, matplotlib nos ofrece la normalización de mapas de color. En el caso concreto de
scatter()
pasaríamos esta normalización mediante el parámetronorm
pero también podemos usar los parámetrosvmin
yvmax
. - Barra de color: Se trata de una leyenda particular en la que mostramos el gradiente de color vinculado a una determinada estética/variable del gráfico. Matplotlib también nos permite personalizar estas barras de color.
Ejemplo
Características del «dataset» con el que vamos a trabajar:
- Información sobre vehículos de la marca BMW5
- Fichero:
bmw-clean.csv
- Fuente: Kaggle
Recrea el siguiente gráfico:

El mapa de color que se ha usado es plasma_r
.
Histogramas¶
Características del «dataset» con el que vamos a trabajar:
- Información sobre «Avengers»6.
- Fichero:
avengers.csv
- Fuente: Kaggle
Como punto de partida vamos a cargar la información y a quedarnos únicamente con la columna que hace referencia al año en el que se crearon los personajes:
>>> df = pd.read_csv('avengers.csv', usecols=['Year'])
>>> df.head()
Year
0 1963
1 1963
2 1963
3 1963
4 1963
>>> df.shape
(173, 1)
Igualmente haremos un pequeño filtrado para manejar sólo registros a partir de 1960:
Ahora ya podemos construir el histograma, que va a representar las frecuencias absolutas de creación de personajes Marvel según su año de creación.
Aunque es posible indicar un número determinado de contenedores («bins»), en este caso vamos a especificar directamente los intervalos (cada 5 años):
Y a continuación el código necesario para montar el gráfico:
>>> fig, ax = plt.subplots(figsize=(8, 4), dpi=100)#(1)!
>>> ax.hist(df,
... bins=bins,#(2)!
... rwidth=0.95,#(3)!
... zorder=2,#(4)!
... color='deeppink',
... alpha=0.5)
>>> ax.spines['right'].set_visible(False)
>>> ax.spines['top'].set_visible(False)
>>> ax.set_xticks(bins)#(5)!
>>> ax.yaxis.grid(color='lightgray', linestyle='--')#(6)!
>>> fig.tight_layout()
- 800x400 px
- Intervalos de agrupación.
- Ancho de cada barra.
- Barras por encima de la rejilla.
- Etiquetas de intervalos en el eje X.
- Rejilla.
Aclaración
Técnicamente este gráfico no es un histograma ya que los años (fechas en general) no representan categorías válidas, pero sirve a efectos demostrativos de cómo se construyen este tipo de diagramas.
Ejercicio
Características del «dataset» con el que vamos a trabajar:
- Datos sobre criaturas «Pokemon»7.
- Fichero:
pokemon.csv
- Fuente: Kaggle
Recrea el siguiente gráfico:

Número de criaturas («pokemos») en función de su velocidad (columna Speed).
Gráficos para series temporales¶
Características del «dataset» con el que vamos a trabajar:
- Información histórica de temperaturas del planeta Tierra.
- Fichero:
global-temperatures.csv
- Fuente: Kaggle
En primer lugar cargamos los datos, renombramos las columnas y eliminamos los valores nulos:
>>> df = pd.read_csv('global-temperatures.csv',
... parse_dates=['dt'],#(1)!
... usecols=['dt', 'LandAverageTemperature'])
>>> df.rename(columns={'dt': 'when', 'LandAverageTemperature': 'temp'}, inplace=True)
>>> df.dropna(inplace=True)
>>> df.head()
when temp
0 1750-01-01 3.034
1 1750-02-01 3.083
2 1750-03-01 5.626
3 1750-04-01 8.490
4 1750-05-01 11.573
>>> df.shape
(3180, 2)
- Conversión a tipo
datetime
.
A continuación montamos un gráfico en el que se representan todas las mediciones históricas de la temperatura media global del planeta y añadimos una línea de tendencia:
>>> from matplotlib.dates import YearLocator, DateFormatter, date2num#(1)!
>>> from matplotlib.ticker import MultipleLocator
>>> fig, ax = plt.subplots(figsize=(8, 4), dpi=100)#(2)!
>>> x = df.when#(3)!
>>> y = df.temp#(4)!
>>> ax.plot(x, y,
... linestyle='None', marker='.', color='tomato',#(5)!
... zorder=2)#(6)!
>>> # Construcción de la línea de tendencia
>>> x = date2num(x)
>>> z = np.polyfit(x, y, 2)#(7)!
>>> p = np.poly1d(z)
>>> plt.plot(x, p(x), linewidth=4, alpha=0.8, color='royalblue')
>>> # Formateo de los ejes
>>> ax.xaxis.set_minor_locator(YearLocator(10))
>>> ax.xaxis.set_minor_formatter(DateFormatter('%Y'))
>>> ax.tick_params(axis='x', which='minor',
... labelsize=8, labelcolor='lightgray', rotation=90)
>>> ax.xaxis.grid(which='minor', color='lightgray', linestyle='dashed')
>>> ax.yaxis.set_major_formatter('{x:.0f}º')
>>> ax.yaxis.set_minor_locator(MultipleLocator(1))
>>> ax.tick_params(axis='y', which='minor',
... labelsize=8, labelcolor='lightgray')
>>> ax.yaxis.grid(which='minor', linestyle='dashed', color='lightgray')
>>> ax.yaxis.set_minor_formatter('{x:.0f}')
>>> ax.tick_params(axis='y', which='minor', labelsize=8, labelcolor='lightgray')
>>> ax.spines['right'].set_visible(False)
>>> ax.spines['top'].set_visible(False)
>>> fig.tight_layout()
- Necesitamos algunas utilidades de gestión de fechas.
- 800x400 px
- Variable auxiliar \(x\).
- Variable auxiliar \(y\).
- Estilo de línea.
- Orden para colocar sobre rejilla.
- Ajuste polinómico de grado 2.
Mapas de calor¶
Características del «dataset» con el que vamos a trabajar:
- Películas más valoradas en IMBD8.
- Fichero:
imdb-top-1000.csv
- Fuente: Kaggle
En primer lugar vamos a cargar los datos quedándonos con las columnas Certificate (clasificación de la película según edades), Genre (géneros de la película) e IMDB_Rating (valoración de la película en IMDB):
>>> df = pd.read_csv('imdb-top-1000.csv',
... usecols=['Certificate', 'Genre', 'IMDB_Rating'])
>>> df.head()
Certificate Genre IMDB_Rating
0 A Drama 9.3
1 A Crime, Drama 9.2
2 UA Action, Crime, Drama 9.0
3 A Crime, Drama 9.0
4 U Crime, Drama 9.0
Ahora creamos una nueva columna en el DataFrame donde guardaremos únicamente el género principal de cada película:
>>> df['Main_Genre'] = df['Genre'].str.split(',', expand=True)[0]
>>> df.head()
Certificate Genre IMDB_Rating Main_Genre
0 A Drama 9.3 Drama
1 A Crime, Drama 9.2 Crime
2 UA Action, Crime, Drama 9.0 Action
3 A Crime, Drama 9.0 Crime
4 U Crime, Drama 9.0 Crime
A continuación agrupamos y obtenemos los valores medios de las valoraciones:
>>> ratings = df.groupby(
... ['Certificate', 'Main_Genre']
... )['IMDB_Rating'].mean().unstack()#(1)!
>>> # Nos quedamos con un subconjunto de certificados y géneros
>>> review_certificates = ['U', 'UA', 'PG-13', 'R', 'A']
>>> review_genres = ['Animation', 'Action', 'Adventure', 'Biography',
... 'Comedy', 'Crime', 'Drama']
>>> ratings = ratings.loc[review_certificates, review_genres]
>>> # Recodificamos los certificados (clasificación) con códigos más entendibles
>>> certs_description = {'U': 'ALL', 'UA': '>12', 'PG-13': '>13', 'R': '>17', 'A': '>18'}
>>> ratings.index = ratings.reset_index()['Certificate'].replace(certs_description)
>>> ratings
Main_Genre Animation Action Adventure Biography Comedy Crime Drama
Certificate
ALL 7.947368 8.165000 7.953571 7.862500 7.940541 8.200000 7.976364
>12 7.883333 7.992424 7.958333 7.971429 7.885714 7.900000 7.953659
>13 7.866667 7.783333 7.600000 7.862500 7.785714 8.000000 7.775000
>17 7.800000 7.812500 7.900000 7.900000 7.824138 7.814286 7.915094
>18 7.866667 7.873171 7.912500 8.017647 7.877778 8.130233 8.036364
unstack()
permite disponer la agrupación en forma tabular (para el heatmap).
Ahora ya podemos construir el mapa de calor usando el DataFrame ratings
generado previamente:
>>> fig, ax = plt.subplots(figsize=(8, 4), dpi=100)
>>> text_colors = ('black', 'white')
>>> im = ax.imshow(ratings, cmap='Reds')#(1)!
>>> cbar = fig.colorbar(im, ax=ax, label='IMDB Rating')#(2)!
>>> cbar.outline.set_visible(False)
>>> x = ratings.columns
>>> y = ratings.index
>>> # Mostrar las etiquetas. El color del texto cambia en función de su normalización
>>> for i in range(len(y)):
... for j in range(len(x)):
... value = ratings.iloc[i, j]
... text_color = text_colors[int(im.norm(value) > 0.5)]#(3)!
... ax.text(j, i, f'{value:.2f}', color=text_color, va='center', ha='center')
>>> # Formateo de los ejes
>>> ax.set_xticks(range(len(x)))
>>> ax.set_xticklabels(x, rotation=90)
>>> ax.set_yticks(range(len(y)))
>>> ax.set_yticklabels(y)
>>> ax.invert_yaxis()
>>> ax.spines[:].set_visible(False)
>>> fig.tight_layout()
- Mapa de calor.
- Leyenda.
- Color de la etiqueta.
Ejercicio
Características del «dataset» con el que vamos a trabajar:
- Información sobre el cambio euro-dólar entre 1999 y 2020.
- Fichero:
euro-dollar-clean.csv
- Fuente: Kaggle
Recrea el siguiente gráfico:

Diagramas de caja¶
Un diagrama de caja permite mostrar la distribución de los valores de manera rápida y muy visual:
Para mostrar el funcionamiento de los diagramas de caja en Matplotlib vamos a hacer uso de distintas distribuciones aleatorias que crearemos mediante funciones de NumPy:
>>> DIST_SIZE = 100#(1)!
>>> boxplots = []
>>> boxplots.append(dict(
... dist=np.random.normal(0, 1, size=DIST_SIZE),
... label='Normal\n$\mu=0, \sigma=1$',
... fill_color='pink',
... brush_color='deeppink'))
>>> boxplots.append(dict(
... dist=np.random.geometric(0.4, size=DIST_SIZE),
... label='Geometric\n$p=0.4$',
... fill_color='lightblue',
... brush_color='navy'))
>>> boxplots.append(dict(
... dist=np.random.chisquare(2, size=DIST_SIZE),
... label='Chi-squared\n$df=2$',
... fill_color='lightgreen',
... brush_color='darkgreen'))
- Tamaño de la muestra.
Ahora ya podemos construir el gráfico de cajas que nos permite visualizar la distribución de las muestras:
>>> fig, ax = plt.subplots(figsize=(8, 6), dpi=100)#(1)!
>>> for i, boxplot in enumerate(boxplots):
... fcolor, bcolor = boxplot['fill_color'], boxplot['brush_color']
... ax.boxplot(boxplot['dist'],
... labels=[boxplot['label']],
... positions=[i],
... widths=[.3],
... notch=True,
... patch_artist=True,
... boxprops=dict(edgecolor=bcolor,
... facecolor=fcolor,
... linewidth=2),
... capprops=dict(color=bcolor, linewidth=2),
... flierprops=dict(color=bcolor,
... markerfacecolor=fcolor,
... linestyle='none',
... markeredgecolor='none',
... markersize=9),
... medianprops=dict(color=bcolor),
... whiskerprops=dict(color=bcolor,
... linewidth=1))
>>> ax.yaxis.grid(color='lightgray')
>>> ax.xaxis.set_ticks_position('none')
>>> ax.yaxis.set_ticks_position('none')
>>> ax.spines[:].set_visible(False)
>>> fig.tight_layout()
- 800x600 px
Complicaciones en código
El código para preparar el gráfico se ha complicado porque se ha incidido en mejorar la estética. En cualquier caso, una vez hecho, se puede refactorizar en una función y reutilizarlo para futuros trabajos.
Gráficos de evolución¶
Características del «dataset» con el que vamos a trabajar:
- Evolución temporal de la criptomoneda «Ethereum» (volumen, precio).
- Fichero:
eth-usd.csv
- Fuente: Kaggle
El objetivo será crear un gráfico que represente el valor de la criptomoneda (a lo largo del tiempo) en contraposición al volumen de unidades.
Lo primero que haremos, además de cargar los datos, será lo siguiente:
- Seleccionar las columnas Date (fecha de referencia), Open (precio de la moneda a la apertura) y Volume (volumen de moneda).
- Parsear el campo fecha.
- Filtrar sólo aquellos registros a partir del 1 de enero de 2017 (por simplicidad).
- Dividir la columna de volumen por 10M de cara a equiparar cantidades con la valoración (ajuste de gráfico).
- Aplicar una media móvil para suavizar las curvas a representar.
>>> import datetime
>>> df = pd.read_csv('eth-usd.csv',
... parse_dates=['Date'],
... usecols=['Date', 'Open', 'Volume'],
... index_col='Date')
>>> min_date = datetime.datetime(year=2017, month=1, day=1)
>>> df = df.loc[df.index > min_date]
>>> df['Volume'] /= 1e7
>>> df_smooth = df.rolling(20).mean().dropna()
>>> df_smooth.head()
Open Volume
Date
2017-01-21 9.968611 2.146882
2017-01-22 10.105573 2.117377
2017-01-23 10.222339 1.985587
2017-01-24 10.273270 1.821968
2017-01-25 10.239854 1.647938
Ahora ya podemos montar el gráfico dedicando algo de esfuerzo a la parte estética:
>>> fig, ax = plt.subplots(figsize=(8, 4), dpi=100)#(1)!
>>> # Alias para facilitar el acceso
>>> x = df_smooth.index
>>> y_open = df_smooth['Open']
>>> y_vol = df_smooth['Volume']
>>> # Líneas de evolución
>>> ax.plot(x, y_open, label='Value ($)', color='skyblue', linewidth=1.5)
>>> ax.plot(x, -y_vol, label='Volume (10M ud.)', color='pink', linewidth=1.5)
>>> # Relleno del área
>>> plt.fill_between(x, y_open, alpha=0.5, color='skyblue', zorder=3)
>>> plt.fill_between(x, -y_vol, alpha=0.5, color='pink', zorder=3)
>>> # Formateo de los ejes
>>> ax.xaxis.set_ticks_position('none')
>>> ax.yaxis.set_ticks_position('none')
>>> y_ticks = [-4000, -2000, 0, 2000, 4000]
>>> y_tick_labels = ['4000', '2000', '0', '2000', '4000']
>>> ax.set_yticks(y_ticks)
>>> ax.set_yticklabels(y_tick_labels)
>>> ax.set_ylim(-6000, 6000)
>>> # Rejilla
>>> ax.xaxis.grid(color='lightgray', linewidth=.5)
>>> for y_tick in y_ticks:
... if y_tick != 0:
... ax.axhline(y_tick, color='lightgray', linewidth=.5)
>>> ax.legend()
>>> ax.spines[:].set_visible(False)
>>> fig.tight_layout()
- 800x400 px
Ejercicio
Características del «dataset» con el que vamos a trabajar:
- Precio de la energía en España durante el año 2021.
- Fichero:
mwh-spain-2021-clean.csv
- Fuente: Kaggle
Recrea el siguiente gráfico:

- Eje X: Tiempo (las marcas tienen una separación de 10 días).
- Eje Y: Precio del MWh9.
-
Se suele usar el término inglés «inches». ↩
-
Parámetros disponibles para creación del grid | Listado de nombres de colores en matplotlib | Estilos de línea en matplotlib ↩
-
Se suele usar el término inglés «ticks». ↩
-
NBA National Basketball League (liga estadounidense de baloncesto). ↩
-
Los Vengadores Equipo de superhéroes publicados por Marvel Comics. ↩
-
Pokemon Franquicia de medios que originalmente comenzó como un videojuego RPG, pero debido a su popularidad ha logrado expandirse a otros medios de entretenimiento como series de televisión, películas, juegos de cartas, ropa, entre otros, convirtiéndose en una marca que es reconocida en el mercado mundial. ↩
-
IMDB Reconocida página web que contiene valoraciones sobre películas y series. ↩
-
Mega Watio Hora (medida de consumo de energía). ↩