import datetime
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
pd.options.plotting.backend = "plotly"
%config InlineBackend.figure_format = 'retina'
Plusieurs dessins dans une même figure¶
Il existe différentes facons illustrées ci-dessous pour afficher plusieurs dessins sur une figure :
- Plotly Express
facet_row
etfacet_col
pour découper la figure en fonction d'une autre variable (ce qui ajoute une dimension)marginal
pour ajouter un bandeau au dessus ou a coté d'un graphique principal
- Plotly
make_subplots
permet de découper la figure en une grille, puis on range des figures dans chaque casedomain
permet d'indiquer exactement la zone de la figure où est la sous-figure, c'est la souplesse absolue
- Dash est la solution complète pour faire un tableau de bord avec plein de figures (cf cours sur Dash)
Plotly Express¶
Les données¶
On prend les accidents de la route en France en 2019. Les codes qui nous intéresse sont :
accidents = pd.read_csv('data/caracteristiques-2019.csv', sep=";", decimal=",")
accidents = accidents[accidents.col > 0] # few collisions are undefined i.d. == -1
accidents.head()
Num_Acc | jour | mois | an | hrmn | lum | dep | com | agg | int | atm | col | adr | lat | long | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 201900000001 | 30 | 11 | 2019 | 01:30 | 4 | 93 | 93053 | 1 | 1 | 1 | 2 | AUTOROUTE A3 | 48.896210 | 2.470120 |
1 | 201900000002 | 30 | 11 | 2019 | 02:50 | 3 | 93 | 93066 | 1 | 1 | 1 | 6 | AUTOROUTE A1 | 48.930700 | 2.368800 |
2 | 201900000003 | 28 | 11 | 2019 | 15:15 | 1 | 92 | 92036 | 1 | 1 | 1 | 4 | AUTOROUTE A86 | 48.935872 | 2.319174 |
3 | 201900000004 | 30 | 11 | 2019 | 20:20 | 5 | 94 | 94069 | 1 | 1 | 1 | 4 | A4 | 48.817329 | 2.428150 |
4 | 201900000005 | 30 | 11 | 2019 | 04:00 | 3 | 94 | 94028 | 1 | 1 | 1 | 2 | A86 INT | 48.776362 | 2.433254 |
On traduit les codes qui nous intéresse (cf doc sur le site web ci-dessus) :
collision = ["Frontale", "Par derrière", "Sur le coté", "A 3 et + en chaîne", "A 3 et +", "Autre", "Sans collision"]
luminosité = ["Jour", "Semi-nuit", "Nuit noire", "Nuit éclairage éteint", "Nuit avec éclairage"]
Pour de simples courbes¶
On obtient une séparation des courbes en ajoutant simplement facet_row
ou facet_col
. Si on veut avoir une grille, on indique le maximum de sous-figures par ligne ou par colonne avec facet_col_wrap
ou facet_row_wrap
.
Ainsi on ajoute une dimension. En ajoutant la couleur on peut donc exprimer 3 dimensions comme dans le graphe suivant où on indique le type de colision, la luminosité et le mois.
Si je donne x = "col"
alors j'aurais les valeurs entières telles qu'elles sont dans le tableau. Aussi je les convertis à
la volée pour un graphique plus lisible.
fig = px.histogram(accidents, x = accidents["col"].apply(lambda i: collision[i-1]),
color = accidents["lum"].apply(lambda i: luminosité[i-1]),
color_discrete_sequence=['lightskyblue','violet','black', 'red', 'orange'],
facet_col = 'mois', facet_col_wrap = 4,
category_orders = {"mois":range(1,13), "x":collision, "color":luminosité},
labels = {'x':'Collision', "color":'Luminosité'},
)
# let move the legend above the figure
fig.update_layout(legend=dict( orientation = "h",
yanchor = "bottom", y = 1.05,
xanchor = "right", x = 1 )
)
fig
Notes en marge¶
Il est possible d'ajouter des informations statistiques à coté où au dessus d'un graphique. Les possibilités sont l'ajout
d'un histogram
, box
, violin
ou d'un rug
, ce dernier consitant à poser une marque pour chaque donnée ce qui permet
de mesurer visuellement la densité suivant l'axe.
Les arguments sont marginal
, marginal_x
et marginal_y
.
accidents = accidents[accidents['lat'] > 40] # let focus on France mainland
accidents = accidents[accidents['long'] > -6]
juillet = accidents[accidents["mois"] == 7]
fig = px.scatter(juillet, x="long", y="lat",
color=juillet['lum'].apply(lambda i:luminosité[i-1]),
color_discrete_sequence=['lightskyblue','violet','black', 'red', 'orange'],
marginal_x="box", marginal_y="violin",
height = 800, width = 800,
category_orders = {"color":luminosité},
labels = {"lat":"Latitude", "long":"Longitude", "color":"Luminosité"},
title="Répartition des accidents en juillet 2019")
fig
Bien sûr dans ce cas, il serait intéressant de superposer les données sur une carte et de pouvoir agrandir. On verra cela plus tard.
Rappel : tous ces graphiques sont interactifs, en particulier on peut sélectionner et déselectionner des catégories en cliquant sur les éléments de la légende.
unrate = pd.read_pickle('data/unrate.pkl')
workers = pd.read_pickle('data/workers.pkl')
unemployed = pd.read_pickle('data/unemployed.pkl')
# unrate_races = unrate.loc[unrate.index > '1975', ['black','latino','white']].copy()
Des figures rangées dans une grille¶
On définit la grille à la main avec make_subplots
puis on range les figures une par une en indiquant sa position.
figure = make_subplots(rows=1, cols=2)
figure.append_trace(fig1, 1, 1)
figure.append_trace(fig2, 1, 2)
from plotly.subplots import make_subplots
data1 = go.Bar(
name = 'unemployed',
x = unemployed.index[::],
y = unemployed['all'],
marker = {'color':'purple'},
)
data2 = go.Bar(
name = 'workers',
x = workers.index[::],
y = workers['all'] - unemployed['all'],
marker = {'color':'cyan'},
)
fig2 = px.bar(unemployed[['black','latino','white']], color_discrete_sequence=['black','orange','pink'])
figure = make_subplots(rows=2, cols=1,
subplot_titles=('Number of unemployed and workers','Number of unemployed per ethnicities'))
figure.append_trace(data1, 1, 1)
figure.append_trace(data2, 1, 1)
for trace in fig2['data']:
figure.append_trace(trace, 2, 1) # add_trace works too
figure['layout'].update( height=600, barmode='stack', title="Main title !")
figure
Un placement libre de chaque sous-figure¶
Il est possible de définir exactement la zone dans laquelle va apparaitre chaque sous figure avec l'argument domain
indiqué dans la trace. Un domaine definit la zone en pourcentage de la figure ainsi {'x':[0.75, 1], 'y':[0, 0.25]}
indique le carré en bas à droite qui occupe 1/8ème de la surface (1/4 en x, 1/4 en y).
N=7
when = [unemployed.index[i] for i in np.random.randint(0,len(unemployed),N)]
data = [unemployed.loc[i,['black','latino','white']] for i in when]
domains = [
{'x': [0.0, 0.3], 'y': [0.0, 0.3]},
{'x': [0.35, 0.65], 'y': [0.1, 0.4]},
{'x': [0.7, 1.0], 'y': [0.0, 0.3]},
{'x': [0.1, 0.4], 'y': [0.35, 0.7]},
{'x': [0.1, 0.4], 'y': [0.75, 1.0]},
{'x': [0.4, 1.0], 'y': [0.4, 1.0]},
{'x': [0., 0.2], 'y': [0.8, 1.0]} # This one is going to overlay another one!
]
traces = [go.Pie(title = f"{when[i].month:02}-{when[i].year}",
textposition="inside",
values = data[i],
domain = domains[i],
labels = ['Black','Latino','White'],
marker = {'colors':['black','orange','pink']}) for i in range(N)]
layout = go.Layout(height = 600,
width = 600,
title = 'Unemployed at random dates',
autosize = False, )
fig = go.Figure(data = traces, layout = layout)
fig