In [1]:
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 et facet_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 case
    • domain 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 :

In [2]:
accidents = pd.read_csv('data/caracteristiques-2019.csv', sep=";", decimal=",")
accidents = accidents[accidents.col > 0]  # few collisions are undefined i.d. == -1
accidents.head()
Out[2]:
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) :

In [3]:
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.

In [4]:
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.

In [5]:
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.

Plotly¶

Plotly permet de ranger

  • en grille avec make_subplots
  • librement avec l'argument domain

Données¶

On reprend les données sur le taux de chômage aux États-Unis.

In [6]:
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)
In [7]:
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).

In [8]:
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
In [ ]: