Con éste Notebook trataremos de dar a conocer plotly
, un servicio web gratuito para generar gráficos interactivos y que permite la creación de proyectos colaborativos.
Éste Notebook emplea los siguentes paquetes:
pandas
0.14.0, para importar y analizar estructuras de datos en Python.matplotlib
1.3.1, para generar diversisos tipos de gráficos con calidad de imprenta.plotly
1.1.2, para generar gráficos interactivos para la web.que iremos cargando conforme los vayamos a utilizar.
Empezaremos por importar los datos con pandas
. Podeis encontrar una introducción a pandas
en éste post de Pybonacci.
La estructura de datos con la que vamos a trajar es un CSV
con datos de telemetría correspondientes a un BMW Z4 GT3 compitiendo en el circuito Brands Hatch Indy.
El paquete de datos nos lo ha proporcionado Steve Barker, un mecánico que pasa la mayor parte de su tiempo en la GP2. Pero aquí sólo vamos a trabajar con los datos de la vuelta 27 de la primera carrera.
El propio pandas
incluye rutinas para importar datos desde diversas fuentes, ya sea un simple CSV
o un Excel de multiples hojas.
# Importamos pandas de la manera habitual
import pandas as pd
Pero antes de proceder a cargar los datos debemos conocer un poco la estructura del archivo en cuestión del que he recortado una muestra.
Format;MoTeC CSV File;;;Workbook;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Venue;brandshatch indy;;;Worksheet;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Vehicle;bmwz4gt3;;;Vehicle Desc;bmwz4gt3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Driver;Steve Barker;;;Engine ID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Device;ADL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Comment;;;;Session;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Log Date;02/07/2014;;;Origin Time;1196.94;s;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Log Time;23:05:43;;;Start Time;1196.95;s;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Sample Rate;60;Hz;;End Time;1240.017;s;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Duration;43.067;s;;Start Distance;49849;m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Range;Lap 27;;;End Distance;51765;m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Time;Distance;Gear;Engine RPM;Manifold Pres;Throttle Pos;Brake Pedal Pos;Clutch
s;m;;rpm;kpa;%;%;%;deg;N.m;;m;km/h;G;G;G;deg;deg;deg;deg/s;deg/s;deg/s;C;kPa;V;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
0;0;5;8657;0.99752352;100;0;100;-5.724613;1.5780677;26;0.6267;225.407808;-0.296
0.017;1;5;8658;0.9975208;100;0;100;-6.10625;1.7365016;26;1.66957;225.514112;-0.
0.033;2;5;8654;0.99751824;100;0;100;-6.160785;1.929843;26;2.71285;225.58688;-0.
0.05;3;5;8670;0.99751552;100;0;100;-6.160785;2.5375902;26;3.7565;225.653952;-0.
Como bien podemos apreciar, las primeras 11 líneas aportan información generada por MoTeC al exportar los datos. Las cabeceras de lo que realmente nos interesa son las líneas 14 y 15, siendo la primera la descripción del parámetro y la segunda las unidades de medida. Y ya en la línea 18 empiezan los datos.
Tomaremos como índice del DataFrame
la variable tiempo (aunque también podríamos optar por la distancia).
# Cargamos el archivo. Recordemos que pandas, y Python en general, es 0-indexed.
data = pd.io.parsers.read_csv('brandshatch_lap27.csv', sep=';', header=13, index_col=0, skiprows=[14,15,16])
# Otra forma alternativa de leer los datos de un archivo CSV sería a través del
# propio DataFrame de pandas, pero éste no nos permite saltarnos filas de datos:
# data = pd.DataFrame.from_csv('brandshatch_lap27.csv', sep=';', header=13, index_col=0)
data.head(5)
Distance | Gear | Engine RPM | Manifold Pres | Throttle Pos | Brake Pedal Pos | Clutch Pedal Pos | Steering Wheel Angle | Steering Wheel Torque | Lap Number | ... | WaterTemp | Yaw | YawRate | dcABS | dcBrakeBias | dcFuelMixture | dcThrottleShape | dcTractionControl | dcTractionControlToggle | Fuel Density | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Time | |||||||||||||||||||||
0.000 | 0 | 5 | 8657 | 0.997524 | 100 | 0 | 100 | -5.724613 | 1.578068 | 26 | ... | 88.536890 | -0.790032 | -0.064797 | 9 | 3570.19424 | 1 | 10 | 3 | 0 | 0.75 |
0.017 | 1 | 5 | 8658 | 0.997521 | 100 | 0 | 100 | -6.106250 | 1.736502 | 26 | ... | 88.540582 | -0.791088 | -0.063974 | 9 | 3570.19424 | 1 | 10 | 3 | 0 | 0.75 |
0.033 | 2 | 5 | 8654 | 0.997518 | 100 | 0 | 100 | -6.160785 | 1.929843 | 26 | ... | 88.544275 | -0.792130 | -0.067104 | 9 | 3570.19424 | 1 | 10 | 3 | 0 | 0.75 |
0.050 | 3 | 5 | 8670 | 0.997516 | 100 | 0 | 100 | -6.160785 | 2.537590 | 26 | ... | 88.547936 | -0.793190 | -0.067915 | 9 | 3570.19424 | 1 | 10 | 3 | 0 | 0.75 |
0.067 | 4 | 5 | 8695 | 0.997512 | 100 | 0 | 100 | -5.997180 | 2.660200 | 26 | ... | 88.551744 | -0.794294 | -0.072090 | 9 | 3570.19424 | 1 | 10 | 3 | 0 | 0.75 |
5 rows × 215 columns
Podemos ver que el archivo es inmenso. Contiene 215 con diferentes parámetros como revoluciones del motor, posición del pedal del acelarador, velocidad, etc. Un sinfín de datos que marearían al más experto de los ingenieros de pista.
De todos modos, aquí no vamos a analizar en detalle la vuelta realizada por Steve Barker, sino que nos centraremos exclusivamente en visualizar los datos.
matplotlib
¶matplotlib
es el paquete más empleado en Python para generar diversos tipos de gráficos de alta calidad.
Y es precisamente lo que utiliza pandas
para generar sus propias gráficas como veremos a continuación.
Empezamos por importar matplotlib
y, de paso, crear un mapa de colores personalizado más vistoso que el que viene por defecto.
La idea la he tomado de Randal S. Olson.
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
# Color map
tableau20 = [(31, 119, 180), (174, 199, 232), (255, 127, 14), (255, 187, 120),
(44, 160, 44), (152, 223, 138), (214, 39, 40), (255, 152, 150),
(148, 103, 189), (197, 176, 213), (140, 86, 75), (196, 156, 148),
(227, 119, 194), (247, 182, 210), (127, 127, 127), (199, 199, 199),
(188, 189, 34), (219, 219, 141), (23, 190, 207), (158, 218, 229)]
tableau10 = [tableau20[i*2] for i in range(10)]
# Scale the RGB values to the [0, 1] range, which is the format matplotlib accepts.
for i in range(len(tableau20)):
r, g, b = tableau20[i]
tableau20[i] = (r / 255., g / 255., b / 255.)
for i in range(len(tableau10)):
r, g, b = tableau10[i]
tableau10[i] = (r / 255., g / 255., b / 255.)
# Set the default color cycle
mpl.rcParams['axes.color_cycle'] = tableau10
Una de las primeras cosas que podemos visualizar son la velocidad en función del tiempo junto con otros parámetros relaciones como son la posición de los pedales y las revoluciones del motor.
fig, ax = plt.subplots(3, sharex=True, figsize=(10,6))
# Revoluciones del motor
data['Engine RPM'].plot(ax=ax[0], linewidth=2)
ax[0].set_xlabel('')
ax[0].set_ylabel('Engine RPM')
ax[0].legend(loc='lower right')
# Velocidad del vehículo
data['Ground Speed'].plot(ax=ax[1], linewidth=2)
ax[1].set_xlabel('')
ax[1].set_ylabel('Speed (kph)')
ax[1].legend(loc='lower right')
# Pedales
data['Throttle Pos'].plot(ax=ax[2], linewidth=2)
data['Brake Pedal Pos'].plot(ax=ax[2], linewidth=2)
ax[2].set_xlabel('Time (s)')
ax[2].set_ylabel('Position (%)')
ax[2].set_ylim(-10,105)
ax[2].legend(loc='center right')
for j in range(3):
ax[j].set_axisbelow(True)
# Quitamos los bordes superior y derecho
ax[j].spines["top"].set_visible(False)
ax[j].spines["right"].set_visible(False)
# Dejamos sólo los ticks abajo y a la izquierda
ax[j].get_xaxis().tick_bottom()
ax[j].get_yaxis().tick_left()
Lo que hemos realizado en la gráfica de arriba son tres subgráficas que comparten el mismo eje x. De ese modo podemos ver la relación entre los diversos parámetros.
Otro tipo de gráfico que nos puede dar mucha información es la relación entre la velocidad del vehículo y las revoluciones del motor.
Para ello podemos utilizar en pandas
el tipo scatter
y colorear los valores en función de la marcha engranada.
Eso lo conseguimos con la herramientas groupby
de pandas
.
fig1, ax1 = plt.subplots(figsize=(10,6))
for key, grp in data.groupby(['Gear']):
grp.plot('Ground Speed','Engine RPM',kind='scatter', ax=ax1, label=key, color=tableau10[key])
ax1.set_xlabel('Ground Speed (kph)')
ax1.set_axisbelow(True)
ax1.legend(title='Gear', loc='upper left')
# Quitamos los márgenes derecho y superior
ax1.spines["top"].set_visible(False)
ax1.spines["right"].set_visible(False)
# Dejamos sólo los ticks abajo y a la izquierda
ax1.get_xaxis().tick_bottom()
ax1.get_yaxis().tick_left()
Mediante un histograma podemos analizar el uso que hace el piloto del pedal del acelerador.
fig2, ax2 = plt.subplots(figsize=(10,6))
data['Throttle Pos'].hist(ax=ax2)
ax2.set_xlabel('Throttle position (%)')
ax2.set_xlim(0,100)
ax2.set_ylabel('Time spent (@ 60 Hz)')
ax2.set_axisbelow(True)
# Quitamos los márgenes derecho y superior
ax2.spines["top"].set_visible(False)
ax2.spines["right"].set_visible(False)
# Dejamos sólo los ticks abajo y a la izquierda
ax2.get_xaxis().tick_bottom()
ax2.get_yaxis().tick_left()
import plotly.plotly as py
from plotly.graph_objs import *
Para poder utilizar plotly
necesitamos una credenciales. Registrarse es gratuito, y ofrecen almacenamiento ilimitado para gráficos públicos. Sin embargo, el número de archivos privados que podemos alojar es limitado en la versión gratuita.
Para mayor comodidad podemos guardar nuestras credenciales en el sistema de modo siguiente,
>>> import plotly.tools as tls
>>> tls.set_credentials_file(username="your_username",
api_key="your_api_key")
que rellenaremos con nuestros datos, que encontraremos en la sección de Configuration
de nuestra cuenta.
Nuestras credenciales las podremos recuperar, a partir de entonces, de la siguente manera.
my_creds = py.get_credentials()
username = my_creds['username']
api_key = my_creds['api_key']
py.sign_in(username, api_key)
Podemos utilizar plotly
de dos maneras.
La primera de ellas es generar las gráficas directamente con la API.
La segunda, y la que resultará más cómoda para la mayoría, es convertir directamente las figuras generadas con matplotlib
.
# Publicar una figura de matplotlib en la web
unique_url = py.iplot_mpl(fig, filename='pybonacci/velocidades', strip_style=True)
C:\Miniconda3\lib\site-packages\plotly\matplotlylib\renderer.py:306: UserWarning: Bummer! Plotly can currently only draw Line2D objects from matplotlib that are in 'data' coordinates! C:\Miniconda3\lib\site-packages\plotly\matplotlylib\renderer.py:427: UserWarning: I found a path object that I don't think is part of a bar chart. Ignoring.
unique_url1 = py.iplot_mpl(fig1, filename='pybonacci/rpm', strip_style=True)
C:\Miniconda3\lib\site-packages\plotly\matplotlylib\renderer.py:384: UserWarning: Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates
unique_url2 = py.iplot_mpl(fig2, filename='pybonacci/acelerador', strip_style=True)
plotly
¶Como podemos ver en las gráficas superiores, la conversión a plotly
todavía no es perfecta. El conversor interpreta las leyendas de matplotlib
como anotaciones de texto. Por suerte, eso lo podemos subsanar fácilmente.
Necesitaremos importar una serie de paquetes adicionales, disponibles en las versiones >1 de plotly
.
# Herramientas Python/Plotly
import plotly.tools as tls
# Objetos para componer Leyendas
from plotly.graph_objs import Legend
Convertimos la figura de matplotlib
a una figura de plotly
,
py_fig = tls.mpl_to_plotly(fig, strip_style=True)
y eliminamos todas las anotaciones de texto y añadimos una leyenda en la esquina superior derecha.
# Borrar anotaciones
py_fig['layout'].pop('annotations',None)
# Añadir leyenda
py_fig['layout'].update(dict(showlegend=True))
Ya podemos publicar la nueva gráfica, ésta vez sólo con py.plot()
o py.iplot()
pues ya hemos hecho la conversión a plotly
unos pasos atrás.
unique_url3 = py.iplot(py_fig, filename='pybonacci/velocidades_edit')
Repetimos el procedimiento para la segunda gráfica, pero en esta ocasión colocaremos la leyenda arriba a la izquierda y cambiamos el hovermode
a closest
.
Con hovermode
controlamos cómo se comporta plotly
al pasar el ratón por encima de la gráfica. En modo closest
nos resaltará el valor más cercano al ratón, y en modo compare
, para cada x nos mostrará los diferentes valores que toma la variable.
py_fig1 = tls.mpl_to_plotly(fig1, strip_style=True)
# Borrar anotaciones
py_fig1['layout'].pop('annotations',None)
# Añadir leyenda
py_fig1['layout'].update(dict(showlegend=True,
legend=Legend(x=0,y=1),
hovermode='closest'))
Pero esta vez tenemos que editar los nombres de los puntos que se muestran en la leyenda.
# Editar leyenda
n=0
for key, grp in data.groupby(['Gear']):
py_fig1['data'][n].update(dict(name='Gear {}'.format(key)))
n += 1
unique_url4 = py.iplot(py_fig1, filename='pybonacci/rpm_edit')
Por último nos falta retocar el histograma de posiciones del pedal del acelerador. En ésta gráfica no quedan anotaciones, pero tampoco necesitamos mostrar la leyenda. Aprovechamos también para hacer las barras más anchar reduciendo el bargap
.
py_fig2 = tls.mpl_to_plotly(fig2, strip_style=True)
# Borrar leyenda
py_fig2['layout'].update(dict(showlegend=False,
bargap=0.01))
unique_url5 = py.iplot(py_fig2, filename='pybonacci/acelerador_edit')
plotly
¶En un próximo Notebook introduciremos el uso de plotly
para crear gráficas sin partir de matplotlib
.